vult-dsp / vult

Vult is a transcompiler well suited to write high-performance DSP code
https://vult-dsp.github.io/vult
Other
490 stars 25 forks source link

Initializations with @[init] ? #55

Closed michelezaccagnini closed 10 months ago

michelezaccagnini commented 10 months ago

Hello, I have been having issues with variable initializations in Vult. Sometimes my plugin behaves erratically outputting very large positive/negative values (no divisions by zero are present). This is probably due to some issues in variable initializations. I am testing the Vult code and I noticed how the default@[init] of the process is not working as expected. For instance: I have a fun lfo function that I want to initialize with phase = 1.0 and I have created:

fun lfo(){
     mem phase;
...
}
and initPhase(){
      phase = 1.0;
}

then

fun process(sampletime:real){
 ....
}
and default()@[init]{
        _ = initPhase();

The variable always gets initialized to 0 or I get erratic outputs in the LFO. In the myLfoModule.cpp I call void myLfo_process_init(); before process. (I also tried to call void myLfo_default()) I asked the VCV forum and it was suggested to implement a dataTo/From/Json to take control of variable initialization, which I did and maybe the situation has improved in terms of erratic behavior but the issue with the variable initialization in Vult is still there. I am using the latest vult.exe on Windows. Is this an issue of the transcompiler or am I doing something wrong?

This is my plugin

modlfo commented 10 months ago

I created the following small program

fun process() {
    mem x = x + 1.0;
    return x;
}
and start()@[init] {
    x = 1.0;
}

when compiling it with vultc -ccode test.vult -o test I get the following pieces of code:

The main initialization function for process is the function Test_process_init

static_inline void Test_process_init(Test__ctx_type_0 &_output_){
   Test__ctx_type_0_init(_output_);
   return ;
}

To initialize you data, you should call this function.

void main() {
   Test_process_type processor;
   Test_process_init(processor);
}

That function calls Test__ctx_type_0_init

void Test__ctx_type_0_init(Test__ctx_type_0 &_output_){
   Test__ctx_type_0 _ctx;
   _ctx.x = 0.0f;
   Test_start(_ctx);
   _output_ = _ctx;
   return ;
}

At that point, the variable x is first initialized to 0.0f, but then the function Test_start is called, which is the function marked with @[init].

static_inline void Test_start(Test__ctx_type_0 &_ctx){
   _ctx.x = 1.f;
};

Calling Test_start should put the value of x to 1.0.

Can you check in your generated code that this sequence looks correct?

I have encountered in the past some patterns that hit bugs in the gcc compiler (only for Windows). For my plugins, I have switched to use clang instead.

michelezaccagnini commented 10 months ago

Thanks for looking into this. I used the RackPlayground to test this. To monitor the variable initialization in VCV I changed the process to simply return the variable (without adding 1), otherwise it was hard for me to check where it started from.


fun process() {
    mem x;
    return x;
}
and start()@[init] {
    x = 5.0;
}

In the engine.cpp I have

void Processor__ctx_type_0_init(Processor__ctx_type_0 &_output_){
   Processor__ctx_type_0 _ctx;
   _ctx.x = 0.0f;
   Processor_start(_ctx);
   _output_ = _ctx;
   return ;
}

in the engine.h I have:

static_inline void Processor_process_init(Processor__ctx_type_0 &_output_){
   Processor__ctx_type_0_init(_output_);
   return ;
}

and

static_inline void Processor_start(Processor__ctx_type_0 &_ctx){
   _ctx.x = 5.f;
};

So, all of the above is exactly how you reported it and in fact the output is right: 5.

This is what the module Playground.cpp looks like

struct Playground : Module {
   enum OutputIds {
      OUT1,
      NUM_OUTPUTS
   };
   Processor_process_type processor;

   Playground();
   void process(const ProcessArgs &args) override;
};

Playground::Playground() {
   Processor_process_init(processor);
}

void Playground::process(const ProcessArgs &args) {
   outputs[OUT1].setVoltage(Processor_process(processor) );

}

About the compiler: I am using the MSYS2 as suggested in the plugin development page, how can I switch to clang to test whether the problem is come from the compiler? Sorry, I am new to this...

modlfo commented 10 months ago

There are a few steps that you have to follow to test it.

1.- Make sure you are using the latest gcc compiler by running $ pacman -Syu. Once everything is updated, run your code to see if the problem still happens. 2.- Install clang with $ pacman -S mingw-w64-x86_64-clang. 3.- You have to edit the file arch.mk in your Rack folder (or Rack-SDK). You need to change the line 23 that says mingw32 to windows. 4.- You need to silence some asserts. In file include/dsp/convert.hpp line 23. Comment out // static_assert(sizeof(Int24) == 3, "Int24 type must be 3 bytes"); 5.- In your shell you have to change the default compiler with $ export CC=clang and $ export CXX=clang++.

After that, you should be able of compiling your plugin.

michelezaccagnini commented 10 months ago

Thanks! I followed all of the steps and the plugin compiles though the erratic behavior remains. I get this error message when I compile

In file included from src/GraviHarmModule.cpp:1:
In file included from src/plugin.hpp:2:
In file included from C:/Code/Rack-SDK/include/rack.hpp:22:
C:/Code/Rack-SDK/include/common.hpp:141:35: warning: 'static_assert' with no message is a C++17
      extension [-Wc++17-extensions]
  141 | static_assert(sizeof(wchar_t) == 2);
      |                                   ^
      |                                   , ""

I commented out the line in question in common.hpp

#if defined ARCH_WIN
// wchar_t on Windows should be 2 bytes
//static_assert(sizeof(wchar_t) == 2);

the error disappears but the plugin is still acting up. The scope below is supposed to be just some LFO image

michelezaccagnini commented 10 months ago

I found out that the problem was in the Model.cpp I followed the structure of RackPlayground.cpp with this code

Processor_process_type processor;

   Playground();
   void process(const ProcessArgs &args) override;

inside the struct after which I have the equivalent of this

Playground::Playground() {
   config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);

   configParam(Playground::KNOB1, 0.0, 1.0, 0.5, "Knob 1", " %", 0.0f, 100.f);
   configParam(Playground::KNOB2, 0.0, 1.0, 0.5, "Knob 2", " %", 0.0f, 100.f);
   configParam(Playground::KNOB3, 0.0, 1.0, 0.5, "Knob 3", " %", 0.0f, 100.f);
   configParam(Playground::KNOB4, 0.0, 1.0, 0.5, "Knob 4", " %", 0.0f, 100.f);

   configParam(Playground::MOD1, -1.0, 1.0, 0.0, "Mod 1", " %", 0.0f, 100.f);
   configParam(Playground::MOD2, -1.0, 1.0, 0.0, "Mod 2", " %", 0.0f, 100.f);
   configParam(Playground::MOD3, -1.0, 1.0, 0.0, "Mod 3", " %", 0.0f, 100.f);
   configParam(Playground::MOD4, -1.0, 1.0, 0.0, "Mod 4", " %", 0.0f, 100.f);

   Processor_process_init(processor);
}

with the initialization, and only after that I have the

void Playground::process(const ProcessArgs &args) {

It seems stable and variables are initialized correctly. Thanks for the help!