welcome: please sign in

No pages like "VcoVca"!

Clear message
location: VcoVca

MICrODEC: Stock Functions

VCO / VCA

This function implements a voltage controlled oscillator (VCO), with the ability to control its amplitude (voltage controlled amplitude - VCA). The control voltage is taken from left channel input, and the output is presented on both the left and right channels. The pot (MOD1) set the oscillators base frequency, and the rotary encoder (MOD2) adjusts the functionality of the VCO. In its simplest form, it can be used as a very low distortion audio sine wave generator, which has been extremely useful for us a number of times already.

The main oscillator is a sinusoid generated from a look-up table in program memory. This consists of a 512 sample, 16 bit, half sine wave. The other half of the sine wave is generated by reading through the table a second time, and inverting all the values. If you were to play through this table at normal rate (44.1ks/s), it would produce a tone of 43Hz. To get other frequencies, it is either read through at slower or faster rate.

The problem with reading through at faster rates, is that you need samples from points in time when you did not take a sample. The most common method of dealing with this problem 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) adjusts this playback rate from 0Hz all the way to a max value, which is determined by MOD2. Each 'click' of the rotary encoder indexes the current VCO/VCA mode. It has 8 consecutive oscillator modes. This means it just outputs a sinusoid, and does neither the VCO or VCA mode. These have max values of 86Hz, 171Hz, 343Hz, 686Hz, 1.4KHz, 2.8kHz, 5.5kHz, and 11kHz. The next 8 modes are simple VCO's, with the frequency of the output being determined by the sum of MOD1 and the analog input voltage on the left channel. In these modes, adjusting MOD2 varies the amount that the input signal is allowed to change the frequency of the VCO. The VCO has a range of 0Hz to 11Hz for all VCO modes.

The next 8 modes are VCA modes, which means the input on the left channel determines the amplitude of the oscillator. This is done by taking the long term average of the peak input signal (called envelope stripping), and multiplying it with the oscillator. Setting the output volume to match the input peak values, rather than just multiplying the two together, allows the output to maintain a much more pure tone, as multiplying two signals directly adds their harmonics together. Basically, its setting the internal oscillator volume to match the input signal's volume. The VCA mode uses the same 8 max oscillator values as the simple oscillator mode described above.

The last 8 modes are both VCO and VCA, with the output signal's frequency and amplitude determined by the input signal. They work in the same fashion as above, except the frequency range is fixed at 0Hz to 11kHz. In all of the VCO modes, the frequency is limited to this range, so values do not wrap around, i.e. an input command for a lower value than 0Hz does not become and output of 11kHz. The MICrODEC starts in this mode when switched to the VCO/VCA function.

All of the input control voltages are first low-passed at approximately 1Khz before being sent to the VCO or VCA. This is done to keep the output less jittery, and free of higher harmonics added by the input signal.

It is often difficult to tell exactly which mode the VCO/VCA function is in, and an external readout on the serial port would be a good mod.

