Plug-ins, Hosts, Apps,
Hardware, Soundware
Developers
(Brands)
Videos Groups
Whats's in?
Banks & Patches
Download & Upload
Music Search
KVR
   
KVR Forum » DSP and Plug-in Development
Thread Read
Distortion in tremolo effect
LemonLime
KVRist
- profile
- pm
- e-mail
PostPosted: Wed Apr 18, 2012 1:59 pm reply with quote
Hello.

I am in the process of writing code for DSP effects and I've run into a snag for the tremolo effect. The code applies the tremolo to an existing (mono or stereo) file at a specified frequency (LFO of course) and depth. However, when the frequency and depth of the oscillator get higher, I start to hear subtle distortion in the result. Have a listen:

http://www.christianfloisand.com/tremsample.wav

Here is the code from the loop that's processing the effect (I've expanded the code here just for clarity.. many of these operations are optimized in the actual code and the LFO is implemented as a class):



chanMode = 2; // for stereo
readCount = num. items read from input file into buffer (1024)
DEPTH = modulation depth, between 0 and 1
RATE = frequency of LFO

for (i = 0; i < chanMode; i++) {
         
   for (j = i; j < readCount; j+=chanMode) {
      tremSignal = (1 - DEPTH) + DEPTH * sin(phase);
      buffer[j] *= tremSignal;
      phase += TWOPI / SR * RATE;
      if (phase >= TWOPI) phase -= TWOPI;
      if (phase < 0.0) phase += TWOPI;
   }
         
}



Any thoughts? At first I thought aliasing, but I've examined the frequency spectrum plot (in Audacity) many times and it shows no frequencies above Nyquist. The distortion starts to become noticeable around a frequency of 4 Hz+ and depth that exceeds 70%.

Any help.. words of advice.. would be greatly appreciated. Thanks!

Chris
^ Joined: 15 Apr 2012  Member: #278696  Location: Toronto, ON
LemonLime
KVRist
- profile
- pm
- e-mail
PostPosted: Wed Apr 18, 2012 2:22 pm reply with quote
Upon continued further testing, this distortion/artifacting seems only to appear when it's a stereo file. There is no problem when I converted the soundfile to mono format and processed it as such. So I must be processing the stereo file incorrectly...?
^ Joined: 15 Apr 2012  Member: #278696  Location: Toronto, ON
aciddose
KVRAF
- profile
- pm
- e-mail
- www
PostPosted: Wed Apr 18, 2012 2:25 pm reply with quote
apparently you're trying to read interlaced? nobody can help you when you've stripped all typing and the main body of the code surrounding the function.
^ Joined: 07 Dec 2004  Member: #50793  
eigentone
KVRist
- profile
- pm
PostPosted: Wed Apr 18, 2012 3:02 pm reply with quote
Amplitude Modulation can introduce alias frequencies. The faster you modulate it, the more evident the effects -- depth and shape are also contributors.

Quote:
Upon continued further testing, this distortion/artifacting seems only to appear when it's a stereo file.


Not so. They should be identical when the modulation signal is identical for each channel. If that's true, it suggests an unrelated issue (see aciddose's response).

Quote:
At first I thought aliasing, but I've examined the frequency spectrum plot (in Audacity) many times and it shows no frequencies above Nyquist.


That's not how sampling works. It's already mirrored/aliased by the time you view it, unless you are doing something special you've not mentioned. Modulate deep, fast, and at several frequencies, and you will see harmonic information below your signal's fundamental. Try modulating a square wave at 1kHz for your input in this test.
^ Joined: 03 Apr 2007  Member: #146135  
mystran
KVRAF
- profile
- pm
- e-mail
- www
PostPosted: Wed Apr 18, 2012 4:46 pm reply with quote
The artifacts in the audio file linked are not aliasing. Such clicking is most commonly some sort of buffer-related artifact, in this case an issue with LFO phase continuity at buffer boundaries.

The problem is that you loop over channels, then in the inner loop you loop over samples. Since you only have one phase-accumulator, the other channel starts from where the first channel ended. Then in the next block the first channel starts from where the second channel ended in the previous block.

So essentially you take the first "readCount" samples from the LFO sine for the first channel. Then you take the next "readCount" samples for the second channel, and so on. That means several things but it's easier to fix than explain in words, so let's do that instead.

