These forums are read only, please use our new forums here.

Main :: ToneCore Development Kit



Pitch detection/octave effect
by audioartillery on 2011-04-25 21:50:41

I kinda got busy with other projects and haven't finished this to the point where I would consider it a viable effect.  But I figured I'd throw it out there and maybe some day post the code for it.

http://www.soundclick.com/bands/page_songInfo.cfm?bandID=538074&songID=10568297

The clip is clean, then square wave with no octave shift, then -1 octave, then +1 octave, then messing around.

The algorith is rather simple.  Amplitude tracking is a simple filtered version of peak detects.  Pitch tracking is determined essentially from zero crossing.  It tracks a single note rather well but there are a lot of artifacts as the note decays.

The last thing I was working on for this was a sine wave output.  I was hoping to get kind of a theremin sound out of it.  Some day!



Re: Pitch detection/octave effect
by awesomecoolguy on 2011-04-26 20:24:41

that sounds sweet!  i've been wondering about pitch detection as I've been working on my delay effect as well.  i learned about some algorithms in a class i took, but the only one i can remember is a double fft algorithm, but that seems like it would take too much time.  sounds like you have this zero crossing stuff working well though!

let me know if you need any help with the sine wave generation. i've implemented a sine wave algorithm successfuly before and might be able to offer some good advice.



Re: Pitch detection/octave effect
by MusicalCircuitry on 2011-04-26 20:57:40

That's a pretty trippy effect! I'd enjoy checking it out if you finish it and share the code.

Regarding the artifacts - are you at the stage of looking for a solution yet, or still the cause?

If you don't already have a sine wave generator, there are some quick and dirty techniques. A state variable filter can be modified to act as an oscillator and is extremely simple:

http://www.earlevel.com/main/2003/03/02/the-digital-state-variable-filter/

You can probably get slightly better results using IIR, but calculating dynamic coefficients might be a bit trickier:

http://www.ee.ic.ac.uk/pcheung/teaching/ee3_Study_Project/Sinewave%20Generation(708).pdf



Re: Pitch detection/octave effect
by awesomecoolguy on 2011-04-26 21:26:54

Alright, since MusicalCircuitry already offered some suggestions, I might as well get my 2cents in:

