Wusik 4000 Module SDK Docs Ready

DSP, Plugin and Host development discussion.
Post Reply New Topic
RELATED
PRODUCTS
Wusik 8008

Post

Just a bump to check out if anyone wants to check this out. :D

Post

The line "You cannot use any of this code for any other products", shouldn't this simply be:

Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3

?

Also what is the point of using Affero with the ASP loophole solution when you also license under GPL v2?

Also how is it possible to provide such a license when the two licenses are incompatible?

Edit: I see now it is located in "Wusik 4000 Module SDK.pdf", so most of the quote below doesn't make sense. I would recommend adding a line about that to the root README.md or "documentation.txt" or something.

A direct link from the info page on github and the post here might be good too, although I see you did link to the documentation directory.
Do you have a step-by-step guide with instructions on compiling a module? Apparently I need to link in the JUCE lib, but I have never used JUCE and have no desire to do so. This makes it a matter of "whenever I get around to researching how to use JUCE to build a module" which is probably never.

By distributing a VS2010 / 2012 project/solution files and a step-by-step guide with instructions on placing the JUCE lib directory in a certain location: (Wusik_4000_SDK/JUCE/ ?) where you can simply hit compile and output the basic gain example (or others) this will probably motivate people to write modules a lot more.

As it stands now you're making different statements at different points which makes it seem a bit ambiguous what the license terms are, and compiling a module is a lot more difficult than simply creating a project file and hitting compile since there is no documentation about how to include JUCE or even what is required to actually build a module.
Free plug-ins for Windows, MacOS and Linux. Xhip Synthesizer v8.0 and Xhip Effects Bundle v6.7.
The coder's credo: We believe our work is neither clever nor difficult; it is done because we thought it would be easy.
Work less; get more done.

Post

Okay, an actually useful issue now:
Jules wrote: Ah, yes, that juce_gui_audio

Ah, yes, that juce_gui_audio thing was actually caused by a typo in the old introjucer, which would add it to the modules list when you create a new project, but in fact there's no such module! The old version's module list only displayed modules which existed, so it would never appear on screen, but the new version attempts to show everything that the project contains.

Just select it in the list and hit 'delete'!
Free plug-ins for Windows, MacOS and Linux. Xhip Synthesizer v8.0 and Xhip Effects Bundle v6.7.
The coder's credo: We believe our work is neither clever nor difficult; it is done because we thought it would be easy.
Work less; get more done.

Post

wusik4000mod.h ln 441:

Using "float" instead of "fT" typedef.

patch:

Code: Select all

		fT vibratoBuffer;
		fT tremoloBuffer;
		fT filterEnvBuffer[128];
		fT pitchEnvBuffer[128];
Free plug-ins for Windows, MacOS and Linux. Xhip Synthesizer v8.0 and Xhip Effects Bundle v6.7.
The coder's credo: We believe our work is neither clever nor difficult; it is done because we thought it would be easy.
Work less; get more done.

Post

I'll refactor the code and post it to github, you can decide if you want to use it or not.

I've split the code into separate files and used a namespace "w4k" instead, replacing w4kMod with w4k::module and so on.

I'll probably finish this up and clean up some of the modules and upload that before making further changes.
Free plug-ins for Windows, MacOS and Linux. Xhip Synthesizer v8.0 and Xhip Effects Bundle v6.7.
The coder's credo: We believe our work is neither clever nor difficult; it is done because we thought it would be easy.
Work less; get more done.

Post

You don't need to call the destructor of the inherited class, if it is virtual that happens automatically.

That is the point of virtual:

If the class is deleted via a pointer typecast to its base class, its destructor would normally not be called.

By declaring the baseclass destructor as virtual it automatically takes that into account and calls the derived destructor first, which then automatically calls down through base classes.

In fact you should never call the baseclass destructor yourself because doing so can lead to it being called twice, which can cause serious havoc.
Free plug-ins for Windows, MacOS and Linux. Xhip Synthesizer v8.0 and Xhip Effects Bundle v6.7.
The coder's credo: We believe our work is neither clever nor difficult; it is done because we thought it would be easy.
Work less; get more done.

Post

You got my attention, but to be honest I don't understand everything yet, sorry, but I will try. In any even, thank you for the help. :hug:

Post

aciddose wrote:You don't need to call the destructor of the inherited class, if it is virtual that happens automatically.
Please please please, could you tell me more about this? I am getting a very random bug with the whole thing. The first time I run the program it will crash upon loading up, after that, it will work the whole day without problems. Also, the first time I load up modules, it crashes randomly, but once all modules were loaded at least once, it will start working for the whole day without problems. I couldn't figure this one out yet and it is frustrating me a LOT. :cry: Any help would be much appreciated... :hug:

Post

