Open Sound Control (OSC) Teensy Audio Library Implementation

While I think, and back on topic a bit more (!), do we want to think about some sort of /subscribe capability? Could the GUI++ be set up to generate those (as defined by the user, of course), and then display the results? I suspect so, given your calculator demo. I'm thinking of allowing for e.g. a CPU%, free memory, audio block usage etc. sort of overlay.
 
ok when I tried to add a picture the page froze and whole prev. message got lost :mad:

here is a summary

Think I should also have a option that defines the edit dialog structure it in the node def. or when I think about this further there should not be needing any such structure at all, because it's kind of already defined in the def. Structure.

turns out I already did that
here is the json for that
(I did comment it prev. note comments not possible in JSON)
Code:
{
    "defaults": { // order of items matter for editor
        "name": {
            "type": "c_cpp_name"
        },
        "id": {
            "noEdit": "" <- this means not edit of value
        },
        "comment": {},
        "color": {
            "editor": { <- customize editor
                "type": "color" <- editor type (only text & color available)
            },
            "value": "#E6E0F8"
        },
        "inputs": {
            "value": 1, <- this only define the default value, but works for dynamic inputs
            "maxval": 255,
            "minval": 1,
            "type": "int"
        }
    },
    "editor": "autogen", <- this defines that the editor should be autogenerated from the above defaults, note. the index.html editor code is overriden
    "shortName": "mixerStereo",
    "inputs": 1, <- static real def.
    "outputs": 2, <- static real def.
    "category": "mixer",
    "color": "#E6E0F8",
    "icon": "arrow-in.png",
    "help": "this is a <b>Stereo</b> mixer"
}

also some proof of concept stuff (not yet implemented)
Tab I/O can now be adjustable to allow "bus" signals
also there is suitable bus joiner/splitter objects
to make Stereo signals easier to manage in the tool:

StereoAndQuadObject.png

StereoAndQuadObject_main.png

@h4yn0nnym0u5e
do you have the /subscribe command implemented?
 
this is a response to
https://forum.pjrc.com/threads/6879...Implementation?p=297139&viewfull=1#post297139
WithoutBusLinks.png

Here is your example using "bus" links
(the BusJoin and BusSplit is just a demonstration of a future feature)

BusLinksRealUse.png

By using bus links it's much easier to determine pairs when two lines go from a stereo array source to a stereo mixer destination.

the result would otherwise be
WithoutBusLinks_expanded.png

instead of (by using bus links)
BusLinksRealUse_expanded.png

back to topic little more
I hope that I can use the same code to generate the link connections
for the OSC messages
that I use for the code exports
Will see tomorrow.
 
No, I don't have /subscribe even started yet! It just occurred to me that it's present in some implementations of OSC, and would probably be useful. However, like /fs it may not directly be part of OSCaudio. There's also the consideration that the audio objects rarely have much support for reading their current status, and it would be a fairly big job to implement it: there are 86 different objects providing about 160 control and 260 status functions...

Your bus links look good - can you actually colour the connections differently depending on their type, or is that just done for your images? I can see it being very useful.

I've just pushed updates to OSCAudio and audio which implement the AudioMixerStereo and expose its functionality to OSC. (Note that I've merged the groups feature branch back into trunk for OSCAudio, and I'm now working in trunk unless we come up with a radical change which needs a branch to develop on.) The only real change is the addition of AudioMixerStereo/setPanLaw(float), which varies how the LR levels change as pan() and balance() go from -1.0 to +1.0. I've found values from 0.71 down to 0.01 usable (default is 0.22), with my preference being for lower values, but I think that'll be very user-dependent. It's also quite subtle, but it takes immediate effect so it's fairly easy to play with. The setting is per-mixer.
 
