Open Sound Control (OSC) Teensy Audio Library Implementation

I did just realized that I only allow one destination
In AudioHalfConnection:
int connect(AudioStream &audioStream, unsigned char audioStreamPortIndex)

The connected flag should only be set to true when the "dir" is 1 (input)
 
I've been looking at your AudioHalfConnection concept, and a thought occurred to me that it may be simpler than I realised. Every input can have only one source, and therefore the class it belongs to can (should?) provide an AudioConnection for it. However, the outputs can have more than one destination, and therefore cannot provide AudioConnections; but that's OK, because they will be connected to (class) inputs which do provide a connection. (There's a possible exception at the root level, but that's probably not a major issue).

I think what confused things is that I said classes should provide both input and output AudioConnections, which in hindsight is clearly nonsense, because even with a simple one-to-one connection topology, you end up with twice as many AudioConnection objects as you need. :(

So for class export purposes I think we need (in addition to the internal connections) an extra AudioConnection for every patchcord emerging from a TabInput node, with an associated connectInput(src,port) function; and only a connectOutput(dst,port) function for every patchcord entering a TabOutput node. For multi-port TabInputs or TabOutputs the functions could be connectNode(nodePort,srcORdst,port) with just one connectFunction per node.


The OSCAudio::callBack() function is intended to scan the "tree" of objects looking for matches, and then execute a callback function whenever it finds a complete match; the scan doesn't have to start at the root, or recurse into groups. It's used to find, count, create, delete and rename objects. Sometimes there's a find within a find: for example, when a "leaf name" is matched during a rename, another callback search is done starting within the current group to check for any matches to the new leaf name, in order to prevent duplicate names. Maybe the name is confusing, and I should call it scanGroups() or something, though it's now marked private so it may matter less than it did...


In other news, I've tidied up the access specifiers in OSCAudio, so you shouldn't be able to get to anything really dangerous. Because I'm lazy I've left a few members public to aid debugging, e.g. using the listObjects() utility. While I did that, I found there wasn't a proper error code if you tried to create a duplicate group name, so I fixed that. And the OSCAudioMIDIsubsFS.ino example has a /system/listObjects function added, so you can request a dump of the current topology to the serial port.
 
OSC live updating while using dyn. mixers is really hard.

things that currently don't work while edit live:

1. changing the size of any connected array source, or changing it to/from array
if you want to add a array source live, you need to do a complete export

2. removing any of the 'first' wires of mixer ( only remove/add them from the end)
otherwise the names get the wrong ones, as they are autogenerated
could solve it by storing the names somewhere when the links are first created

3. changing the 'internal' structure of any classes live (have a function that gets the ref. paths, but not still used)

4. surely still other things that I did not think about

but otherwise you can:
1. export the whole design

2. remove/add links from/to the end of mixer
that have either array/buswire sources
 
Did some fixes
now the complete export-data is stored in each link, when doing complete export and creating links live
that means links can now be removed in any order from the dyn. mixers,
however it do not change the size of the mixer when removing links

Consider the following example:
DynMixerLiveEdit.png

So if for example
1. wavesB[2] link is removed
2. array size is changed to 4 (wavesB[4])
(note. see note1 in the end)

3. wavesB[4] link is reconnected to the mixer (if note1 was possible)

3alt. there was a alternative wavesD[4] that would connect in place of wavesB[2]

then the mixer would not resize to fit the new wavesB size
that would mean that the last links from the wavesB[4] would not connect

but anything that fits the gap i.e. have the same size
(as wavesB have stereo out that would mean 4 inputs)
are now free in that gap

the only current way to resize the mixer is to remove wavesC as well
and connect wavesB[4] before reconnecting wavesC again


note1.
as stated before array sizes cannot still not be edited live
that would require
1. the group would need additional items to be added
(if the items are classes that would mean some wires+additional items as well)
and if changed to/from array would require removal of the whole group
2. resizing of the whole mixer if wires are connected,
resizing the mixer always require all links to first be removed + the mixer
then a new sized mixer is added + all old + new links

think I will store all export data in every item
links (already done)
nodes
classes
then it would be much easier to delete/rename things
 
It's definitely a challenge - not sure how much help I can be, but here are some thoughts.

As a general principle, I've found it very error-prone to store duplicates of information, unless the duplication is very local and guaranteed to be changed when its "parent data" are changed. For example, it'd be easy to lose track of connection names if they contain port numbers, and those port numbers might change. Much better to have a single function that can create the connection name from the data structure, even if it takes a bit of CPU time and crawling over the structure every time you need to figure the name out.

I've taken a look at the exported JSON, and I may have spotted some "improvements" which would (I think!) make life easier. The nodes' "wires" properties are arrays of arrays of strings, and they might be better as arrays of arrays of objects. As it is, the strings contain the destination node name and port separated by a colon, so the new object would be something like {"dst": id, "port": number}. The port number must be the logicalport number only, i.e. a number between 0 and N-1 where there are N visible connections on the design. The physical port numbers could well be different, and should be calculated on the fly when live editing or exporting. In a similar way, you currently have the "name" of a node possibly indicating that it is an array, if it has a number in square brackets at the end. I think this should be an "arraySize" property (zero for a non-array), with the name value stored without the array indicator, and the displayed string re-created only for display purposes. This should make it easier to scan the design structure figuring out the physical connection counts for a mixer or tab.

As far as I can see you have to be prepared scan the entire design structure for almost any change to a bus-capable entity, because connections can go between groups. Similarly, the only way to "resize" anything is actually to destroy the old one and re-create it at the new size: maybe not groups, you can delete the last few members if it's getting smaller, or create just the new ones if it's getting larger. But definitely the variable-width mixers...
 
The structure for the wires internally
are not the same as the saved JSON
(that is the way the Node-RED team saves some space for the exported JSON, or actually I don't really know why it's like that, but it could be)

the internal working structure have all wires saved into a separate array
var links = [];

with each wire having the following structure
{source, sourcePort, target, targetPort}
where source and target are references to the actual node:s

also then imagine if that structure was saved to the JSON
then it would look like this
{"sourceId",sourcePort,"targetId",tagetPort}
that would mean the sourceId + sourcePort would be duplicated for every output wire from that specific source, so my guess about space saving is surely correct

back to the JSON structure, yes the arrays of arrays look a little strange
but I would not currently prioritize changing that, as it's not actually a problem
as it's only in the saved JSON,


Yes I could do the array thing like you propose
and show the displayed text name[] for arrays
and only name for non arrays.
That's really much possible.
It would however need to convert 'old' style array def. nodes
but that should not be any problem.

Good that I use a 'Global editor' for most of the audio objects,
as I only need to change that to add the array def. property.
Can also use my autogen feature, as I would need to add the new property to every object anyway.


Yes I do rescan the whole design for every change,
what it also do in the background is sorting every object by columns (top-bottom)
before saving the JSON to local storage, it's crazy how fast JavaScript really is.

Did you see my post when It first outperformed both python and C# for Serial data receive?

The other day I did however see a bug for the Serial receive
it was suddenly unable to receive any data,
(transmit was ok, could verify that by the debug port)
and I had to restart my computer to solve that.
 
Can also use my autogen feature, as I would need to add the new property to every object anyway.

While I do that, and did just discovered a easy way of joining objects together
(by using ES6 spread operator)

I will define two Global node def.

Code:
var NodeTypeBase = {
    "defaults":{
        "name":{"type":"c_cpp_name_no_array"},
        "id":{"noEdit":""},
        "comment":{},
        "color":{"type":"color"},
    },
    "editor":"autogen",
    "shortName":"newType",
    "editorhelp":"",
    "inputs":0,
    "outputs":0,
    "category":"",
    "color":"#E6E0F8",
    "icon":"arrow-in.png"
};
var arraySize_Help = "selects the array size,<br>a value of 1 mean no array<br>the max value is 255";
var NodeTypeArrayBase = {
    ...NodeTypeBase,
    "defaults":{
        ...NodeTypeBase.defaults,
        "arraySize":{"value":1,"maxval":255,"minval":1,"type":"int",
            "editor":{
                "label":"Array Size","rowClass":"form-row-mid",
                "help":arraySize_Help
            }
        }
    }
};

they can then by used on all node def

some examples:

Code:
"AudioInputI2S":{...NodeTypeBase,"shortName":"i2s","outputs":2,"category":"input-i2s1"},
"AudioOutputI2S":{...NodeTypeBase,"shortName":"i2s","inputs":2,"category":"output-i2s1"},
"AudioMixer4":{...NodeTypeArrayBase,"shortName":"mixer4","inputs":4,"outputs":1,"category":"mixer"},
"AudioSynthWaveform":{...NodeTYpeArrayBase,"shortName":"waveform","outputs":1,"category":"synth"},

that makes it much easier to add/remove properties on all node types
 
Last edited:
I'm having a play with exporting an OSCAudio-capable design to C++ classes. It's going to be slow because my Javascript is pretty much non-existent and I'm totally unfamiliar with the data structures involved... will do PRs if I think I've come up with anything useful.
 
sounds good

Because of that, I have now implemented a draft for exporting as OSC style c++,
It's using the 'Export Mode'-toolbar setting to select if OSC c++ export should be used.
ExportModeOSC.png

note. the setting is stored in the RED.arduino namespace,
but do not have a setting in the settings-tab, as there is no point having it at two places :)

