Open Sound Control (OSC) Teensy Audio Library Implementation

I've lost many of the errors but still one "error: 'setMaxBuffers' was not declared in this scope"
That will be because I'm using Teensyduino 1.56b3 - Paul has rolled one of MarkT's improvements right in ... I think there's something wrong with his scheduler, though to be fair it does look as if the Audio library pull request backlog is getting some attention. Either update to TD1.56b3, or just the AudioPlayQueue files, or comment out the offending line (though it'll come right back on the next pull!)
 
Agree, dual serial +MIDI + audio would be a great addition. Having said that, at this point the source of the OSC packets is entirely programmer-driven, so we can use serial, network, MIDI or anything else we fancy.

Looks like some sort of subscribe option is a good plan, though it has to be borne in mind that most audio objects don't currently support read-back of their internal state. I think the subscribe would need to be told which channel(s) to report on.
 
though it has to be borne in mind that most audio objects don't currently support read-back of their internal state

A standard /subscription would pass the request on to the subscribed clients so no need for read-back. All clients would have to pay close attention if they want to be in sync! :) Meters would work fine as the historical data is relatively irrelevant.

An /info or /update type call looking for the current state or a /subscribe-update call would require each audio library object to support read-back.

Interesting.
 
I'm very slightly struggling to see how to be sure that a subscribed status message doesn't end up getting misinterpreted as a command, but that's mainly because I've not RTFM yet! I will, but I'd really like to get the dynamic construction stuff vaguely working first. It's nearly there - I think.
 
I have now implemented so that event-scripts are executed for the following tasks:

Class Added
Class Renamed
Class Removed
AudioObject Added
AudioObject Renamed
AudioObject Removed
AudioConnection Added
AudioConnection Removed

the scripts are set to defaults which only outputs at the bottom log
but they can easily be replaced by "OSC messages sending" instead
when we have the spec. ready

The scripts editors are at the "settings tab" - OSC

And the functions to send messages are just like in the prev. post
https://forum.pjrc.com/threads/6879...Implementation?p=294846&viewfull=1#post294846
 
this is the output when I'm creating a simple synth
Code:
renamed Workspace fromSheet_1 to Voice
added node In
added node Out
added node waveformMod
added link (In, 0, waveformMod, 0)
added node mixer4
added node envelope1
added link (mixer4, 0, envelope1, 0)
added link (envelope1, 0, Out, 0)
added link (waveformMod, 0, mixer4, 0)
added node waveformMod1
added node waveformMod2
added link (waveformMod1, 0, mixer4, 1)
added link (waveformMod2, 0, mixer4, 2)
added link (In, 0, waveformMod1, 0)
added link (In, 0, waveformMod2, 0)
added node wavetable1
added link (wavetable1, 0, mixer4, 3)
added Workspace Sheet_1
renamed Workspace fromSheet_1 to Synth
added node Voice
added node waveform1
renamed node from waveform1 to lfo
added link (lfo, 0, Voice, 0)
renamed node from Voice to voices[16]
added node mixer1
added link (voices[16], 0, mixer1, 0)
added node pt8211_2
added link (mixer1, 0, pt8211_2, 0)
added link (mixer1, 0, pt8211_2, 1)
 
Phew! Got there! For a given value of there...

OK, the d83b746 (latest) commit can create and destroy connections and a subset of the audio objects, and can use a created connection to connect two audio ports. There should be a way to disconnect while leaving the connection object in existence, but for now that crashes the system... just destroy the object and create a new one, for now.

  • you will need the latest audio core for this to work, as I broke it and didn't notice until today
  • there are a couple of Python demos, one controlling existing objects and the other creating and destroying. Fun may be had running one after the other!
