DDS with an envelope scaling
dmwilson86
Posts: 27
I have been up all night working with PASM, writing to main memory, using the serial debugger, and trying to work out what I need to do, however, there are just things that are happening that I really don't understand. So I'll post what I have and what is happening, and also what I'm trying to do, and hopefully one of you geniuses can enlighten my soupy brain. This one might be a challenge but I'll be active on it all day, so here we go.
What I'm trying to do:
I have built a 25-key keyboard with resistive pressure sensors that go into 4 8-channel adcs, and are read into an array in main memory by the mcp3208-driver, that works great. I want to create the frequency of the key at an amplitude proportional to the pressure the key being hit.
Alright, so I started with this code from kuroneko, and tdlivings in order to have a continuously updated sine wave frequency. Got this to work well.
What I'm trying to do:
I have built a 25-key keyboard with resistive pressure sensors that go into 4 8-channel adcs, and are read into an array in main memory by the mcp3208-driver, that works great. I want to create the frequency of the key at an amplitude proportional to the pressure the key being hit.
Alright, so I started with this code from kuroneko, and tdlivings in order to have a continuously updated sine wave frequency. Got this to work well.
org 0 entry '256 sampled 8-bit sine table. Replace with any sampled waveform you like 'as long as the data table is of the same dimension and depth. Only longs are 'used to make the DDS loop as short as possible in terms of clock cycles. Sine long $80,$83,$86,$89,$8C,$8F,$92,$95,$98,$9C,$9F,$A2,$A5,$A8,$AB,$AE long $B0,$B3,$B6,$B9,$BC,$BF,$C1,$C4,$C7,$C9,$CC,$CE,$D1,$D3,$D5,$D8 long $DA,$DC,$DE,$E0,$E2,$E4,$E6,$E8,$EA,$EC,$ED,$EF,$F0,$F2,$F3,$F5 long $F6,$F7,$F8,$F9,$FA,$FB,$FC,$FC,$FD,$FE,$FE,$FF,$FF,$FF,$FF,$FF long $FF,$FF,$FF,$FF,$FF,$FF,$FE,$FE,$FD,$FC,$FC,$FB,$FA,$F9,$F8,$F7 long $F6,$F5,$F3,$F2,$F0,$EF,$ED,$EC,$EA,$E8,$E6,$E4,$E2,$E0,$DE,$DC long $DA,$D8,$D5,$D3,$D1,$CE,$CC,$C9,$C7,$C4,$C1,$BF,$BC,$B9,$B6,$B3 long $B0,$AE,$AB,$A8,$A5,$A2,$9F,$9C,$98,$95,$92,$8F,$8C,$89,$86,$83 long $80,$7C,$79,$76,$73,$70,$6D,$6A,$67,$63,$60,$5D,$5A,$57,$54,$51 long $4F,$4C,$49,$46,$43,$40,$3E,$3B,$38,$36,$33,$31,$2E,$2C,$2A,$27 long $25,$23,$21,$1F,$1D,$1B,$19,$17,$15,$13,$12,$10,$0F,$0D,$0C,$0A long $09,$08,$07,$06,$05,$04,$03,$03,$02,$01,$01,$00,$00,$00,$00,$00 long $00,$00,$00,$00,$00,$00,$01,$01,$02,$03,$03,$04,$05,$06,$07,$08 long $09,$0A,$0C,$0D,$0F,$10,$12,$13,$15,$17,$19,$1B,$1D,$1F,$21,$23 long $25,$27,$2A,$2C,$2E,$31,$33,$36,$38,$3B,$3E,$40,$43,$46,$49,$4C long $4F,$51,$54,$57,$5A,$5D,$60,$63,$67,$6A,$6D,$70,$73,$76,$79,$7C mov dira, PinMsk 'LSB to PA0, LSB to PA7 :loop rdlong M_,par 'Get Freq Control Value add Acc, M_ 'M $1FF max if literal with #$xxx ror Acc, #16 'Postion index movs :inline, Acc 'Store 9-bit index andn :inline, #%1_00000000 'Clamp to 8-bits rol Acc, #16 'Restore accumulator :inline mov outa, 0-0 'Get long value from table jmp #:loop 'Loop again PinMsk long $FF '8-bit DAC, LSB on PA0, MSB on PA7 state long $100 M_ long 0 'Tuning word var init Acc long 0 'Accumulator var init AddrFreq long 0 'Address of Freq Control in Main Spin CodeThen, (trying to take baby steps) instead of movs of the Acc to :inline, I moved it to a local variable x so I could perform scaling operations on the value with:
movs :scale, Acc :scale movs x, 0-0 movs :inline, xI realize that I will have to do (probably pin) synchronization at the output with a counter since multiplication and division operation take variable amounts of time. I tried so many different ways to do this, including not even using dynamic source addressing and just going: mov outa, x....or using the literal for x in the movs: movs, :inline,#x.......but no acceptable output. Anytime I do get an output waveform, every other period is completely scrambled: . So I don't understand why this is. I have to be able to copy the value from the wavetable, and manipulate it in order to do the scaling operations, and then be able to put out the result. But before I do that I just want to create the sine wave from values copied from the wavetable so that I know I can reliably operate on the waveform. I only need to be able to produce frequencies up to about 2kHz, so I'm pretty sure this is doable. Here is the entire DAT file I'm working with, there is some debugging code commented out.
DAT org 0 entry '256 sampled 8-bit sine table. Replace with any sampled waveform you like 'as long as the data table is of the same dimension and depth. Only longs are 'used to make the DDS loop as short as possible in terms of clock cycles. Sine long $80,$83,$86,$89,$8C,$8F,$92,$95,$98,$9C,$9F,$A2,$A5,$A8,$AB,$AE long $B0,$B3,$B6,$B9,$BC,$BF,$C1,$C4,$C7,$C9,$CC,$CE,$D1,$D3,$D5,$D8 long $DA,$DC,$DE,$E0,$E2,$E4,$E6,$E8,$EA,$EC,$ED,$EF,$F0,$F2,$F3,$F5 long $F6,$F7,$F8,$F9,$FA,$FB,$FC,$FC,$FD,$FE,$FE,$FF,$FF,$FF,$FF,$FF long $FF,$FF,$FF,$FF,$FF,$FF,$FE,$FE,$FD,$FC,$FC,$FB,$FA,$F9,$F8,$F7 long $F6,$F5,$F3,$F2,$F0,$EF,$ED,$EC,$EA,$E8,$E6,$E4,$E2,$E0,$DE,$DC long $DA,$D8,$D5,$D3,$D1,$CE,$CC,$C9,$C7,$C4,$C1,$BF,$BC,$B9,$B6,$B3 long $B0,$AE,$AB,$A8,$A5,$A2,$9F,$9C,$98,$95,$92,$8F,$8C,$89,$86,$83 long $80,$7C,$79,$76,$73,$70,$6D,$6A,$67,$63,$60,$5D,$5A,$57,$54,$51 long $4F,$4C,$49,$46,$43,$40,$3E,$3B,$38,$36,$33,$31,$2E,$2C,$2A,$27 long $25,$23,$21,$1F,$1D,$1B,$19,$17,$15,$13,$12,$10,$0F,$0D,$0C,$0A long $09,$08,$07,$06,$05,$04,$03,$03,$02,$01,$01,$00,$00,$00,$00,$00 long $00,$00,$00,$00,$00,$00,$01,$01,$02,$03,$03,$04,$05,$06,$07,$08 long $09,$0A,$0C,$0D,$0F,$10,$12,$13,$15,$17,$19,$1B,$1D,$1F,$21,$23 long $25,$27,$2A,$2C,$2E,$31,$33,$36,$38,$3B,$3E,$40,$43,$46,$49,$4C long $4F,$51,$54,$57,$5A,$5D,$60,$63,$67,$6A,$6D,$70,$73,$76,$79,$7C {{mov dira, PinMsk 'LSB to PA0, LSB to PA7 mov fPtr, par add t1, fPtr add t1, #48 mov envPtr, t1 :loop rdlong M1_, fPtr 'Get Freq Control Value add Acc1, M1_ wz 'M $1FF max if literal with #$xxx ror Acc1, #16 'Postion index movs :setMult, Acc1 rdlong x, envPtr :setMult mov y, 0-0 call #multiply mov y, #0100 'dividing must be done b/c no floating point call #divide movs :inline, x andn :inline, #%1_00000000 'Clamp to 8-bits rol Acc1, #16 'Restore accumulator :inline mov outa, 0-0 'Get long value from table jmp #:loop 'Loop again 'Loop again }} mov dira, PinMsk 'LSB to PA0, LSB to PA7 :loop rdlong M_,par 'Get Freq Control Value add Acc, M_ 'M $1FF max if literal with #$xxx ror Acc, #16 'Postion index ' movs :inline, Acc 'Store 9-bit index movs :scale, Acc :scale movs x, 0-0 movs :inline, x ' mov y,#2 ' call #divide mov t1, par add t1, #96 wrlong x, t1 andn :inline, #%1_00000000 'Clamp to 8-bits rol Acc, #16 'Restore accumulator :inline mov outa, 0-0 'Get long value from table djnz count, #:loop jmp #:loop 'Loop again 'ret '' Multiply x[15..0] by y[15..0] (y[31..16] must be 0) ' on exit, product in y[31..0] ' mult shl x,#16 'get multiplicand into x[31..16] mov t1,#16 'ready for 16 multiplier bits shr y,#1 wc 'get initial multiplier bit into c :loop if_c add y,x wc 'if c set, add multiplicand to product rcr y,#1 wc 'put next multiplier in c, shift prod. djnz t1,#:loop 'loop until done mult_ret ret 'return with product in y[31..0] ' Divide x[31..0] by y[15..0] (y[16] must be 0) ' on exit, quotient is in x[15..0] and remainder is in x[31..16] divide shl y,#15 'get divisor into y[30..15] mov t1,#16 'ready for 16 quotient bits :loop cmpsub x,y wc 'y =< x? Subtract it, quotient bit in c rcl x,#1 'rotate c into quotient, shift dividend djnz t1,#:loop 'loop until done divide_ret ret 'quotient in x[15..0] PinMsk long $FF '8-bit DAC, LSB on PA0, MSB on PA7 t1 long 0 x long 0 y long 0 M_ long 0 'Tuning word var init Acc long 0 'Accumulator var init fPtr long 0 'Address of Freq Control in Main Spin envPtr long 0 count long 256Please help! I can't wait to get this working!
Comments
movs instructions in a row. The manual says you need at least one instruction between a movs and the
use of the target. The :scale sequence for sure looks like it needs a nop added between.
Tom
Thanks for responding guys....that makes sense. Tried it out and I still seem to be having this scrambling issue:
I just can't think of what's causing it, it seems like an issue with Acc, since it's happening once every (what would be) 2 periods.
Edit: Wait, no that's not the problem... let me see again... (I'm using a simulator )
Edit2: Nonono, the problem is when you do the second movs because you allow x to be loaded with any value in the 0..511 range instead of 0.255 where your waveform resides... (the andn should be for the instruction at scale and not at inline as the waveform has values in the 0..255 range).
Here is my code: I'm only using the envPtr to report values back to the hub for serial debugging.
I'm trying to do this:
SineValue = (SineValue - (pressVal/4095)*SineValue)
What you do wrong is: You multiply the index into the sine table and not the value read from the sine table.
There are other issues in your code, so I took the initial Sine DDS code and added the modulation of the amplitude by my own. I think this makes it more clear than modifying your last posted code.
This is as simple as possible, but not perfect. It calculates:
SineValue = pressVal * SineValue / 4096
If you want the loudest tone if presVal is 0 then calculate it like that: presVal := 4095 - ADCvalue
Instead of a division by 4095 you can divide by 4096 and that is a simple right shift by 12.
The multiplication is not optimized, you need only a 8bit * 16bit mult which then would be two times faster.
And the zero point of the output signal is not in the middle (value 128) instead the signal goes to value 0 with the EnvValue=0. To make it right you need to calculate:
SineValue = (SineValue - 128) * pressVal / 4096 + 128
with the used sine table, but for that you need a signed multiplication, which makes it more complicated.
Here is the code:
Andy
I see now that the "other issues" I mentoined are in a commented out section of the code, so I think the above is the main error.
Andy
I have all the frequencies and envelopes in being set in a 6-long array in main memory. For now I'm just trying to mix 2 signals (the rest is commented out). I tried setting z, and checking for that when outputting the signal....and I'm pretty sure that 'should' work for 2 channels, but I don't think it would work properly for any more than that because if more than one, but less than all channels were being played, then the channels being played would have unequal amounts of time at the dac. I'm up for suggestions. Thanks again to Arriba for the envelope solution.
And here is a sample picture of the output when 2 keys are pressed simultaneously:
The waveform is perfect when only one key is pressed.
Instead you have a write to outa for each scaled sine wave component.
The result is that the output is "chopping" from the value of one sine wave frequency to another, twice per loop, we can see this in your scope trace.
So perhaps instead of "mov outa, y" you should "add output, y" for each frequency component and then "mov outa, output" at the end of the loop.
Don't forget to zero output afterwards.
P.S. Don't forget to scale down the totalled output value as it may well have exceeded 8 bits. So for two frequencies shift right by one, for four frequencies shift right by two etc.
Oh, I was just noting that you introduced me to the idea of interleaving.....I just initially thought that's what you meant, I should have dug deaper on that...but that makes much more sense. Thanks, I will try that out asap.