Open Sound Control (OSC) Teensy Audio Library Implementation

OSCAudioGroup is a good name

About the file saving/sending, do it need a CRC check for file integrity,
if I gonna implement save/load json then if file corruption occurs
the design could get lost.

There is also a need to do file listing, or do it already work by loading a folder instead of a file(i have seen a example of file listing doing it that way)

I need to implement pre check when loading json, as that is not done at the moment, and it means if you import a json string that contains errors then you can get into a corrupted state in the tool.
 
I have tried your new FILE implementation

but we would require some things

I did a file listing thingy
Code:
void listFiles(OSCMessage& msg, int addressOffset)
{
  char fn[50];
  uint8_t* buf;
  int remain;
  bool success = false;
  OSCAudioBase::error retval = OSCAudioBase::OK;
  OSCMessage& repl = OSCAudioBase::staticPrepareReplyResult(msg,*replyStack);
  File dir;
  
  msg.getString(0,fn,50);
  dir = SD.open(fn);
  if (dir)
  {
    char *intStr = (char*)malloc(21); // to store file sizes (uint64 is 20 digits long) don't think we really need that as it's 18exabytes
    repl.add(fn);
    while (true)
    {
      File entry =  dir.openNextFile();
      if (! entry)
      {
        // no more files
        break;
      }
      if (entry.isDirectory())
      {
        repl.add("dir");
      }
      else
      {
        // files have sizes, directories do not
        repl.add("file");
        itoa(entry.size(),intStr,10);
        repl.add(intStr);
      }
      repl.add(entry.name());
      entry.close();
    }
    free(intStr);
  }
  else
  {
    repl.add(fn);
    retval = OSCAudioBase::NOT_FOUND;
    repl.add("failed");
  }  
  repl.add(retval);
}
it gives this reply at the root dir (structured for easier read)
Code:
{"address":"/reply","args":[
{"type":"s","value":"/teensy*/fs/list"},{"type":"s","value":"/"},
{"type":"s","value":"dir"},{"type":"s","value":"System Volume Information"},
{"type":"s","value":"file"},{"type":"s","value":"11073"},{"type":"s","value":"furelise.mid"},
{"type":"s","value":"file"},{"type":"s","value":"97291"},{"type":"s","value":"design.json"},
{"type":"s","value":"dir"},{"type":"s","value":"OSC"},
{"type":"s","value":"file"},{"type":"s","value":"1496"},{"type":"s","value":"design.osc"},
{"type":"s","value":"dir"},{"type":"s","value":"JSON"},
{"type":"s","value":"dir"},{"type":"s","value":"DirInDir"},
{"type":"s","value":"file"},{"type":"s","value":"49095"},{"type":"s","value":"TeensyAudioDesign.json"},
{"type":"i","value":0}]}
(the folders OSC JSON and DirInDir is just example folders)

so that mean I can implement a File select dialog

Also I did changes to the different file actions
so that they always sends back the filename
(otherwise it is really hard to decipher what data the reply contains)
for example when I request the JSON I need to know where the data should go

And by always send back the filename it's much easier to find typing errors.
here is the .ino file with the changes
https://github.com/manicken/OSCAudioPlatformIO/blob/main/src/OSCAudioEmpty.ino



Problems with receiving some blobs:


(the problem is at the Tool side, as the raw received data is complete)

however when the data is going into the javascript DataView:s
it gets lost
and it reports only 73 bytes for some packages


Added Tool features:


I have now a new setting, that makes only the last message visible in the log,
which is good for debugging but don't slow the GUI down
(the thing that takes time is the Automatic scroll function)

* file handling stuff at the OSC menu

here is the json url
https://raw.githubusercontent.com/manicken/OSCAudioPlatformIO/main/DesignToolGui.json
containing buttons for testing file handling/ and export stuff (json/osc)
 
