FM synthesis on Pokitto?

Yes, thanks to the help of you all I already have a lot to digest, so I figure that learning some C++ basics first (classes, calling by reference vs. calling by value, pointers,…) is a good idea. For instance, in the code snippet above I have no idea how I could access the data stored in the wavetable from inside the class. Or should I write a constructor that initializes an object with a wavetable so that I potentially could have different oscillators with different wavetables? I think that learning some basics first will surely help. There are plenty of tutorials on the web and the github repository is also very helpful! But I guess it will take some time…

Array name is actually pointer. Create array and pass it’s name to class constructor. For example:

int array[size] = {values}

class classname{
    private:
        int * table;
    public:
        classname(int * _array);
        int getDataFromArray(int index);
};

classname::classname(int * _array){
    table = _array;
}

int classname::getDataFromArray(int index){
    return *(table + index);
}

classname Foo(array);
1 Like

Ah, yes, perfect! I think I am getting it, was reading about passing arrays in and out of a function and there they say that the array name is a pointer to the first array element. The pointer concept is something I have to wrap my head around as it seems to be quite useful.

1 Like

If you’d like some help or resources then let me know.
I maintain a collection of links to useful resources on my GitHub:

And the closest I have to a ‘Pharap approved’ tutorial is http://www.learncpp.com/

Without meaning to undermine you, I’d like to point out a slightly better way:

class WaveTable
{
private:
	const int * table;
	std::size_t size;
	
public:
	// C++03 version
	template< std::size_t size >
	WaveTable(const int (&table)[size])
	{
		this->table = &table;
		this->size = size;
	}

	// C++11 version
	template< std::size_t size >
	WaveTable(const int (&table)[size])
		: table(&table), size(size)
	{
	}
	
	int operator [](std::size_t index) const
	{
		return this->table[index];
	}
};

The templated constructor allows you to capture the size of the array, which can then be used for bounds checking.
It also stops you from trying to pass a non-array pointer, e.g. WaveTable(nullptr).

Although I’d say that the class isn’t really worth much here since it doesn’t give you anything that you don’t already get from the array.
The main advantage it has is to allow you to do:

void processWaveTable(WaveTable waveTable)
{
}

Rather than:

template< std::size_t size >
void processWaveTable(const int (&waveTable)[size])
{
}

If you were to then add some extra functionality, then it would be much more worth it, but if it’s just a way to access the array then the only advantage is to avoid using template syntax, and maybe to avoid generating additional templates if the compiler isn’t very good at unifying them.
(Though there’s another way to solve that.)

2 Likes

“If there is some code, @Pharap will be there”
Ancient proverb
B.C. 2135
I’m just kidding :smile: your knowledge is too important for community. I think i need to start with Object Oriented Programming chapter :slight_smile:

4 Likes

@nullmember, thank you so much! I tried to integrate your suggestions into the Oscillator code and it actually runs on the Pokitto, here it is:

#include "Pokitto.h"
#include "Wavetable.h" // uint16_t wave data (sin_table[512])
#include "My_settings.h"

Pokitto::Core game;
Pokitto::Display disp;

uint16_t pos = 0; //position of value in wave table

class FMOsc
{
    private:
        const uint16_t * wavetable; //pointer

    public:
        FMOsc(const uint16_t * _array); // constructor
        uint16_t getValue(uint16_t pos); // get value
};

FMOsc::FMOsc(const uint16_t * _array)
{
    wavetable=_array;
}

uint16_t FMOsc::getValue(uint16_t index)
{
   return *(wavetable + pos); // Dereferencing, return the value stored in ponter address
}

int main ()
{
    disp.persistence = true;
    disp.color = 7;
    disp.bgcolor = 0;
    disp.clear();

    FMOsc OSC1(sin_table); // create Osc
	game.begin();

	pos=0;

	while (game.isRunning()) //game loop
	{
		if (game.update())
		{
			disp.clear();

			disp.println("Position:");
			disp.print((int) pos);
			disp.print("\n");
			disp.println("Value:");
			disp.print((int) OSC1.getValue(pos));

			if(pos++ == 512){pos=0;}

		}
	}
    return 0;
}