Assuming your Teensy is running the OSCAudioTesting demo:
  • /teensy1/dynamic/createObject<Audiotype><name> creates a new audio object of type Audiotype (e.g. AudioSynthWaveform), named name (both parameters are strings)
  • /teensy1/dynamic/createConn<name> creates a new connection object, named name
  • /teensy1/dynamic/destroy<name> destroys the audio or connection object, named name
  • /teensy1/audio/<conn>/connect<src><srcPort><dst><dstPort> uses connection object conn to connect <src> to <dst> using the port numbers given: you can omit the port numbers if they're both 0, though there's probably little point
Various abbreviations are possible: cr*O*, cr*C*, d*, c*, but note there appears to be a bug in the OSC * matching so it sometime needs one more character in the address than you'd expect - see demo Python for things that work!

Now to take a look at the GUI - at last!
 
After a lot of sweat, I have a slider that controls the frequency and a two-octave piano working via OSC! Haven't figured out placing and connecting yet, the data structures available are rather unclear to me right now. Time to put the light out for a bit...
 
where are the SAFE_RELEASE defined
and how do you use it

I did try by defining it before the
Code:
#define SAFE_RELEASE
#include "OSCAudioBase.h"
is that correct?

I'm using platformio
and have completely replaced the whole "cores" folder with
the one from
https://github.com/h4yn0nnym0u5e/cores

and I did also comment out AudioLib missing classes/"function calls"


for all that don't know!
to get the latest dynamic updates branch use this command:
Code:
git clone -b feature/Audio/dynamic-updates https://github.com/h4yn0nnym0u5e/cores.git

I did create a compilable example using VSCODE + platformio
here it is
https://github.com/manicken/OSCdynamicTest
 
no wait
it is really existing in that latest branch
did find it just now by Agent ransack
it's some kind of macro
found in AudioStream.h
Code:
#define SAFE_RELEASE(...) __disable_irq(); active = false; release(__VA_ARGS__)
#define SAFE_RELEASE_MANY(n,...) __disable_irq(); active = false; {audio_block_t* blx[]={__VA_ARGS__}; release(blx,n);}
 
I have now tested it
with the GUI
only at the moment with buttons
the current state of that GUI
is in DesignToolGui.json
@
https://github.com/manicken/OSCdynamicTest

To be able to receive OSCBundles
I changed the example code to receive in bundles
this also takes care of simple messages,
so special treatment is not needed

note. the oscBundle.route function don't work
so I did it "externally" instead

the current state of the loop() code
Code:
void loop()
{
  OSCBundle  bundle;
  OSCMessage *msg;
  int msgLen;
  int msgCount;
  char prt[200];
  
  while (!HWSERIAL.endofPacket())
  {
    
    msgLen = HWSERIAL.available();
    while (msgLen--)
    {
      char c = HWSERIAL.read();
      bundle.fill((uint8_t) c);
    }
  }
  Serial.println();

  if (!bundle.hasError())
  {
    msgCount = bundle.size();
    for (int i = 0; i < msgCount; i++) {
      msg = bundle.getOSCMessage(i);
      msg->getAddress(prt);
      Serial.println(prt);
      Serial.flush();
      msg->route("/teensy*/audio*",routeAudio); // see if this object can use the message
      msg->route("/teensy*/dynamic*",routeDynamic); // see if this object can use the message
      
    }
    Serial.println("---------------------");
    listObjects();
    Serial.println("=====================");
  }
}
 
@Jonathan
Fantastic work with dynamic connections
And now dynamic objects
It is like a dream come true, now we don't need to compile/upload for every little change of the design.
 
Don't think there is any need for reusing AudioConnections as the connections cannot be named in the tool anyway, and if they could it would not make any sense as when a wire is rewired the old one must first be deleted.
But for other applications it can matter because then the routing can be changed without first removing the "wire"
 
I look forward to catching up with these amazing developments next week as I wind up my work for the year. I have noted the issues observed with surprising pattern matching results in the OSC library and will take a close look at that soon. At some point we had unit tests so I should be able to track down the regression.
 