WilliamK wrote:
aciddose wrote:You don't need to call the destructor of the inherited class, if it is virtual that happens automatically.
Please please please, could you tell me more about this? I am getting a very random bug with the whole thing. The first time I run the program it will crash upon loading up, after that, it will work the whole day without problems. Also, the first time I load up modules, it crashes randomly, but once all modules were loaded at least once, it will start working for the whole day without problems. I couldn't figure this one out yet and it is frustrating me a LOT. :cry: Any help would be much appreciated... :hug:
Sounds like an issue with heap corruption.

This means when some memory is written which is not allocated correctly.

For example the most common case is a buffer-overrun, writing to an array past the end of the array.

The same thing can happen when attempting to delete the same object twice, depending on the implementation of new/delete.

Normally when you do:

Code: Select all

char *myarray = new char[999];
delete [] myarray;
This will work fine, but:

Code: Select all

char *myarray = new char[999];
delete [] myarray;
delete [] myarray;
delete [] myarray;
delete [] myarray;
delete [] myarray;
This will (I hope) crash. The "myarray" is never set to zero, so delete[]() has no way to know it was already deleted. Some implementations will create a header in a block of memory which will be used in debug to mark if a block has been deleted. Sometimes no header is used, but memory is filled with 0xFEFEFEFE when deleted.

You can constantly read through memory to look for 0xBAADF00D (uninitialized heap from new) 0xFEFEFEFE (freed) and 0xCDCDCDCD (uninitialized too).

In some modules I see you have called the baseclass destructor, for example:

Wusik_4000_SDK / Modules / Envelopes / ADSR Envelope / Source / ADSR Envelope.cpp, ln 78

Code: Select all

ADSRenvelope::~ADSRenvelope()
{
        W4kMod::~W4kMod(); // ad: this should never be done!
        if (envVoices != nullptr) deleteAndZero(envVoices);
}


What will happen is you do this:

Code: Select all

w4kMod *module = new ADSRenvelope(...);
delete module;
When you ask the module to be deleted, this happens:

w4kMod::vtable[destructor] = ADSRenvelope::~ADSRenvelope()

so, calling delete module calls:

ADSRenvelope::~ADSRenvelope()
{
W4kMod::~W4kMod()
...
} // then automatically ~ADSRenvelope() will call w4kMod once it has finished.
... W4kMod::~W4kMod()

So it has been called twice, and the object containing ADSRenvelope() is deleted while you're still inside the destructor of ADSRenvelope! That is unacceptable because w4kMod must maintain its state until destruction of ADSRenvelope has completed because during that it may attempt to access functions from w4kMod.

Also, any member of w4kMod will be deleted twice, and so if anywhere there is a case like:

Code: Select all

class myclass
{
 myclass()
 {
  myarray = new char[999];
 }

 ~myclass()
 {
  delete [] myarray;
 }
}
This will cause myarray to be deleted twice, which will cause the heap to be trashed.
Free plug-ins for Windows, MacOS and Linux. Xhip Synthesizer v8.0 and Xhip Effects Bundle v6.7.
The coder's credo: We believe our work is neither clever nor difficult; it is done because we thought it would be easy.
Work less; get more done.

Post

The solution to this is to first, start using debug builds with the debug CRT. For example in visual studio project settings:

config properties -> c++ -> code generation -> runtime library

Set to "multithreaded debug".

If you were not using this setting before, it should now make it crash 100% of the time while you are using debug mode.

With the normal release runtime the memory is not initialized for you with 0xBAADF00D, so the first time in new memory it will crash. After that, the "good" values were already written in memory and the application will be loaded into the same space in memory, which will run fine.

These types of bugs are horrible to try to track down, there are "heap monitoring tools" you can run which constantly check memory for these values and alert you when there is an issue with the heap. I don't use these regularly but they can be useful when a bug does appear.

One example is valgrind on linux. There are many other free tools you might want to try.

First though, make sure you are using the debugging runtime and eliminate these obvious bugs calling destructors where you shouldn't... see if that solves the problem. If you are lucky it should work fine.

Otherwise it will be time to add 100s of assertions and use my refactor of the module to alert when something is accessed "out of bounds".
Free plug-ins for Windows, MacOS and Linux. Xhip Synthesizer v8.0 and Xhip Effects Bundle v6.7.
The coder's credo: We believe our work is neither clever nor difficult; it is done because we thought it would be easy.
Work less; get more done.

Post

Ah, also I didn't mention uninitialized variables although I was thinking that in the back of my mind - using the debug runtime will help with that also.

Here is the code I use to help with that:

Code: Select all

#if _DEBUG
#include <stdint.h>