WaveTable lookup (also featured in Hal Chamberlin's book)!

It's super easy!  It might take up a little memory, but seriously, not that much.  All you need is a single period of a not so low frequency sine wave, say 20Hz.  step through it at different rates to increase the frequency.



Re: Pitch detection/octave effect
by audioartillery on 2011-04-27 08:22:30

Thanks guys, yes, I've already put in a lookup table for the sine generation, just need to squash a bug or two.

The artifacts exist in my simulation.  When the SNR gets small the algorithm kind of goes to hell.  Even as it is it's a useful algorthm.  One effect I'd like to make is a colored noise synthesizer (noise with a noise power curve that centers around the current note).  And this is entirely suitable for that kind of thing.



Re: Pitch detection/octave effect
by MusicalCircuitry on 2011-04-29 09:47:59

audioartillery wrote:

Thanks guys, yes, I've already put in a lookup table for the sine generation, just need to squash a bug or two.

The artifacts exist in my simulation.  When the SNR gets small the algorithm kind of goes to hell.  Even as it is it's a useful algorthm.  One effect I'd like to make is a colored noise synthesizer (noise with a noise power curve that centers around the current note).  And this is entirely suitable for that kind of thing.

How many lookup table values did you use? Taking advantage of symmetry and periodicity and then applying interpolation, I suppose it wouldn't be so bad. Did you use linear interpolation, or something higher order (Cauchy, Lagrange, etc)?



Re: Pitch detection/octave effect
by audioartillery on 2011-04-29 12:32:33

The table size is 1024 words but only 1/4 of that is necessary to store in memory due to symmetry.

I was playing with this last night and I'm stuck on the stupidest thing:  just putting the table in memory and using it.  Originally I just stuck it in the program memory somewhere but can't seem to read values from that.  So I put it in X memory but I don't think that actually gets loaded by the MCU at boot time.  I guess I need to keep the table in X memory but initialize it from P memory?  Annoying.  If anyone has some example code for this I'd take it.



Re: Pitch detection/octave effect
by MusicalCircuitry on 2011-05-01 17:20:09

I was feeling a bit curious about how good sine generation could be with a lookup table, so I did some experiments.

Here are the results with linear interpolation:

Size
SNR
Noise
846.760919 dB0.4591494%
1659.993181 dB

0.1000785%

3272.602406 dB0.0234358%
6484.921192 dB0.0056747%
12897.09963 dB0.0013964%
256109.20907 dB0.0003464%
512121.28429 dB0.0000863%
1024133.34248 dB0.0000215%

Here are the results without interpolation:

SizeSNRNoise
823.755056 dB6.4900372%
1630.321217 dB3.0474681%
3236.622135 dB1.4753439%
6442.773921 dB0.7266143%
12848.860759 dB0.3605471%
25654.915114 dB0.1795743%
51260.952074 dB0.0896182%
102466.981162 dB0.0447653%

I've attached the SciLab script I created to tinker with sine generation. Feel free to experiment with it yourself. The script is controlled by three parameters:

interpolate = %T; // controls whether interpolation is used
lookup_size = 128; // size of lookup table
lookup_mult = 10; // controls number of data points between lookup table values

Note that the lookup table only defines the first 1/4 of a sine period, and then symmetry is used for the rest of the period.

It's interesting that you can get better results with a 32 value lookup table with interpolation than a 1024 value lookup table without interpolation. Since you can't really create a lookup table bigger than 1024 (if you plan on having any other code at any rate), this makes it pretty clear that interpolation will be critical for better results. Real world 24 bit systems give a dynamic range of 120 dB, so with a 512 value lookup table and linear interpolation, you could generate a sine wave that would be indistinguishable from a pure sine wave at that bit depth. Personally, I think I'd go with a lookup table of 64 or 128 values and linear interpolation. A 16 bit system provides a theoretical max of 96 dB and real world 90 dB of dynamic range, so 64 would be slightly below and 128 would be slightly above what you need for a 16 bit system. Considering the limitations of the platform, I don't think generating sine waves at better than CD quality accuracy would be a top priority.

I also looked into using some better interpolation methods - polynomial interpolation, spline interpolation, etc. They can give much better results, but they also get quite complicated - for example, even with only 8 values in your lookup table, you'd need to calculate a 7th degree polynomial, and then you need to worry about oscillation artifacts (Runge's phenomenon) too. Spline interpolation only requires cubic polynomials and doesn't suffer from the same artifacts, but then you need a different cubic polynomial between each lookup value, which is going to be messy in assembly and chew up more code space. Considering how good the results can be with a small lookup table and linear interpolation, I'd be inclined to just add more data points before resorting to more complicated interpolation. Of course, for something other than a simple sine wave or on a different platform, better interpolation methods might make more sense.



Re: Pitch detection/octave effect
by audioartillery on 2011-05-01 18:14:09

Wow, MC, that's a great analysis!  I will enable the interpolation once I have things working.  I finally was able to access the table the other night... movec was the magic instruction I was missing.  I'm still grabbing the wrong table entry but I'm sure I'll get the bugs out soon enough.

You could have a lot more room for lookup tables if the MCU loaded X/Y memory from flash.  I think this is fundamentally possible but requires a change to the MCU code.



Re: Pitch detection/octave effect
by MusicalCircuitry on 2011-05-01 23:56:21

audioartillery wrote:

Wow, MC, that's a great analysis!  I will enable the interpolation once I have things working.  I finally was able to access the table the other night... movec was the magic instruction I was missing.  I'm still grabbing the wrong table entry but I'm sure I'll get the bugs out soon enough.

You could have a lot more room for lookup tables if the MCU loaded X/Y memory from flash.  I think this is fundamentally possible but requires a change to the MCU code.

