0
$\begingroup$

I am developing an embedded DSP audio processor and am trying to implement a chorus effect. To my understanding, the chorus effect is multiple delayed versions of the original signal, where the delay itself is random. In my program I have set the delays to be randomly defined upon initialization and then remain static during the program execution.

The challenge I'm faced with is that in using a circular buffer to store my past signal, I cannot simply wrap delayed signals back around the circular buffer as there is multiple delays present.

With a single delay I can simply check if the delay required exceeds the current position in the circular buffer, if so, I can jump to the end of the circular buffer subtract the delay and add my current position in the circular buffer.

if (Delay > Current_Position_in_Buffer)
{
    Circular_Array[Length_of_Array + Current_Position_in_Buffer - Delay];
}
else
{
    Circular_Array[Current_Position_in_Buffer - Delay];
}

Is there an efficient way to check and adjust the position in my circular array of each delayed signal, without separately checking each delay with an if statement?

$\endgroup$
  • $\begingroup$ Are you open to making the length of the buffer something nice? Like a power of 2 say? $\endgroup$ – hops Apr 23 '18 at 16:08
  • $\begingroup$ I have now made my buffer 2^12 long and tried to implement it in my system. I'm sure the fault is on my end and I need to troubleshoot to get it to work, but I am unsure on how this would work for values where Delay > Current_Position_in_Buffer ( Current_Position_in_Buffer - Delay < 0). How does bit-wise and work on negative values? Or am I interpreting your answer wrong? $\endgroup$ – C. Faldon Apr 23 '18 at 17:07
  • $\begingroup$ Signed numbers should be interpreted as strings of bits in 2's complement format. Can you post a snippet of your code attempt? $\endgroup$ – hops Apr 23 '18 at 20:54
  • $\begingroup$ My code inserts data into the circular array in blocks, and I have already implemented an and operation to cycle the buffer. Using hotpaw's suggestion, I added a buffer in the memory before the circular array ,inputing the data from the last circular buffer block into it. I used my code above, only keeping the code in the else bracket for the main circular buffer. Seems to work fine in my code, I think I'm somewhat half implementing what you guys were getting at. Thanks everyone for your suggestions. $\endgroup$ – C. Faldon Apr 23 '18 at 21:17
1
$\begingroup$

If I understood correctly what you are asking, then this is probably more of a programming question... But the solution would be to use modular arithmetic to compute the position of the delay. With a length equal to a power of 2, this could be accomplished with an AND instruction.

const int Mask = (1 << 16) - 1;
Circular_Array[(Current_Position_in_Buffer - Delay) & Mask];

In this example, the length of your buffer is $2^{16}$.

More generally you can use the modulo operator, but it will be slower.

$\endgroup$
1
$\begingroup$

In many DSPs, you get a programmable Address Calculation Unit – I think that's what mainly caused the (former) popularity of the Motorola 56k DSP family. You can typically just program that to do your "wraparound indexing" for you, without your code needing to check any addresses.

Now, the fact that you're writing this in C points to you using some processor platform with mature compiler support.

In that case, you should possibly not fret at all – memory access is typically way slower than a branch (what an if statement typically compiles to), so this might not make a difference at all – especially since many platforms (ARM, x86, SPARC (I think),…) do have conditional moves, which basically means that the loading of memory might start very early in the pipeline.

$\endgroup$
1
$\begingroup$

I was going to suggest a power of two length with an AND mask as hops answered. As MM suggests, memory fetch time may swamp slight optimizations like that. The modulo for the general case would be a much worse solution as it brings in a divide operation. For the general case, you still need an if statement, but you can improve it (insignificantly?) slightly by coding it like this:

Index = Current_Position_in_Buffer - Delay;
if( Index < 0 ) Index += Length_of_Array;

Circular_Array[Index];

Two cents worth, maybe.

Ced

$\endgroup$
1
$\begingroup$

Depending on memory available, processor cache size(s), and branch prediction performance (etc.), it might be a manual optimization to write data into the circular buffer twice, into the circular buffer, but also into an equal sized memory block directly before the circular buffer. That way, after the initial pre-fill, there will always be valid data in memory after subtracting the delay from the current index, and no modulo wrap-around of each offset will be required to read those offset samples.

On some systems, it might be possible to memory map a circular buffer into a memory address block right before that buffer, and thus a data write only needs to be done once for data to appear at both required memory addresses for non-modulo offset reads to be possible.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.