==== Sampler with Pitch Shifter ==== This function implements a sampler with a 6s sample time. The input is mono on the left channel, and the output is mono on both left and right channels. The pushbutton on the rotary encoder (MOD2) stores data to memory when pressed, and plays back data when released. The pot (MOD1) varies the playback speed, from -2 octaves to +1 octave. 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 cross-fading method. This is the same as cross-fading between records when DJing. As one sample gets close to the buffer boundary, its volume is faded down, and a sample from the other side of the boundary is faded up. This continues as the sample moves forward in the buffer, with the volume of the first sample being reduced to zero by the time it gets to the buffer boundary. At this point, the sample on the other side is playing full volume, and takes over. This gives a relatively smooth transition across the buffer boundary, with only a slight dipping noticeable, but also keeps the playback very true to the original signal during the majority of playback, which is not near the boundary. The crossfade time is preset at the begging of the code, which also determines the minimum sample size, as there has to be enough data for both fading up and down on either side of the boundary. 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 pushbutton on MOD2 controls the sample size. It begins recording when the button is pressed, and stops when released. If the button is held down for a period less than the minimum buffer size, the sample is increased to this minimum size (although its next to impossible to hit the button that quickly). If the button is held down longer than the buffer size (6s), then only the first 6s of audio will be stored and played back when the button is released. [[attachment:sampler_6s.asm|sampler_6s.asm]] ---- {{{#!highlight nasm ; program: sampler-18b-pot.asm ; UID = 000058 - unique id to eliminate conflicts between variables ; 18b address space (6s sample time) ; mono data in on left channel, mono data out on left and right ; pot (MOD1) controlled playback speed ; program overview ; ; data is read in from the codec and placed into memory. a memory address ; pointer is incrmented to find the next sample, and this is sent to the ; codec. the pointer is incremented at a variable rate, with fractional ; values less than one slowing down the playback speed, and fractional ; values above one increasing the playback speed. the output data is an ; interpolation of the two samples adjacent to the pointer. left channel ; data is taken in, and the result is placed on both left and right. ADC0 ; is averaged over 256 samples and is used to create the pointer increment ; value. the pushbutton takes in data when depressed, and plays back when ; released. the volume is reduced around the sample boundary to reduce ; clicking sounds. ; constant definitions ; .equ stepsize_000058 = ($0100 / fade_000058) ; crossfade counter decrement .equ fade_000058 = $04 ; crossfade sample distance ($02 - $70 valid) ; crossfade time [ms] = ((fade x 256) / 44.1) .equ minbuff_000058 = (($0240 * fade_000058 * 2) + $0200) ; minimum buffer size to ensure that there is enough time for fading .equ mem_000058 = $0200 ; memory location for opposing buffer address ; i ran out of registers ; register usage - may be redefined in other sections ; ; r0 multiply result lsb ; r1 multiply result msb ; r2 left lsb in ; r3 left msb in ; r4 left/right lsb out ; r5 left/right msb out ; r6 temporary swap register ; r7 temporary swap register ; r8 playback speed fractional byte ; r9 adc accumulation fractional byte ; r10 adc accumulation lsb ; r11 adc accumulation msb ; r12 playback speed lsb ; r13 playback speed msb ; r14 null register ; r15 switch sample counter ; r16 temporary swap register ; r17 temporary swap register ; r18 temporary swap register ; r19 temporary swap register ; r20 fade state register ; r21 read address fractional byte ; r22 write address/buffer size high byte ; r23 read address high byte ; r24 write address/buffer size lsb ; r25 write address/buffer size msb ; r26 crossfade distance lsb ; r27 crossfade distance msb ; r28 read address lsb ; r29 read address msb ; r30 jump location for interrupt lsb ; r31 jump location for interrupt msb ; t sampler record indicator ;program starts here first time ; initialize registers ; memory is not blanked in case you want to sample a neighboring function ldi r30,$11 ; set jump location to program start clr r14 ; set up null register clr r24 ; set initial buffer size to first 16b clr r25 ldi r22,$01 ldi r16,$01 clr r12 ; initialize playback speed to normal mov r13,r16 clr r9 ; clear accumulation registers clr r10 clr r11 clr r20 ; initialize fading state register clr r28 ; initialize read address clr r29 clr r23 clt ; initialize sampler indicator reti ; return and wait for next interrupt ;program starts 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 wait1_000058: ; check if byte has been sent in r17,spsr sbrs r17,spif rjmp wait1_000058 in r3,spdr ; recieve in left channel msb out spdr,r4 ; send out left channel lsb wait2_000058: ; check if byte has been sent in r17,spsr sbrs r17,spif rjmp wait2_000058 in r2,spdr ; recieve in left channel lsb out spdr,r5 ; send out right channel msb wait3_000058: ; check if byte has been sent in r17,spsr sbrs r17,spif rjmp wait3_000058 in r17,spdr ; recieve in right channel msb out spdr,r4 ; send out right channel lsb wait4_000058: ; check if byte has been sent in r17,spsr sbrs r17,spif rjmp wait4_000058 in r17,spdr ; recieve in left channel lsb ;check pushbutton lds r16,pinj ; get pushbutton data sbrc r16,$02 ; check if pushbutton depressed rjmp interpolate_000058 ; playback if button is not depressed brts write_000058 ; skip initialization if already done set ; set the t register to indicate sampling clr r24 ; initialize the write address clr r25 clr r22 clr r19 ; initialize buffer overflow indicator ; (r19 not used elsewhere during sampling period) ldi r17,high(minbuff_000058) ; check if buffer is too small cpi r24,low(minbuff_000058) cpc r25,r17 cpc r22,r14 ; r14 is cleared above brsh write_000058 ; continue if large enough ldi r24,low(minbuff_000058) ; else set buffer size to minbuff mov r25,r17 write_000058: ; write left channel data to sram movw r5:r4,r3:r2 ; pass data through while recording sbrc r19,$00 ; check if overflow occured rjmp adcsample_000058 ; finish off if overflow out portd,r24 ; else 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,r2 ; set data out portc,r3 sbi portg,portg2 ; pull we high to write out ddra,r14 ; set porta as input for data lines out ddrc,r14 ; set portc as input for data lines adiw r25:r24,$01 ; increment write address adc r22,r14 ; r14 is cleared above sbrc r22,$02 ; check for buffer overflow ldi r19,$01 ; set overflow indicator if overflow rjmp adcsample_000058 ; else finish off interpolate_000058: ; interpolate data based upon speed setting brtc interpolate1_000058 ; check if pushbutton just released clr r28 ; initialize read address clr r29 clr r23 clt ; clear the sampling indicator interpolate1_000058: ; continue with interpolation add r21,r12 ; increment read register adc r28,r13 adc r29,r14 ; r14 is cleared above adc r23,r14 read1_000058: ; get left channel sample 1 data from sram ori r23,$04 ; set we\ bit in high byte register out portg,r23 ; pull ce low, we high, and set high bits of register out portd,r28 ; set address sts porth,r29 nop ; wait input latch time of 2 clock cycles nop in r4,pina ; get data in r5,pinc ; get data ;increment read address to next sample movw r17:r16,r29:r28 ; move read address to temporary register mov r18,r23 ldi r19,$01 ; increment read address add r16,r19 adc r17,r14 ; r14 is cleared above adc r18,r14 read2_000058: ; get left channel sample 2 data from sram ori r18,$04 ; just to be sure we\ is high out portg,r18 ; pull ce low, we high, and set high bits of register out portd,r16 ; set address sts porth,r17 nop ; wait input latch time of 2 clock cycles nop in r2,pina ; get data in r3,pinc ; get data ;multiply sample 1 by distance movw r17:r16,r5:r4 ; move sample to multiply register mov r18,r21 ; get distance from sample 1 com r18 mulsu r17,r18 ; (signed)Ah * (unsigned)B movw r5:r4,r1:r0 mul r16,r18 ; (unsigned)Al * (unsigned)B add r4,r1 ; accumulate result adc r5,r14 ; r14 is cleared above mov r19,r0 ;multiply sample 2 by distance movw r17:r16,r3:r2 ; move sample to multiply register mulsu r17,r21 ; (signed)Ah * (unsigned)B add r4,r0 ; accumulate result adc r5,r1 mul r16,r21 ; (unsigned)Al * (unsigned)B add r19,r0 ; accumulate result adc r4,r1 adc r5,r14 ; r14 is cleared above sbrc r20,$00 ; check if fading rjmp crossfade_000058 ; crossfade if appropriate ; else check if time to do so ;get distance to boundary movw r17:r16,r25:r24 ; move buffer size to temporary register clr r19 mov r18,r22 andi r23,$03 ; mask off unused bits in read high byte sub r19,r21 ; find distance to buffer boundary sbc r16,r28 sbc r17,r29 sbc r18,r23 ;subi r16,$01 ; buffer boundary is 1 sample past last sample ;sbc r17,r14 ; uncomment this if glitches occur around buffer boundary ;sbc r18,r14 ; although its been fine so far ;check if within fade distance ldi r19,fade_000058 ; fetch fade distance ;scale fade distance by playback speed (r13:r12) mul r13,r19 ; (unsigned)Ah x (unsigned)B movw r7:r6,r1:r0 mul r12,r19 ; (unsigned)Al x (unsigned)B add r6,r1 ; accumulate result adc r7,r14 ; r14 is cleared above ;compare current distance to fade distance cp r0,r16 ; compare current distance to scaled fade distance cpc r6,r17 cpc r7,r18 brsh initialize_000058 ; initialize counters if within fade distance rjmp adcsample_000058 ; else finish off initialize_000058: ; initialize crossfade registers clr r26 ; initialize crossfade counter clr r27 sts mem_000058,r26 ; initialize opposing buffer address sts (mem_000058 + 1),r27 sts (mem_000058 + 2),r21 subi r26,stepsize_000058 ; prepare crossfade counter for next cycle sbc r27,r14 ; r14 is cleared above ldi r20,$01 ; set crossfade indicator crossfade_000058: ; crossfade across sample boundary lds r16,mem_000058 ; fetch opposing buffer address lds r17,(mem_000058 + 1) lds r18,(mem_000058 + 2) add r18,r12 ; add in playback speed adc r16,r13 adc r17,r14 ; r14 is cleared above sts mem_000058,r16 ; re-store opposing buffer address sts (mem_000058 + 1),r17 sts (mem_000058 + 2),r18 ;get left channel sample 3 data from sram ldi r19,$04 out portg,r19 ; pull ce low, we high, and set high bits of register out portd,r16 ; set address sts porth,r17 nop ; wait input latch time of 2 clock cycles nop in r2,pina ; get data in r3,pinc ; get data ;increment read address to next sample ldi r19,$01 ; increment read address to next sample add r16,r19 adc r17,r14 ; r14 is cleared above ;get left channel sample 4 data from sram ;ldi r19,$04 ; portg already set above ;out portg,r19 ; pull ce low, we high, and set high bits of register out portd,r16 ; set address sts porth,r17 nop ; wait input latch time of 2 clock cycles nop in r16,pina ; get data in r17,pinc ; get data ;multiply sample 4 by distance mulsu r17,r18 ; (signed)Ah * (unsigned)B movw r7:r6,r1:r0 mul r16,r18 ; (unsigned)Al * (unsigned)B add r6,r1 ; accumulate result adc r7,r14 ; r14 is cleared above mov r19,r0 ;multiply sample 3 by distance com r18 ; get distance from sample 3 movw r17:r16,r3:r2 ; move sample to multiply register mulsu r17,r18 ; (signed)Ah * (unsigned)B add r6,r0 ; accumulate result adc r7,r1 mul r16,r18 ; (unsigned)Al * (unsigned)B add r19,r0 ; accumulate result adc r6,r1 adc r7,r14 ; r14 is cleared above ;add samples 1/2 and 3/4 together ;multiply sample 1/2 movw r17:r16,r5:r4 ; move sample 1/2 to signed multiply register movw r19:r18,r27:r26 ; move fade distance to multiply register mulsu r17,r19 ; (signed)Ah * (unsigned)Bh - multiply high bytes movw r5:r4,r1:r0 ; store high bytes result for later mul r16,r18 ; (unsigned)Al * (unsigned)Bl ; multiply low bytes movw r3:r2,r1:r0 ; store low byets for later mulsu r17,r18 ; (signed)Ah * (unsigned)Bl - multiply middle bytes sbc r5,r14 ; r14 is cleared above - subtract sign bit add r3,r0 ; accumulate result adc r4,r1 adc r5,r14 ; r14 is cleared above mul r19,r16 ; (unsigned)Bh * (unsigned)Al - multiply middle bytes add r3,r0 ; accumulate result adc r4,r1 adc r5,r14 ; r14 is cleared above ;multiply and accumulate sample 3/4 movw r17:r16,r7:r6 ; move data to signed multiply register movw r19:r18,r27:r26 ; move fade distance to multiply register com r18 ; invert distance for sample 2 com r19 mulsu r17,r19 ; (signed)Ah * (unsigned)Bh - multiply high bytes add r4,r0 ; accumulate result adc r5,r1 mul r16,r18 ; (unsigned)Al * (unsigned)Bl ; multiply low bytes add r2,r0 ; accumulate result adc r3,r1 adc r4,r14 adc r5,r14 mulsu r17,r18 ; (signed)Ah * (unsigned)Bl - multiply middle bytes sbc r5,r14 ; r14 is cleared above - subtract sign bit add r3,r0 ; accumulate result adc r4,r1 adc r5,r14 ; r14 is cleared above mul r19,r16 ; (unsigned)Bh * (unsigned)Al - multiply middle bytes add r3,r0 ; accumulate result adc r4,r1 adc r5,r14 ; r14 is cleared above ;check if done crossfading subi r26,stepsize_000058 ; reduce crossfade counter sbc r27,r14 ; r14 is cleared above breq fadedone_000058 ; reset if crossfade over brcs fadedone_000058 ; reset if crossfade over rjmp adcsample_000058 ; else finish off fadedone_000058: ; turn off crossfade lds r28,mem_000058 ; set new buffer read address lds r29,(mem_000058 + 1) lds r21,(mem_000058 + 2) clr r23 clr r20 ; reset crossfade indicator adcsample_000058: ; get speed settings lds r17,adcsra ; get adc control register sbrs r17,adif ; check if adc conversion is complete rjmp done_000058 ; skip adc sampling lds r16,adcl ; get low byte adc value lds r17,adch ; get high byte adc value add r9,r16 ; accumulate adc samples adc r10,r17 adc r11,r14 ; r14 is cleared above ldi r17,$f7 sts adcsra,r17 ; clear interrupt flag dec r15 ; countdown adc sample clock brne switchsample_000058 ; get adc value if its been long enough lsr r11 ; divide accumulated value by 2 ror r10 ror r9 ldi r17,$40 ; place in offset add r10,r17 adc r11,r14 ; r14 is cleared above ;check for deadband movw r17:r16,r11:r10 ; move adc sample to temporary register mov r18,r9 sub r18,r8 sbc r16,r12 ; find difference between adc sample and playback speed sbc r17,r13 brsh check_000058 ; check for deadband if positive com r18 ; invert if negative com r16 ; only 1 lsb error with ones complement com r17 check_000058: ; check if difference is greater than deadband cpi r18,$80 ; check if difference is less than 1 lsb cpc r16,r14 cpc r17,r14 ; r14 cleared above brlo empty_000058 ; do nothing if less than $02 movw r13:r12,r11:r10 ; move adc sample to playback speed mov r8,r9 ; if large enough change empty_000058: ; empty accumulation registers and finish off clr r9 ; empty accumulation registers clr r10 clr r11 switchsample_000058: ;check switch lds r16,pinj ; get switch data andi r16,$78 ; mask off rotary switch lsr r16 ; adjust switch position to program memory location lsr r16 subi r16,$fe ; same as adding $02 cpse r16,r31 ; check if location has changed clr r30 ; reset jump register to intial state mov r31,r16 done_000058: reti ; return to waiting }}}