Yes the connections are automatically colored, if they connect to either a class containing a bus tab IO (a tab IO that have it's IO count set to anything larger than 1)
I have defined two global styles in css for both the wire border and the actual wire, so it can easily be changed.
(link_line_bus and link_outline_bus)

However the wires can also be individually changed by code, I did that before creating the styles.

I'm not really convinced that is how bus links should look like, but
It will do for the moment, maybe it could be user customized.

I did a script for a UI_ScriptButton that changes the styles
Code:
[{"id":"Main_scriptBtn1","type":"UI_ScriptButton","name":"changeBusStyle","tag":"","comment":"var style = document.createElement('style');\r\nstyle.type = 'text/css';\r\ndocument.getElementsByTagName('head')[0].appendChild(style);\r\n\r\n//here is the magic.\r\nstyle.innerHTML = '.link_line_bus {'+\r\n    'stroke: #7f7f7f;'+\r\n    'stroke-width: 6;'+\r\n    'fill: none;'+\r\n    'pointer-events: none;'+\r\n    'stroke-dasharray: 10, 5;'+\r\n  '}'+\r\n  '.link_outline_bus {'+\r\n    'stroke:#aef2ff;'+\r\n    'stroke-width: 8;'+\r\n    'cursor: crosshair;'+\r\n    'fill: none;'+\r\n    'pointer-events: none;'+\r\n  '}';","w":117,"h":30,"textSize":14,"nodes":[],"x":346.5,"y":369,"z":"Main","bgColor":"#DDFFBB","wires":[]}
]
 
Last edited:
While I think, and back on topic a bit more (!), do we want to think about some sort of /subscribe capability? Could the GUI++ be set up to generate those (as defined by the user, of course), and then display the results? I suspect so, given your calculator demo. I'm thinking of allowing for e.g. a CPU%, free memory, audio block usage etc. sort of overlay.

I brought up the /subscribe functionality only as one way to receive the data, but I haven't confirmed that is the only or best way yet. I don't know a ton about OSC so I'm still learning.

I checked Reaper, Ableton, and some other DAWs to see how they are doing it. Here is the configuration window for the Reaper DAW.

reaper_control_surface_settings.png

In this instance Reaper would be in the same space as the Teensy. It's what is being controlled. Notice the "local IP" is greyed out because that's what the current address is for Reaper (it's on my desktop PC at that IP). But then there is a "Device Port", this port is where the controller is, it's where the GUI is. We don't really have any networking setup for Teensy OSC control yet, only serial. So this explanation is just so I can start to grasp exactly what's going on.

It seems to me that the most standard way to do this would be to setup the reverse of what you've setup now. Currently the Teensy is Listening (aka server), it accepts commands and responds if needed. For proper implementation within standard practice (undefined in the OSC definition) we would want the teensy to add Send (aka client) functionality; I believe.

The Teensy would be a client that initiates commands to the GUI (server). It can start spitting out as much data as it wants to the server (GUI or otherwise), but unless the GUI knows what to do with the data, it is speaking to deaf ears. So we could theoretically just start sending all the data we have to a serial port. (IE Send CPU/memory/block data to an address /GUI1, /GUI2, etc) but this would create a lot of traffic. Alternatively, we could have the GUI tell the Teensy what to send, where, at what interval, and for how long. Then the GUI could also tell the Teensy to stop sending. This is somewhat like the subscribe concept, maybe a little more advanced.

Steps to set it all up (potentially).

1. Configure the Teensy client by communicating with its server... In this step we tell the Teensy what data it should start sending, where it should send the data, at what interval, and for how long. Doing this could mean that the Teensy may send data to multiple clients too at different ports). Just each client would need to subscribe and tell the Teensy where it is. IE /teensy1/rms1/read/subscribe/<port> <OSC Address> <interval> <Duration>.
2. The Teensy then begins sending the data to the specified port at the specified interval and duration (or infinitely). /GUI1/rms1/read/<data>... and/or /controlSurface2/rms1/read/<data>...
3. The GUI then receives the information and uses it. Note, the GUI would be a client (which is not implemented at the moment, if I'm not mistaken).
4. The timeframe can expire and the data will stop, or the GUI can "unsubscribe" /teensy1/rms/read/unsubscribe or /teensy1/rms/read/subscribe/stop

No, I don't have /subscribe even started yet! It just occurred to me that it's present in some implementations of OSC, and would probably be useful. However, like /fs it may not directly be part of OSCaudio. There's also the consideration that the audio objects rarely have much support for reading their current status, and it would be a fairly big job to implement it: there are 86 different objects providing about 160 control and 260 status functions...

I agree, not all functions require a "send from Teensy to GUI" command. Although setting up the scaffold for it in the future might be quite nice. Setup the scaffold, and then in the future other objects can respond to some request. It seems to me that any of the "Analyze" functions would be a prime target for this. (peak, rms, fft256, fft1024, tone, notefreq, print). Then in the future, if the other objects wanted to implement read functionality they can. In the meantime, the /subscribe command would just be ignored. (IE /teensy1/mixer1/subscribe does nothing, but exists maybe still?).

Does my analysis make sense on this? I hope maybe it's becoming clearer on how this could work. My ultimate goal is simply to get the meter data in the GUI (for monitoring audio levels, and for eye candy).

It has also dawned on me that maybe making the Teensy an OSC client can help if/when the Teensy wants to become a control surface in itself! Picture two Teensy's. One is the audio processor. The other is the control surface. The control surface would need to be an OSC client so it can control the audio processor. Then the audio processor is also a client, sending audio data to the control surface for displaying a Meter on LEDs... How cool would that be! It sure would be nice if your work would also help move in that direction so that the Teensy can be configured as a client and send control data to another device.
 
Last edited:
I agree it would be possible to make the Teensy an OSC client, and equally possible to use Ethernet as the transport stream. I've not touched Ethernet on the Teensy as yet, though I do have an adaptor kit waiting for me to pay it some attention.

Having said that, the concept is pretty much the exact opposite of your original post #1! And implementing an OSC client is just a Small Matter Of Programming, which would work equally well with the original static audio library. Using the Audio Design Tool as the OSC server / display is probably not the best choice, though a few well-chosen readouts would undoubtedly be helpful, e.g. being able to show memory use and CPU load as a design grows. Far better to have your concept of control surfaces on tablets, as I think you posted some time back. Or maybe as tiny standalone applications which you can tile together on a PC screen.

At the moment I'm prioritising any support needed for @manicksan to get the GUI++ working to his / our satisfaction, and a secondary goal of seeing if any of this is useful on a Teensy 3.x. For the latter, I need to implement the dynamic audio library, as it's only on 4.x at the moment.

Interesting that Reaper supports OSC, even if only via Ethernet. I must take a look at that, once I've got Ethernet going.
 
I agree it would be possible to make the Teensy an OSC client, and equally possible to use Ethernet as the transport stream.

Yeah, that's the ideal solution in a nutshell. Ethernet or an ESP32... The ESP32 would have to work as an OSC bridge (two ways for server/client). :)

Having said that, the concept is pretty much the exact opposite of your original post #1! And implementing an OSC client is just a Small Matter Of Programming, which would work equally well with the original static audio library.

It's worth mentioning my appreciation. You and manicksan have contributed dearly to the Audio Library with your work on this. Thank you for your hard work.

Using the Audio Design Tool as the OSC server / display is probably not the best choice

It does feel funny doesn't it. But I think that's how two way communication works with OSC. To get the meter data the control surface listens to what the device sends out to the controller, usually send to some address. What we discussed previously is more of a request/response functionality. I saw that functionality in the X32 but nowhere else.

I wonder, CAN a browser be an OSC server? Or would it have to be a Request/Response situation only?
 
I wonder, CAN a browser be an OSC server? Or would it have to be a Request/Response situation only?

only supported by Opera Unite in the Opera browser

https://en.wikipedia.org/wiki/List_of_server-side_JavaScript_implementations


The only thing that all modern browsers support is WebSockets but that is client only.

Updates on the Tool++:
* bug fixes + improvements
https://forum.pjrc.com/threads/65740-Audio-System-Design-Tool-update?p=297453&viewfull=1#post297453
* code restructure

OSC group export:
think I have the generation of objects working now
it support the following structure
Code:
{"version":1,"settings":{"arduino":{"ProjectName":"GroupExampleStereo2","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","isOSCmain":true,"generateCppDestructor":false,"extraClassDeclarations":"","settings":{"workspaceBgColor":"#EDFFDF","scaleFactor":0.8,"showGridHminor":false,"showGridHmajor":false,"showGridVminor":false,"showGridVmajor":false,"snapToGridHsize":1,"snapToGridVsize":1,"useCenterBasedPositions":false},"nodes":[{"id":"Main_waveform1","type":"AudioSynthWaveform","name":"LFO","comment":"","x":5,"y":155,"z":"3629fcd9.ccc604","bgColor":"#E6E0F8","wires":[["Main_MonoSynth1:0","Main_MonoSynth3:0","Main_waveformMod1:0","Main_waveformMod2:0"]]},{"id":"Main_MonoSynth3","type":"MonoSynth","name":"MonoSynthC","x":160,"y":20,"z":"3629fcd9.ccc604","bgColor":"#CCFFCC","wires":[["Main_mixer3:0"]]},{"id":"Main_MonoSynth1","type":"MonoSynth","name":"MonoSynthA[6]","x":165,"y":80,"z":"3629fcd9.ccc604","bgColor":"#CCFFCC","wires":[["Main_mixer1:0"]]},{"id":"Main_waveformMod1","type":"AudioSynthWaveformModulated","name":"wfmA[3]","comment":"","x":145,"y":225,"z":"3629fcd9.ccc604","bgColor":"#E6E0F8","wires":[["Main_mixer4:0"]]},{"id":"Main_waveformMod2","type":"AudioSynthWaveformModulated","name":"wfmB[3]","comment":"","x":145,"y":265,"z":"3629fcd9.ccc604","bgColor":"#E6E0F8","wires":[["Main_mixer4:1"]]},{"id":"Main_mixer4","type":"AudioMixer","name":"mixer[3]","comment":"","inputs":2,"x":290,"y":245,"z":"3629fcd9.ccc604","bgColor":"#E6E0F8","wires":[["Main_mixer2:0"]]},{"id":"Main_mixer1","type":"AudioMixer","name":"mixerL","comment":"","inputs":1,"x":360,"y":80,"z":"3629fcd9.ccc604","bgColor":"#E6E0F8","wires":[["Main_mixer3:1"]]},{"id":"Main_mixer3","type":"AudioMixer","name":"mixerL1","comment":"","inputs":2,"x":488,"y":28,"z":"3629fcd9.ccc604","bgColor":"#E6E0F8","wires":[["Main_i2s1:0"]]},{"id":"Main_mixer2","type":"AudioMixer","name":"mixerR","comment":"","inputs":1,"x":489,"y":246,"z":"3629fcd9.ccc604","bgColor":"#E6E0F8","wires":[["Main_i2s1:1"]]},{"id":"Main_sgtl5000_1","type":"AudioControlSGTL5000","name":"sgtl5000","comment":"","x":685,"y":199,"z":"3629fcd9.ccc604","bgColor":"#E6E0F8","wires":[]},{"id":"Main_i2s1","type":"AudioOutputI2S","name":"i2s","comment":"","x":721.1111145019531,"y":132.77777290344238,"z":"3629fcd9.ccc604","bgColor":"#E6E0F8","wires":[]}]},{"type":"tab","id":"de8d0e25.32ebb","label":"MonoSynth","inputs":0,"outputs":0,"export":true,"isMain":false,"mainNameType":"tabName","mainNameExt":".ino","isOSCmain":false,"generateCppDestructor":false,"extraClassDeclarations":"","settings":{"workspaceBgColor":"#EDFFDF","showGridHminor":false,"showGridHmajor":false,"showGridVminor":false,"showGridVmajor":false,"useCenterBasedPositions":false},"nodes":[{"id":"MonoSynth_In1","type":"TabInput","name":"In","comment":"","outputs":1,"x":105,"y":80,"z":"de8d0e25.32ebb","bgColor":"#cce6ff","wires":[["MonoSynth_waveformMod1:0"]]},{"id":"MonoSynth_Out1","type":"TabOutput","name":"Out1","comment":"","inputs":1,"x":620,"y":85,"z":"de8d0e25.32ebb","bgColor":"#cce6ff","wires":[]},{"id":"MonoSynth_waveformMod1","type":"AudioSynthWaveformModulated","name":"wav[4]","comment":"","x":210,"y":85,"z":"de8d0e25.32ebb","bgColor":"#E6E0F8","wires":[["MonoSynth_mixer1:0"]]},{"id":"MonoSynth_mixer1","type":"AudioMixer","name":"mixer","comment":"","inputs":1,"x":325,"y":85,"z":"de8d0e25.32ebb","bgColor":"#E6E0F8","wires":[["MonoSynth_envelope1:0"]]},{"id":"MonoSynth_envelope1","type":"AudioEffectEnvelope","name":"env","comment":"","x":425,"y":85,"z":"de8d0e25.32ebb","bgColor":"#E6E0F8","wires":[["MonoSynth_Eq1:0"]]},{"id":"MonoSynth_Eq1","type":"Eq","name":"eq","x":530,"y":85,"z":"de8d0e25.32ebb","bgColor":"#ccffcc","wires":[["MonoSynth_Out1:0"]]}]},{"type":"tab","id":"40738f40.4e3d7","label":"Eq","inputs":0,"outputs":0,"export":true,"isMain":false,"mainNameType":"tabName","mainNameExt":".ino","isOSCmain":false,"generateCppDestructor":false,"extraClassDeclarations":"","settings":{"workspaceBgColor":"#EDFFDF","showGridHminor":false,"showGridHmajor":false,"showGridVminor":false,"showGridVmajor":false,"useCenterBasedPositions":false},"nodes":[{"id":"SubObject_In1","type":"TabInput","name":"In","comment":"","outputs":1,"x":110,"y":80,"z":"40738f40.4e3d7","bgColor":"#cce6ff","wires":[["SubObject_filter1:0"]]},{"id":"SubObject_Out1","type":"TabOutput","name":"Out","comment":"","inputs":1,"x":415,"y":85,"z":"40738f40.4e3d7","bgColor":"#cce6ff","wires":[]},{"id":"SubObject_filter1","type":"AudioFilterStateVariable","name":"filter[3]","comment":"","x":200,"y":80,"z":"40738f40.4e3d7","bgColor":"#E6E0F8","wires":[[],["SubObject_mixer1:0"],[]]},{"id":"SubObject_mixer1","type":"AudioMixer","name":"mixer","comment":"","inputs":1,"x":310,"y":85,"z":"40738f40.4e3d7","bgColor":"#E6E0F8","wires":[["SubObject_Out1:0"]]}]}],"nodeAddons":{"h4yn0nnyNodes":{"isAddon":true,"label":"h4yn0nnyNodes","description":"","credits":"h4yn0nnym0u5e","homepage":"","url":"https://api.github.com/repos/h4yn0nnym0u5e","types":{"AudioEffectExpEnvelope":{"defaults":{"name":{"type":"c_cpp_name"},"comment":{},"color":{"value":"#c6c0d8"}},"shortName":"expEnv","inputs":1,"outputs":1,"inputTypes":{"0":"i16"},"outputTypes":{"0":"i16"},"category":"effect","help":"","color":"#c6c0d8","icon":"arrow-in.png"},"AudioMixerStereo":{"defaults":{"name":{"type":"c_cpp_name"},"id":{},"inputs":{"value":1,"maxval":255,"minval":1,"type":"int"},"comment":{},"color":{"value":"#E6E0F8"}},"dynInputs":"","shortName":"mixerS","inputs":1,"outputs":2,"category":"mixer","color":"#E6E0F8","icon":"arrow-in.png"}}}}
}
note. that it support arrays of dynmixers

the audio connections part is still not finished
I think I have to do that part from scratch
as the old version (c++ export) was very clumsy

the new version could also be used to export a class based design
to a classic flat design
as that share the same structure as required by the OSC lib

another improvement of the Tool++ (not really visible to the user)
is that it's always sorting the objects in the background now
1. block of all ui objects (the ui objects are not sorted in that block, to preserve draw order)
2. block of all TabInputs and TabOutputs (only sorted by y pos, top to bottom)
3. block of all other objects (sorted in workspace columns top to bottom)

this above makes it much easier to find the right TabInputs & TabOutputs

when the osc group export is done it can also be used for non group designs and therefore the osc simple export is obsolete.

another improvement is easier to read osc export dialog "messages"
 
Last edited:
The only thing that all modern browsers support is WebSockets but that is client only.

I would then say that full Teensy OSC-Client support doesn't make much sense for the GUI tool. The only real option is for the request/response format. Yes, the client support is necessary for something like TouchOSC.

a few well-chosen readouts would undoubtedly be helpful, e.g. being able to show memory use and CPU load as a design grows

These are really just "extra" OSC commands, adding some non-audio tool related calls to the OSC address list. This information is outside of the audio library. I guess you'll just want to define them separately from the object list that has been created automatically. It will be low interval, right? It's just a call and a response - or would you have a subscription type method with duration and interval?
 
But as long as the websocket connection is open, data can be sent in both ways.

And if the final system need multiple control surfaces/audio generators to be updated maybe it needs some kind of central hub server that links everything together.

Don't mind the OSC touch control, as it's available for Android as well, do you know about roboremo? (Downside with that is no support for OSC messages, but a server could translate that)
 
this is my version of the mixed fade functionality
no need for any special calculations

in .cpp file
Code:
void AudioMixer::update(void)
{
	audio_block_t *in, *out=NULL;
	unsigned int channel;

	for (channel=0; channel < _ninputs; channel++) {
		
		if (currentMultiplier[channel] != multiplier[channel])
		{
			if (currentMultiplier[channel] < multiplier[channel])
			{
				currentMultiplier[channel] += multUpdateRate; // this could overflow the currentMultiplier datatype, but that would be very rare, as gains often don't get over 1.0 anyway 
				// overflow check
				if (currentMultiplier[channel] > multiplier[channel])
					currentMultiplier[channel] = multiplier[channel]; // stop here
			}
			else /*if (currentMultiplier[channel] > multiplier[channel])*/
			{
				currentMultiplier[channel] -= multUpdateRate; // this could underflow the currentMultiplier datatype, but that would be very rare, as gains often don't get over 1.0 anyway 
				// underflow check
				if (currentMultiplier[channel] < multiplier[channel])
					currentMultiplier[channel] = multiplier[channel]; // stop here
			}
		}
		
		if (NULL != out) {
			in = receiveReadOnly(channel);
			if (in == NULL) continue;
            applyGainThenAdd(out->data, in->data, currentMultiplier[channel]);
			release(in);
		} else {
            out = receiveWritable(channel);
            if (NULL == out) continue;
            int32_t mult = currentMultiplier[channel];
			if (mult == MULTI_UNITYGAIN) continue;
            applyGain(out->data, mult);
		}
	}
	if (NULL == out) return;
    transmit(out);
	release(out);
}

in .h file
Code:
class AudioMixer : public AudioStream
{
#if defined(__ARM_ARCH_7EM__)
public:
	AudioMixer(unsigned char ninputs, audio_block_t **iqueue) : AudioStream(ninputs, iqueue),
    inputQueueArray(iqueue), _ninputs(ninputs)
    {
        //Serial.printf("\nninputs = %d %d\n\n", _ninputs, sizeof(this));
        multiplier = (int32_t*)malloc(_ninputs*4);
		currentMultiplier = (int32_t*)malloc(_ninputs*4);
		for (int i=0; i<_ninputs; i++) {
			multiplier[i] = 65536;
			currentMultiplier[i] = 65536;
		}
	}
    ~AudioMixer()
    {
        free(multiplier);
		free(currentMultiplier);
        free(inputQueueArray);
    }
	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?
		if (fadeRate == 0)
			currentMultiplier[channel] = gain * 65536.0f; // TODO: proper roundoff?
	}
	void fadeRate(int32_t rate)
	{
		if (rate > 32767*65536) rate = 127*127;
		else if (rate < 0) rate = 0;
		multUpdateRate = rate;
		if (multUpdateRate == 0)
		{
			for (int i=0; i<_ninputs; i++)
			{
				currentMultiplier[i] = multiplier[i];
			}
		}
	}
private:
    unsigned char _ninputs;
	int32_t *multiplier;
	int32_t *currentMultiplier;
	int32_t multUpdateRate;
    //audio_block_t *toBeIgnored[1];
	audio_block_t **inputQueueArray;

#elif defined(KINETISL)
public:
	AudioMixer(unsigned char ninputs, audio_block_t **iqueue) : AudioStream(ninputs, iqueue) {
        inputQueueArray = iqueue;
        _ninputs = ninputs;
        multiplier = (int16_t*)malloc(_ninputs);
		currentMultiplier = (int16_t*)malloc(_ninputs*4);
		for (int i=0; i<_ninputs; i++) {
			multiplier[i] = 256;
			currentMultiplier[i] = 256;
		}
	}
    ~AudioMixer()
    {
        free(multiplier);
		free(currentMultiplier);
        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?
		if (multUpdateRate == 0) // if set to zero 
			currentMultiplier[channel] = gain * 256.0f; // TODO: proper roundoff?
	}
	void fadeRate(int32_t rate)
	{
		if (rate > 127*256) rate = 127*256;
		else if (rate < 0) rate = 0;
		
		multUpdateRate = rate;
		if (multUpdateRate == 0)
		{
			for (int i=0; i<_ninputs; i++)
			{
				currentMultiplier[i] = multiplier[i];
			}
		}
	}
private:
	int16_t *multiplier;
	int16_t *currentMultiplier;
	audio_block_t **inputQueueArray;
#endif
};
 
That's fine as far as it goes, but it's going to suffer from "zipper noise" at some fade rates because the multiplier is only changed once per audio update cycle. It really needs updating more often - maybe not every sample, but surely better than once every AUDIO_BLOCK_SAMPLES!

You might want to pull in my updates to your original DynMixer code, as I've spent some time removing the need to maintain two blocks of near-identical code just to support the Teensy LC.
 
It really needs updating more often
Is not the update called 44100 times a second?

I've spent some time removing the need to maintain two blocks of near-identical code just to support the Teensy LC.
I did that as well with the "c++ template mixer code"(now replaced with Tool Built in code),
don't really know why the original mixer code looks that way.

And yes I have already looked at you mixer code, looks good (will use that for the built in code).


OSC group/class export progress:
can now export the following
(not yet support for arrays of classes)

main:
ClassSupport_Main.png

voice (note I have included every possible situation):
ClassSupport_Voice.png

sub (used inside voice):
ClassSupport_Sub.png

I have now created a separate 'tab setting' that selects the
Audio Main 'tab'
so that the Main File (now called C++ Main File) can be set separately
 
Is not the update called 44100 times a second?
No, the update interrupt is called 44100/128 times a second, and processes 128 samples (or technically AUDIO_BLOCK_SAMPLES, which is usually set to 128), so about every 2.9ms.

At the moment I'm finding the OSC export is broken, as it doesn't create the connections. Is there any chance these could be made to work, even in a limited way (e.g. not bothering about array or stereo connections)? I'm exploring the limits of Teensy 3.x, and at the moment all my design uploads have to use Python, and I keep making mistakes! Doesn't have to be on the main development trunk.
 
No, the update interrupt is called 44100/128 times a second, and processes 128 samples (or technically AUDIO_BLOCK_SAMPLES, which is usually set to 128), so about every 2.9ms.

so if I understand this code correct (taken from effect_fade.cpp)
Code:
dir = direction;
for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
    index = pos >> 24;
    val1 = fader_table[index];
    val2 = fader_table[index+1];
    scale = (pos >> 8) & 0xFFFF;
    val2 *= scale;
    val1 *= 0x10000 - scale;
    val = (val1 + val2) >> 16;
    sample = block->data[i];
    sample = (sample * val) >> 15;
    block->data[i] = sample;
    if (dir > 0) {
        // output is increasing
        if (inc < 0xFFFFFFFF - pos) pos += inc;
        else pos = 0xFFFFFFFF;
    } else {
        // output is decreasing
        if (inc < pos) pos -= inc;
        else pos = 0;
    }
}
position = pos;
for example the currentMultiplier[channel] += multUpdateRate;
need to be executed for every sample

