Module programming, execution and debugging

I have a mixture of data updates and rendering in both draw() and step(). How often are draw() and step() called? It would take quite a bit of rewrite to limit rendering to draw() and structure changes to step(). What is the reasoning for segregation in this manner? Would you expect any problems having such a mixture?

They are called both each frame. Only Module::process is called each sample.

I’m definitely still learning. My module does no DSP audio processing but just uses the DSP timing via the process() function. Since no audio, really not much needs to be done at every step(), which is probably why I put almost everything in the draw() function. In process() I set flags to indicate variable changes and display dirty status and then I just update or render when something changes.

I do have a lot of variables changing during runtime, but I use the clock/timer functionality to only update the variables at certain times, such as at measure begin or on the beat etc. So, although there is quite a bit of data, there is no need to update it more than a few times per second. I thought something updates with the frame rate. I thought that was step() and it was used for low frequency UI updates. I guess I was wrong.

Ok, my mistake. Maybe the difference is related to the thread where the method is called ?

Actually it is not entirely true what I said. As far as I remember Widget::step is called every frame, Widget::draw is called every frame if the widget is currently visible in Rack. @Vortico may correct me if I’m wrong.

There was some details in this topic on why Widget should be completely stateless. With the forthcoming v2.0 rack will be able to run in headless mode therefore the widget will not be processing at all.

If you need to run code every n sample to save up performances in your process(), you can use the dsp::clockDivider class as a helper :wink:

2 Likes

This is a valid requirement and there is dsp::ClockDivider for use in Module::process if something has not to be done for every sample, like updating LED status.

Keep in mind that in Rack v2 there will be a headless mode which runs modules without an active widget.

Edit: As @23volts said :smiley:

1 Like

Looks like I need to read up on v2 changes. Can you sum up simply what it means “Widget should be completely stateless”? Does this apply only to the module widget or to any widget? Will modules have to be specifically rewritten to run headless or with a GUI? I’ll take a look at dsp::clockDivider class. I’m just manually counting clock ticks from the DSP in process() and dividing.

What I mean by that is that any parameters or variable that are necessary for your module to function properly. Stateless is a bit of an overstatement, as a button object may of course have a on off state , but this state should be linked to the parameter from the module. In short, the module holds the data/process them, and the moduleWidget, when needs to draw, refers to the module data and not the other way around.

And yes for the dsp::clockDivider it’s nothing fancy really.

I’ll have to think about that some more. I just wrote out a bare bones flow chart of my Meander module program logic and it took 4 pages. I’m still a beginner. Thanks for the info.

my offer still stands.

I think the idea is that the modules need to be able to run without the GUI widgets around. Which is how you should be writing your code already anyway. The GUI widgets should just be a “view” into the DSP module’s state and not have any particular custom state of their own.

2 Likes

I appreciate your offer and I will take you up on it if I cannot redesign Meander to be more array safe. I’m taking @23volts suggestion and experimenting with converting all of my C arrays to C++ std::array usage. I have approximately 1100 array expressions in the main Meander cpp file, so this is a challenge but obviously a big opportunity to make Meander array safe without address sanitizer since I am on Windows. The speed penalty will be worth it.

Thanks again.

1 Like

Is the source code on github? if you are getting an out of bounds error with that many array’s it would be a needle in a field of haystacks.

indeed. you should be fixing the problem, not the symptoms. converting to std::array only fixes the symptoms.

1 Like

What do you mean by ‘only fixes the symptoms’ ?

The approach I suggested is by using std::array he could have an exception thrown if an out of bound access happens, therefore making it way easier to locate the origin of the problem. That’s what I do if I would encounter that kind of problem in a large codebase. But maybe there’s an easiest approach ?

simply replacing c-style array access won’t:

#include <array>
#include <iostream>

int main() {
  std::array<int, 3> a1{ {1, 2, 3} };

  std::cout << a1[0] << a1[1] << a1[2] << a1[3] << std::endl;

  return 0;
}

prints: 1230

and ignores the out of bounds array by virtue of simply returning 0.

against 1100 array instances? likely, yes :slight_smile: heck, even building valgrind to run in msys2, or running a local linux vm could end up being easier/cleaner than trying to scalpel in 1100 array changes.

Didn’t say that; you should read my original suggestion. " One possible workaround is to replace C-style arrays with std::array , and accessing them with the ->at() method, which will do the boundary check for you."

Still only a few month in C++ dev myself so I didn’t know valgrind, will look into it.

I looked for the original suggestion, was having an issue finding it. still, given the number of array accesses quoted, that’s going to be a huge undertaking and likely a pretty large performance hit (essentially the same as running with asan), and still ends up just masking the issue in the end (operator[] access in std::array uses pointer math, but could still end up being a cache spoiler, whereas at() includes the bounds-checking code and will both be a cache spoiler and a ton of extra instructions for each access).

Sure it’s a bit of an overhead, but given that most code seems to run at frame rate, not at sample rate, I guess it could be just fine.