template <class T>
void check_init_func(T *p) 
{
	// a struct or class may contain aligned members.
	// in the case of a 64-bit int packed with 32-bit ints, 
	// it's possible to see 0xreal_valBAADF00D, for example.
	// therefore on x64 this check must be with qwords.
	if (sizeof(void *) == 4)
	{
		int size = (sizeof(T) + (sizeof(void *) - 1)) / sizeof(void *);
		int32_t *intptr = (int32_t *)p;
		for (int i = 0; i < size; i++)
		{
			ASSERT(intptr[i] != 0xBAADF00D);
			ASSERT(intptr[i] != 0xCDCDCDCD);
			ASSERT(intptr[i] != 0xFEFEFEFE);
		}
	} else if (sizeof(void *) == 8) {
		int size = (sizeof(T) + (sizeof(void *) - 1)) / sizeof(void *);
		int64_t *intptr = (int64_t *)p;
		for (int i = 0; i < size; i++)
		{
			ASSERT(intptr[i] != 0xBAADF00DBAADF00D);
			ASSERT(intptr[i] != 0xCDCDCDCDCDCDCDCD);
			ASSERT(intptr[i] != 0xFEFEFEFEFEFEFEFE);
		}
	} else {
		ASSERT(false);
	}
}

#define check_init(p) { check_init_func(p); } 
// warning, check_init_once is dangerous in any situation where 
// parameters passed to the constructor may ever influence program flow
// only use this if overhead is unbearable!
#define check_init_once(p) { static bool checked = false; if (!checked) { check_init_func(p); checked = true; } }
#endif

#else // !_DEBUG
	#define check_init(p)
	#define check_init_once(p)
#endif
If you put this in check_init.h or somewhere, include it in every .cpp file with a constructor. Initialize the object, then at the end call check_init(this)

Code: Select all

myclass.cpp

myclass::myclass()
{
 myvar = 0;
 mypointer = 0;
 mystruct.init();
 check_init(this);
}
Start following that rule: everything must be initialized in the constructor.

I would also recommend this rule: never initialize to valid defaults.

For example it is common to see people init sample_rate = 44100.0f;

That is insane, because it won't catch uninitialized bugs. It should be expected to be init in the constructor, but "set" to a valid value elsewhere in the code. Instead, it should be: sample_rate = 0;

Every time it is used, it should be: ASSERT(sample_rate > 0.0f);

This is not error handling code. This is an assertion, the purpose is to ensure that things you do with the code are valid during runtime. It will only be used in debug, for example:

Code: Select all

#if _DEBUG
	// break to debugger immediately if failed
	#if defined(_WIN32)
		#define ASSERT(N) if (!(N)) { __debugbreak(); }
	#elif defined(__GNUC__)
		#define ASSERT(N) if (!(N)) { asm("int %3" : "" : "" : ""); }
	#else // will usually trigger an annoying message box, but oh well
		#include <assert.h>
		#define ASSERT(N) assert(N)
	#endif

	// print the code asserted if failed
	#define ASSERTX(N) if (!(N)) { printf("assertx(%s)\n", #N); }
	
	// print a nice error message if failed
	#define ASSERTMSG(N, MSG) if (!(N)) { printf("assertmsg(%s) failed: %s\n", #N, MSG); }

	// break, but only once after loaded
	// for potentially frequent errors you want to know about the first time, but not 100s of times
	#define ASSERT_ONCE(N) { static bool asserted = false; if (!asserted) { ASSERT(N); asserted = true; } }
#else
	// don't do anything if not in debug
	#define ASSERT(N)
	#define ASSERTX(N)
	#define ASSERTMSG(N, MSG)
	#define ASSERT_ONCE(N)
#endif
If you put that in assert.h and make it a part of the w4k sdk as well, that will allow you to use it in many different cases which should help to eliminate any of these types of programmer error.

You may want to use W4K_ prefix on all these to avoid name collisions.
Free plug-ins for Windows, MacOS and Linux. Xhip Synthesizer v8.0 and Xhip Effects Bundle v6.7.
The coder's credo: We believe our work is neither clever nor difficult; it is done because we thought it would be easy.
Work less; get more done.

Post

WilliamK wrote:
aciddose wrote:You don't need to call the destructor of the inherited class, if it is virtual that happens automatically.
Please please please, could you tell me more about this?
The short version: if you have a pointer, then the type of the pointer decides which class will get it's destructor called. That destructor (as chosen for the type of the pointer) can be virtual, in which case the usual dynamic dispatch logic will apply. Otherwise (not virtual) it will be whatever the pointer claimed the type to be, which will usually result in garbage.

This applies to automatically generated destructors as well: if a base-class has a virtual destructor, the automatically generated destructors will override it correctly.

In other words: the first type in an inheritance hierarchy (typically, but not necessarily that would be the actual "root" base-class) that defines a virtual destructor is the most general type that you can use (a pointer to) to delete the objects safely, because that's the most general type where you have a destructor-entry in the virtual method dispatch tables [and this is why it's not a default: it forces the type to have a vtable pointer].

Post

Olivier Tristan
Developer - UVI Team
http://www.uvi.net

Post

I like http://aszt.inf.elte.hu/~gsd/halado_cpp/ (I think it's that one anyway) :P

Post

I haven't read the whole thing yet, but I will, just give me time. But one thing, in most places I'm using the OwnedArray class from JUCE. I will check the code for places where I use new and delete and maybe just replace with a JUCE alternative and see how it goes.

Post Reply

Return to “DSP and Plugin Development”