Object Knowing If It's Inside a Polyphonic Sub-Patch?


#1

Is there any way for an object to know (without having to patch something into an inlet) whether it's been placed inside a polyphonic sub-patch?

I'm working on a couple of objects that won't really work properly in this setup, so I want them to warn the user in the console if this is the case.

a|x


#2

probably not easily/effectively ... by design, sub patches are really just 'patches', no different from the main patch.

you probably could play with 'parents' to determine it for one level deep
e.g. something like if(parent_.=&rootc) log("in top patch")

i think there is a voice count as well, if you are more worried about the polyphony than the sub patch... you'd need to look at the xpatch.cpp to see whats there.
(it might be you can only create a compiler error, depending on how things are done)

but its not really advisable, the best approach is actually to design the object to work in subpatch/poly context, as you never quite know what the end user has in mind :slight_smile:


#3

I'll investigate. Might be worth adding that in a future firmware.

That's true. On the other hand, this particular object just doesn't really make sense to have in a poly sub-patch (in the same way that the 'tables/alloc 16bt sdram' object doesn't, really), and will end up filling up SDRAM with duplicate data if used in a sub-patch.

a|x


#4

What's the recommended approach to make a poly-safe file-loader object for a file of arbitrary size?

@lokki was investigating the possibility of having a poly-safe pointer to a chunk of data in SDRAM dynamically allocated with malloc.

I guess you'd use your suggested singleton object method to make sure the data was only copied to SDRAM once.

Downside would be that that would preclude loading more than one file, even when multiple instances of the object were used. Or am I incorrect on that?

Would that potentially work?

a|x


#5

on a related note, what is the size of a pointer in axoloti? is it uint64_t e.g. 8bytes?


#6

I'd assumed 32-bits, but maybe I'm wrong.

Can you do?

sizeof(&my_pointer);

a|x


#7

4-bytes:

uint32_t *ptr_test_pointer = NULL;
LogTextMessage("Pointer size = %d", sizeof(&ptr_test_pointer));

Pointer size = 4

#8

thanks! figured it out (somebody whispered it to me :slight_smile: )


#9

design, all depends what you are trying to achieve ...

whilst i mentioned the singleton pattern, because its the simplest, I often use variations on this , e.g. factory patterns , which are kind of related.

an example... say you want a set of wavetables accessed by a name, you can create classes that look like
Wavetable.get("wavetable1.wav").play(iobuf,pos);

which would for the first invocation, load the wav file, and then play it, and on subsequent calls, would use the loaded data.... this is basically a singleton (Wavetable object) using a factory pattern.

(note: im not saying this is a good idea, since id recommend against loading a file in the audio thread , but even this can be avoided ,with a bit of clever coding)

p.s. Im not a huge fan of patterns, but they are pretty good for newer programmers for inspiration, and also a way to communicate ideas using a single word... but just remember, like music theory, great to know the rules, and then bend and break them :wink:

I think more fundamentally, once you get into writing more complex objects in C++, I think its easier to forget Axoloti objects, and just start writing C++ code, and then just use Axo objects to glue this code into the patcher.

you say:

well this is not quite true, for some patches you do want to allocate tables per voice, e.g. if you were doing some kind of per voice delay line - ok, this is a made up (possibly useless :wink: ) example... but the point is, with patching often you dont know what crazy use another user might have in mind.


#10

Thanks @thetechnobear, that all sounds like great advice, and it's much appreciated.

I'll admit, I know nothing about C++, but now would seem like a good time to pick up the basics, so your advice is very useful.

To get back to specifics, though, if I need to create an object that loads data to SDRAM, which can potentially be read by objects inside a polyphonic sub-patch, and where the 'reading' objects reference the 'Loader' object outside the sub-patch by its assigned object name, what are my options?

Is the only way to do this to follow the method used in the factory table and table-reader objects?

Using something like this:

static int16_t _array[attr_poly][LENGTH] __attribute__ ((section (".sdram")));
array = &_array[parent->polyIndex][0];

That method seems to preclude using a singleton or factory loader class .

It's probably an edge-case, where my objects will be used polyphonically, but as you way, you never know how your objects are going to be used, once they're released into the wild.

a|x


#11

your missing what i said about using sdram_malloc()/sdram_free() in the other thread - these have no requirements to use these arrays, nor the declaration syntax - so therefore you can use whatever design you wish (including factory/sington designs)
p.s they also use void pointers, so are a bit clearer on the casting requirements.

btw; the important point about the above declaration, is the way it uses attr_poly, as this means that the data is size for the correct number of voices, and similarly the polyindex, means the correct voice is used ... but those features can be used however the memory is allocated (or not if you wish to share across voices) e.g. you could pass them into methods/constructors

again, all depends on what your trying to achieve.

ps. small tech note, if when using c++ just be careful if you start using new/delete, this will use a default allocator, which is id assume is not sdram ... though it would be possible to create custom allocators, but that's getting advanced, so not going down that route :wink:


#12

No, I got that bit, and created an object using plain C to dynamically allocate and load from a file to SDRAM.

Downside is that the object will (I assume) load the data multiple times to SDRAM, in a polyphonic sub-patch.

I can fix that using a singleton, as you advised @lokki to do.

Ah, that's a good explanation, thank you!

Ah, I'd love to see an example of what you mean, here.

What I want to do, I think, is to ensure that data copied to SDRAM by Object A is accessible by Object B, even if Object B is inside a sub-patch, and Object A is outside it.

If the sub-patch is polyphonic, I want all 'virtual' instances of the Object B to be able to access the data Object A has placed in SDRAM.

If Object A is also inside the poly sub-patch, I don't want the data to be copied to SDRAM multiple times.

As I understand it, this is where the singleton/factory class come in, because the singleton instance is effectively created in the global scope, and can therefore be access by any object, anywhere in a patch.

What I can't get my head around is if it's possible to allow Object B to access the pointer pointing to the SDRAM data copied by Object A in the same way that eg a factory delay object accesses data from a table object- by using the name of the other object in an objRef attribute.

If this isn't possible, then I'll give up on the idea of a separate Loader object, and just roll the Loader into the Reader. If possible though, I'd like to be consistent with the standard way of doing things.

Do you mean the objects will be created in SRAM, rather than SDRAM? That's OK, I think, as long as my class instances can still allocate SDRAM, and load data to it.

a|x


#14

exactly a singleton would allow you to do this..

yes, this is possible too... if the variable is declared in object A, the objRef will allow you to access it. ObjRef is really nothing more than a link to class instance used for the object. its really got nothing to do with tables... you can also use it to access a pointer (or anything else) that has been declared in another object.

(ok, it has one special syntax trick which is ../ObjRef generates parent.objRef, but that's just syntax)

again if you start creating simple patches, and looking at the generated xpatch.cpp, you'll soon see its a pretty simple mapping, and i guess that's when you start seeing it all just as C++ code

(rather than two individual worlds of axo objects/xml and C++... which yeah, i can see its possibly confusing how the two worlds interact)

note: I will say for most users creating object this is not necessary....
its just once things get more complex and you want to start sharing things between objects, need more structure in your code - i think it potentially helps....
but I could be wrong, it might be just the way my head likes to see things.


#15

OK, that's great!

I think you're right. I was looking through the contrib library just now, and only came across a handful of instances of external C/C++ files being used. Some of the few examples were, like your Push objects, C libraries, rather than C++ classes implementing the bulk of the object's functionality.

I have found myself re-using a few C functions, and I'll definitely be dumping those into an external library, for re-use. I guess there's limited mileage in rolling these functions into into C++ objects, in most cases.

a|x


#16

@lokki and I have managed to implement data-sharing between objects, more or less as we want it, now.

I have some (noob) questions about C++, as it relates to Axoloti, specifically, which I'd love some advice on.

What are the relative advantages/disadvantages of the following methods of sharing data within an Axoloti patch?

  1. Singleton class
  2. Standard class, instantiated as normal, but with all data members static
  3. Uninstantiated class used as namespace for static members (what we went for)

The aim is to have a single point where shared variables are stored, and which can be accessed by any object within the patch (including by objects within sub-patches).

I'm having some issues working out how to create a run-once class method to initialise data members in the last scenario. Any tips?

a|x