I'm just getting my head round a dsp buffer delay, this works but crackles as I make the delay longer,
I can see why it's going wrong, [I should be using just 1 float array] but I can't figure out how to fix
every guess so far has had problems.
I'm just getting my head round this so it's likely I'm doing something stupid
I can see why it's going wrong, [I should be using just 1 float array] but I can't figure out how to fix
every guess so far has had problems.
I'm just getting my head round this so it's likely I'm doing something stupid
HRESULT VDJ_API CLocoDog4::OnStart()
{
k = -1;
mbuff = 0;
std::memset(myBuffer1, 0, sizeof(myBuffer1));
std::memset(myBuffer0, 0, sizeof(myBuffer0));
return S_OK;
}
HRESULT VDJ_API CLocoDog4::OnParameter(int id)
{//16.35 = C 0
switch (id)
{
case LocoDog4_Param_Slider_Delay:
sldMplr = 16.35 + (parSliderDelay * 1030.5);
sampleCount = SongBpm / sldMplr;
break;
}
return S_OK;
}
HRESULT VDJ_API CLocoDog4::OnProcessSamples(float *buffer, int nb)
{
float SampleOut_Left, SampleOut_Right;
for (i = 0; i < nb; i++)
{
if (k < (sampleCount)) { k++; }
else{ k = 0; mbuff = !mbuff; };
if (mbuff == 0)
{
myBuffer1[2 * k] = buffer[2 * i];
myBuffer1[2 * k + 1] = buffer[2 * i + 1];
SampleOut_Left = buffer[2 * i] + myBuffer0[2 * k];
SampleOut_Right = buffer[2 * i + 1] + myBuffer0[2 * k +1];
}
else if (mbuff == 1)
{
myBuffer0[2 * k] = buffer[2 * i];
myBuffer0[2 * k + 1] = buffer[2 * i + 1];
SampleOut_Left = buffer[2 * i] + myBuffer1[2* k ];
SampleOut_Right = buffer[2 * i + 1] + myBuffer1[2 * k + 1 ];
}
buffer[2 * i] = SampleOut_Left * 0.707;
buffer[2 * i + 1] = SampleOut_Right * 0.707;
}
return S_OK;
}
Posted Fri 13 Mar 20 @ 12:37 pm
Usually, a ring buffer is used for that.
Basically you keep track of a write pointer, and you calculate the read pointer from that.
So every sample, you increase the write pointer, and you store the sample in the correct position:
Every time your pointer reaches the end of the buffer, you want to wrap it back around to the start, you can use modulo for that, either while increasing the pointer or when using it:
or
If you want to read from a point in the past, you can calculate a read pointer:
A negative read pointer is of course not possible, so it needs to wrap around to the back of the buffer.
Since % does not produce the expected modulo for negative values, you either have to use an if, or you can add the buffer size prior to taking the modulo:
Changing the delay would indeed cause a discontinuity that causes a click if it's short, or a crackle if it's continuously updating.
It's a bit tricky to solve this. One solution is to only update delayTarget, and in your loop let delay slowly catch up to delayTarget.
Another solution is to continue calculating the buffer using the original delay value, but reducing the volume of that delay (over a minimum of 64 but can be up to 512 samples to ensure no clicks heard)
At the same time you add a second delay with the new delay value and ramp up the volume of that delay over the same period of time.
If you do it this way, you'll also need to ensure that this new delay is not updated by parameter changes during this ramp, but a new ramp is started when the previous ramp is finished.
Basically you keep track of a write pointer, and you calculate the read pointer from that.
So every sample, you increase the write pointer, and you store the sample in the correct position:
myBuffer[writePointer] = sourceBuffer[j];
writePointer++;
Every time your pointer reaches the end of the buffer, you want to wrap it back around to the start, you can use modulo for that, either while increasing the pointer or when using it:
writePointer = (writePointer+1)%myBufferSize;
or
myBuffer[writePointer%myBufferSize] = sourceBuffer[j];
If you want to read from a point in the past, you can calculate a read pointer:
readPointer = writePointer - delay;
A negative read pointer is of course not possible, so it needs to wrap around to the back of the buffer.
Since % does not produce the expected modulo for negative values, you either have to use an if, or you can add the buffer size prior to taking the modulo:
readPointer = (writePointer + myBufferSize - delay) % myBufferSize;
Changing the delay would indeed cause a discontinuity that causes a click if it's short, or a crackle if it's continuously updating.
It's a bit tricky to solve this. One solution is to only update delayTarget, and in your loop let delay slowly catch up to delayTarget.
Another solution is to continue calculating the buffer using the original delay value, but reducing the volume of that delay (over a minimum of 64 but can be up to 512 samples to ensure no clicks heard)
At the same time you add a second delay with the new delay value and ramp up the volume of that delay over the same period of time.
If you do it this way, you'll also need to ensure that this new delay is not updated by parameter changes during this ramp, but a new ramp is started when the previous ramp is finished.
Posted Fri 13 Mar 20 @ 1:07 pm
As adion said dynamic ringbuffer may be the solution
You can prevent crackles by clearing the readed data once readed, this will delay the effect the difference time cleanly
only when samplecount reduce slightly effect may read already unreaded data which is not a problem as they are right ones to be processed
This way read offset can compute
By the way code can divide by 2 because right and left channels have the same treatment
k is the writing offset
mbuff the reading offset
of course the size of buffer have to be at least [MaxSampleCount*2*2]
something like that:
HRESULT VDJ_API CLocoDog4::OnProcessSamples(float *buffer, int nb)
{
float SampleOut_Left, SampleOut_Right;
int mbuff; // read offset (can be local because it is computed)
for (i = 0; i < nb*2; i++) // because Left and right have the same treatment
{
k = (k + 1) % (sampleCount*2*2); // left & right writing offset
mbuff = (k + sampleCount*2) % (sampleCount*2*2); // left & right reading offset (readPointer = writePointer + delay, the same as readPointer = writePointer - delay in a ring buffer of 2*delay size, delay being sampleCount here)
myBuffer[k] = buffer;
buffer = (buffer + myBuffer[mbuff]) * 0.707; // same thing for left and right
myBuffer[mbuff]=0; // security to prevent reading obsolete data in case sampleCount increases (crackles)
}
return S_OK;
}
Hope i didn't messed something, not on my dev machine :\
You can prevent crackles by clearing the readed data once readed, this will delay the effect the difference time cleanly
only when samplecount reduce slightly effect may read already unreaded data which is not a problem as they are right ones to be processed
This way read offset can compute
By the way code can divide by 2 because right and left channels have the same treatment
k is the writing offset
mbuff the reading offset
of course the size of buffer have to be at least [MaxSampleCount*2*2]
something like that:
HRESULT VDJ_API CLocoDog4::OnProcessSamples(float *buffer, int nb)
{
float SampleOut_Left, SampleOut_Right;
int mbuff; // read offset (can be local because it is computed)
for (i = 0; i < nb*2; i++) // because Left and right have the same treatment
{
k = (k + 1) % (sampleCount*2*2); // left & right writing offset
mbuff = (k + sampleCount*2) % (sampleCount*2*2); // left & right reading offset (readPointer = writePointer + delay, the same as readPointer = writePointer - delay in a ring buffer of 2*delay size, delay being sampleCount here)
myBuffer[k] = buffer;
buffer = (buffer + myBuffer[mbuff]) * 0.707; // same thing for left and right
myBuffer[mbuff]=0; // security to prevent reading obsolete data in case sampleCount increases (crackles)
}
return S_OK;
}
Hope i didn't messed something, not on my dev machine :\
Posted Fri 13 Mar 20 @ 6:49 pm