Actually it does exactly the same as the old code, but now most parts of the oscillator are encapsulated into a class. This has several advantages:

  • possibility to create more that one Oscillator with arbitrary wavetables
  • cleaner code

Actually I think it would be nice as a next step to implement an update method that simply skips through the wavetable without the need of tracking the position in main(). It would be also cool if this could work for reasonably arbitrary wavetable lengths without a position (phase) overflow, this trick involving the 2^x idea could be handy there.
For now I just used uint16_t as a type for convenience, but this surely could be optimized later.

@Pharap: Thanks for the resources! The solution using templates is looking quite interesting but it is a bit over my head at the moment. I would be happy having a simple version working first, but maybe later it could be optimized?

Is this supposed to be stopping pos from reaching 512?
Because at the moment it lets it reach 512.

If the table is 512 elements then the last index is 511 and this code has a buffer overflow.

Using templates isn’t about optimisation, it’s about safety (particularly type-safety and avoiding bare pointers) and (this will sound ironic) ease of use.

There’s a saying:

C makes it easy to shoot yourself in the foot; C++ makes it harder, but when you do it blows your whole leg off.

The problem with the non-template approach is how you deal with the size of the array.
You’ll need the size to figure out how to step through the table.

Using the template approach, it’s easy:

#include "Pokitto.h"
#include "Wavetable.h" // uint16_t wave data (sin_table[512])
#include "My_settings.h"

Pokitto::Core game;
Pokitto::Display disp;

std::size_t pos = 0; //position of value in wave table

class WaveTable
{
private:
	const int * table;
	std::size_t size;
	
public:
	// C++03 version
	template< std::size_t size >
	WaveTable(const int (&table)[size])
	{
		this->table = &table;
		this->size = size;
	}

	// C++11 version
	/*template< std::size_t size >
	WaveTable(const int (&table)[size])
		: table(&table), size(size)
	{
	}*/
	
	std::size_t getSize(void) const
	{
		return this->size;
	}
	
	int operator [](std::size_t index) const
	{
		return this->table[index];
	}
};

int main ()
{
    disp.persistence = true;
    disp.color = 7;
    disp.bgcolor = 0;
    disp.clear();

    WaveTable OSC1(sin_table); // create Osc
	game.begin();

	pos=0;

	while (game.isRunning()) //game loop
	{
		if (game.update())
		{
			disp.clear();

			disp.println("Position:");
			disp.print((int) pos);
			disp.print("\n");
			disp.println("Value:");
			disp.print((int) OSC1.getValue(pos));

			++pos;
			if(pos >= OSC1.getSize())
				pos = 0;

		}
	}
    return 0;
}

Here’s the equivalent without using templates:

#include "Pokitto.h"
#include "Wavetable.h" // uint16_t wave data (sin_table[512])
#include "My_settings.h"

Pokitto::Core game;
Pokitto::Display disp;

std::size_t pos = 0; //position of value in wave table

class WaveTable
{
private:
	const int * table;
	std::size_t size;
	
public:
	// C++03 version
	WaveTable(const int * table, std::size_t size)
	{
		this->table = &table;
		this->size = size;
	}

	// C++11 version
	/*WaveTable(const int * table, std::size_t size)
		: table(&table), size(size)
	{
	}*/
	
	std::size_t getSize(void) const
	{
		return this->size;
	}
	
	int operator [](std::size_t index) const
	{
		return this->table[index];
	}
};

int main ()
{
    disp.persistence = true;
    disp.color = 7;
    disp.bgcolor = 0;
    disp.clear();

    WaveTable OSC1(sin_table, sizeof(sin_table) / sizeof(sin_table[0])); // create Osc
	game.begin();

	pos=0;

	while (game.isRunning()) //game loop
	{
		if (game.update())
		{
			disp.clear();

			disp.println("Position:");
			disp.print((int) pos);
			disp.print("\n");
			disp.println("Value:");
			disp.print((int) OSC1.getValue(pos));

			++pos;
			if(pos >= OSC1.getSize())
				pos = 0;

		}
	}
    return 0;
}

