hii, i cannot find an example for how to use the limiter…
is there none?
Hi wilkins!
We don’t have a limiter example for DaisyDuino.
There is a limiter in DaisySP (DaisySP/limiter.cpp at master · electro-smith/DaisySP · GitHub), so that could be worth looking into.
thanks for the hint @Takumi_Ogata
i already had a look, but failed with including it in my signal chain.
Limiter myLimiter
myLimiter.Init()
limitedSignal = myLimiter.ProcessBlock(float *in, size_t size, float pre_gain)
unfortunately i have no idea how to pass a signal as first argument and what to pass as the second argument (buffer size?)
i (and surely not only me) would highly appreciate a simple example.
I recommend referencing other example codes, like compressor, moogladder, or reverb, to see how effects are implemented in DaisyDuino.
static Limiter myLimiter
at the top of the code
In setup(){}
:
myLimiter.Init(sample_rate);
and setting the limiter parameters as well.
In MyCallback(){}
You’ll need something like
...
sig_out = myLimiter.Process(osc_out);
for (size_t chn = 0; chn < num_channels; chn++) {
out[chn][i] = sig_out;
}
...
maybe i was not clear…
i’ve already sucessfully implemented various effects in daisyduino, but with the limiter i fail.
it does not provide
limiter.Process(), but, as i mentioned above
ProcessBlock(float *in, size_t size, float pre_gain)
that makes the problem for me, that i cannot reference other examples like compressor or so.
you know what i mean?
The Limiter class works on a block of samples, just like the AudioCallback. So, if you’re using the non-interleaved callback, you can simply pass the address of the out array, the block size, and a value for pre gain. If you’re using the Interleaved AudioCallback, you’d need to copy each channel’s samples into a contiguous array.
Works fine in C++, using BlockSize of 4. Things start going wrong when using larger block sizes, and in DaisyDuino, it doesn’t work the same.
I used the Pluck example code in both Arduino and C++. I haven’t figured out what is going on to make them behave differently. On Arduino, I didn’t see how to adjust the block size down from its apparent default of 48, so I broke the 48 Blocksize into 12 ProcessBlock() calls of 4 bytes each.
I’m still tinkering with this, my curiosity has been piqued.
great!
do you have any idea why the limiter is different from the other classes?
wouldn’t it be better to have some alternative, more consistent source code for a limiter?
Actually, you should be able to use limiter.Process(out[0], size, pre_gain);
to limit the left channel and limiter.Process(out[1], size, pre_gain);
for the right channel.
Yes, except you mean limiter.ProcessBlock(out[0], size, pre_gain).
As I wrote above, this will only work in the non-interleaved callback. Additionally, since most other processing is done per-sample, this has to be done after the loop which populates each block sample-by-sample.
And, for some reason, I can’t make the limiter work in Arduino, though it does work, sort of, in C++,
So, we’re back to - it would be nice if there was a simple example which actually demonstrates the limiter working.
UPDATE: problems I had with the limiter in both Arduino and C++ were user error.
I’ll post an Arduino example later (modified Pluck example)
The limiter is exactly as simple to use as I thought before I made a silly error which led to confusing results.
Here is an example, which turns the limiter on and off each time through the arpeggio, lighting LED_BUILTIN while limiter is active.
In response to the earlier question, “wouldn’t it be better to have some alternative, more consistent source code for a limiter?”, well, a function that works on a block of samples is more efficient, and if it’s really desired to do it sample-by-sample, it can be easily done, as is shown in this example.
// pluck_limit - Daisy Seed pluck example with added DaisySP limiter, by RF
// changes are marked by LIMITER comments
// Title: pluck
// Description: Plucked major chord
// Hardware: Daisy Seed
// Author: Stephen Hensley
#include "DaisyDuino.h"
DaisyHardware hw;
size_t num_channels;
// Helper Modules
static Metro tick;
static Pluck plk;
// MIDI note numbers for a major triad
const float kArpeggio[3] = {48.0f, 52.0f, 55.0f};
uint8_t arp_idx;
float init_buff[256]; // buffer for Pluck impulse
Limiter lim; // LIMITER
float pre_gain = 1000.f; // LIMITER
bool use_limiter = true; // LIMITER
void MyCallback(float **in, float **out, size_t size) {
float sig_out, freq, trig;
for (size_t i = 0; i < size; i++) {
// When the Metro ticks:
// advance the kArpeggio, and trigger the Pluck.
trig = 0.0f;
if (tick.Process()) {
if (!arp_idx) use_limiter = !use_limiter; // LIMITER
freq = mtof(kArpeggio[arp_idx]); // convert midi nn to frequency.
arp_idx =
(arp_idx + 1) % 3; // advance the kArpeggio, wrapping at the end.
plk.SetFreq(freq);
trig = 1.0f;
}
sig_out = plk.Process(trig);
if (use_limiter) // LIMITER
lim.ProcessBlock(&sig_out, 1, pre_gain); // LIMITER
for (size_t chn = 0; chn < num_channels; chn++) {
out[chn][i] = sig_out;
}
}
}
void setup() {
float sample_rate;
pinMode(LED_BUILTIN, OUTPUT); // LIMITER
// Initialize for Daisy pod at 48kHz
hw = DAISY.init(DAISY_SEED, AUDIO_SR_48K);
num_channels = hw.num_channels;
sample_rate = DAISY.get_samplerate();
// Set up Metro to pulse every second
tick.Init(1.0f, sample_rate);
lim.Init(); // LIMITER
// Set up Pluck algo
plk.Init(sample_rate, init_buff, 256, PLUCK_MODE_RECURSIVE);
plk.SetDecay(0.95f);
plk.SetDamp(0.9f);
plk.SetAmp(0.3f);
arp_idx = 0;
DAISY.begin(MyCallback);
}
void loop() {
digitalWrite(LED_BUILTIN, use_limiter ? true : false); // LIMITER
delay(100); // LIMITER
}
thank you very much for the limiter example! @tele_player
it’s working here:)
… but i still try to find out why it’s not working within my code
may i ask, where do you get the buffer size (1) from?
and how do you know about the pregain range? factor (1000)?
“where do you get the buffer size (1) from?”
This is very common, idiomatic C++ (and C). The ProcessBlock() method can handle an array of one or more samples. In this case, I chose to do it sample-by-sample, like the plk.Process() method. So I treated sig_out as an array with one element.
“how do you know about the pregain range? factor (1000)?”
First, I read the source code for the limiter. It’s the ultimate documentation for this stuff.
I chose that number arbitrarily, to make the limiting obvious. If you apply a gain of 1000 to the sample, without the limiter, as in ‘sig_out = sig_out * 1000.f’, clipping will be obvious. With the limiter, hard clipping is prevented. You can go much higher, try 100000, or a million.
This is just an example. In a real program, you might use a pot to adjust pre_gain.
I worked as a C/C++ programmer for 20+ years. Figuring out undocumented stuff was a big part of the work. Reality is, Daisy documentation is minimal, but we have the source code. Still, this isn’t an easy way to learn C++ programming.
Finally, in a forum like this, posting a small, runnable example of code which doesn’t work for you is your best chance of getting help finding the error. It’s up to you to narrow it down as much as possible.
i hardly remember what i did the last 20 years;)
i can report that after stripping down my code to the minimum:
input
limiter
output
i got it working!
thanks for your help.
while this is a limiter thread…
do you have any idea what to change in the limiter source to achieve a faster “back to normal level” response?
No idea - but it’s only a few lines of code. One thing I noticed comparing this limiter code to the original Mutable Instruments code is this difference:
In DaisyExamples/DaisySP/Source/Dynamics/limiter.cpp, this line does the limiting:
*in++ = SoftLimit(pre * gain * 0.7f);
In DaisyExamples/stmlib/dsp/limiter.h, this is how it’s done (aside from a slight change in variable names);
*in_out++ = s * gain * 0.8f;
Otherwise, they’re the same. SO, you might try using this in DaisyExamples/DaisySP/Source/Dynamics/limiter.cpp:
*in++ = pre * gain * 0.8f;
All of the code from the limiter can just be copied into your own source file, so you can do this without modifying the original code in DaisySP. Just rename the class to something else, like MyExperimentalLimiter.
Now, I don’t claim that this will do what you like, but it’s easy to try to see what effect this SoftLimit has. It doesn’t seem to me that it should affect the time to return to normal gain - since the only variable which carries data from one sample to the next is the peak_ variable, and that’s only written by the SLOPE macro.
after finding out that i edited the wrong limiter.cpp…
and of course nothing changed:)
i think, that now, after trying the edit you proposed
*in++ = pre * gain * 0.8f;
i notice an audible change in volume response.
no i’m sure there is!
thank you @tele_player