This looks useful. You may want to start from the OSCAudioUseMIDI example (where I've started to split out the various functions into separate .ino files within the sketch), and add the file listing stuff into FS.ino. I'm beginning to think that most examples will need to be quite short to demonstrate individual aspects of what OSCAudio can do, with one OSCAudioEverything sketch which does ... um ... everything? And I've noticed that OSC messages that don't reach a target don't (typically) give a very useful response, which needs fixing. Ideally clients can then behave gracefully if /dynamic, /fs, /midi etc. aren't implemented on the target.

CRC check could also be useful in the Real World, yes. I got /fs as far as a proof of concept, then dropped it because it's not really part of the core library :)

OSCAudioGroup began to behave late last night. I was chasing a problem I thought was due to groups, and turned out to have been introduced in 661203c. Until I push a fix, don't try to delete any object which has a pointer to externally-allocated memory (e.g. AudioSynthWaveform, which has int16_t* arbdata): I didn't NULL them out on construction, so on destruction they get freed, but being uninitialised cause a pretty immediate crash :( I still need to implement deleting everything within a group if the group node itself is deleted, and dealing with connections within, into and out of groups.
 
the "ino splitting" don't work in VSCODE + platformio
so I have to put every ino contents into the main ino/cpp
just saying

the best way to implement the splitting is to have .h files instead
but you already know that


Arduino IDE have slow compilation, so I try to avoid that for big projects
(I don't want to turn off the antivirus just to make it fast, it's not worth it)
Also Arduino IDE don't have autocomplete and "code following"
Then there is no built in GIT which I find very useful
as it can rollback to a working state if I mess something up.

and I need to use git more, recently lost some of my projects due to a failing usb flash drive but where able to recover some of the data


OSCAudioHelpers just throws out errors
so I don't use that for the moment

I also don't use the latest Audio Lib beta

What would be the easiest and fastest way of getting that last beta version?
And which version do you run?
 
I found the receive error

I did use it like this
Code:
OSC_SERIAL.beginPacket();
reply.send(SerialUSB1); 
OSC_SERIAL.endPacket();
which skips the SLIP encoding
that resulted in when a message contained the hex value 0xC0 (this is the SLIP eot)
then the decoder though that was the end of the message


but it should be like this (the problem here was that at some point I got errors here, but not anymore)
Code:
// for real!
OSC_SERIAL.beginPacket();
reply.send(OSC_SERIAL); 
OSC_SERIAL.endPacket();
 
Glad you found it.

The latest commit of OSCAudio (70e49da) should fix the delete crash, as well as allowing use with the old static Audio library - I'm not regression testing that often, as it's not really the main reason to be doing all this...

Forgot to answer your question on which versions I'm running: currently Arduino 1.8.15, Teensyduino 1.56b3, and usually a pre-push version of the OSCAudio library plus the latest push of cores and Audio, both on the dynamic-audio branches. I'm tempted by other IDEs, but fear I'll end up depending on something that doesn't work for the average Arduino user...

Groups are proving challenging - just realised that all the stuff about checking for duplicate names needs a complete re-write, as the whole point of groups is to implement your voice arrays.
 
I have now tried to create a "Dynamic" Mixer called AudioMixerX (for the moment)

I did implement it into my "fork" of the OSC lib
https://github.com/manicken/OSCAudioPlatformIO

the code files are DynMixer.h DynMixer.cpp


However I run into some problems while allocating the inputQueueArray (not actually but seems to be related to)
of what I can track the errors to, is some free function
Code:
AudioMixerX(unsigned char ninputs/*, audio_block_t **iqueue*/) : AudioStream(ninputs, new audio_block_t*[ninputs]),

is the code above correct?
or how else should it be allocated
I did try it "outside" as you can see from the /*, audio_block_t **iqueue*/

then the "outside" code looked like this
Code:
audio_block_t **amxInputs = (audio_block_t **)malloc(ninputs);
if (NULL != amxInputs) {
    pNewObj = new OSCAudioMixerX(objName, ninputs, amxInputs); 
}
else {
   OSC_SPLN("out of memory");
   retval = OSCAudioBase::NO_MEMORY;
}
 
Away visiting family at the moment, so can’t test … but … that looks close to working. malloc() allocates bytes so the external version should have malloc(ninputs * sizeof(audio_block_t**)) - similar for your array of multipliers. I’m not sure about the technique using “new”, but I think it should work.

When you destroy an instance the AudioStream library will release() any audio blocks currently allocated and referenced by the input queue array, so you just need to free memory you’ve malloc()ed and delete things created using new.

Ah. I’ve just spotted a long-standing problem, I think. The order of calling destructors will mean the inputQueue array no longer exists when the base class release()s the allocated audio blocks! Though it will work as it is a lot of the time, mostly by luck. I’ll have to fix that, and the documentation.
 
Thanks
that fixed it

I needed to allocate enough memory.
That also explains the corruption of "OSC names" in the serial debug output.

(due to overwriting memory not belonging to either *multipler or **inputQueueArray)
 
Have now made some OSC related additions to the Tool

* Contains file browser to select files from SD-card (used on load .osc/.json)
requires my list files function in addition to the FS example by h4yn0nnym0u5e

* Fix for objects supporting changing of the input count,
now it removes connected links/wires when setting a lower input count

* Future support for Dynamic Mixer creation allowing up to "library max" input count of 255
(note. my c++ template mixer is static allocated)
support of live update of the input count

* new OSC setting: Direct Export that will make exports faster by not showing the export dialog.

* made it even simpler to add items to bundles in scripts

from:
Code:
bundle.packets.push(OSC.CreatePacket(addr,"if",0,gain));
to
Code:
bundle.add(addr,"if",0,gain);
if the packet don't contains any parameters it can be written as
Code:
bundle.add(addr);
//or when just sending one message
OSC.SendMessage(addr);

in the future the file browser could be expanded to a "fully" working file manager
(this could surely be useful for ethernet based solutions)
* text file edit
* file download/upload
* file/folder creation/deletion
 
Phew ... finally. I think I have audio groups working, please see https://github.com/h4yn0nnym0u5e/OSCAudio/tree/features/groups. For now I've not merged it into trunk as there are probably bugs and we may want to change the features around a bit. There's a lot going on under the hood, but externally the changes should be fairly easy to deal with:
  • a member of the OSCAudioGroup class can be created using /dynamic/crGr,ss<name><parent path>: if the parent path is the "root", use "/"
  • an audio object can now have an optional parent group, by sending /dynamic/crOb,sss<type><name><parent path>
  • an object's name need only be unique within its group, so a 4-voice definition can have /voice1/i0 through /voice1/i3, and all the wave forms can be called "wav", so /voice1/i0/wav through /voice1/i3/wav is valid
  • wildcards can be used to make object definitions quick, so /dynamic/crOb,sss"AudioSynthWaveform""wav""/voice1/i*" will create one instance in each group
  • because they can connect between groups, OSCAudioConnection objects are not created as part of a group...
  • ...but the object targeting accepts <path-to-group> syntax, for example /audio/patchCord/co,ss"/voice1/i0/wav""/voice1/i0/env"
  • deleting a group will also delete all its sub-groups, members and connections (even those to/from outside the group). This seems necessary, as otherwise we could end up with items that we've lost track of, duplicate names, or other problems.
There's a new example file called OSCAudioMakeASynth.py which demonstrates much of this; you can use it along with OSCAudioMIDIgroupFS.ino to create a simple 4-voice synth with some CC capability. Of course, it gets really interesting when coupled with the GUI++ capability to design one voice on a tab, and generate an array of them on the "main" tab... I need to take a look at the DynamicMixer to see if any tweaks are needed, and fold the result into the dynamic audio library. On which note ... how does one add a new type of audio object to the GUI++ menus?

The examples with filesystem capability have been updated to include the /list command - thanks @manicksan! I renamed the function to listFS(), but that's internal-only, no change to how it works, and I've tested it briefly.
 
Good work!

[*]because they can connect between groups, OSCAudioConnection objects are not created as part of a group...
Would it be hard to include the connections in the group "namespace"
otherwise a connection that connects the inside items of a group needs to be called
voice1_i0_wav_0_env_0 (underlines included for easy read) to be unique name and even longer if groups are nested (do it support nesting at the moment?)

is voice1 the group name in this case?


how does one add a new type of audio object to the GUI++ menus?
check post #48
@Node definitions manager "manual"
https://forum.pjrc.com/threads/65740-Audio-System-Design-Tool-update?p=277457&viewfull=1#post277457
for background information

check
post #63
https://forum.pjrc.com/threads/65740-Audio-System-Design-Tool-update?p=296570&viewfull=1#post296570
for a manual to add a new Object
 
Good work!

Would it be hard to include the connections in the group "namespace" otherwise a connection that connects the inside items of a group needs to be called voice1_i0_wav_0_env_0 (underlines included for easy read) to be unique name and even longer if groups are nested (do it support nesting at the moment?)

is voice1 the group name in this case?
Thank you! Not sure if it'll be hard, it's certainly a good suggestion - I just wanted to get the bare bones out as soon as I could, as it's fundamental to being able to make a structured design in GUI++ which can translate fairly directly to internal objects running inside a Teensy. I'll take a look at it.

In this case there are actually two group names: voice1 and its sub-group i0: in GUI++ I would expect to see a tab called voice1, and on the main sheet a use of that tab as voice1[4]. Each instance of voice1 is a sub-group: I've used i0 through i3 because I seem to recall there's a problem with names starting with numbers. Nesting to any depth is supported: in effect a group object (inside the Teensy) is just providing linkage and separation without making any sound.

I haven't made a working example, but it's possible to link audio objects as well as sub-groups from a group, so voice1 can not only have i0 through i3 as sub-groups (each of which then has the per-voice waveforms and so on), but also its own LFO and output mixer. Here's a rough diagram of part of such a scheme: the "connections" shown are group linkages, not audio paths. This is shown "flattened out" - for a real design you'd obviously have separate tabs for each group level.
2021-12-30 09_34_20-Audio System Design Tool++ for Teensy Audio Library.png
and here's the JSON that might work for a full implementation of the above flattened diagram:
Code:
{"version":1,"settings":{"arduino":{"ProjectName":"GroupExample","Board":{"Platform":"","Board":"","Options":""}},"BiDirDataWebSocketBridge":{},"workspaces":{},"sidebar":{"autoSwitchTabToInfoTab":false},"palette":{},"editor":{},"devTest":{},"IndexedDBfiles":{"testFileNames":"testFile.txt"},"NodeDefGenerator":{},"NodeDefManager":{},"NodeHelpManager":{},"OSC":{"LiveUpdate":false,"UseDebugLinkName":true}},"workspaces":[{"type":"tab","id":"3629fcd9.ccc604","label":"Main","inputs":0,"outputs":0,"export":true,"isMain":false,"mainNameType":"tabName","mainNameExt":".ino","generateCppDestructor":false,"extraClassDeclarations":"","settings":{"workspaceBgColor":"#EDFFDF","scaleFactor":0.9,"showGridHminor":false,"showGridHmajor":false,"showGridVminor":false,"showGridVmajor":false,"useCenterBasedPositions":false},"nodes":[{"id":"Main_Poly4withLFO1","type":"Poly4withLFO","name":"voice1","x":692.2222222222222,"y":455.55555555555554,"z":"3629fcd9.ccc604","bgColor":"#CCFFCC","wires":[["Main_i2s1:0","Main_i2s1:1"]]},{"id":"Main_i2s1","type":"AudioOutputI2S","name":"i2s","comment":"","x":890,"y":455,"z":"3629fcd9.ccc604","bgColor":"#E6E0F8","wires":[]},{"id":"Main_sgtl5000_1","type":"AudioControlSGTL5000","name":"sgtl5000","comment":"","x":860,"y":505,"z":"3629fcd9.ccc604","bgColor":"#E6E0F8","wires":[]},{"id":"Main_Button1","type":"UI_Button","name":"use connection to connect wafo to mixer1 input 1","comment":"","w":360,"h":34,"textSize":14,"midiCh":"0","midiId":"0","pressAction":"","repeatPressAction":false,"releaseAction":"","repeatReleaseAction":false,"local":"true","sendCommand":"var addr = \"/teensy1/audio/wafo2mixer1/connect*\"\nvar data = OSC.GetSimpleOSCdata(addr,\"sisi\", \"wafo\", 0, \"mixer1\", 1);\nOSC.SendAsSlipToSerial(data);","x":2200,"y":170,"z":"3629fcd9.ccc604","bgColor":"#F6F8BC","wires":[]},{"id":"Main_Button2","type":"UI_Button","name":"create new AudioSynthWaveForm wafo","comment":"","w":275,"h":34,"textSize":14,"midiCh":"0","midiId":"0","pressAction":"","repeatPressAction":false,"releaseAction":"","repeatReleaseAction":false,"local":"true","sendCommand":"var addr = \"/teensy1/dynamic/createObject*\"\nvar data = OSC.GetSimpleOSCdata(addr,\"ss\", \"AudioSynthWaveform\", \"wafo\");\nOSC.SendAsSlipToSerial(data);","x":2260,"y":90,"z":"3629fcd9.ccc604","bgColor":"#F6F8BC","wires":[]},{"id":"Main_Button3","type":"UI_Button","name":"create connection wafo2mixer1","comment":"","w":223,"h":30,"textSize":14,"midiCh":"0","midiId":"0","pressAction":"","repeatPressAction":false,"releaseAction":"","repeatReleaseAction":false,"local":"true","sendCommand":"var addr = \"/teensy1/dynamic/createConn*\"\nvar data = OSC.GetSimpleOSCdata(addr,\"s\", \"wafo2mixer1\");\nOSC.SendAsSlipToSerial(data);","x":2260,"y":130,"z":"3629fcd9.ccc604","bgColor":"#F6F8BC","wires":[]},{"id":"Main_Button4","type":"UI_Button","name":"do all above in one go","comment":"","w":157,"h":34,"textSize":14,"midiCh":"0","midiId":"0","pressAction":"","repeatPressAction":false,"releaseAction":"","repeatReleaseAction":false,"local":"true","sendCommand":"var data = osc.writeBundle({\r\n        timeTag: osc.timeTag(0),\r\n        packets: [\r\n            {\r\n                address: \"/teensy1/dynamic/createObject*\",\r\n                args: [\r\n\t\t\t\t\t{type: \"s\", value: \"AudioSynthWaveform\"},\r\n\t\t\t\t\t{type: \"s\", value: \"wafo\"}\r\n                ]\r\n            },\r\n            {\r\n                address: \"/teensy1/dynamic/createConn*\",\r\n                args: [\r\n                    {type: \"s\", value: \"wafo2mixer1\"}\r\n                ]\r\n            },\r\n            {\r\n                address: \"/teensy1/audio/wafo2mixer1/connect*\",\r\n                args: [\r\n                    {type: \"s\", value: \"wafo\"},\r\n\t\t\t\t\t{type: \"i\", value: 0},\r\n\t\t\t\t\t{type: \"s\", value: \"mixer1\"},\r\n\t\t\t\t\t{type: \"i\", value: 1}\r\n                ]\r\n            }\r\n\t\t\t\r\n        ]\r\n    });\r\nOSC.SendAsSlipToSerial(data);","x":2280,"y":210,"z":"3629fcd9.ccc604","bgColor":"#F6F8BC","wires":[]},{"id":"Main_Button7","type":"UI_Button","name":"Button","comment":"","w":100,"h":34,"textSize":14,"midiCh":"0","midiId":"0","pressAction":"","repeatPressAction":false,"releaseAction":"","repeatReleaseAction":false,"local":"true","sendCommand":"var addr = \"/teensy1/dynamic/createObject*\"\nvar data = OSC.GetSimpleOSCdata(addr,\"ss\", \"AudioSynthWaveform\", \"   \");\nOSC.SendAsSlipToSerial(data);","x":2340,"y":280,"z":"3629fcd9.ccc604","bgColor":"#F6F8BC","wires":[]},{"id":"Main_Button8","type":"UI_Button","name":"rename waveform2 to wafo1","comment":"","w":275,"h":34,"textSize":14,"midiCh":"0","midiId":"0","pressAction":"","repeatPressAction":false,"releaseAction":"","repeatReleaseAction":false,"local":"true","sendCommand":"var addr = \"/teensy1/dynamic/ren*\"\nvar data = OSC.GetSimpleOSCdata(addr,\"ss\", \"waveform2\", \"wafo\");\nOSC.SendAsSlipToSerial(data);","x":2295,"y":370,"z":"3629fcd9.ccc604","bgColor":"#F6F8BC","wires":[]}]},{"type":"tab","id":"c0ded12a.5c0c9","label":"Poly4withLFO","inputs":0,"outputs":0,"export":true,"isMain":false,"mainNameType":"tabName","mainNameExt":".ino","generateCppDestructor":false,"extraClassDeclarations":"","settings":{"workspaceBgColor":"#EDFFDF","scaleFactor":0.8,"showGridHminor":false,"showGridHmajor":false,"showGridVminor":false,"showGridVmajor":false,"useCenterBasedPositions":false},"nodes":[{"id":"AudioStream_waveform1","type":"AudioSynthWaveform","name":"LFO","comment":"","x":450,"y":315,"z":"c0ded12a.5c0c9","bgColor":"#E6E0F8","wires":[["AudioStream_MonoSynth1:0"]]},{"id":"AudioStream_MonoSynth1","type":"MonoSynth","name":"MonoSynth[4]","x":565,"y":315,"z":"c0ded12a.5c0c9","bgColor":"#CCFFCC","wires":[["AudioStream_mixer1:0"]]},{"id":"AudioStream_mixer1","type":"AudioMixer","name":"mixer","inputs":"1","comment":"","x":740,"y":315,"z":"c0ded12a.5c0c9","bgColor":"#E6E0F8","wires":[["Poly4withLFO_Out1:0"]]},{"id":"Poly4withLFO_Out1","type":"TabOutput","name":"Out","comment":"","x":880,"y":315,"z":"c0ded12a.5c0c9","bgColor":"#cce6ff","wires":[]}]},{"type":"tab","id":"de8d0e25.32ebb","label":"MonoSynth","inputs":0,"outputs":0,"export":true,"isMain":false,"mainNameType":"tabName","mainNameExt":".ino","generateCppDestructor":false,"extraClassDeclarations":"","settings":{"workspaceBgColor":"#EDFFDF","scaleFactor":0.8,"showGridHminor":false,"showGridHmajor":false,"showGridVminor":false,"showGridVmajor":false,"useCenterBasedPositions":false},"nodes":[{"id":"MonoSynth_In1","type":"TabInput","name":"Modulation","comment":"","x":440,"y":280,"z":"de8d0e25.32ebb","bgColor":"#CCE6FF","wires":[["MonoSynth_waveformMod1:0","MonoSynth_waveformMod1:1","MonoSynth_waveformMod2:0","MonoSynth_waveformMod2:1"]]},{"id":"MonoSynth_waveformMod1","type":"AudioSynthWaveformModulated","name":"wav1","comment":"","x":620,"y":255,"z":"de8d0e25.32ebb","bgColor":"#E6E0F8","wires":[["MonoSynth_mixer4_1:0"]]},{"id":"MonoSynth_waveformMod2","type":"AudioSynthWaveformModulated","name":"wav2","comment":"","x":620,"y":310,"z":"de8d0e25.32ebb","bgColor":"#E6E0F8","wires":[["MonoSynth_mixer4_1:1"]]},{"id":"MonoSynth_mixer4_1","type":"AudioMixer4","name":"mixer","comment":"","inputs":"4","x":770,"y":280,"z":"de8d0e25.32ebb","bgColor":"#E6E0F8","wires":[["MonoSynth_envelope1:0"]]},{"id":"MonoSynth_envelope1","type":"AudioEffectEnvelope","name":"env","comment":"","x":905,"y":295,"z":"de8d0e25.32ebb","bgColor":"#E6E0F8","wires":[["MonoSynth_Out1:0"]]},{"id":"MonoSynth_Out1","type":"TabOutput","name":"Out","comment":"","x":1015,"y":295,"z":"de8d0e25.32ebb","bgColor":"#cce6ff","wires":[]}]}],"nodeAddons":{}
}


Thanks for that, I'll check it out
 
Would it be hard to include the connections in the group "namespace"
Turns out, not as hard as I feared... see latest commit at https://github.com/h4yn0nnym0u5e/OSCAudio/tree/features/groups. To use it, send /dynamic/crCo,ss<name><path>, e.g. /dynamic/crCo,ss"wav_mix""/voice1/i*". The Python example has been updated - should be fairly clear what's going on, even if you don't grok Python!

Right now I can't see a way to establish the connection (once the object itself has been created) using wildcards for the source and destination. Bear in mind we have to be able to use them for connections across as well as within groups, so the meaning of /dynamic/voice1/i*/env_mix/co,sisi"env"0"mixer"3 is ambiguous where there's a mixer both inside and outside the group. If inspiration strikes, or someone else comes up with a suggestion, I'll give it a go.
 
First beta prototype non working OSC groups export
(Only exports the objects at the moment)
I believe audio connections will be much harder to do

Anyway it's available at the OSC menu as "Export Complete (grouped design)"
you will also need to set the "main entry"

Double click the tab that you want as the main and check the 'Main File' checkbox,
ignore the 'exported Main File Name' as it's not used with OSC


I will go for "vacation" (3900km from here) 1-9th January
and will not have any access to my computer at that time.
so further development on the Tool will not be until then.
 
Just took a look at the non-working export - one thing you need to do for the group paths is include the / separators, as these are valid within group path parameters, and needed for the OSC dynamic engine to place the object or nested group at the correct point in the tree. Added here as needed (I believe...), in red for emphasis:
Code:
{"address":"/teensy*/dynamic/crGrp","args":[{"type":"s","value":"voice1"},{"type":"s","value":"/"}]} [COLOR="#FF0000"]this is OK, voice1 group is in the root[/COLOR]
{"address":"/teensy*/dynamic/crOb","args":[{"type":"s","value":"AudioSynthWaveform"},{"type":"s","value":"LFO"},{"type":"s","value":"[COLOR="#FF0000"][B]/[/B][/COLOR]voice1"}]}
{"address":"/teensy*/dynamic/crGrp","args":[{"type":"s","value":"MonoSynth"},{"type":"s","value":"[COLOR="#FF0000"][B]/[/B][/COLOR]voice1"}]}
{"address":"/teensy*/dynamic/crGrp","args":[{"type":"s","value":"i0"},{"type":"s","value":"[COLOR="#FF0000"][B]/[/B][/COLOR]voice1[COLOR="#FF0000"][B]/[/B][/COLOR]MonoSynth"}]}
I've adjusted the sanitisation code internally to permit the / when it's valid, though the other non-valid characters should still be replaced by underscores as previously.

Have a good vacation! :D
 
Can you post the json for that design?

yes I did now notice when creating a "sub item" inside the voice class

I got this
Code:
{"address":"/teensy*/dynamic/crGrp","args":[{"type":"s","value":"subItem"},{"type":"s","value":"/voices/i*"}]}
{"address":"/teensy*/dynamic/crOb","args":[{"type":"s","value":"AudioSynthWaveform"},{"type":"s","value":"waveform"},{"type":"s","value":"subItem"}]}

but it should be like this?
Code:
{"address":"/teensy*/dynamic/crGrp","args":[{"type":"s","value":"subItem"},{"type":"s","value":"/voices/i*"}]}
{"address":"/teensy*/dynamic/crOb","args":[{"type":"s","value":"AudioSynthWaveform"},{"type":"s","value":"waveform"},{"type":"s","value":"[COLOR="#FF0000"]/voices/i*/[/COLOR]subItem"}]}


There are now "internal only" connections generated in the prototype as well
 
Can you post the json for that design?
Sorry - which design? I quoted from part of the export of the JSON from post#163... I'm not really using JSON at the moment, though it would probably be good to work up a Python example that can take your GUI++ JSON export and send the correct OSC output to a Teensy.
 
Just realised, for anyone out there playing with this at the moment, that renaming and deleting objects will be broken for the time being...
 
Added commit 92e164f to the repo just now. You will need the latest Audio and cores for this to be useful, as there were some pretty massive problems with deleting objects that I think I've now dealt with. But it's quite possible I haven't.

Renaming and deleting via OSC are now functional again, with similar schemes to object creation, i.e. you can use the OSC pattern matching to rename or delete multiple objects at once. If you delete a group then all its members and connections are also deleted (since there's now no way of keeping track of them, and there could well be duplicate names if we kept them). OSCAudioGroupDynamicTest.py has test messages to show what can be done, pending my getting around to doing some documentation.
 
I have now tried to create a "Dynamic" Mixer called AudioMixerX (for the moment)

I did implement it into my "fork" of the OSC lib
https://github.com/manicken/OSCAudioPlatformIO

the code files are DynMixer.h DynMixer.cpp
I've very quickly pulled this into my audio library, and made some changes to the DynMixer.h file only:
Code:
<license removed for brevity>
#ifndef DYNMIXER_H_
#define DYNMIXER_H_

#include "Arduino.h"
#include "AudioStream.h"

if !defined(SAFE_RELEASE_INPUTS)
#define SAFE_RELEASE_INPUTS(...)
endif // !defined(SAFE_RELEASE_INPUTS)

class AudioMixerX : public AudioStream
{
#if defined(__ARM_ARCH_7EM__)
public:
	AudioMixerX(unsigned char ninputs) : // for now this WILL crash if the malloc fails!
		AudioStream(ninputs, inputQueueArray = (audio_block_t **) malloc(ninputs * sizeof *inputQueueArray)),
		_ninputs(ninputs)
    {
        //Serial.printf("\nninputs = %d %d\n\n", _ninputs, sizeof(this));
        multiplier = (int32_t*)malloc(_ninputs*sizeof *multiplier);
		for (int i=0; i<_ninputs; i++) multiplier[i] = 65536;
	}
    ~AudioMixerX()
    {
	SAFE_RELEASE_INPUTS();
        //Serial.println("freeing multipler!");
        free(multiplier);
        //Serial.println("freeing inputQueueArray!");
        free(inputQueueArray);
        //Serial.println("freeing all done!");
    }
	virtual void update(void);
	void gain(unsigned int channel, float gain) {
		if (channel >= _ninputs) return;
		if (gain > 32767.0f) gain = 32767.0f;
		else if (gain < -32767.0f) gain = -32767.0f;
		multiplier[channel] = gain * 65536.0f; // TODO: proper roundoff?
	}
private:
    unsigned char _ninputs;
	int32_t* multiplier;
    //audio_block_t *toBeIgnored[1];
	audio_block_t **inputQueueArray;

#elif defined(KINETISL)
public:
	AudioMixerX(unsigned char ninputs) : // for now this WILL crash if the malloc fails!
		AudioStream(ninputs, inputQueueArray = (audio_block_t **) malloc(ninputs * sizeof *inputQueueArray)),
		_ninputs(ninputs)
    {
        //Serial.printf("\nninputs = %d %d\n\n", _ninputs, sizeof(this));
        multiplier = (int32_t*)malloc(_ninputs*sizeof *multiplier);
		for (int i=0; i<_ninputs; i++) multiplier[i] = 256;
	}
    ~AudioMixerX()
    {
	SAFE_RELEASE_INPUTS();
        free(multiplier);
        free(inputQueueArray);
    }
	virtual void update(void);
	void gain(unsigned int channel, float gain) {
		if (channel >= _ninputs) return;
		if (gain > 127.0f) gain = 127.0f;
		else if (gain < -127.0f) gain = -127.0f;
		multiplier[channel] = gain * 256.0f; // TODO: proper roundoff?
	}
private:
	int16_t *multiplier;
	audio_block_t **inputQueueArray;
#endif
};


#endif // DYNMIXER_H_
Using the malloc() in the AudioStream initialiser is legal, but because the AudioStream library doesn't currently check for a NULL pointer it will crash horribly if there's not enough memory. I'll fix that at some point. The SAFE_RELEASE_INPUTS() macro is used to ensure any audio blocks awaiting use at the time the AudioMixerX is destroyed are returned to the pool for re-use - it applies only to the dynamic audio library, but I've provided a blank definition so the code should also work with the static library (haven't tested this).

Because OSCAudio doesn't (yet) have the ability to pass extra parameters to /audio/crObject, I've made that default to an 8-input mixer, and built and tested a 6-poly synth using it. I think it might be useful to have a getInputCount() function so the application can determine the voice's polyphony at run-time.
 
That's why I did the malloc outside of the mixer instance.
And I was able to send extra parameters, maybe it was a little messy but it worked.
 
That's why I did the malloc outside of the mixer instance.
And I was able to send extra parameters, maybe it was a little messy but it worked.
Understood, but I believe it's preferable to hide the grim details of the input queue memory allocation from the user, where possible! It's a simple enough fix in the AudioStream library, and it's almost certainly not going to cause a problem in the short term.

I think I spotted your sending extra parameters, it's just that OSCAudio currently doesn't know how to deal with them. And it opens up the question of how to generalise the API so in the future we can deal with new object types that need parameters to construct them ... though for now I may just hack in an optional integer for AudioMixerX, to get us going.
 
Back
Top