Hopefully you can see why I say the template version is actually easier to use and harder to make mistakes with.

The reason the template version works is because the compiler does something called ‘pattern matching’ to infer the template arguments from the usage.

If you still want to avoid the templates because you don’t understand them then feel free to, but I offered them up as an option because they have clear benefits over the other option.

1 Like

Hey Pharap, yes, you a right about the buffer overflow, the Pokitto prints some lager number at pos=512. As for the template approach, the advantage of handling the size of the array is certainly an argument, I think I have to read up on the topic.

1 Like

This is why I don’t like using increments in the middle of expressions, it’s easy to end up with an off-by-one error.

Something like:

++pos;
if(pos >= 512)
	pos = 0;

or

if(pos < 511)
	++pos;
else
	pos = 0;

Is usually clearer.

You don’t necessarily have to understand templates in general to understand that particular example.
I think learning the entirety of templates just to deal with arrays is probably overkill.

That said if you’re interested in learning about them then I’m happy to try to help, but they’re quite a complex topic so you’re best off getting to grips with functions and classes first.
A lot of C++ programmers never learn how to write templates, just how to use them.

What about Pokitto LPT hat + opl3lpt??? Thats real stuff…Is LPT hat possible???

2 Likes

yes you could control that with a pokitto

2 Likes

Wow, this is an interesting idea! Why trying to emulate it in the pokitto, if you can have the real deal. Also, a SID head comes to mind…

1 Like

OPL3 is beast comparing to SID…Google YMF262.Lot of channels.Youtube adlibtracker and hear it!It was used in Soundblaster16 and DX7 synths…

Hey, I see no competition between the SID or the Yamaha FM chips. From my perspective, it is a matter of choosing a palette for your very own creative purposes. For instance, to me, very personally, the SID chip is interesting because it implements the Moog paradigm of synthesis in a chip.

FM algorithms are interesting to me, at the moment, because they represents an approach to audio synthesis that seems to be quite successful in a vast number of applications and maybe are still a bit underexplored - artistically- at this point in time.

Now, from my perspective, it is difficult for me to say which one is “better”. I mean, in terms of technical specs, sure, you could always say more voices in chip A are better than less voices in chip B, etc. I tend to think more in terms of choosing from various brushes that one would select for a specific purpose.

I’ve wrote some DDS engine code. You can generate sound using wavetables or triangle, saw and pulse wave types. Here is DDSOsc.h file i’ve wrote

and wavetables.h (just containing sine wave for now :smile:)

https://gist.github.com/NullMember/d2a8f36bd491cc60ceecfee853bbb4d8

wavetables must be signed 16-bit

Here is pseudo usage:

#include "DDSOsc.h"
#include "wavetables.h"

DDSOsc osc1(SINE_TABLE);
DDSOsc osc2(TRIANGLE_WAVE);
DDSOsc osc3(PULSE_WAVE);
DDSOsc osc4(SAWTOOTH_WAVE);

void update(){
    osc3.PM(osc4.update());
    osc2.FM(osc3.update());
    osc1.AM(osc2.update());
    dac_write(osc1.output());
}

void AudioInterrupt(){
    update()
}

int main(void){
    osc1.frequency(100);
    osc2.frequency(20);
    osc3.frequency(1000);
    osc4.frequency(500);
    osc1.volume(1);
    osc2.volume(1);
    osc3.volume(1000);
    osc4.volume(1);
    for(;;){}
}

Changed pseudo code a little bit for real chaos (\m/) output is here (1/10 second output):
https://plot.ly/~NullMember/156/

Not tried on real hardware yet and don’t know about performance but I can optimise it.

4 Likes