Glad you got that sorted out! After you brought up MOVEC, I figured I  should review the other MOVE types, and I can't help but think that  MOVEM seems more appropriate. In the family manual, the MOVEM  documentation lists "P:ea" and "P:aa" in the syntax section, but the  MOVEC documentation does not. That said, while the family manual is incredibly comprehensive, it's not always the easiest thing to figure out, so who knows.

I've been digging a lot into ways of loading the X and Y memory without initializing it from P memory, but it doesn't seem possible through normal means. The bootstrap ROM, which can't be changed by us, can start execution directly from PROM, or load the contents of an EPROM into PRAM then start execution, or can load data from Serial Host Interface (SHI) in one of 3 modes (slave SPI, slave I2C clock freeze enabled and slave I2C clock freeze disabled) into PRAM then start execution. None of these modes provide for initializing X or Y memory, and bootstrap mode is only triggered by a hard reset, so you couldn't for example hack around this by doing one bootstrap with PRAM code designed to initialize X and Y memory, then bootstrap again with PRAM code to do the DSP operations. There is a burn-in mode which tests X and Y memory, but this is a reserved mode and is only used for testing the chip.

Now, just because it's not possible through normal means doesn't mean it's not possible! The MCU is able to communicate with the DSP using the SPI protocol. You could flash the MCU with extra code containing lookup table values and other data you want stored in X and Y memory, then use the SPI to send that data to the DSP. The SPI is a synchronous, clocked protocol, so you'd have to coordinate all of that on both sides - the DSP would indicate it's ready for data, the MCU would send a chunk of data over the SPI, the DSP would read this data and store in X or Y memory as desired, the DSP would indicate it's ready for more data, etc. You'd be firmly out in no-man's land in terms of documentation and sample code, but it's theoretically possible. I'm not sure how far you could take this - the MCU has 16K of flash memory, which is more than enough to fill P, X and Y memory, but I'm not sure how much of that is already in use for other MCU tasks. You would also lose the convenience of loading your code through the TCDDK GUI while developing, since changes to data initialized for X and Y memory would require changes to the MCU code as well.

It'd be interesting to see a working implementation of the above scheme, but I'd be inclined to push the initialization through P memory as far as possible before resorting to that!



Re: Pitch detection/octave effect
by audioartillery on 2011-05-02 20:52:22

You're right, it's MOVEM not MOVEC.  I was confused. I'm honestly not very good at th 563xx assembly, I'm only really familiar with ARM processors.

I got it working but I'm a little confused.  Here is what I'm doing:

move #SINE_LOOKUP_001,x0 ; this is the 2nd index in my table

move x0,a

sub #$6,a ; <--------- account for address being off by 6

move a,r0

movem p:(r0),r1

move r1,x0

move x0,x:Debug_Read_from_DSP_3 ; <--------- I see the 2nd element in the table

As my comments indicate, my MOVEM instruction seems to be fetching from the address 6 words ahead of where I want it to.  Any idea what I'm doing wrong?

As far as the MCU initialization of X/Y mem, I think the simplest thing to do is to use the shared memory interface that is currently used to pass knob values and come up with some protocol that is used at boot time to transfer your gobs of data.  But with the linear interpolation I think my table will end up being quite small so this is a non-issue.



Re: Pitch detection/octave effect
by MusicalCircuitry on 2011-05-02 22:37:41

audioartillery wrote:

You're right, it's MOVEM not MOVEC.  I was confused. I'm honestly not very good at th 563xx assembly, I'm only really familiar with ARM processors.

I got it working but I'm a little confused.  Here is what I'm doing:

move #SINE_LOOKUP_001,x0 ; this is the 2nd index in my table

move x0,a

sub #$6,a ; <--------- account for address being off by 6

move a,r0

movem p:(r0),r1

move r1,x0

move x0,x:Debug_Read_from_DSP_3 ; <--------- I see the 2nd element in the table