I did also do some rework of my code
specially getTypeName and RED.export.links.getDynInputDynSize is now used directly instead of wrapping it into getDynamicInputCount

actually RED.export.links.getDynInputDynSize(node) is a wrapper for RED.export.links.getDynInputDynSizePortStartIndex(node, undefined);
but it is at least at the same namespace/module

also did a draft to handle autogeneration of AudioMixerStereo code
but there is no code in mixersCode.js for the mixerStereo

Then I wonder what would be the downside of only using the dynmixer:s for everything,
and having them as the standard mixers for the whole audio lib?


Currently the draft OSC c++ export (as usual :p) don't support the following:
* arrays don't get proper osc names
* the AudioConnections don't get the OSC names
* OSC groups
* busWire (not supported at any c++ export modes either)
* array source with multiple outputs connecting to the same mixer
 
After some additional thinking

OSC export/mode maybe need a separate setting/checkbox?
or should all possible modes exist in the 'combobox'?

here are the all (in theory) possible export situations

OSC live whole design export
OSC live partial edit

OSC c++ simple export
OSC c++ class based export
OSC c++ class to zip export

c++ simple export
c++ class based export
c++ class to zip export


mostly think that a user only use one export mode for one project
and therefore there only needs to be one export button?

also one plan is to use the export mode to verify the design.

but in the end that would not be needed,
as there should only be one set of rules that apply for all modes.

Think the simple/class export modes could be minimized to
some autodetect functionality,
i.e. if there is any class/tab-node at the 'Audio'-main then the class export should be used,
otherwise the simple plain export could be used.
 
Think we need a special case while having array of objects inside a class while using the OSC c++ export

consider your example:

Code:
class OSCVoice1grp : public OSCAudioGroup
{
public:
    // the actual voice elements we want to group together
    OSCAudioGroup& wav; // this would be holding the array of wav:s
    OSCAudioSynthWaveform* wavs[4]; // to hold the actual instances
    //OSCAudioSynthWaveform&  wav1;
    //OSCAudioSynthWaveform&  wav2;
    OSCAudioMixer4&         mixer;
    OSCAudioEffectEnvelope& env;

    // internal patch cords
    // total patchCordCount:3 including array typed ones.
    OSCAudioConnection*     patchCord[5];

    // create as sub-group
    OSCVoice1grp(const char* _name,OSCAudioGroup* parent) : // constructor 
      OSCAudioGroup(_name,parent), // construct our base class instance
      wav(*new OSCAudioGroup{"wav", ((OSCAudioGroup*) this)}), // group is not a pointer argument here????
      //wav1(*new OSCAudioSynthWaveform{"wav1",*((OSCAudioGroup*) this)}),
      //wav2(*new OSCAudioSynthWaveform{"wav2",*((OSCAudioGroup*) this)}),
      mixer(*new OSCAudioMixer4{"mixer",*((OSCAudioGroup*) this)}),
      env(*new OSCAudioEffectEnvelope{"env",*((OSCAudioGroup*) this)})
    {
        int pci = 0; // used only for adding new patchcords
        OSCAudioGroup& grp = *((OSCAudioGroup*) this);
        char arrayItemName[5]; // ixxx'\n'
        char pcName[20];

        for(int i=0;i<4;i++) {
            sprintf(arrayItemName, "i%d", i);
            wavs[i] = new OSCAudioSynthWaveform(arrayItemName, wav);
            sprintf(pcName, "wav_i%d_0_mixer_%d", i, i);
            patchCord[pci++] = new OSCAudioConnection(pcName, grp, *wavs[i],   0, mixer, i);
        }
        patchCord[pci++] = new OSCAudioConnection("mixer_env", grp, mixer, 0, env,   0);                
    } 

    // create at root
    OSCVoice1grp(const char* _name) : OSCVoice1grp(_name,NULL) {}

    // no destructor needed, the base OSCAudioGroup   
    // destroys its members when it's destroyed
};

One thing about the current state of the arduino exports
they still use the exported structure
which makes that code a bit slower and also ineffective
because of a lot of unnecessary lockups

The export simple is mostly done
only need to replace the RED.nodes.eachWire

the class export is kind of a mess,
and in the current state would make implementing OSC export harder than necessary

if you look at the simple export you can see that I go through all nodes one time
and utilize different variables to contain the different parts of the exported code
those parts are then put together in the end

var cpp = getCppHeader(jsonString, includes, false);

// here I add the different mixer variants not included in this example

cpp += "\n" + codeFiles + "\n" + defines + "\n" + cppAPN + "\n" + cppAC + "\n" + cppCN + "\n" + globalVars + "\n" + functions + "\n";
cpp += getCppFooter();


that is also needed for the OSC class export
 