@Jonathan
Fantastic work with dynamic connections
And now dynamic objects
It is like a dream come true, now we don't need to compile/upload for every little change of the design.
Thanks - it just needed this thread to make it useful! And couldn't have done it without OSC and your enhanced GUI, that's for sure...

I'm assuming from your previous posts you've worked out the SAFE_RELEASE thing. I was just being lazy using a macro I happen to know I've defined [only] in the dynamic audio library, but I should really create something more meaningful like AUDIO_LIBRARY_DYNAMIC.

Don't think there is any need for reusing AudioConnections as the connections cannot be named in the tool anyway, and if they could it would not make any sense as when a wire is rewired the old one must first be deleted.
But for other applications it can matter because then the routing can be changed without first removing the "wire"
True, but it annoys me that it's not working - could be a sign of an underlying bug, too. The GUI will need to keep track of connections by name so it knows which ones to delete, unless I change the audio library to delete connections when the object is deleted. But the GUI will still have to be able to request deletion of just a connection...

I look forward to catching up with these amazing developments next week as I wind up my work for the year. I have noted the issues observed with surprising pattern matching results in the OSC library and will take a close look at that soon. At some point we had unit tests so I should be able to track down the regression.
Excellent, thanks Adrian. Pesky day jobs, eh? Looking at the github code, I have my suspicions of OSCmatch.c line 113, which appears to refuse to match the * if there's nothing left of the address. But equally, I'm unfamiliar with OSC so I could easily be mistaking a feature for a bug!

Cheers

Jonathan
 
Here are two ways of auto-naming the connections
1. "<sourcename><sourceport><targetname><targetport>"
2. "ac<number that increments for every new connection>"

The number increments are just for the current instance of the Tool
So when the Tool "restarts" the numbering restarts from zero.
The numbering makes short names but they are not remembered in the tool yet.

The first naming scheme always uses the same names and ports so that don't require any saving of connection names.
 
Here are two ways of auto-naming the connections
1. "<sourcename><sourceport><targetname><targetport>"
2. "ac<number that increments for every new connection>"

The number increments are just for the current instance of the Tool
So when the Tool "restarts" the numbering restarts from zero.
The numbering makes short names but they are not remembered in the tool yet.

The first naming scheme always uses the same names and ports so that don't require any saving of connection names.
Either of those makes sense, and to an extent it doesn't matter to the OSCAudio library. If there is a possibility of re-starting the GUI and rebuilding the design based on information retrieved from the Teensy, scheme 1 makes the GUI programmer's life easier. However, it's entirely possible to add a function somewhere to get the source/destination information from the connection objects. That's probably a good idea anyway, to make it possible to mirror system activity on a remote interface, as suggested by @JayShoe in #40. We can't necessarily rely on every device having seen all messages...
 
@adrianfreed I'm very interested in hearing your thoughts on.

1) Synchronization between server<->client and keeping multiple clients synchronized.
2) Sending/Receiving VU Meter data from Server->Client to display meters and flags on the client application (peak, rms, fft256, fft1024, tone, notefreq).

If you had any input on standards and common configurations I'd love to hear. In the meantime, I'm looking into other application configurations and studying them.
 
@h4yn0nnym0u5e
can you make a rename "name" function to the OSCAudioBase
as you clearly know c++ a lot better that me
 
or is this ok?
Code:
void rename(const char* newName)
{
    if (NULL == newName) return;
    
    Serial.printf("Renamed %s to %s\n\n",name,newName);
    free(name);
    nameLen = strlen(newName);
    name = (char*) malloc(nameLen+3); // include space for // and null terminator
    if (NULL != name)
    {
      name[0] = '/'; // for routing
      strcpy(name+1,newName);
    }
}
 
or is this ok?
Code:
void rename(const char* newName)
{
    if (NULL == newName) return;
    
    Serial.printf("Renamed %s to %s\n\n",name,newName);
    free(name);
    nameLen = strlen(newName);
    name = (char*) malloc(nameLen+3); // include space for // and null terminator
    if (NULL != name)
    {
      name[0] = '/'; // for routing
      strcpy(name+1,newName);
    }
}

