Welcome. Please log in or register.

Why does the theremin's ground pin need to be floating?
anyfoo Posted: 6 May 2022, 06:05 AM

Posts: 2
Joined: 6-May 22
All the project writeups are great, massively so. I spent a lot of time reading them.

I have a question about the tiny Theremin project: Why does it have to be floating? I understand that one of the I/O pins has to be either connected to ground or to the player to form the capacitor, but why does the entire circuit floating matter?

Heh, now that I type this out... is it because the vicinity of the I/O pins to ground would form a much larger capacitance, drowning out the player?

While I'm at it, a comment about one of the synth players where the subject was reusing memory for different algorithms in C, without resorting to unions. Macros and direct addressing were suggested, but I think the simplest solution would just be to declare the data area in the wanted size, such as:

uint8_t data[4096]; // must at least be maximum size required by any algorithm

And then, have each algorithm have its own structure type, but addressed through a pointer with all pointers simply pointing to your data area:

struct alg_foo_t *alg_foo = (struct alg_foo_t*)data;
struct alg_bar_t *alg_bar = (struct alg_bar_t*)data;
struct alg_another_t *alg_another = (struct alg_another_t*)data;

But the union has the benefit that you do not need to figure the size out. So instead of the opaque data area, do this:

union {
struct alg_foo_t alg_foo;
struct alg_bar_t alg_bar;
struct alg_another_t alg_bar;
} data;

struct alg_foo_t *alg_foo = &data.alg_foo; // could simply point to data, but this spares a cast and looks cleaner
struct alg_bar_t *alg_bar = &data.alg_bar;
struct alg_another_t *alg_another = &data.alg_another;

And you have the best of both worlds: An automatically sized data area, and easy addressing through a direct pointer to the structure with no union indirection.

Last edit by anyfoo at 6 May 2022, 06:05 AM

mit Posted: 6 May 2022, 09:05 AM
yeah whatever

Posts: 405
Joined: 4-May 16
I can't answer the theremin question. Not only have I forgotten the details, but I didn't really understand how it worked at the time. It was mostly just about experimenting with different arrangements until I found something that works.

Good point about the C memory though. I think if I had written it from scratch with sharing memory in mind, I would have gone with something like that. It gets a little uglier because there are arrays of structs, and some structs contain arrays. The channels/polyphonic oscillators looks like this:

struct channel {
int32_t bend;
uint8_t volume;
uint8_t RPNstack[4];
uint8_t pbSensitivity;
uint8_t mod;
float lfo_depth;
const float* tuning;
unsigned sustain:1;
} channels[16];

struct oscillator {
uint8_t channel;
uint8_t notenumber;

uint8_t stolen;
uint8_t stolenChannel;

float phase;
float amplitude;
float fm_phase;
float fm_amplitude;
float fm_depth_cache;
float lfo_phase;
uint32_t starttime;

uint16_t velocity;
unsigned alive:1;
unsigned released:1;
unsigned sustained:1;
unsigned intAttack:1;
} oscillators[POLYPHONY];

Polyphonic algorithm 3 uses the same oscillator struct, because it's nearly all the same, but the fm_whatever floats are used for different purposes. There's also concern about what values are valid, though I suppose when changing algorithm it could wipe them.

The monophonic algorithms have their own array elsewhere... but it's only about 20 bytes, so it doesn't really matter, and I like how readable the struct definition is.

The prototypes for the algorithms look like this:

typedef void doOscStereo_t(struct oscillator* osc, struct oscillator* osc2, uint16_t* buf, uint16_t* buf2);
doOscStereo_t oscAlgo1Stereo;
doOscStereo_t oscAlgo2Stereo;
doOscStereo_t oscAlgo1Quad;
doOscStereo_t oscAlgo2Quad;
doOscStereo_t *doOscillatorStereo = &oscAlgo1Stereo;

typedef void monophonicAlgo_t(float f, uint16_t* buf, uint16_t* buf2);
monophonicAlgo_t algoMonophonic1;
monophonicAlgo_t algoMonophonic2;
monophonicAlgo_t algoMonophonic3_square;
monophonicAlgo_t algoMonophonic3_saw;
monophonicAlgo_t *monophonicAlgo = &algoMonophonic2;


typedef void generateIntoBuffer_t(uint16_t* buf, uint16_t* buf2);
generateIntoBuffer_t generateIntoBufferFullPoly;
generateIntoBuffer_t generateIntoBufferDualOsc;
generateIntoBuffer_t generateIntoBufferPoly4;
generateIntoBuffer_t generateIntoBufferMonophonic;
generateIntoBuffer_t *generateIntoBuffer = &generateIntoBufferFullPoly;



Sign in to post a reply.