Yes, it's not easy to follow! And I'm not 100% sure which parts might be original NodeRED code that's best left untouched. I've made some progress and pushed it up to the development branch on my fork just now. It won't result in working code, but it's getting there. I think I have some of the possible very weird cases covered - the test design is this at the moment:
Code:
{"version":1,"settings":{"main":{},"OSC":{},"arduino":{"ExportForOSC":true,"useExportDialog":true,"ProjectName":"MultiTimbralLinEnv","Board":{"Platform":"","Board":"teensy41","Options":""}},"BiDirDataWebSocketBridge":{},"workspaces":{},"sidebar":{},"palette":{},"editor":{},"devTest":{},"IndexedDBfiles":{"testFileNames":"testFile.txt"},"NodeDefGenerator":{},"NodeDefManager":{},"NodeHelpManager":{}},"workspaces":[{"type":"tab","id":"Main","label":"Main","nodes":[{"id":"20220302T113319_446Z_335b","type":"AudioSynthWaveform","isClass":false,"name":"LFO","comment":"","x":90,"y":260,"z":"Main","bgColor":"#E6E0F8","wires":[["20220302T113305_119Z_4b2:0","20220302T113305_119Z_4b2:1","20220227T150834_852Z_11ed:3"]]},{"id":"20220228T194812_823Z_dfa8","type":"SampleAndHold","isClass":true,"name":"SnH[6]","x":265,"y":170,"z":"Main","bgColor":"#CCFFCC","wires":[["20220227T150834_852Z_11ed:0"]]},{"id":"Main_MinHammond1","type":"MinHammond","isClass":true,"name":"HammondVoice[8]","x":235,"y":230,"z":"Main","bgColor":"#CCFFCC","wires":[["20220227T150834_852Z_11ed:2","20220302T170700_396Z_4a3b:0"]]},{"id":"20220302T113305_119Z_4b2","type":"WaveFormVoice","isClass":true,"name":"modWaves[2]","x":240,"y":290,"z":"Main","bgColor":"#CCFFCC","wires":[["20220227T150834_852Z_11ed:1","20220227T150834_852Z_11ed:4","20220302T170700_396Z_4a3b:1"]]},{"id":"20220227T150834_852Z_11ed","type":"AudioMixerStereo","isClass":false,"name":"mixerI2S","comment":"","inputs":5,"ExtraInputs":0,"RealInputs":19,"x":475,"y":245,"z":"Main","bgColor":"#E6E0F8","wires":[["Main_i2s1:0"],["Main_i2s1:1"]]},{"id":"20220302T170700_396Z_4a3b","type":"AudioMixerStereo","isClass":false,"name":"mixerUSB","comment":"","inputs":2,"ExtraInputs":0,"RealInputs":10,"x":470,"y":315,"z":"Main","bgColor":"#E6E0F8","wires":[["Main_usb1:0"],["Main_usb1:1"]]},{"id":"Main_i2s1","type":"AudioOutputI2S","isClass":false,"name":"i2s","comment":"","x":690,"y":225,"z":"Main","bgColor":"#E6E0F8","wires":[]},{"id":"Main_usb1","type":"AudioOutputUSB","isClass":false,"name":"usb","comment":"","x":695,"y":275,"z":"Main","bgColor":"#E6E0F8","wires":[]}],"links":[],"export":true,"isMain":false,"mainNameType":"tabName","mainNameExt":".ino","isAudioMain":true,"generateCppDestructor":true,"extraClassDeclarations":"","settings":{}},{"type":"tab","id":"a47e9f.1128a16","label":"SampleAndHold","nodes":[{"id":"SampleAndHold_Out1","type":"TabOutput","isClass":false,"name":"Out","comment":"","x":865,"y":195,"z":"a47e9f.1128a16","bgColor":"#cce6ff","wires":[]},{"id":"Sheet_1_waveform1","type":"AudioSynthWaveform","isClass":false,"name":"waveform1","x":195,"y":180,"z":"a47e9f.1128a16","bgColor":"#E6E0F8","wires":[["20220227T151017_148Z_7e09:0"]]},{"id":"Sheet_1_noise1","type":"AudioSynthNoiseWhite","isClass":false,"name":"noise1","x":155,"y":315,"z":"a47e9f.1128a16","bgColor":"#E6E0F8","wires":[["Sheet_1_bitcrusher1:0"]]},{"id":"20220227T151017_148Z_7e09","type":"AudioEffectEnvelope","isClass":false,"name":"envelope","comment":"","x":345,"y":180,"z":"a47e9f.1128a16","bgColor":"#E6E0F8","wires":[["Sheet_1_filter1:0"]]},{"id":"Sheet_1_bitcrusher1","type":"AudioEffectBitcrusher","isClass":false,"name":"bitcrusher1","x":319,"y":314,"z":"a47e9f.1128a16","bgColor":"#E6E0F8","wires":[["Sheet_1_filter1:1"]]},{"id":"Sheet_1_filter1","type":"AudioFilterStateVariable","isClass":false,"name":"filter1","x":485,"y":190,"z":"a47e9f.1128a16","bgColor":"#E6E0F8","wires":[["20220227T172000_754Z_e580:0"],["20220227T172000_754Z_e580:1"],["20220227T172000_754Z_e580:2"]]},{"id":"20220227T172000_754Z_e580","type":"AudioMixer","isClass":false,"name":"mixer","comment":"","inputs":3,"ExtraInputs":0,"RealInputs":3,"x":690,"y":190,"z":"a47e9f.1128a16","bgColor":"#E6E0F8","wires":[["SampleAndHold_Out1:0"]]}],"links":[],"export":true,"isMain":false,"mainNameType":"tabName","mainNameExt":".ino","isAudioMain":false,"generateCppDestructor":true,"extraClassDeclarations":"","settings":{}},{"type":"tab","id":"6eff372d.78f988","label":"MinHammond","nodes":[{"id":"MinHammond_Out1","type":"TabOutput","isClass":false,"name":"Out1","comment":"","x":827,"y":300,"z":"6eff372d.78f988","bgColor":"#cce6ff","wires":[]},{"id":"MinHammond_waveform7","type":"AudioSynthWaveform","isClass":false,"name":"waveform1","x":263,"y":99,"z":"6eff372d.78f988","bgColor":"#E6E0F8","wires":[["MinHammond_mixer4_1:0"]]},{"id":"MinHammond_waveform8","type":"AudioSynthWaveform","isClass":false,"name":"waveform2","x":264,"y":144,"z":"6eff372d.78f988","bgColor":"#E6E0F8","wires":[["MinHammond_mixer4_1:1"]]},{"id":"MinHammond_waveform9","type":"AudioSynthWaveform","isClass":false,"name":"waveform3","x":265,"y":185,"z":"6eff372d.78f988","bgColor":"#E6E0F8","wires":[["MinHammond_mixer4_1:2"]]},{"id":"MinHammond_waveform5","type":"AudioSynthWaveform","isClass":false,"name":"waveform4","x":257,"y":226,"z":"6eff372d.78f988","bgColor":"#E6E0F8","wires":[["MinHammond_mixer4_1:3"]]},{"id":"MinHammond_waveform6","type":"AudioSynthWaveform","isClass":false,"name":"waveform5","x":257,"y":266,"z":"6eff372d.78f988","bgColor":"#E6E0F8","wires":[["MinHammond_mixer4_2:0"]]},{"id":"MinHammond_waveform4","type":"AudioSynthWaveform","isClass":false,"name":"waveform6","x":256,"y":310,"z":"6eff372d.78f988","bgColor":"#E6E0F8","wires":[["MinHammond_mixer4_2:1"]]},{"id":"MinHammond_waveform1","type":"AudioSynthWaveform","isClass":false,"name":"waveform7","x":255,"y":350,"z":"6eff372d.78f988","bgColor":"#E6E0F8","wires":[["MinHammond_mixer4_2:2"]]},{"id":"MinHammond_waveform2","type":"AudioSynthWaveform","isClass":false,"name":"waveform8","x":255,"y":392,"z":"6eff372d.78f988","bgColor":"#E6E0F8","wires":[["MinHammond_mixer4_2:3"]]},{"id":"MinHammond_waveform3","type":"AudioSynthWaveform","isClass":false,"name":"waveform9","x":255,"y":446,"z":"6eff372d.78f988","bgColor":"#E6E0F8","wires":[["MinHammond_mixer4_3:2"]]},{"id":"MinHammond_noise1","type":"AudioSynthNoiseWhite","isClass":false,"name":"noise1","x":258,"y":535,"z":"6eff372d.78f988","bgColor":"#E6E0F8","wires":[["MinHammond_envelope1:0"]]},{"id":"MinHammond_mixer4_1","type":"AudioMixer4","isClass":false,"name":"mixer1","x":477,"y":163,"z":"6eff372d.78f988","bgColor":"#E6E0F8","wires":[["MinHammond_mixer4_3:0"]]},{"id":"MinHammond_mixer4_2","type":"AudioMixer4","isClass":false,"name":"mixer2","x":482,"y":290,"z":"6eff372d.78f988","bgColor":"#E6E0F8","wires":[["MinHammond_mixer4_3:1"]]},{"id":"MinHammond_envelope1","type":"AudioEffectEnvelope","isClass":false,"name":"envelope1","x":493,"y":523,"z":"6eff372d.78f988","bgColor":"#E6E0F8","wires":[["MinHammond_mixer4_3:3"]]},{"id":"MinHammond_mixer4_3","type":"AudioMixer4","isClass":false,"name":"mixer3","x":656,"y":297,"z":"6eff372d.78f988","bgColor":"#E6E0F8","wires":[["MinHammond_Out1:0"]]}],"links":[],"export":true,"isMain":false,"mainNameType":"tabName","mainNameExt":".ino","isAudioMain":false,"generateCppDestructor":false,"extraClassDeclarations":"","settings":{}},{"type":"tab","id":"ea305170.fb2f9","label":"WaveFormVoice","nodes":[{"id":"20220302T113212_611Z_f670","type":"TabInput","isClass":false,"name":"freqMod","comment":"","outputs":1,"x":110,"y":283,"z":"ea305170.fb2f9","bgColor":"#cce6ff","wires":[["20220302T113145_789Z_77c0:0"]]},{"id":"WaveFormVoice_Out1","type":"TabOutput","isClass":false,"name":"Out2","comment":"","x":570,"y":320,"z":"ea305170.fb2f9","bgColor":"#cce6ff","wires":[]},{"id":"20220302T113218_689Z_d58b","type":"TabInput","isClass":false,"name":"shapeMod","comment":"","outputs":1,"x":106,"y":336,"z":"ea305170.fb2f9","bgColor":"#cce6ff","wires":[["20220302T113145_789Z_77c0:1"]]},{"id":"WaveFormVoice_vars1","type":"Variables","isClass":false,"name":"vars","comment":"// Start of variables\nprivate:\n    bool isNew;\n    static short wave_type[4];\npublic:\n// End of variables\n","x":290,"y":105,"z":"ea305170.fb2f9","bgColor":"#DDFFBB","wires":[]},{"id":"WaveFormVoice_constructor code1","type":"ConstructorCode","isClass":false,"name":"constructor code","comment":"      env.attack(129.2);\r\n      env.hold(2.1);\r\n      env.decay(181.4);\r\n      env.sustain(0.3);\r\n      env.release(284.5);\r\n      amp.gain(0.5);","x":283,"y":151,"z":"ea305170.fb2f9","bgColor":"#DDFFBB","wires":[]},{"id":"WaveFormVoice_code1","type":"Function","isClass":false,"name":"WaveFormVoice_code","comment":"// Start of voice class manually-entered code\r\n    void noteOn(int MIDInote, int MIDIvel, int chan=-1){};\r\n    void noteOn(float freq, float vel, int chan=-1)\r\n    {\r\n      if (isNew)\r\n        wave.begin(vel,freq,(chan<0)?WAVEFORM_SINE:(wave_type[chan&3]));\r\n      else\r\n      {\r\n        wave.amplitude(vel);\r\n        wave.frequency(freq);\r\n      }\r\n      env.noteOn();\r\n      isNew = false;\r\n    }\r\n\r\n    void noteOff(void){env.noteOff();};\r\n    bool isPlaying(void) {return env.isActive();};\r\n}; // terminates class\r\n\r\nstatic short WaveformVoice::wave_type[] = {\r\n    WAVEFORM_SINE,\r\n    WAVEFORM_SQUARE,\r\n    WAVEFORM_SAWTOOTH,\r\n    WAVEFORM_TRIANGLE // no closing brace, GUI tool puts it in\r\n// End of voice class manually-entered code\r\n","x":290,"y":220,"z":"ea305170.fb2f9","bgColor":"#DDFFBB","wires":[]},{"id":"20220302T113145_789Z_77c0","type":"AudioSynthWaveformModulated","isClass":false,"name":"waveformMod","comment":"","x":285,"y":320,"z":"ea305170.fb2f9","bgColor":"#E6E0F8","wires":[["WaveFormVoice_amp1:0"]]},{"id":"WaveFormVoice_destructor code1","type":"DestructorCode","isClass":false,"name":"destructor code","comment":"// extra code for destructor","x":480,"y":148,"z":"ea305170.fb2f9","bgColor":"#DDFFBB","wires":[]},{"id":"WaveFormVoice_amp1","type":"AudioAmplifier","isClass":false,"name":"amp","comment":"","x":460,"y":320,"z":"ea305170.fb2f9","bgColor":"#E6E0F8","wires":[["WaveFormVoice_Out1:0"]]}],"links":[],"export":true,"isMain":false,"mainNameType":"tabName","mainNameExt":".ino","isAudioMain":false,"generateCppDestructor":false,"extraClassDeclarations":"","settings":{}}],"nodeAddons":{}
}
Won't be able to do much for the next couple of days - Real Work and other stuff getting in the way...
 
