Hooking touchOSC into antz
Three test cases:
The antz OSC address space is [OSC_Schema]
To get touchOSC controlling antz, any incoming OSC message needs to map to any antz structure.
Here's how I'm doing this currently:
RegisterCommand("/2", kNPcmdCamera); -- this causes any incoming "/2" message to be converted into a call to npCtrlCommand( kNPcmdCamera, dataRef );
The registration as text: command /2 4050
Currently NPmapType2 has two uses, it might be better to break into two structures.
The first is specifying the antz OSC address space.
The second is mapping third party OSC messages into antz. That is an entirely different problem, and the way I'm looking at this one, it's a mapping from a third party OSC address space into antz OSC address space -- a mapping from one OSC message to another.
I think that second area needs a different structure entirely, it's dealing with two different OSC messages, for example:
Incoming: /1/fader1 ,f 0.837
Outgoing: /np/gl/1/background_r ,f 0.837
We need that two way mapping when we want to send back messages that make sense to the third party, and we need it when we want there's multiple parameters:
Incoming: /3/xy ,ff 0.33 0.55
Outgoing: /np/gl/1/background_r ,f 0.33
Outgoing: /np/gl/1/background_g ,f 0.55
So how do I specify both these cases? What's the minimum information needed to specify both these cases?
NPmapType - structure is universal for all mapping types and combos.
Maps (any) external schema to internal memory structures and vice versa.
//idea is to support dynamically creating a new C-struct and schema mapping struct NPmapType { // C-struct mapPtr to the data in memory with ID and type mapping void* mapPtr; //pointer to an element of the C data map structure int mapID; //mapPtr = npGetMapID(mapID); //and vice versa int type; //mapPtr (c-struct) data type // human-readable Descriptor char map[kNPmapTypeTagMax]; // db table path or RAM ptr by name int itemID; //itemID = np_map_row_id = array[item] char element[kNPmapTypeTagMax]; //name of the sub-member object // external schema mapping and formatting for OSC, CSV, JSON, MySQL... char format[kNPmapTypeTagMax]; // formatID by name, 'osc_mrmr' char address[kNPmapTypeDataMax]; // OSC address, URL, table field, name char tag[kNPmapTypeTagMax]; // OSC type tag or data type char desc[kNPmapTypeTagMax]; // description, notes, etc };
On the OSC side, all it needs is:
int npRxOSC (int oscID, char* addr, char* tag, void** arguments, void* dataRef); int npTxOSC (int oscID, char* addr, char* tag, void** arguments, void* dataRef);
Incoming: /3/xy ,ff 0.33 0.55
Q) Here's the problems with npRxOSC: it's a re-implementation of oscpack library
functionality, and it only supports a single parameter, and it requires hard coding
memory addresses. Those are all problems as far as it being a viable alternative to
mapping OSC message to OSC message. I'm still having a problem making sense of it.
A) npRxOSC supports multiple parameters, NOT just one...
a.1 ANTz is a C API framework library and application. We are using 'oscpack' as a means to properly format OSC Packets and that is all. Routing of OSC Messages will be completely handled in C. For now this is very simple and we route an entire address as a single string value to searching NPmapType structure to get the pointer, type, etc.... Additional parsing will be added to support OSC semantics for address space wild cards and ranges. For now this can be handled with case specific situations.
ie:
/np/node/42/translate fff 0.5 0.5 0.1
If an un-recognized address comes in (not in the type list) then a default handler routes known address patterns. These dynamically mapped addresses are routed (using a basic switch statement) based on the first part of the address \np\node\ then the id '42' and element that follows 'translate'. Therefore any node can be modified on the fly specified by an id within the OSC Address.
a.2 void arguments is a list of argument pointers, not a single argument, but all arguments in a pointer list. We use the number of type tag elements = strlen(tag) to determine how many argument pointers are in the list (list size) and the tag tells us how to cast each void into a float, int, char etc.
a.3 - The mapType->mapPtr IS NOT hard-coded. Can be changed at runtime and new entries added to the typeMap lists. Data is immediately copied and the void* argument pointers only need to be retained until npRxOSC returns (immediately.)
a.4 - The entire mapType list can be dynamically modified (learn new schemas) and is saved to antzosc.csv and it will soon be able to load back in. For now just add new entries to the hard-coded list located in npcsv.c this list will be moved (likely to nposc.c)
npRxOSC() processes incoming arguments using the type tag to determine the number of pointers in the void** arguments list.
Important to specify oscID to keep track of connection pairs for echoing commmands.
Specifying the oscID (connectionID) allows echoing messages back:
// Returns a pointer to the mapping item structure based on the oscID and address pNPmapType npMapAddressToType (oscID, address, dataRef); //converts OSC Message to global memory map data then vice-versa npEchoOSC (oscID, address, arguments, dataRef) { int result = 0; int x, y; pNPfloatRGBA color; // get the specific mapping item descriptor using the OSC address pNPmapType mapTypeItem = npMapAddressToType(oscID, address, dataRef); // format the OSC argument based on OSC Type Tag and mapPtr 'type' // default formatting based on (OSC Type) Tag and mapPtr type // custom format functions can be created by adding format types, ie 'osc_custom_format' // 'format' string maps to 'np_format_id' table used for range and element mapping // // example of default 'osc' format mapping for 2 floats to RGBA switch (mapItem->type) { case kNPfloatRGBA: color = (pNPfloatRGBA)mapItem->mapPtr; switch (strlen(mapItem->tag,)) { case 2 : // get the OSC Argument values from the void** list x = *(float*)argument[0] y = *(float*)argument[1] // then apply some weird custom 2:3 color conversion color->r = (x + y) * 0.5f; color->g = x * x; color->b = (x-y) * (x-y); break; } break; } // now reverse the process to send the memory location back out // note that color->b = (x-y) * (x-y) is non reversible // however can be calculated from all 3 components // x = sqrt(color->g); // reverse color->g = x * x; y = (2.0f * color->r) - x; // reverse color->r = (x + y) * 0.5f; *(float*)argument[0] = x; *(float*)argument[1] = y; npTxOSC (oscID, mapType->address, mapType->tag, mapType->arguments, dataRef); }
A call to npTxOSC would send the message back to the same connection by specifying the outbound connection oscID based on the Rx oscID (stored by whatever function is bouncing messages back.)
Think in terms of piping one way: 3rd Party -> antz-space -> 3rd Party...
Outgoing: /3/xy ,ff 0.33 0.55
One table type with multiple instances of it....
A table for OSC<->ANTz another for CSV<->ANTz, both using the same data structure type.
Two tables together can be used to translate across schemas: OSC - oscmap - ANTz - sqlmap - MySQL
Connections are handled separately from the value mapping.
oscID = connectionID
an oscID refers to a descriptor that specifies a RXTX IP/port pair
for now just hard-code a couple of ID's to match a couple of connections...
connectID is needed to properly match up responses to there appropriate source, OSC, MySQL, file, other...
the connection management is not part of the address schema... nor should it be.
your example would be touchOSC -> ANTz -> ANTz/neattools/grandVJ
would not (generally) make sense to send touchOSC and antz address schema.
but other apps...
Incoming: /1/fader1 ,f 0.837
Outgoing: /np/gl/1/background_r ,f 0.837
(for now) don't worry about the command mapping so much as being able to support the basic type tag conversion for (int, float, string.... to a ptr list for RxTx functions.)
and multiple connections is high priority...
at that point... STOP... and GO to NeatTools!
and i will build out the node, global, command mapping... and a connection manager.
What I'd like to specify, as text, is something like:
For single incoming value:
osc /1/fader1 f 0 /np/gl/1/background_r
For multiple values:
osc /3/xy f 0 => antz /np/gl/1/background_g => osc 9000 /1/fader5
osc /3/xy f 1 => antz /np/gl/1/background_b => osc 9000 /1/fader4
This allows me to take any incoming OSC message with float values, and map them directly to global background. NPmapType2 only helps in the conversion of the final antz OSC address into a memory address.
The third party use of NPmapType2 ignores the antz address space entirely, which is needed when changes need to be propagated back to touchOSC
CHANGES propagated back to OSC should always go through main antz memory...
in other words the osc message handler is dumb... and just routes OSC <-> Memory <-> OSC and NOT OSC <-> OSC direct.
Also, pass the entire set of arguments as a pointer list. There will be a separate OSC parser that maps OSC messages directly to RAM based on the OSC type tag, the 'format' type and the memory pointer map_id and type. This is plenty to write lots of logic to auto-matically handle 90% of OSC schema mappings. For the corner cases format = 'osc' specifies additional formatting parameters in a separate 'np_format' table that provides ranges etc.
Use 'osc' as the default message handler routines (with switch statements for default tag type structures... that is 'f' 'i' 'fff' etc... The logic can use any of known parameters of the NPmapType item to auto-convert.
For the 10%... add a new format type such as 'osc_accel' which would refer to a new entry in the format table. The format table will allow for range conversion parameters and parameter mapping. For example you could have 'osc_xy_color' would be a specific format for mapping an XY OSC message to RGBA memory.... in whatever way you want to arguments to be mapped to color in RAM.
For now the format table does not exist... but feel free to hard-code a couple of new format names and use them in the switch statement called by npRxOSC()