that would mean that currentMultiplier need to be updated in the applyGain and applyGainThenAdd respective, instead

At the moment I'm finding the OSC export is broken, as it doesn't create the connections. Is there any chance these could be made to work
can you post a image of your design?
is it because it's missing the leading / of the connections?
strange I have not changed anything in the simple export

You can try the group export it's also working on flat designs.
 
Nothing weird in the design at all. I think it's the OSC library's bizarre "pattern matching", which sort of works some of the time. Looking at the code it's allowed to have a * wildcard in both address and pattern, but it doesn't seem to work the way you'd expect. As there are precisely 3 comments in the entire OSCMatch.cpp we're on to a bit of a loser trying to figure it out. The only thing you might have done is use crOb and crGrp to create objects and groups, but createConnection to create connections. I'd like users to have a few options to use long or short forms for readability or efficiency, but maybe that's not going to be possible.

Too late to do more now.
 
I always felt as if the pattern matching function was for the purposes of selecting multiple mixers, for example. Call /mixer* and get /mixer1, /mixer2, and /mixer3_diff. I envisioned it as a client function not a server function. If I'm not mistaken, you've utilized the wildcards for the server addresses.
 
All the "commands" in the Tool are declared in one place
that means it's very easy to change them.
I took
crOb
crGrp
crCo (which I renamed to createConnection for clarify the debug output in the export dialog)