FYI

You don't need the
}; // terminates class
workaround anymore

there is the 'eof code' object that can be used instead
where you can put the whole of
static short WaveformVoice::wave_type[] = {
//...
};
 
Breaking News ... the latest push to my development branch (same as above) compiles and doesn't crash the Teensy, though I've not tested generating audio or destroying the classes as yet. I've only tested the class export via copy and paste. The exported classes are rather inelegant because of the need to generate unique patchcord names: I've not thought through whether that's just a consequence of a poor design structure. If not, I suspect it may be useful to add an "auto name connection" capability to the OSCAudio library.

The test design is now:
Code:
{"version":1,"settings":{"main":{},"OSC":{},"arduino":{"ExportForOSC":true,"useExportDialog":true,"ProjectName":"MultiTimbralLinEnv","StandardIncludeHeader":"#include <Arduino.h>\n#include <Audio.h>\n#include <Wire.h>\n#include <SPI.h>\n#include <SD.h>\n#include <SerialFlash.h>\n\n#include <OSCAudioBase.h>\n","Board":{"Platform":"","Board":"teensy41","Options":""}},"BiDirDataWebSocketBridge":{},"workspaces":{},"sidebar":{},"palette":{},"editor":{},"devTest":{},"IndexedDBfiles":{"testFileNames":"testFile.txt"},"NodeDefGenerator":{},"NodeDefManager":{},"NodeHelpManager":{}},"workspaces":[{"type":"tab","id":"Main","label":"Main","nodes":[{"id":"20220302T113319_446Z_335b","type":"AudioSynthWaveform","isClass":false,"name":"LFO","comment":"","x":90,"y":260,"z":"Main","bgColor":"#E6E0F8","wires":[["20220302T113305_119Z_4b2:0","20220302T113305_119Z_4b2:1","20220227T150834_852Z_11ed:3"]]},{"id":"20220228T194812_823Z_dfa8","type":"SampleAndHold","isClass":true,"name":"SnH[6]","x":265,"y":170,"z":"Main","bgColor":"#CCFFCC","wires":[["20220227T150834_852Z_11ed:0"]]},{"id":"Main_MinHammond1","type":"MinHammond","isClass":true,"name":"HammondVoice[8]","x":235,"y":230,"z":"Main","bgColor":"#CCFFCC","wires":[["20220227T150834_852Z_11ed:2","20220302T170700_396Z_4a3b:0"]]},{"id":"20220302T113305_119Z_4b2","type":"WaveFormVoice","isClass":true,"name":"modWaves[2]","x":250,"y":290,"z":"Main","bgColor":"#CCFFCC","wires":[["20220227T150834_852Z_11ed:4","20220302T170700_396Z_4a3b:2"],["20220302T170700_396Z_4a3b:1","20220227T150834_852Z_11ed:1"]]},{"id":"20220227T150834_852Z_11ed","type":"AudioMixerStereo","isClass":false,"name":"mixerI2S","comment":"","inputs":5,"ExtraInputs":0,"RealInputs":19,"x":475,"y":245,"z":"Main","bgColor":"#E6E0F8","wires":[["Main_i2s1:0"],["Main_i2s1:1"]]},{"id":"20220302T170700_396Z_4a3b","type":"AudioMixerStereo","isClass":false,"name":"mixerUSB","comment":"","inputs":3,"ExtraInputs":0,"RealInputs":12,"x":470,"y":315,"z":"Main","bgColor":"#E6E0F8","wires":[["Main_usb1:0"],["Main_usb1:1"]]},{"id":"Main_i2s1","type":"AudioOutputI2S","isClass":false,"name":"i2s","comment":"","x":600,"y":245,"z":"Main","bgColor":"#E6E0F8","wires":[]},{"id":"Main_usb1","type":"AudioOutputUSB","isClass":false,"name":"usb","comment":"","x":605,"y":315,"z":"Main","bgColor":"#E6E0F8","wires":[]}],"links":[],"export":true,"isMain":false,"mainNameType":"tabName","mainNameExt":".ino","isAudioMain":true,"generateCppDestructor":true,"extraClassDeclarations":"","settings":{}},{"type":"tab","id":"a47e9f.1128a16","label":"SampleAndHold","nodes":[{"id":"SampleAndHold_Out1","type":"TabOutput","isClass":false,"name":"Out","comment":"","x":865,"y":195,"z":"a47e9f.1128a16","bgColor":"#cce6ff","wires":[]},{"id":"Sheet_1_waveform1","type":"AudioSynthWaveform","isClass":false,"name":"waveform1","x":195,"y":180,"z":"a47e9f.1128a16","bgColor":"#E6E0F8","wires":[["20220227T151017_148Z_7e09:0"]]},{"id":"Sheet_1_noise1","type":"AudioSynthNoiseWhite","isClass":false,"name":"noise1","x":155,"y":315,"z":"a47e9f.1128a16","bgColor":"#E6E0F8","wires":[["Sheet_1_bitcrusher1:0"]]},{"id":"20220227T151017_148Z_7e09","type":"AudioEffectEnvelope","isClass":false,"name":"envelope","comment":"","x":345,"y":180,"z":"a47e9f.1128a16","bgColor":"#E6E0F8","wires":[["Sheet_1_filter1:0"]]},{"id":"Sheet_1_bitcrusher1","type":"AudioEffectBitcrusher","isClass":false,"name":"bitcrusher1","x":319,"y":314,"z":"a47e9f.1128a16","bgColor":"#E6E0F8","wires":[["Sheet_1_filter1:1"]]},{"id":"Sheet_1_filter1","type":"AudioFilterStateVariable","isClass":false,"name":"filter1","x":485,"y":190,"z":"a47e9f.1128a16","bgColor":"#E6E0F8","wires":[["20220227T172000_754Z_e580:0"],["20220227T172000_754Z_e580:1"],["20220227T172000_754Z_e580:2"]]},{"id":"20220227T172000_754Z_e580","type":"AudioMixer","isClass":false,"name":"mixer","comment":"","inputs":3,"ExtraInputs":0,"RealInputs":3,"x":690,"y":190,"z":"a47e9f.1128a16","bgColor":"#E6E0F8","wires":[["SampleAndHold_Out1:0"]]}],"links":[],"export":true,"isMain":false,"mainNameType":"tabName","mainNameExt":".ino","isAudioMain":false,"generateCppDestructor":true,"extraClassDeclarations":"","settings":{}},{"type":"tab","id":"6eff372d.78f988","label":"MinHammond","nodes":[{"id":"MinHammond_Out1","type":"TabOutput","isClass":false,"name":"Out1","comment":"","x":827,"y":300,"z":"6eff372d.78f988","bgColor":"#cce6ff","wires":[]},{"id":"MinHammond_waveform7","type":"AudioSynthWaveform","isClass":false,"name":"waveform1","x":263,"y":99,"z":"6eff372d.78f988","bgColor":"#E6E0F8","wires":[["MinHammond_mixer4_1:0"]]},{"id":"MinHammond_waveform8","type":"AudioSynthWaveform","isClass":false,"name":"waveform2","x":264,"y":144,"z":"6eff372d.78f988","bgColor":"#E6E0F8","wires":[["MinHammond_mixer4_1:1"]]},{"id":"MinHammond_waveform9","type":"AudioSynthWaveform","isClass":false,"name":"waveform3","x":265,"y":185,"z":"6eff372d.78f988","bgColor":"#E6E0F8","wires":[["MinHammond_mixer4_1:2"]]},{"id":"MinHammond_waveform5","type":"AudioSynthWaveform","isClass":false,"name":"waveform4","x":257,"y":226,"z":"6eff372d.78f988","bgColor":"#E6E0F8","wires":[["MinHammond_mixer4_1:3"]]},{"id":"MinHammond_waveform6","type":"AudioSynthWaveform","isClass":false,"name":"waveform5","x":257,"y":266,"z":"6eff372d.78f988","bgColor":"#E6E0F8","wires":[["MinHammond_mixer4_2:0"]]},{"id":"MinHammond_waveform4","type":"AudioSynthWaveform","isClass":false,"name":"waveform6","x":256,"y":310,"z":"6eff372d.78f988","bgColor":"#E6E0F8","wires":[["MinHammond_mixer4_2:1"]]},{"id":"MinHammond_waveform1","type":"AudioSynthWaveform","isClass":false,"name":"waveform7","x":255,"y":350,"z":"6eff372d.78f988","bgColor":"#E6E0F8","wires":[["MinHammond_mixer4_2:2"]]},{"id":"MinHammond_waveform2","type":"AudioSynthWaveform","isClass":false,"name":"waveform8","x":255,"y":392,"z":"6eff372d.78f988","bgColor":"#E6E0F8","wires":[["MinHammond_mixer4_2:3"]]},{"id":"MinHammond_waveform3","type":"AudioSynthWaveform","isClass":false,"name":"waveform9","x":255,"y":446,"z":"6eff372d.78f988","bgColor":"#E6E0F8","wires":[["MinHammond_mixer4_3:2"]]},{"id":"MinHammond_noise1","type":"AudioSynthNoiseWhite","isClass":false,"name":"noise1","x":258,"y":535,"z":"6eff372d.78f988","bgColor":"#E6E0F8","wires":[["MinHammond_envelope1:0"]]},{"id":"MinHammond_mixer4_1","type":"AudioMixer4","isClass":false,"name":"mixer1","x":477,"y":163,"z":"6eff372d.78f988","bgColor":"#E6E0F8","wires":[["MinHammond_mixer4_3:0"]]},{"id":"MinHammond_mixer4_2","type":"AudioMixer4","isClass":false,"name":"mixer2","x":482,"y":290,"z":"6eff372d.78f988","bgColor":"#E6E0F8","wires":[["MinHammond_mixer4_3:1"]]},{"id":"MinHammond_envelope1","type":"AudioEffectEnvelope","isClass":false,"name":"envelope1","x":493,"y":523,"z":"6eff372d.78f988","bgColor":"#E6E0F8","wires":[["MinHammond_mixer4_3:3"]]},{"id":"MinHammond_mixer4_3","type":"AudioMixer4","isClass":false,"name":"mixer3","x":656,"y":297,"z":"6eff372d.78f988","bgColor":"#E6E0F8","wires":[["MinHammond_Out1:0"]]}],"links":[],"export":true,"isMain":false,"mainNameType":"tabName","mainNameExt":".ino","isAudioMain":false,"generateCppDestructor":false,"extraClassDeclarations":"","settings":{}},{"type":"tab","id":"ea305170.fb2f9","label":"WaveFormVoice","nodes":[{"id":"20220302T113212_611Z_f670","type":"TabInput","isClass":false,"name":"freqMod","comment":"","outputs":1,"x":110,"y":283,"z":"ea305170.fb2f9","bgColor":"#cce6ff","wires":[["20220302T113145_789Z_77c0:0"]]},{"id":"WaveFormVoice_Out1","type":"TabOutput","isClass":false,"name":"OutA","comment":"","inputs":"","x":570,"y":320,"z":"ea305170.fb2f9","bgColor":"#cce6ff","wires":[]},{"id":"20220302T113218_689Z_d58b","type":"TabInput","isClass":false,"name":"shapeMod","comment":"","outputs":1,"x":106,"y":336,"z":"ea305170.fb2f9","bgColor":"#cce6ff","wires":[["20220302T113145_789Z_77c0:1"]]},{"id":"20220304T200046_288Z_b82f","type":"TabOutput","isClass":false,"name":"OutW","comment":"","inputs":1,"x":570,"y":380,"z":"ea305170.fb2f9","bgColor":"#cce6ff","wires":[]},{"id":"WaveFormVoice_vars1","type":"Variables","isClass":false,"name":"vars","comment":"// Start of variables\nprivate:\n    bool isNew;\n    static short wave_type[4];\npublic:\n// End of variables\n","x":290,"y":105,"z":"ea305170.fb2f9","bgColor":"#DDFFBB","wires":[]},{"id":"WaveFormVoice_constructor code1","type":"ConstructorCode","isClass":false,"name":"constructor code","comment":"      env.attack(129.2);\r\n      env.hold(2.1);\r\n      env.decay(181.4);\r\n      env.sustain(0.3);\r\n      env.release(284.5);\r\n      amp.gain(0.5);","x":283,"y":151,"z":"ea305170.fb2f9","bgColor":"#DDFFBB","wires":[]},{"id":"WaveFormVoice_code1","type":"Function","isClass":false,"name":"WaveFormVoice_code","comment":"// Start of voice class manually-entered code\r\n    void noteOn(int MIDInote, int MIDIvel, int chan=-1){};\r\n    void noteOn(float freq, float vel, int chan=-1)\r\n    {\r\n      if (isNew)\r\n        wave.begin(vel,freq,(chan<0)?WAVEFORM_SINE:(wave_type[chan&3]));\r\n      else\r\n      {\r\n        wave.amplitude(vel);\r\n        wave.frequency(freq);\r\n      }\r\n      env.noteOn();\r\n      isNew = false;\r\n    }\r\n\r\n    void noteOff(void){env.noteOff();};\r\n    bool isPlaying(void) {return env.isActive();};\r\n}; // terminates class\r\n\r\n/* static */ short WaveFormVoice::wave_type[] = {\r\n    WAVEFORM_SINE,\r\n    WAVEFORM_SQUARE,\r\n    WAVEFORM_SAWTOOTH,\r\n    WAVEFORM_TRIANGLE // no closing brace, GUI tool puts it in\r\n// End of voice class manually-entered code\r\n","x":290,"y":220,"z":"ea305170.fb2f9","bgColor":"#DDFFBB","wires":[]},{"id":"20220302T113145_789Z_77c0","type":"AudioSynthWaveformModulated","isClass":false,"name":"wave","comment":"","x":285,"y":320,"z":"ea305170.fb2f9","bgColor":"#E6E0F8","wires":[["WaveFormVoice_amp1:0","20220306T195324_547Z_8d5d:0"]]},{"id":"WaveFormVoice_destructor code1","type":"DestructorCode","isClass":false,"name":"destructor code","comment":"// extra code for destructor","x":480,"y":148,"z":"ea305170.fb2f9","bgColor":"#DDFFBB","wires":[]},{"id":"WaveFormVoice_amp1","type":"AudioAmplifier","isClass":false,"name":"amp","comment":"","x":460,"y":320,"z":"ea305170.fb2f9","bgColor":"#E6E0F8","wires":[["WaveFormVoice_Out1:0"]]},{"id":"20220306T195324_547Z_8d5d","type":"AudioEffectEnvelope","isClass":false,"name":"env","comment":"","x":460,"y":380,"z":"ea305170.fb2f9","bgColor":"#E6E0F8","wires":[["20220304T200046_288Z_b82f:0"]]}],"links":[],"export":true,"isMain":false,"mainNameType":"tabName","mainNameExt":".ino","isAudioMain":false,"generateCppDestructor":false,"extraClassDeclarations":"","settings":{}}],"nodeAddons":{}
}
(Your FYI comment noted, just haven't got round to fixing it yet).
 
The exported classes are rather inelegant because of the need to generate unique patchcord names: I've not thought through whether that's just a consequence of a poor design structure. If not, I suspect it may be useful to add an "auto name connection" capability to the OSCAudio library.

The best way would to have no connections at all and each audio object had it own connect function
could surely be implemented into the AudioStream base class

and the connections could just be stored into a 'linked list'(to make expansion retraction easier)

Code:
struct {
   //sourcePort;
   //*destination; // just a pointer to the address so it can be looked up at disconnect
   //destinationPort;
   //acNext;
} AudioConnection;

so that you could just do (just a example using ordinary objects)

Code:
AudioSynthWaveform wav1;
AudioSynthWaveform wav2;
AudioEffectEnvelope   env1;
AudioEffectEnvelope   env2;
AudioMixer4              mix;

// connect all
void connectEverything() {
    wav1.connect(0,mix,0);
    wav2.connect(0,mix,1);
    wav1.connect(0,env1,0);
    wav2.connect(0,env2,0);
    env1.connect(0,mix,2);
    env2.connect(0,mix,3);
}

// disconnect all
void disconnectEverything() {
    wav1.disconnect(0,mix,0);
    wav2.disconnect(0,mix,1);
    wav1.disconnect(0,env1,0);
    wav2.disconnect(0,env2,0);
    env1.disconnect(0,mix,2);
    env2.disconnect(0,mix,3);

   // or more elegant

   // these can be used when for example the mix object gets destroyed
   // could also be a automatic thing as a input can only have one source
   // and therefore can easily send a message to 
   // that source to disconnect that specific connection 
   wav1.disconnectAll(mix);
   wav2.disconnectAll(mix);
   env1.disconnectAll(mix);
   env2.disconnectAll(mix);

   // providing no parameters mean disconnect all connections going out
   wav1.disconnectAll();
   wav2.disconnectAll();
   env1.disconnectAll();
   env2.disconnectAll();
   
}

but maybe that structure should just be implemented into the OSC lib for now

it would make the AudioConnection:s obsolete
 
I have also made classes for the following objects

REDWorkspace
REDNode
REDLink
REDLinkSvgPaths
REDLinkInfo

so that JSDoc info can be utilized much easier
thus making understanding of the different structures much easier

it's mostly used in nodes.js but view.js also needs some JSDoc specially where REDNode is used

plan to implement a class for 'JSONNode' as well but that is not that important
as it's only used for the exported JSON structure which only is used as the save/load functionality.

Currently working on a new more structured arduino-export.js (should maybe just be called cpp-export.js)
could also be called teensy-export.js but as it can export 'ordinary' c++ code as well maybe cpp-export is a better name?
 
I've tried to add OSC extensions in a manner as similar as possible to the existing Audio library, and make essentially no changes to the Audio library API itself (apart from the ability to destroy objects and disconnect connections...). The original Audio library already did connections as a linked list to allow multiple destinations from a given source.

When I was working through the existing export_classBased() I found it very difficult to follow what data structures were used and what the code was doing - the function is nearly 600 lines long! Assuming it's not too late, I'd encourage you to split it into calls to separate functions (that maybe you can partially re-use for non-class and OSC export?), and in particular not interleave generating intermediate data structures and generating output.
 
I have now 'kind of' 'merged' your osc export code into my 'master' branch
but instead used a different 'namespace' RED.arduino.export.osc @ arduino-export-osc.js

The settings that you use are at the original place @ arduino.js
And the export buttons are at the OSC menu

So now you could make a fork of that,

This would make it easier to have 'both' variants available simultaneous,
and would also make it much easier to merge your changes into my branch,
as I would not make any changes to arduino-export-osc.js



I did spot one thing, while exporting as class I get a undefined error:
Uncaught TypeError: Cannot read properties of undefined (reading 'outputs')
at getMaxConnName (arduino-export-osc.js:1311:19)
at export_classBased (arduino-export-osc.js:841:25)

This is because the bus var in getMaxConnName is undefined

don't know where the origin of this problem occur as I have not
had the time to understand your code yet.


Have not yet completed the new arduino-export2.js as I have been busy with other things.
 
Apologies, been offline with other stuff for a while - it's not that I've lost interest, far from it!

I've been having a few issues with the editor, too. Here's the design:
Code:
{"version":1,"settings":{"main":{},"OSC":{},"arduino":{"ExportForOSC":true,"useExportDialog":true,"ProjectName":"MultiTimbralLinEnv2","StandardIncludeHeader":"#include <Arduino.h>\n#include <Audio.h>\n#include <Wire.h>\n#include <SPI.h>\n#include <SD.h>\n#include <SerialFlash.h>\n\n#include <OSCAudioBase.h>\n","Board":{"Platform":"","Board":"teensy41","Options":""}},"BiDirDataWebSocketBridge":{},"workspaces":{},"sidebar":{},"palette":{},"editor":{},"devTest":{},"IndexedDBfiles":{"testFileNames":"testFile.txt"},"NodeDefGenerator":{},"NodeDefManager":{},"NodeHelpManager":{}},"workspaces":[{"type":"tab","id":"Main","label":"Main","nodes":[{"id":"20220302T113319_446Z_335b","type":"AudioSynthWaveform","name":"LFO","comment":"","x":80,"y":195,"z":"Main","bgColor":"#E6E0F8","wires":[["20220302T113305_119Z_4b2:0","20220302T113305_119Z_4b2:1","20220227T150834_852Z_11ed:4"]]},{"id":"20220228T194812_823Z_dfa8","type":"SampleAndHold","name":"SnH[6]","arraySize":6,"x":265,"y":170,"z":"Main","bgColor":"#CCFFCC","wires":[["20220227T150834_852Z_11ed:0"]]},{"id":"20220316T174639_836Z_37cc","type":"MinHammond1","name":"Hammond[8]","comment":"","arraySize":8,"x":240,"y":240,"z":"Main","bgColor":"#CCFFCC","wires":[["20220227T150834_852Z_11ed:2","20220302T170700_396Z_4a3b:1","20220302T170700_396Z_4a3b:0","20220227T150834_852Z_11ed:3"]]},{"id":"20220302T113305_119Z_4b2","type":"WaveFormVoice","name":"modWaves[2]","arraySize":2,"x":245,"y":295,"z":"Main","bgColor":"#CCFFCC","wires":[["20220227T150834_852Z_11ed:5","20220302T170700_396Z_4a3b:3"],["20220227T150834_852Z_11ed:1","20220302T170700_396Z_4a3b:2"]]},{"id":"20220227T150834_852Z_11ed","type":"AudioMixerStereo","name":"mixerI2S","comment":"","inputs":6,"ExtraInputs":0,"RealInputs":27,"x":475,"y":200,"z":"Main","bgColor":"#E6E0F8","wires":[[],["Main_i2s1:1"]]},{"id":"20220302T170700_396Z_4a3b","type":"AudioMixerStereo","name":"mixerUSB","comment":"","inputs":4,"ExtraInputs":0,"RealInputs":20,"x":475,"y":330,"z":"Main","bgColor":"#E6E0F8","wires":[["Main_usb1:0"],["Main_usb1:1"]]},{"id":"Main_i2s1","type":"AudioOutputI2S","name":"i2s","comment":"","x":610,"y":200,"z":"Main","bgColor":"#E6E0F8","wires":[]},{"id":"Main_usb1","type":"AudioOutputUSB","name":"usb","comment":"","x":605,"y":330,"z":"Main","bgColor":"#E6E0F8","wires":[]}],"links":[],"export":true,"isMain":false,"mainNameType":"tabName","mainNameExt":".ino","isAudioMain":true,"generateCppDestructor":true,"extraClassDeclarations":"","settings":{}},{"type":"tab","id":"a47e9f.1128a16","label":"SampleAndHold","nodes":[{"id":"SampleAndHold_Out1","type":"TabOutput","name":"Out","comment":"","x":865,"y":195,"z":"a47e9f.1128a16","bgColor":"#cce6ff","wires":[]},{"id":"Sheet_1_waveform1","type":"AudioSynthWaveform","name":"waveform1","x":195,"y":180,"z":"a47e9f.1128a16","bgColor":"#E6E0F8","wires":[["20220227T151017_148Z_7e09:0"]]},{"id":"Sheet_1_noise1","type":"AudioSynthNoiseWhite","name":"noise1","x":155,"y":315,"z":"a47e9f.1128a16","bgColor":"#E6E0F8","wires":[["Sheet_1_bitcrusher1:0"]]},{"id":"20220227T151017_148Z_7e09","type":"AudioEffectEnvelope","name":"envelope","comment":"","x":345,"y":180,"z":"a47e9f.1128a16","bgColor":"#E6E0F8","wires":[["Sheet_1_filter1:0"]]},{"id":"Sheet_1_bitcrusher1","type":"AudioEffectBitcrusher","name":"bitcrusher1","x":319,"y":314,"z":"a47e9f.1128a16","bgColor":"#E6E0F8","wires":[["Sheet_1_filter1:1"]]},{"id":"Sheet_1_filter1","type":"AudioFilterStateVariable","name":"filter1","x":485,"y":190,"z":"a47e9f.1128a16","bgColor":"#E6E0F8","wires":[["20220227T172000_754Z_e580:0"],["20220227T172000_754Z_e580:1"],["20220227T172000_754Z_e580:2"]]},{"id":"20220227T172000_754Z_e580","type":"AudioMixer","name":"mixer","comment":"","inputs":3,"ExtraInputs":0,"RealInputs":3,"x":690,"y":190,"z":"a47e9f.1128a16","bgColor":"#E6E0F8","wires":[["SampleAndHold_Out1:0"]]}],"links":[],"export":true,"isMain":false,"mainNameType":"tabName","mainNameExt":".ino","isAudioMain":false,"generateCppDestructor":true,"extraClassDeclarations":"","settings":{}},{"type":"tab","id":"6eff372d.78f988","label":"MinHammond1","nodes":[{"id":"MinHammond_Out1","type":"TabOutput","name":"Out","comment":"","inputs":"","x":680,"y":280,"z":"6eff372d.78f988","bgColor":"#cce6ff","wires":[]},{"id":"MinHammond_waveform7","type":"AudioSynthWaveform","name":"waveform1","x":263,"y":99,"z":"6eff372d.78f988","bgColor":"#E6E0F8","wires":[["20220307T120440_881Z_fb5:0"]]},{"id":"MinHammond_waveform8","type":"AudioSynthWaveform","name":"waveform2","x":264,"y":144,"z":"6eff372d.78f988","bgColor":"#E6E0F8","wires":[["20220307T120440_881Z_fb5:1"]]},{"id":"MinHammond_waveform9","type":"AudioSynthWaveform","name":"waveform3","x":265,"y":185,"z":"6eff372d.78f988","bgColor":"#E6E0F8","wires":[["20220307T120440_881Z_fb5:2"]]},{"id":"MinHammond_waveform5","type":"AudioSynthWaveform","name":"waveform4","x":257,"y":226,"z":"6eff372d.78f988","bgColor":"#E6E0F8","wires":[["20220307T120440_881Z_fb5:3"]]},{"id":"MinHammond_waveform6","type":"AudioSynthWaveform","name":"waveform5","x":257,"y":266,"z":"6eff372d.78f988","bgColor":"#E6E0F8","wires":[["20220307T120440_881Z_fb5:4"]]},{"id":"MinHammond_waveform4","type":"AudioSynthWaveform","name":"waveform6","x":256,"y":310,"z":"6eff372d.78f988","bgColor":"#E6E0F8","wires":[["20220307T120440_881Z_fb5:5"]]},{"id":"MinHammond_waveform1","type":"AudioSynthWaveform","name":"waveform7","x":255,"y":350,"z":"6eff372d.78f988","bgColor":"#E6E0F8","wires":[["20220307T120440_881Z_fb5:6"]]},{"id":"MinHammond_waveform2","type":"AudioSynthWaveform","name":"waveform8","x":255,"y":392,"z":"6eff372d.78f988","bgColor":"#E6E0F8","wires":[["20220307T120440_881Z_fb5:7"]]},{"id":"MinHammond_waveform3","type":"AudioSynthWaveform","name":"waveform9","x":255,"y":446,"z":"6eff372d.78f988","bgColor":"#E6E0F8","wires":[["20220307T120440_881Z_fb5:8"]]},{"id":"MinHammond_noise1","type":"AudioSynthNoiseWhite","name":"noise1","x":258,"y":535,"z":"6eff372d.78f988","bgColor":"#E6E0F8","wires":[["MinHammond_envelope1:0"]]},{"id":"MinHammond_envelope1","type":"AudioEffectEnvelope","name":"env","comment":"","x":410,"y":535,"z":"6eff372d.78f988","bgColor":"#E6E0F8","wires":[["20220307T120440_881Z_fb5:9"]]},{"id":"20220307T120440_881Z_fb5","type":"AudioMixer","name":"mixer","comment":"","inputs":10,"ExtraInputs":0,"RealInputs":10,"x":540,"y":280,"z":"6eff372d.78f988","bgColor":"#E6E0F8","wires":[["MinHammond_Out1:0"]]}],"links":[],"export":true,"isMain":false,"mainNameType":"tabName","mainNameExt":".ino","isAudioMain":false,"generateCppDestructor":false,"extraClassDeclarations":"","settings":{}},{"type":"tab","id":"20220307T120627_405Z_a479","label":"Hammond_x8","nodes":[{"id":"20220307T120721_021Z_29bd","type":"TabOutput","name":"OutL","comment":"","inputs":1,"x":510,"y":195,"z":"20220307T120627_405Z_a479","bgColor":"#cce6ff","wires":[]},{"id":"20220307T122215_132Z_aca6","type":"TabOutput","name":"OutR","comment":"","inputs":1,"x":510,"y":240,"z":"20220307T120627_405Z_a479","bgColor":"#cce6ff","wires":[]},{"id":"20220307T120648_593Z_b118","type":"MinHammond1","name":"minhammond1[8]","arraySize":8,"x":165,"y":215,"z":"20220307T120627_405Z_a479","bgColor":"#CCFFCC","wires":[["20220307T122152_547Z_8a89:0"]]},{"id":"20220307T122152_547Z_8a89","type":"AudioMixerStereo","name":"mixS","comment":"","inputs":1,"ExtraInputs":0,"RealInputs":8,"x":335,"y":215,"z":"20220307T120627_405Z_a479","bgColor":"#E6E0F8","wires":[["20220307T120721_021Z_29bd:0"],["20220307T122215_132Z_aca6:0"]]}],"links":[],"export":true,"isMain":false,"mainNameType":"tabName","mainNameExt":".ino","isAudioMain":false,"generateCppDestructor":false,"extraClassDeclarations":"","settings":{}},{"type":"tab","id":"ea305170.fb2f9","label":"WaveFormVoice","nodes":[{"id":"20220302T113212_611Z_f670","type":"TabInput","name":"freqMod","comment":"","outputs":1,"x":110,"y":283,"z":"ea305170.fb2f9","bgColor":"#cce6ff","wires":[["20220302T113145_789Z_77c0:0"]]},{"id":"WaveFormVoice_Out1","type":"TabOutput","name":"OutA","comment":"","inputs":"","x":570,"y":320,"z":"ea305170.fb2f9","bgColor":"#cce6ff","wires":[]},{"id":"20220302T113218_689Z_d58b","type":"TabInput","name":"shapeMod","comment":"","outputs":1,"x":106,"y":336,"z":"ea305170.fb2f9","bgColor":"#cce6ff","wires":[["20220302T113145_789Z_77c0:1"]]},{"id":"20220304T200046_288Z_b82f","type":"TabOutput","name":"OutW","comment":"","inputs":1,"x":570,"y":380,"z":"ea305170.fb2f9","bgColor":"#cce6ff","wires":[]},{"id":"WaveFormVoice_vars1","type":"Variables","name":"vars","comment":"// Start of variables\nprivate:\n    bool isNew;\n    static short wave_type[4];\npublic:\n// End of variables\n","x":290,"y":105,"z":"ea305170.fb2f9","bgColor":"#DDFFBB","wires":[]},{"id":"WaveFormVoice_constructor code1","type":"ConstructorCode","name":"constructor code","comment":"      env.attack(129.2);\r\n      env.hold(2.1);\r\n      env.decay(181.4);\r\n      env.sustain(0.3);\r\n      env.release(284.5);\r\n      amp.gain(0.5);","x":283,"y":151,"z":"ea305170.fb2f9","bgColor":"#DDFFBB","wires":[]},{"id":"WaveFormVoice_code1","type":"Function","name":"WaveFormVoice_code","comment":"// Start of voice class manually-entered code\r\n    void noteOn(int MIDInote, int MIDIvel, int chan=-1){};\r\n    void noteOn(float freq, float vel, int chan=-1)\r\n    {\r\n      if (isNew)\r\n        wave.begin(vel,freq,(chan<0)?WAVEFORM_SINE:(wave_type[chan&3]));\r\n      else\r\n      {\r\n        wave.amplitude(vel);\r\n        wave.frequency(freq);\r\n      }\r\n      env.noteOn();\r\n      isNew = false;\r\n    }\r\n\r\n    void noteOff(void){env.noteOff();};\r\n    bool isPlaying(void) {return env.isActive();};","x":290,"y":220,"z":"ea305170.fb2f9","bgColor":"#DDFFBB","wires":[]},{"id":"20220302T113145_789Z_77c0","type":"AudioSynthWaveformModulated","name":"wave","comment":"","x":285,"y":320,"z":"ea305170.fb2f9","bgColor":"#E6E0F8","wires":[["WaveFormVoice_amp1:0","20220306T195324_547Z_8d5d:0"]]},{"id":"20220307T115003_039Z_3cc2","type":"EndOfFileCode","name":"eof code","comment":"/* static */ short WaveFormVoice::wave_type[] = \r\n{\r\n    WAVEFORM_SINE,\r\n    WAVEFORM_SQUARE,\r\n    WAVEFORM_SAWTOOTH,\r\n    WAVEFORM_TRIANGLE \r\n};","x":289,"y":435,"z":"ea305170.fb2f9","bgColor":"#DDFFBB","wires":[]},{"id":"WaveFormVoice_destructor code1","type":"DestructorCode","name":"destructor code","comment":"// extra code for destructor","x":480,"y":148,"z":"ea305170.fb2f9","bgColor":"#DDFFBB","wires":[]},{"id":"WaveFormVoice_amp1","type":"AudioAmplifier","name":"amp","comment":"","x":460,"y":320,"z":"ea305170.fb2f9","bgColor":"#E6E0F8","wires":[["WaveFormVoice_Out1:0"]]},{"id":"20220306T195324_547Z_8d5d","type":"AudioEffectEnvelope","name":"env","comment":"","x":460,"y":380,"z":"ea305170.fb2f9","bgColor":"#E6E0F8","wires":[["20220304T200046_288Z_b82f:0"]]}],"links":[],"export":true,"isMain":false,"mainNameType":"tabName","mainNameExt":".ino","isAudioMain":false,"generateCppDestructor":false,"extraClassDeclarations":"","settings":{}}],"nodeAddons":{}
}
And here are two different errors that show in the console when I try to connect mixerI2S to i2s - one occurs when I try to make the connection immediately after an F5 refresh, the second when I try again.
Code:
Uncaught TypeError: Cannot set properties of undefined (setting 'tabOut')
    at setLinkInfo (nodes.js:2602:23)
    at Object.addLink (nodes.js:565:13)
    at portMouseUp (view.js:2131:23)
    at SVGRectElement.<anonymous> (view.js:2818:58)
    at SVGRectElement.i [as __onmouseup] (d3.v3.min.js:515:23)
setLinkInfo @ nodes.js:2602
addLink @ nodes.js:565
portMouseUp @ view.js:2131
(anonymous) @ view.js:2818
i @ d3.v3.min.js:515



view.js:3042 Uncaught TypeError: Cannot read properties of undefined (reading 'isBus')
    at redraw_link_notation (view.js:3042:20)
    at SVGPathElement.redraw_link (view.js:3057:9)
    at SVGPathElement.a (d3.v3.min.js:413:23)
    at d3.v3.min.js:3959:15
    at Tn (d3.v3.min.js:531:81)
    at Array.Ma.each (d3.v3.min.js:3958:16)
    at Array.Ma.attr (d3.v3.min.js:3808:21)
    at redraw_links (view.js:3037:9)
    at Object.setSelectedWorkspace [as onchange] (view.js:725:9)
    at activateTab (tabs.js:357:29)
redraw_link_notation @ view.js:3042
redraw_link @ view.js:3057
a @ d3.v3.min.js:413
(anonymous) @ d3.v3.min.js:3959
Tn @ d3.v3.min.js:531
Ma.each @ d3.v3.min.js:3958
Ma.attr @ d3.v3.min.js:3808
redraw_links @ view.js:3037
setSelectedWorkspace @ view.js:725
activateTab @ tabs.js:357
onTabClick @ tabs.js:300
dispatch @ jquery-1.9.1.js:3074
elemData.handle @ jquery-1.9.1.js:2750
This is with your current master branch - my feature branch is working OK.
 
And here are two different errors that show in the console when I try to connect mixerI2S to i2s - one occurs when I try to make the connection immediately after an F5 refresh, the second when I try again.

This is now fixed
It was because I forgot to use the REDLink class when creating new links,
as the REDLink class initializes the info property which both tabOut and isBus belongs to
 
Great, seems to work for me. I've done a PR which restores the ability to export OSC-based classes, seems to be back to where it was before. See PR comment for why the fix was needed...

Note that the latest version of the export code assumes the use of the latest OSCAudio commit, which can generate connection names semi-automatically - given a "base" name, it appends the source and target objects and port numbers. If you forget to update, it will fail to compile :D
 
I've just pushed an update to the OSCAudio repository, which allows for easier maintenance of objects which need constructors with parameters. At the moment this applies to the AudioMixer and AudioMixerStereo objects, which were implemented as special cases but are now (slightly) more general, and AudioEffectDelayExternal, which previously was poorly supported. You can now send something like ("/teensy*/dynamic/crOb", "ssif", "AudioEffectDelayExternal", "delayExt", 3, 2000.0) which will create a 2s delay in a PSRAM on the audio adaptor, or ("/teensy*/dynamic/crOb", "sssif", "AudioEffectDelayExternal", "delay", "/looper/i*", 6, 20000.0) which will create N delay objects of 20s in EXTMEM, in the /looper group. Note these examples need my updated AudioEffectDelayExternal, but it should work OK if you only have a 1.5s 23LC1024 on the audio adaptor.
 
I've just realised that when live-editing an AudioEffectDelayExt object, e.g. changing the maxDelay[ms] parameter, GUI++ will need to destroy and re-create it, in a similar manner to what happens when AudioMixer and AudioMixerStereo objects have the number of inputs changed.
 
Back
Top