As my comments indicate, my MOVEM instruction seems to be fetching from the address 6 words ahead of where I want it to.  Any idea what I'm doing wrong?

As far as the MCU initialization of X/Y mem, I think the simplest thing to do is to use the shared memory interface that is currently used to pass knob values and come up with some protocol that is used at boot time to transfer your gobs of data.  But with the linear interpolation I think my table will end up being quite small so this is a non-issue.

It's hard to say for sure without seeing the rest of the code, but I doubt this is doing what you want:

move #SINE_LOOKUP_001,x0

Since the destination is x0, the immediate data is treated as a signed fraction. What happens if you remove the offset by 6 logic, and force the immediate data to be interpreted as an unsigned integer like this?

move #>SINE_LOOKUP_001,x0

Unless there's something else funky going on, I think you should be able to just do this:

move    #SINE_LOOKUP_001,r0

movem   p:(r0),x0

move    x0,x:Debug_Read_from_DSP_3

Note that you shouldn't need to force interpretation as an unsigned integer here since the destination is r0. Hopefully that helps a bit!

Regarding the idea of initializing X or Y memory from the MCU, there isn't really a shared memory location as such. The MCU and DSP communicate via Serial Peripheral Interface (SPI), which is just a serial data bus. This is all managed with handshaking in a state machine. From the TCDDK Users Guide:

The Tx State consists of the following:
    1. Send the Write command

    2. Send the address of the DSP RAM location where data will be written

    3. Send the data to write

Inside the MCU code, there are a bunch of functions like this:

static void vfnDSP_Knob1(void)

Having looked at this more, in retrospect it wouldn't be so horrible. You'd need to write a similar state function yourself, and add your function to the vfnDSP_CommunicationStateMachine array of function pointers. The vfnDSP_DebugVar4FromDSP() function resets the gu8DSP_CommunicationActualState variable, which simply loops over the array of function pointers. If you insert your function before vfnDSP_DebugVar4FromDSP() in the list, you could leave this alone, otherwise you'd have to adjust the looping logic. Inside of your state function, you'd send a single table entry for each call, incrementing the DSP address and pointer to your own data table. You would need to coordinate DSP memory locations with both sides, and if you choose to store your table in X memory, the work of storing that data DSP side is already taken care of for you in DoReadCommand() in the assembly template. Since this state machine is run every 1ms, you could initialize 1,000 entries in a second, which I think is more than fast enough that there's no need to worry about creating a separate program flow just for initialization.

I'm tempted to try this myself, but I'm running XP in a virtual image, which isn't happy making for flashing the MCU. I have access to physical Win7 machines, but the installer doesn't support Win7. I may have to give in and pick up a cheap netbook/notebook running XP for flashing the MCU.



Re: Pitch detection/octave effect
by RedPandaCurt on 2011-05-03 16:45:06

For initializing tables in X or Y memory from program memory, you can define a global data section after the rest of your code:

section P_DATA_MEMORY global


org p:


; Table values

CONST_FOO_0 dc $000000

CONST_FOO_1 dc $000001

.....

The in your initialization code, copy it to an address in X memory:

; initialize table

move #CONST_FOO_0,r0    ; address in program memory

move #foo_table,r3      ; address in X memory

do #100,init_table_done ; repeat for number of entries

move p:(r0)+,x1

move x1,x:(r3)+

init_table_done:

Hope that helps.



Re: Pitch detection/octave effect
by audioartillery on 2011-05-03 21:43:27

MC, yes, I was being bitten by the fixed point register behavior.  And I even put an entry about that in the FAQ I wrote way back when!  I'm not sure exactly how it's working at all, but I'm sure once I change it to use a general purpose register it will work perfect.

Panda, glad to see you chiming in after so long.  That's a good way to handle loading.  Probably better than what I'm doing - looking into P memory every time I do a lookup.  Though if you do this you blow both your P memory and X/Y memory.

Aight... hopefully next time I post I'll have a working sine wave and it will sound bitchin' .




The information above may not be current, and you should direct questions to the current forum or review the manual.