Easiest thing (if you don't need separate LFOs for the two channels) is to move the channel loop inside the sample loop. That will already fix all the distortion problems. There remains two additional problems though: the phase of the LFOs for the two channels will be slightly offset (not obvious unless you push the LFO into audio rates) and the rate of the LFO will be RATE*chanMode (ie twice the nominal for stereo). We can further fix this easily by moving the phase update out of the (now inner) channel loop. As a bonus you get completely linear memory-access pattern which is usually ideal for performance (ie, if your data is interleaved, you normally want to process it in interleaved format.. which can be the more efficient option for other reasons as well):


chanMode = 2; // for stereo
readCount = num. items read from input file into buffer (1024)
DEPTH = modulation depth, between 0 and 1
RATE = frequency of LFO

for (j = i; j < readCount; j+=chanMode) {
   tremSignal = (1 - DEPTH) + DEPTH * sin(phase);
   for (i = 0; i < chanMode; i++) {
      buffer[j+i] *= tremSignal;
   }
   phase += TWOPI / SR * RATE;
   if (phase >= TWOPI) phase -= TWOPI;
   if (phase < 0.0) phase += TWOPI;
}


I also moved the "tremSignal" calculation out of the inner loop because it's now loop-invariant so there's no point in calculating the expensive sin() twice... but if you're lucky the compiler might even do that for you anyway.
----
<- my plugins | my music -> @Soundcloud
^ Joined: 11 Feb 2006  Member: #97939  Location: Helsinki, Finland
LemonLime
KVRist
- profile
- pm
- e-mail
PostPosted: Thu Apr 19, 2012 5:23 am reply with quote
Thanks everyone for the feedback!
mystran, that was indeed the issue and it has been fixed. The outputs now sound smooth as silk.
^ Joined: 15 Apr 2012  Member: #278696  Location: Toronto, ON
Ichad.c
KVRist
- profile
- pm
- e-mail
- www
PostPosted: Thu Apr 19, 2012 12:13 pm reply with quote
Distortion in tremolo is not essentially a bad thing - just depends on the type Wink

Some people pay extra for it. Google bias-tremolo.

Andrew
^ Joined: 08 Feb 2012  Member: #274678  Location: South - Africa
codehead
KVRer
- profile
- pm
PostPosted: Thu Apr 19, 2012 2:12 pm reply with quote
mystran wrote:
I also moved the "tremSignal" calculation out of the inner loop because it's now loop-invariant so there's no point in calculating the expensive sin() twice... but if you're lucky the compiler might even do that for you anyway.


Minor issue, just with "if you're lucky the compiler might..."—you're not giving modern compilers much credit Wink

I tend to pull loop-invariant stuff out manually anyway by habit (and because it's easier to see what's really taking the time in the loop, plus it matches stepping through in debug with reality in the release, if that's an issue), but...

I can't imagine a C compiler that would not pull such a thing outside the loop—luck doesn't have much to do with it (compiler options perhaps, yes—loop invariant motion disabled for debug...).

I haven't spent a lot of time looking at compiler output since about 20 years ago (back when it was necessary), at least not on a "scrutinize everything" basis—if you have performance issues, then it's time to look at the bottlenecks. Thinking back on reasons I've looked at compiler output over the past 10 years...definitely to see if things are really getting inlined, which is something that you can ask for and still not get...stuff like the float-in conversion under microsoft compilers under windows (horrors of 387 emulation in the rounding bit manipulation)...
^ Joined: 22 Dec 2010  Member: #246113  
aciddose
KVRAF
- profile
- pm
- e-mail
- www
PostPosted: Thu Apr 19, 2012 2:42 pm reply with quote
they have intrinsics now so it isn't an issue any longer.

inline vs. __forceinline have different effects. inline will work when __force wont, and you can combine them!
^ Joined: 07 Dec 2004  Member: #50793  
codehead
KVRer
- profile
- pm
PostPosted: Thu Apr 19, 2012 9:11 pm reply with quote
aciddose wrote:
they have intrinsics now so it isn't an issue any longer.


Sure—I was giving an example of something that forced me to look at compiler output ten years ago.

The point I was trying to make was:

a) 20 years ago I had to pore over the code that came out of the compiler, for critical code

b) over the past 10 years, just when something fishy popped up

Of course, part of that is compiler improvements, and a good chunk of it is the CPUs, for both raw speed and for cleverness (cache, pipelining, etc.)

aciddose wrote:
inline vs. __forceinline have different effects. inline will work when __force wont, and you can combine them!


Again, I was mainly giving an example of something I'd double check on the compiler ten years ago, not speaking to "today". Still, I'm not talking microsoft, and this is compiler dependent (always_inline in gcc)—maybe no big deal with any compiler I'm using currently, and I haven't had a need to check in years. If it's big and complicated, it's probably something I want to do in template meta programming. But ten years ago, you had to check if the compiler really inlined it when you declared it inline, and that's why I gave it as one example of what I may have not trusted the compiler on in the past ten years.
^ Joined: 22 Dec 2010  Member: #246113  
aciddose
KVRAF
- profile
- pm
- e-mail
- www
PostPosted: Thu Apr 19, 2012 9:54 pm reply with quote
it's still an issue in msvc2010.

msvc2008's linker will crash if you're linking a x64 binary containing an inline function with a 32 * 64 signed int multiply inside it.

Smile
^ Joined: 07 Dec 2004  Member: #50793  
codehead
KVRer
- profile
- pm
PostPosted: Thu Apr 19, 2012 11:31 pm reply with quote
aciddose wrote:
it's still an issue in msvc2010.

msvc2008's linker will crash if you're linking a x64 binary containing an inline function with a 32 * 64 signed int multiply inside it.

Smile


Ouch!
^ Joined: 22 Dec 2010  Member: #246113  
mystran
KVRAF
- profile
- pm
- e-mail
- www
PostPosted: Fri Apr 20, 2012 3:19 am reply with quote
codehead wrote:
mystran wrote:
I also moved the "tremSignal" calculation out of the inner loop because it's now loop-invariant so there's no point in calculating the expensive sin() twice... but if you're lucky the compiler might even do that for you anyway.


Minor issue, just with "if you're lucky the compiler might..."—you're not giving modern compilers much credit Wink


It depends on what your sin() actually maps to. If this is C++ you can have a sin() function/method in the class or namespace that performs some approximation, and if it's not properly declared const, then the compiler might in some cases (possibly depending on whether the source is available during the compilation of the current source file) fail to prove that the function is infact "pure" and doesn't change any lasting state. In absence of such a proof it's not safe to cache the value so loop invariant motion is invalid.

Oh and regarding __forceinline, while it doesn't really force anything, you get a compiler warning when a function declared __forceinline isn't inlined. You can then turn all warnings to errors (like everyone should do anyway) and get the build to fail if it's not inlined.
----
<- my plugins | my music -> @Soundcloud
^ Joined: 11 Feb 2006  Member: #97939  Location: Helsinki, Finland
All times are GMT - 8 Hours

Printable version
Page 1 of 1
Display posts from previous:   
ReplyNew TopicPrevious TopicNext Topic
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum
Username: Password:  
KVR Developer Challenge 2012