← Revision 1 as of 2010-08-13 19:11:41 →
Size: 125
Comment:
|
Size: 20149
Comment:
|
Deletions are marked like this. | Additions are marked like this. |
Line 3: | Line 3: |
[[attachment:delay_6s_mono.asm|delay_6s_mono.asm]] |
This function implements a pitch shifter. It takes mono in on the left channel, and outputs a mono signal to both the left and the right channels. The rotary encoder (MOD2) adjusts the pitch shift amount, and the pot (MOD1) adjusts the buffer size (and resultant delay). The easiest way to change the pitch of a signal, is to play back the samples at a faster or slower rate, although this introduces two problems. The first is that if you are sending out data at a rate different from the rate at which you are receiving data, you will eventually run out of data to send out. This is called a buffer over-run or under-run, depending upon whether you're going faster or slower, and hitting the top or bottom of the buffer (the data stored in SRAM). The second problem is that the data is sampled at discreet points in time, and if you want a playback speed that is not a multiple of this time, you will need data from somewhere between those sample periods. There are a number of options for how to deal with buffer boundaries, but the main issue which we are trying to overcome, is the sharp transition as you go from one end of the buffer to the other, and the data is no longer consistent. This creates an audible click in the sample playback. In this case, we are using a fading method. This involves having two samples playing back simultaneously, each from a different point in the buffer (spaced a half-buffer's distance from each other). As one sample gets closer to the boundary, its volume is faded down, and the other is faded up. This continues as each sample moves forward in the buffer, with the volume of the sample being determined by its distance from the buffer boundary. This gives very smooth transitions across the buffer boundary, but also has a slight reverb effect, as multiple delayed signals are being mixed together. The most common method of dealing with the second problem (fractional sample rates) is interpolation. Interpolation is a method of guessing what a value might have been if we actually had sampled at that point in time. For this pitch-shifter function, we use a linear interpolation. This means we draw a straight line between the two adjacent samples from where we want data, and assume our value is on that line. So if we're closer in time to one sample versus the other, than our output value is closer in value to that sample (the output is a sum of the two values, weighted by their distance to our sample point). The pot (MOD1) varies the buffer size used for sample playback, from 12ms to 1.5s. Smaller buffer sizes give a more accurate pitch-shifting effect, as you are playing through only very small samples at a time, and do not hear much of a delay. If the buffer is too small, you begin to hear the rate at which you are moving through the buffer, almost like a slight tremolo. For very large buffer sizes, it becomes a pitch shifted delay, which can be interesting when used with feedback, as each time the signal gets fed back in, its pitch is shifted again, causing an ever rising tone. The rotary encoder (MOD2) varies the pitch shift amount, from -1 octave to +1 octave, in 12 chromatic steps each direction. In this way, the output can always be made to be "in tune" with the original signal. Rotations to the right increase the pitch, and vice versa. To maintain the correct pitch shift amount, a look-up table in program memory is used to store the precalculated values for each step. [[attachment:pitchshifter_chromatic.asm|pitchshifter_chromatic.asm]] |
Line 8: | Line 20: |
; program: pitch_shifter-16b-fading.asm ; UID = 000039 - this is a unique id so variables dont conflict ; 16b address space (1.5s sample time) ; mono data in on left channel, mono out on both left and right ; rotary encoder (MOD2) controlled playback speed ; pot (MOD1) controlled buffer size ; program overview ; ; data is sent out and taken in from the codec. data is taken in on the ; left channel, and played out on both left and right. a buffer of the ; past n seconds is kept and the output is the result of sampling this ; buffer at varying playback speeds. the speed at which it plays through ; the memory is controlled by the rotary encoder (MOD2). turning the ; encoder to the right speeds playback up, and turning it the left slows ; playback down. this playback speed is limited to chromatic steps by ; using a lookup table in program memory to determine playback speed. the ; audio is kept clean over fractional sample periods by interpolating ; between the two closest samples. the output is a mix of the current ; sample, and a sample from the opposite side of the buffer, with the ; relative mix being determined by the distance to the buffer boundary. ; in this way, the audio is faded down as it crosses the buffer boundary. ; the pot (MOD1) controls the buffer size. the adc samples the pot 256 ; times and deadbands the signal to remove glitches. ; constant definitions ; .equ buffer_min_000039 = $0200 ; minimum buffer size .equ delay_mem_000039 = $0200 ; memory position for desired delay time ; ;.equ step-12_000039 = $0080 ; these are the playback speeds used ;.equ step-11_000039 = $0088 ; they are stored in program memory ;.equ step-10_000039 = $0090 ; and not used here ;.equ step-9_000039 = $0098 ;.equ step-8_000039 = $00A1 ;.equ step-7_000039 = $00AB ;.equ step-6_000039 = $00B5 ;.equ step-5_000039 = $00C0 ;.equ step-4_000039 = $00CB ;.equ step-3_000039 = $00D7 ;.equ step-2_000039 = $00E4 ;.equ step-1_000039 = $00F2 ;.equ step00_000039 = $0100 ;.equ step01_000039 = $010F ;.equ step02_000039 = $011F ;.equ step03_000039 = $0130 ;.equ step04_000039 = $0143 ;.equ step05_000039 = $0156 ;.equ step06_000039 = $016A ;.equ step07_000039 = $0180 ;.equ step08_000039 = $0196 ;.equ step09_000039 = $01AF ;.equ step10_000039 = $01C8 ;.equ step11_000039 = $01E3 ;.equ step12_000039 = $0200 ; register usage - may be redefined in other sections ; ; r0 multiply result lsb ; r1 multiply result msb ; r2 sample 3/4 lsb ; r3 sample 3/4 msb ; r4 left/right lsb out ; r5 left/right msb out ; r6 left lsb in/temporary swap register ; r7 left msb in/temporary swap register ; r8 rotary encoder position counter ; r9 adc msb accumulator ; r10 adc fractional byte accumulator ; r11 adc lsb accumulator ; r12 playback speed increment lsb value ($0100 is normal speed) ; r13 playback speed increment msb value ; r14 rotary encoder counter ; r15 switch\adc counter ; r16 temporary swap register ; r17 temporary swap register ; r18 signed multiply register ; r19 signed multiply register ; r20 unsigned multiply register ; r21 unsigned multiply register ; r22 write address third byte/null register ; r23 read address fractional byte ; r24 write address lsb ; r25 write address msb ; r26 buffer length lsb ; r27 buffer length msb ; r28 read address lsb ; r29 read address msb ; r30 jump location for interrupt lsb ; r31 jump location for interrupt msb ; t rotary encoder edge indicator ;program starts here first time ; intialize registers ldi r30,$27 ; set jump location to program start clr r24 ; clear write register clr r25 ldi r22,$00 ; setup write address high byte clr r18 ; setup r18 as null register for carry addition and ddr setting ldi r17,$ff ; setup r17 for ddr setting clear_000039: ; clear delay buffer ; eliminates static when first switching to the delay setting adiw r25:r24,$01 ; increment write register adc r22,r18 ; increment write third byte cpi r22,$01 ; check if 16b memory space has been cleared breq cleardone_000039 ; continue until end of buffer reached out portd,r24 ; set address sts porth,r25 out portg,r22 ; pull ce low,we low,and set high bits of address out ddra,r17 ; set porta as output for data write out ddrc,r17 ; set portc as output for data write out porta,r18 ; set data out portc,r18 ; r18 is cleared above sbi portg,portg2 ; pull we high to write out ddra,r18 ; set porta as input for data lines out ddrc,r18 ; set portc as input for data lines rjmp clear_000039 ; continue clearing cleardone_000039: ; reset registers ldi r24,$00 ; initialize write register ldi r25,$00 clr r22 ; setup null register ldi r28,$00 ; set read address to minimum delay ldi r29,$fd clr r4 ; initialize data output registers clr r5 ldi r26,$00 ; initialize buffer size ldi r27,$06 sts delay_mem_000039,r26 ; store desired buffer size sts (delay_mem_000039 + 1),r27 ; i ran out of registers clr r12 ; initialize playback speed ldi r16,$01 mov r13,r16 reti ; return and wait for next interrupt ;program begins here every time but first ; initiate data transfer to codec sbi portb,portb0 ; toggle slave select pin out spdr,r5 ; send out left channel msb cbi portb,portb0 ;increment write address adiw r25:r24,$01 ; increment write address cp r24,r26 ; check if at end of buffer cpc r25,r27 brlo wait1_000039 ; do nothing if not at end of buffer clr r24 ; reset buffer to bottom clr r25 wait1_000039: ; check if byte has been sent in r17,spsr sbrs r17,spif rjmp wait1_000039 in r7,spdr ; recieve in left channel msb out spdr,r4 ; send out left channel lsb ;increment read address add r23,r12 ; increment read register adc r28,r13 adc r29,r22 ; r22 is cleared above cp r28,r26 ; check if at end of buffer cpc r29,r27 brlo wait2_000039 ; do nothing if not at end of buffer clr r28 ; reset buffer to bottom clr r29 wait2_000039: ; check if byte has been sent in r17,spsr sbrs r17,spif rjmp wait2_000039 in r6,spdr ; recieve in left channel lsb out spdr,r5 ; send out right channel msb ;write left channel data to sram out portd,r24 ; set address sts porth,r25 out portg,r22 ; pull ce low,we low,and set high bits of address ldi r17,$ff out ddra,r17 ; set porta as output for data write out ddrc,r17 ; set portc as output for data write out porta,r6 ; set data out portc,r7 sbi portg,portg2 ; pull we high to write out ddra,r22 ; set porta as input for data lines out ddrc,r22 ; set portc as input for data lines wait3_000039: ; check if byte has been sent in r17,spsr sbrs r17,spif rjmp wait3_000039 in r17,spdr ; recieve in right channel msb out spdr,r4 ; send out right channel lsb ;get left channel sample 1 data from sram movw r17:r16,r29:r28 ; move read address to temporary register out portd,r16 ; set address sts porth,r17 ldi r21,$01 ; increment read address add r16,r21 ; placed here to use 2 cycle wait adc r17,r22 ; r22 is cleared above in r6,pina ; get data in r18,pinc ; get data cp r16,r26 ; check if at end of buffer cpc r17,r27 brlo wait4_000039 ; do nothing if not at end of buffer clr r16 ; reset buffer to bottom clr r17 wait4_000039: ; check if byte has been sent in r19,spsr sbrs r19,spif rjmp wait4_000039 in r19,spdr ; recieve in right channel lsb ;get left channel sample 2 data from sram out portd,r16 ; set address sts porth,r17 nop ; wait 2 cycle setup time nop in r7,pina ; get data in r19,pinc ; get data ;multiply sample 1 by distance mov r20,r23 ; get distance from sample 1 com r20 mulsu r18,r20 ; (signed)Ah * (unsigned)B movw r5:r4,r1:r0 mul r6,r20 ; (unsigned)Al * (unsigned)B add r4,r1 adc r5,r22 ; r22 is cleared above mov r17,r0 ;multiply and accumulate sample 2 by distance mulsu r19,r23 ; (signed)Ah * (unsigned)B add r4,r0 ; accumulate result adc r5,r1 mul r7,r23 ; (unsigned)Al * (unsigned)B add r17,r0 ; accumulate result adc r4,r1 adc r5,r22 ; r22 is cleared above ;get sample from other side of buffer movw r17:r16,r29:r28 ; move current position to temporary register movw r7:r6,r27:r26 ; move buffer size to temporary register lsr r7 ; divide buffer size by 2 ror r6 cp r16,r6 ; check if in lower or upper half of buffer cpc r17,r7 brsh buffer_flip_000039 ; subtract half buffer if in upper half add r16,r6 ; add half buffer size if in lower half adc r17,r7 rjmp getsample3_000039 ; continue buffer_flip_000039: ; adjust to opposite side of memory sub r16,r6 ; subtract half buffer size if in upper half sbc r17,r7 getsample3_000039: ;get left channel sample 3 data from sram out portd,r16 ; set address sts porth,r17 add r16,r21 ; increment read address - r21 set to $01 above adc r17,r22 ; r22 is cleared above in r6,pina ; get data in r18,pinc ; get data cp r16,r26 ; check if at end of buffer cpc r17,r27 brlo getsample4_000039 ; do nothing if not at end of buffer clr r16 ; reset buffer to bottom clr r17 getsample4_000039: ;get left channel sample 4 data from sram out portd,r16 ; set address sts porth,r17 nop ; wait 2 cycle setup time nop in r7,pina ; get data in r19,pinc ; get data ;multiply sample 3 by distance mulsu r18,r20 ; (signed)ah * b movw r3:r2,r1:r0 mul r6,r20 ; al * b add r2,r1 adc r3,r22 ; r22 is cleared above mov r17,r0 ;multiply sample 4 by distance mulsu r19,r23 ; (signed)ah * b add r2,r0 ; accumulate result adc r3,r1 mul r7,r23 ; al * b add r17,r0 ; accumulate result adc r2,r1 adc r3,r22 ; r22 is cleared above ;get distance to boundary movw r17:r16,r29:r28 ; move read address to temporary register mov r18,r23 sub r16,r24 ; find distance to loop boundary sbc r17,r25 brcc half_000039 ; check if result is negative com r16 ; invert distance if negative com r17 com r18 add r18,r21 ; r21 set to $01 above adc r16,r22 ; r22 cleared above adc r17,r22 half_000039: ; check if result is greater than half the buffer size movw r7:r6,r27:r26 ; move buffer size to temporary register lsr r7 ; divide buffer size by 2 ror r6 cp r16,r6 ; check if result is greater than half the buffer size cpc r17,r7 brlo scale_000039 ; skip flip if not sub r16,r26 ; flip result around boundary sbc r17,r27 com r16 com r17 com r18 add r18,r21 ; r21 set to $01 above adc r16,r22 adc r17,r22 scale_000039: ; scale distance to match buffer size - 50% accurate movw r7:r6,r27:r26 ; move buffer size to temporary register sbrc r7,$07 ; check if msb of buffer size is set rjmp attenuate_000039 ; attenuate signal if 16b value shift_000039: ; shift buffer size till it occupies full 16b lsl r6 ; multiply buffer size by 2 rol r7 lsl r18 ; multiply distance by 2 rol r16 rol r17 sbrs r7,$07 ; check if msb of buffer size is set rjmp shift_000039 ; keep checking if not set attenuate_000039: ; multiply sample 1/2 by distance lsl r18 ; multiply distance by 2 since max value is 1/2 buffer size rol r16 rol r17 sub r6,r16 ; find complementary distance of sample 3/4 sbc r7,r17 ; only 1 bit error for not subtracting r18 as well movw r21:r20,r7:r6 ; move distance to signed multiply register movw r19:r18,r5:r4 ; move value to signed multiply register mulsu r19,r17 ; (signed)ah * bh movw r5:r4,r1:r0 mul r18,r16 ; al * bl movw r7:r6,r1:r0 mulsu r19,r16 ; (signed)ah * bl sbc r5,r22 ; r22 is cleared above add r7,r0 adc r4,r1 adc r5,r22 mul r17,r18 ; bh * al add r7,r0 adc r4,r1 adc r5,r22 ;multiply and accumulate sample 3/4 with result from above movw r19:r18,r3:r2 ; move value to signed multiply register mulsu r19,r21 ; (signed)ah * bh add r4,r0 ; accumulate result adc r5,r1 mul r18,r20 ; al * bl add r6,r0 ; accumulate result adc r7,r1 adc r4,r22 ; r22 is cleared above adc r5,r22 mulsu r19,r20 ; (signed)ah * bl sbc r5,r22 ; accumulate result add r7,r0 adc r4,r1 adc r5,r22 mul r21,r18 ; bh * al add r7,r0 adc r4,r1 adc r5,r22 rotary_000039: ; check rotary encoder and adjust playback rate ; rotary encoder is externally debounced, so that is not done here. ; pin1 is sampled on a transition from high to low on pin0. if pin1 is ; high, a left turn occured, if pin1 is low, a right turn occured. dec r14 ; reduce the sampling rate to help with debounce brne check_000039 ; continue if not ready yet ldi r17,$40 ; adjust sample frequency to catch all rising edges (1.5ms) mov r14,r17 lds r17,pinj ; get switch data sbrs r17,$00 ; check if pin0 is low rjmp edge_000039 ; check if pin0 was low on previous sample clt ; clear state register if back high rjmp check_000039 ; finish off edge_000039: ; check for falling edge brts check_000039 ; do nothing if the edge was already detected set ; set state register to indicate a falling edge occured sbrs r17,$01 ; check if pin1 is high rjmp increment_000039 ; increment playback if right rotation ldi r16,$01 ; check if pitch at min cp r8,r16 brlo check_000039 ; do nothing it at bottom dec r8 ; decrement rotary encoder position counter movw r17:r16,z ; store z register ldi zh,$4c ; setup z pointer to fetch tone from lookup table mov zl,r8 lsl zl lpm r12,z+ ; move tone to pitch register lpm r13,z movw z,r17:r16 ; restore z register rjmp check_000039 ; finish off increment_000039: ; increment playback speed ldi r16,$18 ; check if pitch at max cp r8,r16 brsh reset1_000039 ; do nothing if at max already inc r8 ; increment rotary encoder position counter movw r17:r16,z ; store z register ldi zh,$4c ; setup z pointer to fetch tone from lookup table mov zl,r8 lsl zl lpm r12,z+ ; move tone to pitch register lpm r13,z movw z,r17:r16 ; restore z register rjmp check_000039 ; finish off reset1_000039: ; reset tone register in case it goes too high ldi r16,$18 ; set tone register to max mov r8,r16 check_000039: ; check if buffer size is correct lds r16,delay_mem_000039 ; fetch desired buffer size lds r17,(delay_mem_000039 + 1) ; i ran out of registers cp r26,r16 ; compare current delay to desired delay cpc r27,r17 brlo upcount_000039 ; increment if smaller than breq adcsample_000039 ; do nothing if they are same size sbiw r27:r26,$02 ; decrement buffer size rjmp adcsample_000039 ; finish off upcount_000039: ; increment buffer size register adiw r27:r26,$02 ; increment buffer size adcsample_000039: ; get loop setting lds r17,adcsra ; get adc control register sbrs r17,adif ; check if adc conversion is complete rjmp done_000039 ; skip adc sampling lds r16,adcl ; get low byte adc value lds r17,adch ; get high byte adc value add r10,r16 ; accumulate adc samples adc r11,r17 adc r9,r22 ; r22 is cleared above ldi r17,$f7 sts adcsra,r17 ; clear interrupt flag dec r15 ; countdown adc sample clock brne done_000039 ; move adc value to loop setting after 256 samples lsr r9 ; divide accumulated value by 4 to make a 16b value ror r11 ror r10 lsr r9 ror r11 ror r10 ldi r16,low(buffer_min_000039) ; fetch min buffer size ldi r17,high(buffer_min_000039) cp r10,r16 ; compare adc value to min buffer size cpc r11,r17 brsh compare_000039 ; skip if above minimum buffer size movw r11:r10,r17:r16 ; else set to minimum buffer size compare_000039: ; compare to previous value lds r16,delay_mem_000039 ; fetch desired delay time lds r17,(delay_mem_000039 + 1) ; i ran out of registers sub r16,r10 ; find difference between adc value and desired buffer size sbc r17,r11 brcc deadband_000039 ; check for magnitude of change if positive neg r16 ; else invert difference if negative adc r17,r22 ; r22 is cleared above neg r17 deadband_000039: ; see if pot has moved or if its just noise cpi r16,$40 ; see if difference is greater than 1 lsb cpc r17,r22 ; r22 is cleared above brlo nochange_000039 ; dont update loop time if difference is not large enough ldi r16,$fe ; make sure buffer size is even and r10,r16 sts delay_mem_000039,r10 ; store new desired buffer size sts (delay_mem_000039 + 1),r11 ; i ran out of registers nochange_000039: ; clear accumulation registers clr r10 ; empty accumulation registers clr r11 clr r9 switchsample_000039: ; check rotary switch state lds r16,pinj ; get switch data andi r16,$78 ; mask off rotary switch lsr r16 ; adjust switch position to program memory location lsr r16 ldi r17,$02 add r16,r17 cpse r16,r31 ; check if location has changed clr r30 ; reset jump register to intial state mov r31,r16 done_000039: reti ; return to waiting |
Chromatic Pitch Shifter
This function implements a pitch shifter. It takes mono in on the left channel, and outputs a mono signal to both the left and the right channels. The rotary encoder (MOD2) adjusts the pitch shift amount, and the pot (MOD1) adjusts the buffer size (and resultant delay).
The easiest way to change the pitch of a signal, is to play back the samples at a faster or slower rate, although this introduces two problems. The first is that if you are sending out data at a rate different from the rate at which you are receiving data, you will eventually run out of data to send out. This is called a buffer over-run or under-run, depending upon whether you're going faster or slower, and hitting the top or bottom of the buffer (the data stored in SRAM). The second problem is that the data is sampled at discreet points in time, and if you want a playback speed that is not a multiple of this time, you will need data from somewhere between those sample periods.
There are a number of options for how to deal with buffer boundaries, but the main issue which we are trying to overcome, is the sharp transition as you go from one end of the buffer to the other, and the data is no longer consistent. This creates an audible click in the sample playback. In this case, we are using a fading method. This involves having two samples playing back simultaneously, each from a different point in the buffer (spaced a half-buffer's distance from each other). As one sample gets closer to the boundary, its volume is faded down, and the other is faded up. This continues as each sample moves forward in the buffer, with the volume of the sample being determined by its distance from the buffer boundary. This gives very smooth transitions across the buffer boundary, but also has a slight reverb effect, as multiple delayed signals are being mixed together.
The most common method of dealing with the second problem (fractional sample rates) is interpolation. Interpolation is a method of guessing what a value might have been if we actually had sampled at that point in time. For this pitch-shifter function, we use a linear interpolation. This means we draw a straight line between the two adjacent samples from where we want data, and assume our value is on that line. So if we're closer in time to one sample versus the other, than our output value is closer in value to that sample (the output is a sum of the two values, weighted by their distance to our sample point).
The pot (MOD1) varies the buffer size used for sample playback, from 12ms to 1.5s. Smaller buffer sizes give a more accurate pitch-shifting effect, as you are playing through only very small samples at a time, and do not hear much of a delay. If the buffer is too small, you begin to hear the rate at which you are moving through the buffer, almost like a slight tremolo. For very large buffer sizes, it becomes a pitch shifted delay, which can be interesting when used with feedback, as each time the signal gets fed back in, its pitch is shifted again, causing an ever rising tone.
The rotary encoder (MOD2) varies the pitch shift amount, from -1 octave to +1 octave, in 12 chromatic steps each direction. In this way, the output can always be made to be "in tune" with the original signal. Rotations to the right increase the pitch, and vice versa. To maintain the correct pitch shift amount, a look-up table in program memory is used to store the precalculated values for each step.
1 ; program: pitch_shifter-16b-fading.asm
2 ; UID = 000039 - this is a unique id so variables dont conflict
3 ; 16b address space (1.5s sample time)
4 ; mono data in on left channel, mono out on both left and right
5 ; rotary encoder (MOD2) controlled playback speed
6 ; pot (MOD1) controlled buffer size
7
8 ; program overview
9 ;
10 ; data is sent out and taken in from the codec. data is taken in on the
11 ; left channel, and played out on both left and right. a buffer of the
12 ; past n seconds is kept and the output is the result of sampling this
13 ; buffer at varying playback speeds. the speed at which it plays through
14 ; the memory is controlled by the rotary encoder (MOD2). turning the
15 ; encoder to the right speeds playback up, and turning it the left slows
16 ; playback down. this playback speed is limited to chromatic steps by
17 ; using a lookup table in program memory to determine playback speed. the
18 ; audio is kept clean over fractional sample periods by interpolating
19 ; between the two closest samples. the output is a mix of the current
20 ; sample, and a sample from the opposite side of the buffer, with the
21 ; relative mix being determined by the distance to the buffer boundary.
22 ; in this way, the audio is faded down as it crosses the buffer boundary.
23 ; the pot (MOD1) controls the buffer size. the adc samples the pot 256
24 ; times and deadbands the signal to remove glitches.
25
26 ; constant definitions
27 ;
28 .equ buffer_min_000039 = $0200 ; minimum buffer size
29 .equ delay_mem_000039 = $0200 ; memory position for desired delay time
30 ;
31 ;.equ step-12_000039 = $0080 ; these are the playback speeds used
32 ;.equ step-11_000039 = $0088 ; they are stored in program memory
33 ;.equ step-10_000039 = $0090 ; and not used here
34 ;.equ step-9_000039 = $0098
35 ;.equ step-8_000039 = $00A1
36 ;.equ step-7_000039 = $00AB
37 ;.equ step-6_000039 = $00B5
38 ;.equ step-5_000039 = $00C0
39 ;.equ step-4_000039 = $00CB
40 ;.equ step-3_000039 = $00D7
41 ;.equ step-2_000039 = $00E4
42 ;.equ step-1_000039 = $00F2
43 ;.equ step00_000039 = $0100
44 ;.equ step01_000039 = $010F
45 ;.equ step02_000039 = $011F
46 ;.equ step03_000039 = $0130
47 ;.equ step04_000039 = $0143
48 ;.equ step05_000039 = $0156
49 ;.equ step06_000039 = $016A
50 ;.equ step07_000039 = $0180
51 ;.equ step08_000039 = $0196
52 ;.equ step09_000039 = $01AF
53 ;.equ step10_000039 = $01C8
54 ;.equ step11_000039 = $01E3
55 ;.equ step12_000039 = $0200
56
57 ; register usage - may be redefined in other sections
58 ;
59 ; r0 multiply result lsb
60 ; r1 multiply result msb
61 ; r2 sample 3/4 lsb
62 ; r3 sample 3/4 msb
63 ; r4 left/right lsb out
64 ; r5 left/right msb out
65 ; r6 left lsb in/temporary swap register
66 ; r7 left msb in/temporary swap register
67 ; r8 rotary encoder position counter
68 ; r9 adc msb accumulator
69 ; r10 adc fractional byte accumulator
70 ; r11 adc lsb accumulator
71 ; r12 playback speed increment lsb value ($0100 is normal speed)
72 ; r13 playback speed increment msb value
73 ; r14 rotary encoder counter
74 ; r15 switch\adc counter
75 ; r16 temporary swap register
76 ; r17 temporary swap register
77 ; r18 signed multiply register
78 ; r19 signed multiply register
79 ; r20 unsigned multiply register
80 ; r21 unsigned multiply register
81 ; r22 write address third byte/null register
82 ; r23 read address fractional byte
83 ; r24 write address lsb
84 ; r25 write address msb
85 ; r26 buffer length lsb
86 ; r27 buffer length msb
87 ; r28 read address lsb
88 ; r29 read address msb
89 ; r30 jump location for interrupt lsb
90 ; r31 jump location for interrupt msb
91 ; t rotary encoder edge indicator
92
93 ;program starts here first time
94 ; intialize registers
95 ldi r30,$27 ; set jump location to program start
96 clr r24 ; clear write register
97 clr r25
98 ldi r22,$00 ; setup write address high byte
99 clr r18 ; setup r18 as null register for carry addition and ddr setting
100 ldi r17,$ff ; setup r17 for ddr setting
101
102 clear_000039: ; clear delay buffer
103 ; eliminates static when first switching to the delay setting
104
105 adiw r25:r24,$01 ; increment write register
106 adc r22,r18 ; increment write third byte
107 cpi r22,$01 ; check if 16b memory space has been cleared
108 breq cleardone_000039 ; continue until end of buffer reached
109 out portd,r24 ; set address
110 sts porth,r25
111 out portg,r22 ; pull ce low,we low,and set high bits of address
112 out ddra,r17 ; set porta as output for data write
113 out ddrc,r17 ; set portc as output for data write
114 out porta,r18 ; set data
115 out portc,r18 ; r18 is cleared above
116 sbi portg,portg2 ; pull we high to write
117 out ddra,r18 ; set porta as input for data lines
118 out ddrc,r18 ; set portc as input for data lines
119 rjmp clear_000039 ; continue clearing
120
121 cleardone_000039: ; reset registers
122
123 ldi r24,$00 ; initialize write register
124 ldi r25,$00
125 clr r22 ; setup null register
126 ldi r28,$00 ; set read address to minimum delay
127 ldi r29,$fd
128 clr r4 ; initialize data output registers
129 clr r5
130 ldi r26,$00 ; initialize buffer size
131 ldi r27,$06
132 sts delay_mem_000039,r26 ; store desired buffer size
133 sts (delay_mem_000039 + 1),r27 ; i ran out of registers
134 clr r12 ; initialize playback speed
135 ldi r16,$01
136 mov r13,r16
137 reti ; return and wait for next interrupt
138
139 ;program begins here every time but first
140 ; initiate data transfer to codec
141 sbi portb,portb0 ; toggle slave select pin
142 out spdr,r5 ; send out left channel msb
143 cbi portb,portb0
144
145 ;increment write address
146 adiw r25:r24,$01 ; increment write address
147 cp r24,r26 ; check if at end of buffer
148 cpc r25,r27
149 brlo wait1_000039 ; do nothing if not at end of buffer
150 clr r24 ; reset buffer to bottom
151 clr r25
152
153 wait1_000039: ; check if byte has been sent
154
155 in r17,spsr
156 sbrs r17,spif
157 rjmp wait1_000039
158 in r7,spdr ; recieve in left channel msb
159 out spdr,r4 ; send out left channel lsb
160
161 ;increment read address
162 add r23,r12 ; increment read register
163 adc r28,r13
164 adc r29,r22 ; r22 is cleared above
165 cp r28,r26 ; check if at end of buffer
166 cpc r29,r27
167 brlo wait2_000039 ; do nothing if not at end of buffer
168 clr r28 ; reset buffer to bottom
169 clr r29
170
171 wait2_000039: ; check if byte has been sent
172
173 in r17,spsr
174 sbrs r17,spif
175 rjmp wait2_000039
176 in r6,spdr ; recieve in left channel lsb
177 out spdr,r5 ; send out right channel msb
178
179 ;write left channel data to sram
180 out portd,r24 ; set address
181 sts porth,r25
182 out portg,r22 ; pull ce low,we low,and set high bits of address
183 ldi r17,$ff
184 out ddra,r17 ; set porta as output for data write
185 out ddrc,r17 ; set portc as output for data write
186 out porta,r6 ; set data
187 out portc,r7
188 sbi portg,portg2 ; pull we high to write
189 out ddra,r22 ; set porta as input for data lines
190 out ddrc,r22 ; set portc as input for data lines
191
192 wait3_000039: ; check if byte has been sent
193
194 in r17,spsr
195 sbrs r17,spif
196 rjmp wait3_000039
197 in r17,spdr ; recieve in right channel msb
198 out spdr,r4 ; send out right channel lsb
199
200 ;get left channel sample 1 data from sram
201 movw r17:r16,r29:r28 ; move read address to temporary register
202 out portd,r16 ; set address
203 sts porth,r17
204 ldi r21,$01 ; increment read address
205 add r16,r21 ; placed here to use 2 cycle wait
206 adc r17,r22 ; r22 is cleared above
207 in r6,pina ; get data
208 in r18,pinc ; get data
209 cp r16,r26 ; check if at end of buffer
210 cpc r17,r27
211 brlo wait4_000039 ; do nothing if not at end of buffer
212 clr r16 ; reset buffer to bottom
213 clr r17
214
215 wait4_000039: ; check if byte has been sent
216
217 in r19,spsr
218 sbrs r19,spif
219 rjmp wait4_000039
220 in r19,spdr ; recieve in right channel lsb
221
222 ;get left channel sample 2 data from sram
223 out portd,r16 ; set address
224 sts porth,r17
225 nop ; wait 2 cycle setup time
226 nop
227 in r7,pina ; get data
228 in r19,pinc ; get data
229
230 ;multiply sample 1 by distance
231 mov r20,r23 ; get distance from sample 1
232 com r20
233 mulsu r18,r20 ; (signed)Ah * (unsigned)B
234 movw r5:r4,r1:r0
235 mul r6,r20 ; (unsigned)Al * (unsigned)B
236 add r4,r1
237 adc r5,r22 ; r22 is cleared above
238 mov r17,r0
239
240 ;multiply and accumulate sample 2 by distance
241 mulsu r19,r23 ; (signed)Ah * (unsigned)B
242 add r4,r0 ; accumulate result
243 adc r5,r1
244 mul r7,r23 ; (unsigned)Al * (unsigned)B
245 add r17,r0 ; accumulate result
246 adc r4,r1
247 adc r5,r22 ; r22 is cleared above
248
249 ;get sample from other side of buffer
250 movw r17:r16,r29:r28 ; move current position to temporary register
251 movw r7:r6,r27:r26 ; move buffer size to temporary register
252 lsr r7 ; divide buffer size by 2
253 ror r6
254 cp r16,r6 ; check if in lower or upper half of buffer
255 cpc r17,r7
256 brsh buffer_flip_000039 ; subtract half buffer if in upper half
257 add r16,r6 ; add half buffer size if in lower half
258 adc r17,r7
259 rjmp getsample3_000039 ; continue
260
261 buffer_flip_000039: ; adjust to opposite side of memory
262
263 sub r16,r6 ; subtract half buffer size if in upper half
264 sbc r17,r7
265
266 getsample3_000039: ;get left channel sample 3 data from sram
267
268 out portd,r16 ; set address
269 sts porth,r17
270 add r16,r21 ; increment read address - r21 set to $01 above
271 adc r17,r22 ; r22 is cleared above
272 in r6,pina ; get data
273 in r18,pinc ; get data
274 cp r16,r26 ; check if at end of buffer
275 cpc r17,r27
276 brlo getsample4_000039 ; do nothing if not at end of buffer
277 clr r16 ; reset buffer to bottom
278 clr r17
279
280 getsample4_000039: ;get left channel sample 4 data from sram
281
282 out portd,r16 ; set address
283 sts porth,r17
284 nop ; wait 2 cycle setup time
285 nop
286 in r7,pina ; get data
287 in r19,pinc ; get data
288
289 ;multiply sample 3 by distance
290 mulsu r18,r20 ; (signed)ah * b
291 movw r3:r2,r1:r0
292 mul r6,r20 ; al * b
293 add r2,r1
294 adc r3,r22 ; r22 is cleared above
295 mov r17,r0
296
297 ;multiply sample 4 by distance
298 mulsu r19,r23 ; (signed)ah * b
299 add r2,r0 ; accumulate result
300 adc r3,r1
301 mul r7,r23 ; al * b
302 add r17,r0 ; accumulate result
303 adc r2,r1
304 adc r3,r22 ; r22 is cleared above
305
306 ;get distance to boundary
307 movw r17:r16,r29:r28 ; move read address to temporary register
308 mov r18,r23
309 sub r16,r24 ; find distance to loop boundary
310 sbc r17,r25
311 brcc half_000039 ; check if result is negative
312 com r16 ; invert distance if negative
313 com r17
314 com r18
315 add r18,r21 ; r21 set to $01 above
316 adc r16,r22 ; r22 cleared above
317 adc r17,r22
318
319 half_000039: ; check if result is greater than half the buffer size
320
321 movw r7:r6,r27:r26 ; move buffer size to temporary register
322 lsr r7 ; divide buffer size by 2
323 ror r6
324 cp r16,r6 ; check if result is greater than half the buffer size
325 cpc r17,r7
326 brlo scale_000039 ; skip flip if not
327 sub r16,r26 ; flip result around boundary
328 sbc r17,r27
329 com r16
330 com r17
331 com r18
332 add r18,r21 ; r21 set to $01 above
333 adc r16,r22
334 adc r17,r22
335
336 scale_000039: ; scale distance to match buffer size - 50% accurate
337
338 movw r7:r6,r27:r26 ; move buffer size to temporary register
339 sbrc r7,$07 ; check if msb of buffer size is set
340 rjmp attenuate_000039 ; attenuate signal if 16b value
341
342 shift_000039: ; shift buffer size till it occupies full 16b
343
344 lsl r6 ; multiply buffer size by 2
345 rol r7
346 lsl r18 ; multiply distance by 2
347 rol r16
348 rol r17
349 sbrs r7,$07 ; check if msb of buffer size is set
350 rjmp shift_000039 ; keep checking if not set
351
352 attenuate_000039: ; multiply sample 1/2 by distance
353
354 lsl r18 ; multiply distance by 2 since max value is 1/2 buffer size
355 rol r16
356 rol r17
357 sub r6,r16 ; find complementary distance of sample 3/4
358 sbc r7,r17 ; only 1 bit error for not subtracting r18 as well
359 movw r21:r20,r7:r6 ; move distance to signed multiply register
360 movw r19:r18,r5:r4 ; move value to signed multiply register
361 mulsu r19,r17 ; (signed)ah * bh
362 movw r5:r4,r1:r0
363 mul r18,r16 ; al * bl
364 movw r7:r6,r1:r0
365 mulsu r19,r16 ; (signed)ah * bl
366 sbc r5,r22 ; r22 is cleared above
367 add r7,r0
368 adc r4,r1
369 adc r5,r22
370 mul r17,r18 ; bh * al
371 add r7,r0
372 adc r4,r1
373 adc r5,r22
374
375 ;multiply and accumulate sample 3/4 with result from above
376 movw r19:r18,r3:r2 ; move value to signed multiply register
377 mulsu r19,r21 ; (signed)ah * bh
378 add r4,r0 ; accumulate result
379 adc r5,r1
380 mul r18,r20 ; al * bl
381 add r6,r0 ; accumulate result
382 adc r7,r1
383 adc r4,r22 ; r22 is cleared above
384 adc r5,r22
385 mulsu r19,r20 ; (signed)ah * bl
386 sbc r5,r22 ; accumulate result
387 add r7,r0
388 adc r4,r1
389 adc r5,r22
390 mul r21,r18 ; bh * al
391 add r7,r0
392 adc r4,r1
393 adc r5,r22
394
395 rotary_000039: ; check rotary encoder and adjust playback rate
396 ; rotary encoder is externally debounced, so that is not done here.
397 ; pin1 is sampled on a transition from high to low on pin0. if pin1 is
398 ; high, a left turn occured, if pin1 is low, a right turn occured.
399 dec r14 ; reduce the sampling rate to help with debounce
400 brne check_000039 ; continue if not ready yet
401 ldi r17,$40 ; adjust sample frequency to catch all rising edges (1.5ms)
402 mov r14,r17
403 lds r17,pinj ; get switch data
404 sbrs r17,$00 ; check if pin0 is low
405 rjmp edge_000039 ; check if pin0 was low on previous sample
406 clt ; clear state register if back high
407 rjmp check_000039 ; finish off
408
409 edge_000039: ; check for falling edge
410
411 brts check_000039 ; do nothing if the edge was already detected
412 set ; set state register to indicate a falling edge occured
413 sbrs r17,$01 ; check if pin1 is high
414 rjmp increment_000039 ; increment playback if right rotation
415 ldi r16,$01 ; check if pitch at min
416 cp r8,r16
417 brlo check_000039 ; do nothing it at bottom
418 dec r8 ; decrement rotary encoder position counter
419 movw r17:r16,z ; store z register
420 ldi zh,$4c ; setup z pointer to fetch tone from lookup table
421 mov zl,r8
422 lsl zl
423 lpm r12,z+ ; move tone to pitch register
424 lpm r13,z
425 movw z,r17:r16 ; restore z register
426 rjmp check_000039 ; finish off
427
428 increment_000039: ; increment playback speed
429
430 ldi r16,$18 ; check if pitch at max
431 cp r8,r16
432 brsh reset1_000039 ; do nothing if at max already
433 inc r8 ; increment rotary encoder position counter
434 movw r17:r16,z ; store z register
435 ldi zh,$4c ; setup z pointer to fetch tone from lookup table
436 mov zl,r8
437 lsl zl
438 lpm r12,z+ ; move tone to pitch register
439 lpm r13,z
440 movw z,r17:r16 ; restore z register
441 rjmp check_000039 ; finish off
442
443 reset1_000039: ; reset tone register in case it goes too high
444
445 ldi r16,$18 ; set tone register to max
446 mov r8,r16
447
448 check_000039: ; check if buffer size is correct
449
450 lds r16,delay_mem_000039 ; fetch desired buffer size
451 lds r17,(delay_mem_000039 + 1) ; i ran out of registers
452 cp r26,r16 ; compare current delay to desired delay
453 cpc r27,r17
454 brlo upcount_000039 ; increment if smaller than
455 breq adcsample_000039 ; do nothing if they are same size
456 sbiw r27:r26,$02 ; decrement buffer size
457 rjmp adcsample_000039 ; finish off
458
459 upcount_000039: ; increment buffer size register
460
461 adiw r27:r26,$02 ; increment buffer size
462
463 adcsample_000039: ; get loop setting
464
465 lds r17,adcsra ; get adc control register
466 sbrs r17,adif ; check if adc conversion is complete
467 rjmp done_000039 ; skip adc sampling
468 lds r16,adcl ; get low byte adc value
469 lds r17,adch ; get high byte adc value
470 add r10,r16 ; accumulate adc samples
471 adc r11,r17
472 adc r9,r22 ; r22 is cleared above
473 ldi r17,$f7
474 sts adcsra,r17 ; clear interrupt flag
475 dec r15 ; countdown adc sample clock
476 brne done_000039 ; move adc value to loop setting after 256 samples
477 lsr r9 ; divide accumulated value by 4 to make a 16b value
478 ror r11
479 ror r10
480 lsr r9
481 ror r11
482 ror r10
483 ldi r16,low(buffer_min_000039) ; fetch min buffer size
484 ldi r17,high(buffer_min_000039)
485 cp r10,r16 ; compare adc value to min buffer size
486 cpc r11,r17
487 brsh compare_000039 ; skip if above minimum buffer size
488 movw r11:r10,r17:r16 ; else set to minimum buffer size
489
490 compare_000039: ; compare to previous value
491
492 lds r16,delay_mem_000039 ; fetch desired delay time
493 lds r17,(delay_mem_000039 + 1) ; i ran out of registers
494 sub r16,r10 ; find difference between adc value and desired buffer size
495 sbc r17,r11
496 brcc deadband_000039 ; check for magnitude of change if positive
497 neg r16 ; else invert difference if negative
498 adc r17,r22 ; r22 is cleared above
499 neg r17
500
501 deadband_000039: ; see if pot has moved or if its just noise
502
503 cpi r16,$40 ; see if difference is greater than 1 lsb
504 cpc r17,r22 ; r22 is cleared above
505 brlo nochange_000039 ; dont update loop time if difference is not large enough
506 ldi r16,$fe ; make sure buffer size is even
507 and r10,r16
508 sts delay_mem_000039,r10 ; store new desired buffer size
509 sts (delay_mem_000039 + 1),r11 ; i ran out of registers
510
511 nochange_000039: ; clear accumulation registers
512
513 clr r10 ; empty accumulation registers
514 clr r11
515 clr r9
516
517 switchsample_000039: ; check rotary switch state
518
519 lds r16,pinj ; get switch data
520 andi r16,$78 ; mask off rotary switch
521 lsr r16 ; adjust switch position to program memory location
522 lsr r16
523 ldi r17,$02
524 add r16,r17
525 cpse r16,r31 ; check if location has changed
526 clr r30 ; reset jump register to intial state
527 mov r31,r16
528
529 done_000039:
530
531 reti ; return to waiting
532