but now I have invented a kind of comment which I can add to the bundle
that is then shown in the export dialog but removed before sending the OSC data.
comments are just a packet with the address that begins with //
 
Now it's possible to do a design like this
(only in OSC group export)
i.e. arrays of classes and standard objects now possible
(main)
ClassArraysSupport_Main.png
(monoVoice)
ClassArraysSupport_MonoVoice.png
(sub)
ClassSupport_Sub.png

here you can see that it's now possible to put multiple sized items into the mixer
and the mixer in this case would be of size 10
the inputs are added in the following order

0 dcA
1 waveform(0)
2 waveform(1)
3 waveform(2)
4 waveform(3)
5 subsB(0)
6 subsB(1)
7 monovoice(0)
8 monovoice(1)
9 dcB


TODO.
stereo mixer
Junction support
connect array to array:
ArrayToArray.png
 
The stereo mixer works, it is just that the inputs are gonna be put in the following order:
L0 L1 L2 R0 R1 R2
Instead of (expected)
L0 R0 L1 R1 L2 R2
 
I always felt as if the pattern matching function was for the purposes of selecting multiple mixers, for example. Call /mixer* and get /mixer1, /mixer2, and /mixer3_diff. I envisioned it as a client function not a server function. If I'm not mistaken, you've utilized the wildcards for the server addresses.
You're right of course. But looking at the latest code on github there seems to be some sort of intent to provide (limited) wildcards in both. Here's a fragment of code from OSCMatch.c:
Code:
[COLOR="#FF0000"]if(*pattern == '*')[/COLOR]{
	if(!osc_match_star(pattern, address)){
		return 0;
	}
	while(*pattern != '/' && *pattern != '\0'){
		pattern++;
	}
	while(*address != '/' && *address != '\0'){
		address++;
	}
}else[COLOR="#FF0000"] if(*address == '*')[/COLOR]{
	while(*pattern != '/' && *pattern != '\0'){
		pattern++;
	}
	while(*address != '/' && *address != '\0'){
		address++;
	}
}else{
Then I wrote a quick fragment to try various address / pattern combinations, which gave odd results, so I went back to the source code. And found the following:
Code:
// From OSCMessage.cpp
int OSCMessage::match(const  char * pattern, int addr_offset){
	int pattern_offset;
	int address_offset;
	int ret = osc_match([COLOR="#0000CD"]address + addr_offset[/COLOR], [COLOR="#FF8C00"]pattern[/COLOR], &pattern_offset, &address_offset);

// From OSCMatch.c
int osc_match([COLOR="#FF8C00"]const char *pattern[/COLOR], [COLOR="#0000CD"]const char *address[/COLOR], int *pattern_offset, int *address_offset)
Now, just guessing, but that looks suspiciously like a massive bug to me... that's been there since April 2013. I'm going to have a go at swapping those ol' parameters right over to see if it starts to make sense!

EDIT: No , I think I'm wrong. Again. Apparently it makes perfect sense to say the OSC Address Pattern passed in the message is the "pattern", and the thing it matches in the OSC Address Space is the "address". Even if you put the Address Pattern in the OSCMessage using OSCMessage::setAddress(), whose function is to "Set the address of the OSCMessage".

My brain hurts.
 
Last edited:
The stereo mixer works, it is just that the inputs are gonna be put in the following order:
L0 L1 L2 R0 R1 R2
Instead of (expected)
L0 R0 L1 R1 L2 R2
This and the above all sound great. Input order isn't massively important, though I guess it means the user having to know how wide the mixer is ... ah, right ... now I remember ... I implemented getChannels() for just such an occasion! And of course it's easy to make any pan/balance UI objects address the correct channel pairs, just use the balance(chL,chR,position) method.
 
Just pushed an update to OSCAudio, should be more tolerant of some variations we've used on the dynamic engine addressing. It'll also be less efficient, but we can tweak the rules later... I'm currently matching /crOb or /createObject, /crCo or /createConnection, /crGr or /crGrp or /createGroup, deOb or /destroyObject, /renameObject and /clearAll. Using an address pattern in the message means, for example, you can use /cre*Obj* if you want something a bit more readable than /crOb.

Live editing in GUI++ seems to be working OK for me, though I've yet to try the array editing capabilities - that's next!

EDIT: trying the array stuff, and needed to push some changes to allow missing the initial / character off group paths. I think that's reasonable: creating a group at root you can now not pass a parent, or pass either an empty string or "/". Similarly you can create objects in multiple groups by specifying either voice1/i* or /voice1/i*
 
Last edited:
Live edit of array/class is currently not supported.
as I still struggling with the group export

have also implemented the navigator map (taken direct from node-red)
available at the zoom controls corner

I did some changes how the group export behaves
If there is many tabs selected as "Audio Main":
1. If current selected tab is a "Audio Main" then that is used
2. If current selected tab is not a "Audio Main" then the first "Audio Main" in the tab order is used

while I did this I accidentally did
Code:
if (nns.workspaces[wi].id = RED.view.activeWorkspace) {
    mainSelected = wi;
}
instead of
Code:
if (nns.workspaces[wi].id == RED.view.activeWorkspace) {
    mainSelected = wi;
}
this resulted in a huge mess
and my current design got lost into a 25MB json file
good is that I had recently saved a json, so it was not so much loss,
and I could "kind of"(the interface was really slow) look at the old design to make a new one in firefox.

Because of that I have now implemented some safe guards:
1. always making a copy of the workspace when doing exports
2. implemented a save on "page reload" setting @ settings-global-"Auto Download JSON"
this can be a good thing to use while doing huge and important stuff


I have now fixed so that the initial / always are included.
 
Back
Top