Got it, nice idea! I'm guessing the only down sides are that it takes a bit more math to scale my values such that they go from -period to 0 (where period is clkfreq / sample_rate), and the output at 50% is basically a square wave at sample_rate, which means my analog filter needs a lower corner-frequency and a sharper falloff. Though on the demo-board, where I think the RC corner frequency is like 2kHz, this is probably less of an issue. I'm going to try playing around with this over the weekend!
Thanks for the volume update. I'll work on bypassing my digital potentiometer, which will certainly simplify things. Pictures coming soon; I brought my guitar to show off at my local hackerspace and everyone was blown away. They'll update their blog soon and I'll link to it then.
Got it, nice idea! I'm guessing the only down sides are that it takes a bit more math to scale my values such that they go from -period to 0 (where period is clkfreq / sample_rate),
Not really, the NEG instruction nicely combines a negation and a move, likely already had a move instruction. (to ape the apple meme "there's and instruction for that!") The rest is adjustments to scaling and bias point math the code should already be doing.
and the output at 50% is basically a square wave at sample_rate, which means my analog filter needs a lower corner-frequency and a sharper falloff. Though on the demo-board, where I think the RC corner frequency is like 2kHz, this is probably less of an issue. I'm going to try playing around with this over the weekend!
thanks,
Jonathan
Yep. But now all the noise due to pwm is at the sampling frequency or higher. No special cases where pwm noise slides down near 0Hz.
Well, here are two photos of my guitar, being played by someone else at my local makerspace. I haven't done much more on this project since last time I posted, as I was finishing up a bathroom renovation. I plan to start up again tonight or tomorrow, adding features and stuff.
Any fancy updates for the code that I should wait for, before forging ahead with what we've got already?
Those are beautiful pics! And I second the prof's request...I'd love to hear it in action!
No earth-shaking updates yet. I got the higher ranges to work a bit better by "seeding" the initial noise burst with a small pseudo-sine wave at the fundamental frequency. (Unfortunately the easiest way to do it has a small discontinuous jump, which at loud volumes is a bit tiring to the ear.) I would say try the current version as-is, and let me know what features you'd like next. I _was_ going to work on the multiple strings per cog, but I saw your rig only had 4 "strings", so I got lazy [8^)
And so you see why I only requested four strings in the first place. I could only fit four string sensors on a standard guitar fretboard, otherwise I'd have gone for six. The biggest benefit for me in being able to handle multiple strings per cog would be the ability to simulate paired strings. Could be cool.
I'll try to capture an audio sample tonight. I also need to tear the guitar back open and do some changes to the hardware that will hopefully kill some noise, as discussed earlier.
These paired strings...they wouldn't happen to always be plucked simultaneously and at the same volume, would they? If so, I _think_ I could do that with no extra RAM requirements by just using 2 pick-off points at different delay offsets.
These paired strings...they wouldn't happen to always be plucked simultaneously and at the same volume, would they? If so, I _think_ I could do that with no extra RAM requirements by just using 2 pick-off points at different delay offsets.
Jonathan
From what I've read, same pitch, same volume, same sustain, minute difference in pluck time. It's more out of curiosity though, to see how synthetic paired strings sound compared to real ones.
OK, more test results! The first things I did were to change the value of my low-pass filter to a cutoff frequency of about 2.4kHz, and bypass my digital potentiometer. The result? No reduction in noise, but that weird problem I was having with the sound becoming attenuated is gone. So, digipot definitely not playing nice.
But what of the noise? There are different varieties in there. There is a constant "whine" that sounds very periodic, as if a clock-like signal is being introduced. There is also static-type noise that persists after a string is plucked, that sounds like a cross between whitespace on an old TV, and an early modem. It can be killed by ramping down the sustain for a second or two.
I'd get a sample for you to hear but I'm having trouble recording it with my computer. It's introducing a ton of noise all on its own that washes out what's coming from the guitar. (no guitar strum sounds for you folks either, for the same reason)
The "whine" I wrote about in the last post has a frequency of about 216Hz (about 4.64 mS period), though it is definitely not sinusoidal. It's actually more like a sine wave where the negative half is flipped up to the positive side. There's a higher frequency noise on top of it which I think is sitting at about 3.2kHz, but it's hard to measure because it won't sit still...
The sustain doesn't just kill the noise, it also changes it. Sometimes when I drop the sustain, the output frequency of the whine drops a little, too. But not all the time.
And, for the final test: I commented out all the karplus strong code, and instead made the guitar produce a simple tone for one second. When idle, the output is absolutely silent. Intrigued, I dropped in just the code that initializes four strings. Lo and behold, the noise is back. I took that out and put in the code that initializes just one "stereo" string - no noise. So, perhaps my troubles are coming from there.
Well, that's enough for tonight. Gotta get to sleep before I turn into a zombie.
* What is your sampling frequency? 44.1kHz?
* How fast is your main control loop (the one in charge of calling pluck, etc.)?
* When the string is muted do you call pluck( 1 ), or just let it run without any inputs?
Just as a note: the K-S algorithm uses a set or random values as a starting impulse...there is no guarantee that the average of those values is equal to 0. Every time you call pluck it generates another burst of noise to act as the driver...and it generates the noise for 1 period. I was thinking if pluck gets called every 4.64 ms or so, it could be responsible for some of the noise you are hearing.
My sampling rate right now is 48k, though it's the same with 96k. (same as in noisy, not sure about the frequency though). The main "pluck" loop is pretty fast; it is a series of four if statements that check to see if one of the laser strings is blocked. If they are, it calls a pluck command. Right now the volume of a pluck is always at max value.
There is a second loop whose purpose is to read the ADC, and continually update the pitch and sustain. I'll do some experiments with the loops to see if they are causing this, but I dont think they are. Remember, all I need to do is initialize multiple strings and the noise is there.
One other thing I want to try is to physically disconnect three of the four strings, to see if they are feeding back into each other somehow.
OK, try this. I switched the counter to NCO mode as Lawson suggested...hopefully that helps with noise.
Lawson, I made a slight change. Instead of setting FRQA to 1, and managing the range of PHSA, I did the following:
- set FRQx to be equal to POSX / clocks_per_period (period of the sampling rate)
- you change the value by setting PHSx to a value between 0 and POSX (1 << 31 - 1)
- if PHSx starts at 0, adding FRQx to it each clock will yield POSX at the
end of the period, meaning that the high bit never goes to 1
- if PHSx starts at POSX, after the 1st clock FRQx will have been added, so the pin goes high
- if PHSx is set to POSX/2, it will spend 1/2 the period low, then 1/2 the period high
Jeff, I was just thinking it should be fairly easy to pick a pluck volume based on how fast the finger/pick goes through each beam, similar to how cheap velocity-sensitive keyboards work.
Jeff, I was just thinking it should be fairly easy to pick a pluck volume based on how fast the finger/pick goes through each beam, similar to how cheap velocity-sensitive keyboards work.
Jonathan
Excellent idea! It was on my to-do list, but it'll take some better coding than an if statement in a loop...
So, here's what I've found so far. Good news; the new version has eliminated the static-y noise that always followed a string pluck. However, the whine is still there, and sounds about the same, too. I even played with different sampling rates, they seem to have no effect.
I tried eliminating the ADC completely from my program, fixing the strings to set values. That killed the high-pitched portion of the noise, but not the lower frequency. Since there is also noise in the output when I'm changing the contents of the LCD or turning the lasers on and off, I'm fairly certain that this noise is coming from spikes in voltage caused by the rapid changing of serial lines or other current-hungry devices. And remember that there is no noise when I'm not running any KS stuff. So to summarize:
ADC + KS = low + high whine
KS only = low whine
ADC + KS (initialized only) + simple tone output = low + high whine
ADC + simple tone output = no noise
Hmmm, so what shall I do now? It would seem that running KS leaves the outputs "vulnerable" to the effects of other pins switching on and off. Shall I attempt a hardware fix, like adding external pullup or pulldown resistors? Perhaps beef up the power supply decoupling capacitors surrounding the Propeller? Or is there something that can be done in software? I'll look over datasheets and stuff to see what I can find...
Have you looked though your code for aliasing or rounding effects? Are the loops that drive the KS code free running, or do you pace them with WAITCNTs? Ideally I think all the control loops should run synchronously with the KS code even if they run much slower. Your quiet ADC + simple tone output case is one of the things leading me in this direction. That case sounds like it has all the input code running, and simple output code running but none of the code that normally connects the two. (i.e. the connecting code is screwing something up) The other thing is my experience with Z-transform models. Sample/update rate is in nearly every coefficient, so having an uncertain sample/update rate is bound to screw things up. The other thing is that Z-transform models often have numerical precision problems as complexity is increased. This is especially bad while using a "fast" sample rate to simulate "slow" dynamics. A lot of the coefficients in the models end up as almost 1 and almost 0.
On initialization, the K-S delay buffer is initialized with a bunch of 0's, and that doesn't change until pluck is called. But, the pins are set to output and the counters are configured to drive them (now in NCO mode), so you're right...any electrical noise can now come across. But I do want to make sure my code isn't creating any extra noise for you.
How did you inject the simple tone?
Also, is whine still at approx 216 Hz? If so, it's definitely worth finding out which portion of the code (if any) is looping at that rate. Maybe try adding in a call to a time_waster function in various places, trying to see if the whine frequency lowers.
Wow, lots to consider! Both my pluck monitor and ADC read loops are free running. I'll try adding in delays to each to see how it affects the output noise. If the noise changes with the delay, then I'll have to reconsider how I've done my loops. Also, I want to try putting the ADC read and pluck monitor code into the same loop to see how that changes things. I had them separate because I thought the loop would end up being too slow...
Wow, lots to consider! Both my pluck monitor and ADC read loops are free running. I'll try adding in delays to each to see how it affects the output noise. If the noise changes with the delay, then I'll have to reconsider how I've done my loops. Also, I want to try putting the ADC read and pluck monitor code into the same loop to see how that changes things. I had them separate because I thought the loop would end up being too slow...
If the ADC read and pluck monitor loops are less than ~30 lines total, they likely are running in the 3-4KHz range. I suspect the noise dances around a lot because these two loops have some run length variation. I should glance though the KS code too, the exact placement of the WAITCNT that sets the sample rate is important to avoid interactions like this. (i.e. the WAITCNT should be just before the most time critical code, in this case, the PWM output.) *edit* nevermind It's in the right spot in the KS code. *\edit*
First quick test: I moved everything to the same loop, in the following sequence:
1. read all the ADC inputs and load the values into the variables I'm using
2. convert the ADC values into the equivalent fret position
3. load the current MIDI note and sustain value into the string
4. check to see if the string has been plucked, and call a pluck if yes
5. repeat 3 + 4 for the other three strings
The result? The noise is different. There now seem to be at least three different periodic waveforms from what I can hear. I'll keep playing around.
Oh, as a side note, I'm doing all of this using 12blocks, which makes things blissfully simpler to rearrange, modify and disable.
I added a delay to the pluck monitor loop. Depending on the length of the delay (200, 50, 5mS), there is an additional noise component on top of the usual. Adding the same type of delay to the ADC loop produces the same effect.
I moved everything into the same loop, and the noise is about the same. Still a high and low frequency component. There seems to be no drop in responsiveness though, so that's good - I was able to free up the cog that used to be updating the ADC.
A bit of hardware experimentation first. Originally I had a DC blocking capacitor and a resistor on the output right before the output jack. I increased the value of the cap and shorted out the resistor. As expected, this resulted in a louder output signal, and (presumably) better DC blocking and low frequency response. Unfortunately, the noise level also increased proportionately.
I also realized that the Conduit runtime link built into 12blocks, which I was using to watch the variable values, was also adding noise. When I disable it, a component of the noise disappears.
A few posts back someone asked what code Iw as using to generate the simple tone. It's the "FREQOUT" command inside the "BS" library. Here's the bits of code:
PUB FREQOUT(Pin,Duration, Frequency)
{{
Plays frequency defines on pin for duration in mS, does NOT support dual frequencies.
BS2.Freqout(5,500,2500) ' Produces 2500Hz on Pin 5 for 500 mSec
}}
Update(Pin,Frequency,0) ' Set tone using FREQOUT_Set
Pause(Duration) ' duration pause
Update(Pin,0,0) ' stop tone
PRI update(pin, freq, ch) | tctr,tfrq
{updates either the A or B counter modules.
Parameters:
pin - I/O pin to transmit the square wave
freq - The frequency in Hz
ch - 0 for counter module A, or 1 for counter module B
Returns:
The value of cnt at the start of the signal
Adapted from Code by Andy Lindsay
}
if freq == 0 ' freq = 0 turns off square wave
waitpeq(0, |< pin, 0) ' Wait for low signal
dira[pin]~
if ch==0
ctra := 0 ' Set CTRA/B to 0
else
ctrb := 0 ' Set CTRA/B to 0
tctr := pin ' CTRA/B[8..0] := pin
tctr += (%00100 << 26) ' CTRA/B[30..26] := %00100
tfrq:= calcFrq(freq) ' calculate frequency
if ch==0
if tctr<>ctra or tfrq<>frqa
ctra := tctr ' Copy temp to CTRA/B
frqa := tfrq
phsa := 0 ' Clear PHSA/B (start cycle low)
else
if tctr<>ctrb or tfrq<>frqb
ctrb := tctr ' Copy temp to CTRA/B
frqb := tfrq
phsb := 0 ' Clear PHSA/B (start cycle low)
dira[pin]~~ ' Make pin output
result := cnt ' Return the start time
Not sure if it's helpful, but there you go. I guess it just shuts down the pin and counter when not in use, which would explain why there's no noise while sitting idle. Could something similar be done with the Karplus Strong code? Or would it be too difficult to automatically detect when the string is finished "vibrating?" The time it takes to reach that point depends on a number of factors including volume, pitch and sustain, which can change in real time. Perhaps there's a clever way to do it. I'll experiment with an external input to control the pins. But not tonight.
to an external input. The result? A small change in the noise. It drops in pitch a little.
I noticed an interesting thing in the process, though. 12blocks also lets me see what's going on with the pins, and there is definitely something coming out of my four output pins. For the most part they are completely in phase, aside from a skipped pulse every 8 cycles or so. There's a bit of a pattern to it: short short short short short short short long. The number of "shorts" goes up or down by one, and the long is equal to either two or three shorts. *edit* of course, this could be interference from the runtime link...
Oh! And one last thing for tonight. Noise is there when I'm running the simple tone routine with runtime link enabled. I must investigate further once fully awake.
A quick experiment while my kids eat breakfast: I seeded each string a slightly different sampling rate (offset of 1kHz each), and the result was a cacophonous blend of slightly out of phase noise. 'Twas awful. But it made me wonder if there's a way to seed the strings in a way that would make them cancel each other out...
Here's a screenshot. To the left of the screen you can see part of the loop. The fret positions and sustain values are updated, then the loop checks for plucks. Beside it is a big if-else tree used to determine the fret position. To the right are the pin states at the time of capture; normally pins 16-19 are toggling even with no pluck. And in the bottom right, you can see the waveform coming out of the pins during "idle."
More testing. I put the entire loop into an IF statement, controlled by an external switch. All of the karplus strong and ADC stuff is contained inside the if statement, though of course the cogs running the ADC and KS code are still running. This seems to kill the high frequency component of the noise, but not the low.
I sure hope you guys don't mind all these separate posts. It's kinda like an ongoing journal for me. I've discovered something else that is rather interesting.
I changed my loop again. This time I'm using a fixed sustain that is set before the main loop starts. Inside the main loop the ADC is checked, then all four sensors are checked to see if they have been triggered. If yes, the ADC for that string is read, and a pluck is called. The result? When idle, i can see a perfect 48kHz triangle wave (guess what my sampling rate is!) It literally looks like it's coming straight out of a signal generator. But there's something else there riding on top of the sampling rate, again at about 200Hz.
What does this tell me? Well for one thing, my low pass filter is c**p, if it's letting 48kHz through. I'll fix that up. But the 200Hz signal... where is it coming from?
... looking at the 12-blocks screen shot, the logic analyzer traces of the four output pins show 200Hz content. Specifically a long high or low pulse happens about every 5mS. These extra long pulses are really odd, wait, that's aliasing. With the state of the output pins changing 96,000 times a second you'll need to sample pin state at leas every 10uS to avoid aliasing. Even so, this aliasing is showing that the phase of the output signal is shifting at ~200Hz relative to 12-blocks sampling. (a small part of this is that 48KHz does not evenly divide 80MHz, though that should happen about every 52mS) What loops are running at 200Hz?
Also, to really NAIL timing you should use the following two bits of code. (or variations)
'place before the loop
c1 := period
c1 += cnt 'c1 is now a time 'period' [clocks] in the future
and just before the most time critical parts of the loop
waitcnt(c1) 'wait for time c1 to arrive
c1+= period 'advance c1 'period' [clocks] into the future.
'Note: adding 'period' to 'c1' can go anywhere in the loop as long as it happens once during the loop.
Notice that this code does NOT read CNT while in the loop. This insures that all wait targets are calculated relative to the initial value of CNT. This insures that wait times are unaffected by code overhead. I've used this code many times to pace loops, or make delays, that are accurate to a single clock cycle from spin. (within limits ofc. can't make 'period' too short or long)
Comments
thanks,
Jonathan
Not really, the NEG instruction nicely combines a negation and a move, likely already had a move instruction. (to ape the apple meme "there's and instruction for that!") The rest is adjustments to scaling and bias point math the code should already be doing.
Yep. But now all the noise due to pwm is at the sampling frequency or higher. No special cases where pwm noise slides down near 0Hz.
Lawson
Any fancy updates for the code that I should wait for, before forging ahead with what we've got already?
No earth-shaking updates yet. I got the higher ranges to work a bit better by "seeding" the initial noise burst with a small pseudo-sine wave at the fundamental frequency. (Unfortunately the easiest way to do it has a small discontinuous jump, which at loud volumes is a bit tiring to the ear.) I would say try the current version as-is, and let me know what features you'd like next. I _was_ going to work on the multiple strings per cog, but I saw your rig only had 4 "strings", so I got lazy [8^)
Jonathan
I'll try to capture an audio sample tonight. I also need to tear the guitar back open and do some changes to the hardware that will hopefully kill some noise, as discussed earlier.
Jonathan
From what I've read, same pitch, same volume, same sustain, minute difference in pluck time. It's more out of curiosity though, to see how synthetic paired strings sound compared to real ones.
But what of the noise? There are different varieties in there. There is a constant "whine" that sounds very periodic, as if a clock-like signal is being introduced. There is also static-type noise that persists after a string is plucked, that sounds like a cross between whitespace on an old TV, and an early modem. It can be killed by ramping down the sustain for a second or two.
I'd get a sample for you to hear but I'm having trouble recording it with my computer. It's introducing a ton of noise all on its own that washes out what's coming from the guitar. (no guitar strum sounds for you folks either, for the same reason)
More details when I have 'em...
The sustain doesn't just kill the noise, it also changes it. Sometimes when I drop the sustain, the output frequency of the whine drops a little, too. But not all the time.
And, for the final test: I commented out all the karplus strong code, and instead made the guitar produce a simple tone for one second. When idle, the output is absolutely silent. Intrigued, I dropped in just the code that initializes four strings. Lo and behold, the noise is back. I took that out and put in the code that initializes just one "stereo" string - no noise. So, perhaps my troubles are coming from there.
Well, that's enough for tonight. Gotta get to sleep before I turn into a zombie.
* What is your sampling frequency? 44.1kHz?
* How fast is your main control loop (the one in charge of calling pluck, etc.)?
* When the string is muted do you call pluck( 1 ), or just let it run without any inputs?
Just as a note: the K-S algorithm uses a set or random values as a starting impulse...there is no guarantee that the average of those values is equal to 0. Every time you call pluck it generates another burst of noise to act as the driver...and it generates the noise for 1 period. I was thinking if pluck gets called every 4.64 ms or so, it could be responsible for some of the noise you are hearing.
Jonathan
There is a second loop whose purpose is to read the ADC, and continually update the pitch and sustain. I'll do some experiments with the loops to see if they are causing this, but I dont think they are. Remember, all I need to do is initialize multiple strings and the noise is there.
One other thing I want to try is to physically disconnect three of the four strings, to see if they are feeding back into each other somehow.
Lawson, I made a slight change. Instead of setting FRQA to 1, and managing the range of PHSA, I did the following:
- set FRQx to be equal to POSX / clocks_per_period (period of the sampling rate)
- you change the value by setting PHSx to a value between 0 and POSX (1 << 31 - 1)
- if PHSx starts at 0, adding FRQx to it each clock will yield POSX at the
end of the period, meaning that the high bit never goes to 1
- if PHSx starts at POSX, after the 1st clock FRQx will have been added, so the pin goes high
- if PHSx is set to POSX/2, it will spend 1/2 the period low, then 1/2 the period high
Jonathan
Jonathan
Excellent idea! It was on my to-do list, but it'll take some better coding than an if statement in a loop...
I tried eliminating the ADC completely from my program, fixing the strings to set values. That killed the high-pitched portion of the noise, but not the lower frequency. Since there is also noise in the output when I'm changing the contents of the LCD or turning the lasers on and off, I'm fairly certain that this noise is coming from spikes in voltage caused by the rapid changing of serial lines or other current-hungry devices. And remember that there is no noise when I'm not running any KS stuff. So to summarize:
ADC + KS = low + high whine
KS only = low whine
ADC + KS (initialized only) + simple tone output = low + high whine
ADC + simple tone output = no noise
Hmmm, so what shall I do now? It would seem that running KS leaves the outputs "vulnerable" to the effects of other pins switching on and off. Shall I attempt a hardware fix, like adding external pullup or pulldown resistors? Perhaps beef up the power supply decoupling capacitors surrounding the Propeller? Or is there something that can be done in software? I'll look over datasheets and stuff to see what I can find...
Lawson
How did you inject the simple tone?
Also, is whine still at approx 216 Hz? If so, it's definitely worth finding out which portion of the code (if any) is looping at that rate. Maybe try adding in a call to a time_waster function in various places, trying to see if the whine frequency lowers.
Jonathan
If the ADC read and pluck monitor loops are less than ~30 lines total, they likely are running in the 3-4KHz range. I suspect the noise dances around a lot because these two loops have some run length variation. I should glance though the KS code too, the exact placement of the WAITCNT that sets the sample rate is important to avoid interactions like this. (i.e. the WAITCNT should be just before the most time critical code, in this case, the PWM output.) *edit* nevermind It's in the right spot in the KS code. *\edit*
Lawson
1. read all the ADC inputs and load the values into the variables I'm using
2. convert the ADC values into the equivalent fret position
3. load the current MIDI note and sustain value into the string
4. check to see if the string has been plucked, and call a pluck if yes
5. repeat 3 + 4 for the other three strings
The result? The noise is different. There now seem to be at least three different periodic waveforms from what I can hear. I'll keep playing around.
Oh, as a side note, I'm doing all of this using 12blocks, which makes things blissfully simpler to rearrange, modify and disable.
I added a delay to the pluck monitor loop. Depending on the length of the delay (200, 50, 5mS), there is an additional noise component on top of the usual. Adding the same type of delay to the ADC loop produces the same effect.
I moved everything into the same loop, and the noise is about the same. Still a high and low frequency component. There seems to be no drop in responsiveness though, so that's good - I was able to free up the cog that used to be updating the ADC.
I also realized that the Conduit runtime link built into 12blocks, which I was using to watch the variable values, was also adding noise. When I disable it, a component of the noise disappears.
A few posts back someone asked what code Iw as using to generate the simple tone. It's the "FREQOUT" command inside the "BS" library. Here's the bits of code:
Not sure if it's helpful, but there you go. I guess it just shuts down the pin and counter when not in use, which would explain why there's no noise while sitting idle. Could something similar be done with the Karplus Strong code? Or would it be too difficult to automatically detect when the string is finished "vibrating?" The time it takes to reach that point depends on a number of factors including volume, pitch and sustain, which can change in real time. Perhaps there's a clever way to do it. I'll experiment with an external input to control the pins. But not tonight.
to an external input. The result? A small change in the noise. It drops in pitch a little.
I noticed an interesting thing in the process, though. 12blocks also lets me see what's going on with the pins, and there is definitely something coming out of my four output pins. For the most part they are completely in phase, aside from a skipped pulse every 8 cycles or so. There's a bit of a pattern to it: short short short short short short short long. The number of "shorts" goes up or down by one, and the long is equal to either two or three shorts. *edit* of course, this could be interference from the runtime link...
Oh! And one last thing for tonight. Noise is there when I'm running the simple tone routine with runtime link enabled. I must investigate further once fully awake.
I changed my loop again. This time I'm using a fixed sustain that is set before the main loop starts. Inside the main loop the ADC is checked, then all four sensors are checked to see if they have been triggered. If yes, the ADC for that string is read, and a pluck is called. The result? When idle, i can see a perfect 48kHz triangle wave (guess what my sampling rate is!) It literally looks like it's coming straight out of a signal generator. But there's something else there riding on top of the sampling rate, again at about 200Hz.
What does this tell me? Well for one thing, my low pass filter is c**p, if it's letting 48kHz through. I'll fix that up. But the 200Hz signal... where is it coming from?
Also, to really NAIL timing you should use the following two bits of code. (or variations) and just before the most time critical parts of the loop
Notice that this code does NOT read CNT while in the loop. This insures that all wait targets are calculated relative to the initial value of CNT. This insures that wait times are unaffected by code overhead. I've used this code many times to pace loops, or make delays, that are accurate to a single clock cycle from spin. (within limits ofc. can't make 'period' too short or long)
Lawson