vco.asm


   1 ; program: vco.asm
   2 ; UID = 000029 - unique id to eliminate conflicts between variables
   3 ; sram not used
   4 ; mono data in on left channel, identical stereo out
   5 
   6 ; constant definitions
   7 ;
   8 .equ maxvalue_000029 = $0200 ; storage location in internal sram for
   9 ; envelope max value, as ive run out of registers.
  10 .equ decay_000029 = $10 ; decay amount per sample period for envelope
  11 
  12 ; program overview
  13 ;
  14 ; a sinewave is generated internally from a lookup table in flash.  the
  15 ; frequency can be set by either an external voltage applied on the left
  16 ; channel input, or via the adc input.  the rotary encoder changes the
  17 ; functionality, from adc input with varying max values (86Hz, 171Hz, 343Hz,
  18 ; 686Hz, 1.4KHz, 2.8kHz, 5.5kHz, 11kHz), to analog input (in/1, in/2, in/4,
  19 ; in/8, in/16, in/32, in/64, in/128), to envelope control (8 adc input
  20 ; controls, and 8 analog input controls, with the same values as above).  in
  21 ; envelope control mode, the envelope of the input signal also sets the
  22 ; envelope of the vco output. the sinewave is generated from a lookup table
  23 ; in program memory, and is interpolated to get non-integer multiples of
  24 ; the base frequency. it uses a 512 sample, 16b lookup table.  the analog
  25 ; input is lowpassed at ~1kHz to give smoother response, and the adc is
  26 ; oversampled 256 times and deadbanded to remove glitches.  the envelope is
  27 ; generated by keeping track of the iput voltage max value, and decaying
  28 ; that value with time.
  29 
  30 ; register usage - may be redefined in other sections
  31 ;
  32 ; r0  multiply result lsb
  33 ; r1  multiply result msb
  34 ; r2  low pass accumulation fractional byte
  35 ; r3  low pass accumulation lsb
  36 ; r4  right/left lsb out/accumulation lsb
  37 ; r5  right/left msb out/accumulation msb
  38 ; r6  left lsb in
  39 ; r7  left msb in
  40 ; r8  low pass accumulation msb
  41 ; r9  adc accumulation msb
  42 ; r10 adc accumulation fractional byte
  43 ; r11 adc accumulation lsb
  44 ; r12 vco control signal lsb
  45 ; r13 vco control signal msb
  46 ; r14 rotary encoder counter
  47 ; r15 switch/adc sample counter
  48 ; r16 temporary swap register
  49 ; r17 temporary swap register
  50 ; r18 temporary swap register
  51 ; r19 temporary swap register
  52 ; r20 temporary swap register
  53 ; r21 vco function register
  54 ; r22 null register
  55 ; r23 sinetable lookup address fractional byte
  56 ; r24 sinetable lookup address lsb
  57 ; r25 sinetable lookup address msb
  58 ; r26 adc last value lsb
  59 ; r27 adc last value msb
  60 ; r28 read/write address lsb
  61 ; r29 read/write address msb
  62 ; r30 jump location for interrupt lsb
  63 ; r31 jump location for interrupt msb
  64 
  65 ;program starts here first time and after buffer changes
  66 ldi r30,$19 ; set jump location to program start
  67 clr r24 ; clear write register
  68 clr r25
  69 clr r22 ; setup r22 as null register for carry addition and ddr setting
  70 ldi r17,$ff ; setup r17 for ddr setting
  71 
  72 clear_000029: ; clear delay buffer
  73 ; eliminates static when first switching to the delay setting
  74 out portd,r24 ; set address
  75 sts porth,r25
  76 out portg,r22 ; pull ce low,we low,and set high bits of address
  77 out ddra,r17 ; set porta as output for data write
  78 out ddrc,r17 ; set portc as output for data write
  79 out porta,r22 ; set data
  80 out portc,r22 ; r18 is cleared above
  81 sbi portg,portg2 ; pull we high to write
  82 out ddra,r22 ; set porta as input for data lines
  83 out ddrc,r22 ; set portc as input for data lines
  84 inc r24 ; increment write register - only clears first 256 bytes
  85 brne clear_000029 ; continue until end of buffer reached
  86 
  87 cleardone_000029: ; reset registers
  88 
  89 ldi r28,$20 ; set buffer size for lowpass (44.1kHz/value = cutoff frequency)
  90 clr r29
  91 clr r2 ; initialize accumulation registers for lowpass
  92 clr r3
  93 clr r8
  94 ldi r21,$18 ; initialize vco state to envelope mod and analog input
  95 reti ; finish with initialization and wait for next interrupt
  96 
  97 ; program starts here every time but first
  98 ; initiate data transfer to codec
  99 sbi portb,portb0 ; toggle slave select pin
 100 out spdr,r5 ; send out left channel msb
 101 cbi portb,portb0
 102 
 103 ;increment sram addreses
 104 adiw r29:r28,$01 ; increment read/write address
 105 movw r17:r16,r29:r28 ; move to temporary register
 106 subi r16,$20 ; remove buffer size for read address
 107 sbc r17,r22 ; r22 is cleared above
 108 
 109 wait1_000029: ; check if byte has been sent
 110 
 111 in r7,spsr
 112 sbrs r7,spif
 113 rjmp wait1_000029
 114 in r7,spdr ; recieve in left channel msb
 115 out spdr,r4 ; send out left channel lsb
 116 
 117 ;get delayed data
 118 out portd,r16 ; set address
 119 sts porth,r17
 120 nop ; wait input latch time of 2 clock cycles
 121 nop ; wait input latch time of 2 clock cycles
 122 in r0,pina ; get data
 123 in r1,pinc ; get data
 124 
 125 wait2_000029: ; check if byte has been sent
 126 
 127 in r17,spsr
 128 sbrs r17,spif
 129 rjmp wait2_000029
 130 in r6,spdr ; recieve in left channel lsb
 131 out spdr,r5 ; send out right channel msb
 132 
 133 ;write left channel data to sram
 134 out portd,r28 ; set address
 135 sts porth,r29
 136 out portg,r22 ; pull ce low,we low,and set high bits of address
 137 ldi r17,$ff
 138 out ddra,r17 ; set porta as output for data write
 139 out ddrc,r17 ; set portc as output for data write
 140 out porta,r6 ; set data
 141 out portc,r7
 142 sbi portg,portg2 ; pull we high to write
 143 out ddra,r22 ; set porta as input for data lines
 144 out ddrc,r22 ; set portc as input for data lines
 145 
 146 wait3_000029: ; check if byte has been sent
 147 
 148 in r17,spsr
 149 sbrs r17,spif
 150 rjmp wait3_000029
 151 in r17,spdr ; recieve in right channel msb
 152 out spdr,r4 ; send out right channel lsb
 153 
 154 wait4_000029: ; check if byte has been sent
 155 
 156 in r17,spsr
 157 sbrs r17,spif
 158 rjmp wait4_000029
 159 in r17,spdr ; recieve in right channel lsb
 160 
 161 ;accumulate samples for lowpass
 162 add r2,r6 ; add in current sample
 163 adc r3,r7
 164 sbrc r7,$07 ; check if data is negative
 165 ldi r22,$ff ; set high bits if it is
 166 adc r8,r22 ; else r22 is cleared above
 167 clr r22 ; reset null register
 168 sub r2,r0 ; remove last sample in buffer
 169 sbc r3,r1
 170 sbrc r1,$07 ; check if data is negative
 171 ldi r22,$ff ; set high bits if it is
 172 sbc r8,r22 ; else r22 is cleared above
 173 movw r7:r6,r3:r2 ; move data to temporary register
 174 mov r22,r8
 175 asr r22 ; divide by 32 to normalize output
 176 ror r7
 177 ror r6
 178 asr r22
 179 ror r7
 180 ror r6
 181 asr r22
 182 ror r7
 183 ror r6
 184 asr r22
 185 ror r7
 186 ror r6
 187 asr r22
 188 ror r7
 189 ror r6
 190 clr r22 ; reset null register
 191 
 192 ; vco generation
 193 movw r17:r16,r31:r30 ; store z register
 194 ;get sample 1
 195 add r23,r12 ; increment sinetable address
 196 adc r24,r13
 197 adc r25,r22 ; r22 is cleared above
 198 movw r31:r30,r25:r24 ; move to z register for data fetch
 199 lsl r30 ; adjust pointer for 16b fetch
 200 rol r31
 201 andi r31,$03 ; limit value to 10b (512 samples x 2 bytes)
 202 ori r31,$48 ; set to memory address location where table is stored
 203 lpm r18,z+ ; get sine value lsb, increment z register
 204 lpm r19,z ; get sine value msb
 205 sbrc r25,$01 ; flip sign for half of the values
 206 rjmp interpolate_000029 ; dont invert if even
 207 neg r18 ; invert if odd
 208 adc r19,r22 ; r22 is cleared above
 209 neg r19
 210 
 211 interpolate_000029: ; multiply sample 1 by distance
 212 
 213 mov r20,r23 ; get distance from sample 1
 214 com r20 ; invert distance for sample magnitude
 215 mulsu r19,r20 ; (signed)ah * b
 216 movw r5:r4,r1:r0
 217 mul r18,r20 ; al * b
 218 add r4,r1
 219 adc r5,r22 ; r22 is cleared above
 220 mov r20,r0
 221 
 222 ;get sample 2
 223 adiw r25:r24,$01 ; set to next sample
 224 movw r31:r30,r25:r24 ; move to z register for data fetch
 225 lsl r30 ; adjust pointer for 16b fetch
 226 rol r31
 227 andi r31,$03 ; limit value to 10b (512 samples x 2 bytes)
 228 ori r31,$48 ; set to memory address location where table is stored
 229 lpm r18,z+ ; get sine value lsb, increment z register
 230 lpm r19,z ; get sine value msb
 231 sbrc r25,$01 ; flip sign for half of the values
 232 rjmp interpolate1_000029 ; dont invert if even
 233 neg r18 ; invert if odd
 234 adc r19,r22 ; r22 is cleared above
 235 neg r19
 236 
 237 interpolate1_000029: ; multiply sample 2 by distance
 238 
 239 movw r31:r30,r17:r16 ; restore z register
 240 sbiw r25:r24,$01 ; reset address
 241 mulsu r19,r23 ; (signed)ah * b
 242 add r4,r0 ; accumulate samples
 243 adc r5,r1
 244 mul r18,r23 ; al * b
 245 add r20,r0 ; accumulate samples
 246 adc r4,r1
 247 adc r5,r22 ; r22 is cleared above
 248 
 249 ;find envelope value
 250 movw r17:r16,r7:r6 ; move lowpassed value to temporary register
 251 tst r17 ; check if value is negative
 252 brpl envelope_000029 ; dont invert if positive
 253 com r16 ; invert negative values
 254 com r17 ; using ones complement to avoid problem at $8000
 255 
 256 envelope_000029: ; compare current value to max value
 257 
 258 lsl r16 ; convert current value to unsigned number
 259 rol r17 ; this is done to increase output volume
 260 lds r18,maxvalue_000029 ; fetch current max value
 261 lds r19,(maxvalue_000029 + 1)
 262 cp r16,r18 ; compare current value to max value
 263 cpc r17,r19
 264 brlo envelope_dec_000029 ; decrement envelope if below
 265 movw r19:r18,r17:r16 ; move value to max value if same or higher
 266 rjmp restore_000029 ; dont decrement if just incremented
 267 
 268 envelope_dec_000029: ; decay the envelope
 269 
 270 subi r18,decay_000029 ; decay the envelope
 271 sbc r19,r22 ; r22 is cleared above
 272 brcc restore_000029 ; re-store new max value if no underflow
 273 clr r18 ; set envelope to bottom
 274 clr r19
 275 
 276 restore_000029: ; re-store new max value
 277 
 278 sts maxvalue_000029,r18 ; re-store new max value
 279 sts (maxvalue_000029 + 1),r19
 280 
 281 rotary_000029: ; check rotary encoder and adjust function
 282 ; rotary encoder is externally debounced, so that is not done here.
 283 ; pin1 is sampled on a transition from high to low on pin0.  if pin1 is
 284 ; high, a left turn occured, if pin1 is low, a right turn occured.
 285 dec r14 ; reduce the sampling rate to help with debounce
 286 brne adcsample_000029
 287 ldi r17,$40 ; adjust sample frequency to catch all rising edges (1.5ms)
 288 mov r14,r17
 289 lds r17,pinj ; get switch data
 290 sbrs r17,$00 ; check if pin0 is low
 291 rjmp edge_000029 ; check if pin0 was low on previous sample
 292 clt ;  clear state register if back high
 293 rjmp adcsample_000029 ; finish off
 294 
 295 edge_000029: ; check for falling edge
 296 
 297 brts adcsample_000029 ; do nothing if the edge was already detected
 298 set ; set state register to indicate a falling edge occured
 299 sbrs r17,$01 ; check if pin1 is high
 300 rjmp increment_000029 ; increment playback if right rotation
 301 dec r21
 302 rjmp adcsample_000029 ; finish off
 303 
 304 increment_000029: ; increment playback speed
 305 
 306 inc r21
 307 
 308 adcsample_000029: ; get loop setting
 309 
 310 lds r17,adcsra ; get adc control register
 311 sbrs r17,adif ; check if adc conversion is complete
 312 rjmp done_000029 ; skip adc sampling
 313 lds r16,adcl ; get low byte adc value
 314 lds r17,adch ; get high byte adc value
 315 add r10,r16
 316 adc r11,r17 ; accumulate adc samples
 317 adc r9,r22 ; accumulate adc samples - r22 is cleared above
 318 ldi r17,$f7
 319 sts adcsra,r17 ; clear interrupt flag
 320 dec r15 ; countdown adc sample clock
 321 brne done_000029 ; move adc value to loop setting after 256 samples
 322 lsr r9 ; divide value by 4 to get a 16b value
 323 ror r11
 324 ror r10
 325 lsr r9
 326 ror r11
 327 ror r10
 328 
 329 ;check if value changed enough to warrant updating
 330 movw r17:r16,r27:r26 ; make a copy of last adc value for comparison
 331 sub r16,r10 ; find difference between current and last value
 332 sbc r17,r11
 333 brcc deadband_000029 ; see if difference is large enough to indicate a change
 334 neg r16 ; invert difference if negative
 335 adc r17,r22 ; r22 is cleared above
 336 neg r17
 337 
 338 deadband_000029: ; see if pot has moved or if its just noise
 339 
 340 cpi r16,$40 ; see if difference is greater than 1 lsb
 341 cpc r17,r22 ; r22 is cleared above
 342 brlo nochange_000029 ; dont update loop time if difference is not large enough
 343 sbrc r21,$03 ; check if in analog mode
 344 rjmp update_000029 ; dont adjust frequency range if in analog mode
 345 mov r17,r21 ; move vco function to temporary register
 346 andi r17,$07 ; mask off lower 3b
 347 breq update_000029 ; check if no multiply required
 348 
 349 frequencyshift1_000029: ; adjust frequency range
 350 
 351 lsr r11 ; adjust frequency range
 352 ror r10
 353 dec r17
 354 brne frequencyshift1_000029 ; keep adjusting till done
 355 
 356 update_000029: ; update loop time register
 357 
 358 movw r27:r26,r11:r10 ; move adc value to last value register
 359 sbrs r21,$03 ; check if in analog mode
 360 movw r13:r12,r27:r26 ; move adc value to vco increment register
 361 
 362 nochange_000029: ; clear accumulation registers
 363 
 364 clr r10 ; empty accumulation registers
 365 clr r11
 366 clr r9
 367 
 368 switchsample_000029: ; check rotary switch state
 369 
 370 lds r16,pinj ; get switch data
 371 andi r16,$78 ; mask off rotary switch
 372 lsr r16 ; adjust switch position to program memory location
 373 lsr r16
 374 ldi r17,$02
 375 add r16,r17
 376 cpse r16,r31 ; check if location has changed
 377 clr r30 ; reset jump register to intial state
 378 mov r31,r16
 379 
 380 done_000029:
 381 
 382 sbrs r21,$03 ; check if in analog mode
 383 rjmp done1_000029 ; finish if not
 384 mov r17,r21 ; move vco function to temporary register
 385 andi r17,$07 ; mask off lower 3b
 386 breq offset_000029 ; check if no divide required
 387 
 388 frequencyshift_000029: ; adjust frequency range
 389 
 390 asr r7 ; adjust frequency range that input signal effects
 391 ror r6
 392 dec r17
 393 brne frequencyshift_000029 ; keep adjusting till done
 394 
 395 offset_000029: ; add in offset from adc
 396 
 397 tst r7 ; check if input voltage is negative
 398 brmi subtract_000029 ; subtract value and check for low bound
 399 add r6,r26 ; else add input voltage to adc value to get frequency
 400 adc r7,r27
 401 brcc move_000029 ; check if overflow
 402 ldi r17,$ff ; set value to max if overflow
 403 mov r6,r17
 404 mov r7,r17
 405 rjmp move_000029
 406 
 407 subtract_000029:
 408 
 409 add r6,r26 ; add input voltage to adc value to get frequency
 410 adc r7,r27 ; adding a negative value is the same as subtracting
 411 brcs move_000029 ; check if underflow
 412 clr r6 ; set value to min if underflow
 413 clr r7
 414 
 415 move_000029: ; finish off by moving value to vco increment register
 416 
 417 movw r13:r12,r7:r6 ; move combined value to vco increment register
 418 
 419 done1_000029:
 420 
 421 sbrc r21,$04 ; check if in envelope mode
 422 rjmp done2_000029 ; add envelope
 423 
 424 ;reduce output volume because its too loud
 425 asr r5 ; divide by 2
 426 ror r4
 427 reti ; return to waiting if not envelope mode
 428 
 429 done2_000029: ; add envelope
 430 
 431 ;multiply data by envelope - 
 432 movw r17:r16,r5:r4 ; move signal to multiply register
 433 mulsu r17,r19 ; (signed)ah * (unsigned)bh
 434 movw r5:r4,r1:r0
 435 mul     r18,r16 ; (unsigned)al * (unsigned)bl
 436 movw r7:r6,r1:r0
 437 mulsu r17,r18 ; (signed)ah * (unsigned)bl
 438 sbc     r5,r22 ; r22 is cleared above
 439 add     r7,r0
 440 adc     r4,r1
 441 adc     r5,r22
 442 mul r19,r16 ; (unsigned)bh * (unsigned)al
 443 add     r7,r0
 444 adc     r4,r1
 445 adc     r5,r22
 446 reti ; return to waiting
 447