Something like that, yes. I might get paranoid and check for name being NULL before trying to free it, and making a function so the original name setting and the renaming both use the exact same code. But that will do very well until I can get to my home PC!
 
I think it's best if you also check for empty strings @
OSCAudioBase::createObject
So that an object is not created without any name or do not contain any whitespace, you could also get all in and trim any whitespace.
Or if a whitespace exists inside a name it automatically is translated into a _ (underline)
 
think these will do it
as we are actually doing it the C way
Code:
char *ltrim(char *s)
{
    while(isspace(*s)) s++;
    return s;
}

char *rtrim(char *s)
{
    char* back = s + strlen(s);
    while(isspace(*--back));
    *(back+1) = '\0';
    return s;
}

char *trim(char *s)
{
    return rtrim(ltrim(s)); 
}
found them here
https://stackoverflow.com/questions/656542/trim-a-string-in-c

I did also test them
here is the new code for
createObject and createConnection
Code:
void OSCAudioBase::createObject(OSCMessage& msg, int addressOffset)
{
    char* name;
    char* typ;
    
    void* pNewObj = NULL;
    name = (char*) malloc(50);
    char* nameOrigin = name; // so that free can be used later (as the trim function changes the start address)
    typ = (char*) malloc(50);

    msg.getString(0,typ,50);
    msg.getString(1,name,50);
    
    //Serial.printf("createObject(%s,%s)\n",typ,name);
    //dbgPrt(msg,addressOffset);
    name = trim(name);
    replaceWhiteSpace(name, '_');
    Serial.printf("createObject(%s,%s)\n",typ,name);

    if (strlen(name) == 0) Serial.println("empty name"); // don't allow any kind of "empty" name
    else if (NULL != find(name)) Serial.println("duplicate"); // don't allow duplicate name
#define OSC_CLASS(a,o) else if (0 == strcmp(#a,typ)) pNewObj = new o(name);
    OSC_AUDIO_CLASSES // massive inefficient macro expansion to create object of required type
#undef OSC_CLASS
    
    if (NULL != pNewObj)
    {
        Serial.printf("Created %s as a new %s at %08X\n",name, typ, (uint32_t) pNewObj);
    }
    //address:
    //Serial.printf("Address free(name) in memory: %p\n", &&address);
    free(nameOrigin);
    free(typ);
}

//============================== OSCAudioConnection =====================================================
/**
 *    Create a new [OSC]AudioConnection object.
 */
void OSCAudioBase::createConnection(OSCMessage& msg, int addressOffset)
{
    char* name;
    name = (char*) malloc(50);
    char* nameOrigin = name; // so that free can be used later (as the trim function changes the start address)

    //dbgPrt(msg,addressOffset);
    msg.getString(0,name,50);
    name = trim(name);
    replaceWhiteSpace(name, '_');
    Serial.printf("createConnection(%s)\n",name);
    if (strlen(name) == 0) Serial.println("empty name"); // don't allow any kind of "empty" name
    else {
        OSCAudioConnection* pNewConn = new OSCAudioConnection(name);
        (void) pNewConn;
        Serial.printf("Created at: 0x%08X\n",(uint32_t) pNewConn);
    }
    free(nameOrigin);
}

and I did put the following in the OSCAudioBase.h OSCAudioBase class
Code:
static char *ltrim(char *s)
{
    while(isspace(*s)) s++;
    return s;
}

static char *rtrim(char *s)
{
    char* back = s + strlen(s);
    while(isspace(*--back));
    *(back+1) = '\0';
    return s;
}

static char *trim(char *s)
{
    return rtrim(ltrim(s)); 
}
static void replaceWhiteSpace(char *s, char replace)
{
    while (*s) {
        if (isspace(*s)) *s = replace;
        s++;
    }
}
 
Back
Top