Question: VoltageAudioJack.SetValue() best practice

User avatar
Waverley Instruments
Posts: 147
Joined: Thu May 05, 2022 2:10 pm

Question: VoltageAudioJack.SetValue() best practice

Post by Waverley Instruments »

Howdy Developers.

So imagine the scene... We're in ProcessSample() and after checking a jack IsConnected(), GetValue() is returning zero.

Except... We know the thing that's connected hasn't actually made a call to SetValue(), so first stupid question is, where's that zero coming from? Some kind of socket timeout?

I guess if we're emulating the real modular world, in theory, that should never happen as there's no such thing as "nothing volts", so you should always be sending "something" to anything that's connected.

I found this in the documentation, which tends to confirm that as good practice, or at least, that's my interpretation, although I'm not really sure what "stuck" on a cable means here:

You will always want to write a value of 0 to any outputs, even if you’re skipping DSP processing, so that values do not get “stuck” on a cable when a module’s inputs are disconnected.

I must admit, I'd fallen into the trap of skipping a SetValue() on a connected output to infer "no change" whereas it seems to me that's not what you're supposed to do. i.e., if there's no change, you just keep blasting out the current value regardless, which kinda feels odd (even expensive?) to me, but I guess that's how actual hardware modules would do it with control voltages.

So, if a jack is connected, regardless of whether it's audio or CV, do you always feed it something with SetValue() every time ProcessSample() gets invoked?

And to complicate things a little, how about if your UI has the ability to enable / disable outputs, now what do you send on those disabled (albeit connected) outputs, if anything? The value last sent before the output was disabled? Things are starting to get messy now, right?

And now I'm confused as to what's best practice here. :oops:

Any thoughts on this would be much appreciated! Thanks, -Rob
User avatar
Aarnville
Posts: 53
Joined: Sat Jun 18, 2022 5:14 pm

Re: Question: VoltageAudioJack.SetValue() best practice

Post by Aarnville »

You absolutely must write to every output on every ProcessSample, even if it has not changed. This caught me out at first, along with the fact that you can't read back the value from an output jack with GetValue.

I come from an embedded hardware world where it's almost always best to only write an output when it has changed but in VM the concept is that you "pump" the signal down the wire, so it is "consumed" on every tick.

In practice I think the best way to deal with it is to have a global var for every output, modify it in ProcessSample when necessary and then at the very end of ProcessSample write to all the outputs using those vars.

Edit: Just to add that a disabled output should still be written to with a value of your choice, probably 0V but maybe the last value you sent. It depends on what the output is.
User avatar
Waverley Instruments
Posts: 147
Joined: Thu May 05, 2022 2:10 pm

Re: Question: VoltageAudioJack.SetValue() best practice

Post by Waverley Instruments »

Many thanks Ian.

I'm not an embedded guy, but my mindset was definitely more in "MIDI mode" trying to use CV i/o more like discrete messages than a continuous stream of values.

I may have to re-visit some code... :oops:
User avatar
utdgrant
Posts: 625
Joined: Wed Apr 07, 2021 8:58 am
Location: Scotland
Contact:

Re: Question: VoltageAudioJack.SetValue() best practice

Post by utdgrant »

Yep, agree with all the above.

ALWAYS write to VoltageAudioJack on every ProcessSample (or ProcessBypassedSample). I also come from an embedded background, so had assumed that if the output value hadn't changed, then it didn't require another SetValue() call. However, that led to some VERY strange behaviour (jagged-looking stepped waveforms).

The only exception I have found is that if IsConnected() returns false for an output, you can skip the update until it is reconnected. Not sure how much processing time you save (if any) by doing the test, then skipping. I suspect any difference is insignificant, so brute force unconditional writes might be the best policy at all times, even if it goes against your ingrained optimisation instincts.
______________________
Dome Music Technologies
ColinP
Posts: 1000
Joined: Mon Aug 03, 2020 7:46 pm

Re: Question: VoltageAudioJack.SetValue() best practice

Post by ColinP »

General agreement.

I think in almost all cases however I don't write to poly outputs that are not connected. There seems little point in sending 16 zeros to a disconnected socket especially on a module like my Diatonic Triads that has 14 poly outputs. That's a potentially saving of 200+ calls to CA's API per sample and there are potentially more overheads than you might first think in CA's code because of things like bus connections (although my S-Poly connections are slightly different, the principle holds).

Not that it'll make a huge difference but wasted CPU is wasted CPU.
User avatar
Waverley Instruments
Posts: 147
Joined: Thu May 05, 2022 2:10 pm

Re: Question: VoltageAudioJack.SetValue() best practice

Post by Waverley Instruments »

Many thanks for the replies guys.

Re-visited a couple of offending modules, re-worked the code, re-submitted and approved all in a couple of hours! Hats off to Danny at CA.

For the record, I'm checking IsConnected() and only SetValue() when true.
User avatar
utdgrant
Posts: 625
Joined: Wed Apr 07, 2021 8:58 am
Location: Scotland
Contact:

Re: Question: VoltageAudioJack.SetValue() best practice

Post by utdgrant »

One pattern which I've developed has been to create a group of global variables to track the connection status of all VoltageAudioJacks. I update all their values within a method called UpdateConnections (or CheckConnections, depending on my whim that particular day).

This is from ACE Constants & Multipliers:

Code: Select all

private boolean ch1InConnected;
private boolean ch2InConnected;
private boolean ch3InConnected;
private boolean ch4InConnected;
private boolean ch5InConnected;
private boolean ch6InConnected;

private boolean ch1OutConnected;
private boolean ch2OutConnected;
private boolean ch3OutConnected;
private boolean ch4OutConnected;
private boolean ch5OutConnected;
private boolean ch6OutConnected;

private void UpdateConnections()
{
   ch1InConnected = inputJack1.IsConnected();
   ch2InConnected = inputJack2.IsConnected();
   ch3InConnected = inputJack3.IsConnected();
   ch4InConnected = inputJack4.IsConnected();
   ch5InConnected = inputJack5.IsConnected();
   ch6InConnected = inputJack6.IsConnected();
   
   ch1OutConnected = outputJack1.IsConnected();
   ch2OutConnected = outputJack2.IsConnected();
   ch3OutConnected = outputJack3.IsConnected();
   ch4OutConnected = outputJack4.IsConnected();
   ch5OutConnected = outputJack5.IsConnected();
   ch6OutConnected = outputJack6.IsConnected();
   
}
I then make calls to UpdateConnections from Initialize and Notify:

Code: Select all

public void Initialize()
{
   //[user-Initialize]   Add your own initialization code here

   UpdateConnections();

   //[/user-Initialize]
}

public boolean Notify( VoltageComponent component, ModuleNotifications notification, double doubleValue, long longValue, int x, int y, Object object )
{
   //[user-Notify]   Add your own notification handling code between this line and the notify-close comment
   switch( notification )
   {
   
      case Jack_Connected:   // longValue is the new cable ID
      {
         UpdateConnections();
      }
      break;
   
      case Jack_Disconnected:   // All cables have been disconnected from this jack
      {
         UpdateConnections();
      }
      break;
      
   }
}
These booleans can then be accessed directly in ProcessSample(), without having to call the wrapper method IsConnected() every time:

Code: Select all

public void ProcessSample()
{
   //[user-ProcessSample]   Add your own process-sampling code here

   double temp;
   
   temp = smoothCh1.GetSmoothValue();
   if (ch1OutConnected)
   {   
      if (ch1InConnected)
      {
         temp *= inputJack1.GetValue();
      }
      outputJack1.SetValue(temp);
   }

}
I don't know if it really makes much difference in the grand scale of things, but intuition tells me that updating booleans only on Notify events has to be the most efficient way of working.

You can find all the Dome Music Technologies source code in a .ZIP file at the DMT Documentation Page.
______________________
Dome Music Technologies
User avatar
TheGarnet
Posts: 38
Joined: Mon Aug 22, 2022 1:55 pm

Re: Question: VoltageAudioJack.SetValue() best practice

Post by TheGarnet »

I bumped into this issue. A voltmeter hooked up to a gate output jack would go wild when my sequencer was turned off and not supposed be generating any gates. It was hard to catch the glitch in the oscilloscope. But I knew something was happening, because the drum percussion module would go crazy from the very fast triggering. Disconnecting the cable from my output would stop that problem in the drums.

I changed things to write on every ProcessSample, and those bugs went away.

Now I am porting everything I learned to my more complicated sequencer. I havent yet ported the write on every pass, and I also have some early experiments with using named timers, which have bad jitter in the arrival of the notifications.

I ended up discovering a wild non-deterministic rhythm in the sequencer! It changes based on what else is happening on my machine.

I dont know if I can maintain this non-determinism once I get everything set to doing things the proper way.

I think I might have to release this version, just because its cool. What do you think?

Here is the video I just uploaded

https://www.facebook.com/watch/?v=1095268427837490
UrbanCyborg
Posts: 625
Joined: Mon Nov 15, 2021 9:23 pm

Re: Question: VoltageAudioJack.SetValue() best practice

Post by UrbanCyborg »

This post was a miskey; please ignore it.

Reid
Last edited by UrbanCyborg on Fri Dec 09, 2022 7:08 am, edited 1 time in total.
Cyberwerks Heavy Industries -- viewforum.php?f=76
UrbanCyborg
Posts: 625
Joined: Mon Nov 15, 2021 9:23 pm

Re: Question: VoltageAudioJack.SetValue() best practice

Post by UrbanCyborg »

Grant, I've been doing pretty much what you're doing, maybe not quite so systematically; I started doing it to save multiple calls to the same interface function in ProcessSample(). One difference in what I do is that I test to discover which cable is being updated, and only update the variable for that cable. In addition, when the poly count changes, I go through all my UI controls that I might be disabling, reenable any that need reenabling, send zeroes to them, then disable the ones that need it. In the following code sample, some names are variables, while others, like channelCount, are static finals.

Code: Select all

    @Override
    public void Initialize()
    {
        //[user-Initialize]   Add your own initialization code here
        polyCount     = GetNumberOfPolyVoices();
        channelsInUse = Math.min(polyCount, channelCount);

        for(int iDex = 0; iDex < channelsInUse; ++iDex) {
            toggles[iDex].SetEnabled(true);
            toggles[iDex].SetValueNoNotification(1.0, true);
            led[iDex].SetVisible(true);
        }
        for(int iDex = channelsInUse; iDex < channelCount; ++iDex) {
            toggles[iDex].SetValueNoNotification(0.0, true);
            toggles[iDex].SetEnabled(false);
            led[iDex].SetVisible(false);
        }

        // Set truth values to default, since jacks aren't connected yet
        minimum   = -5.0;
        threshold =  2.5;
        maximum   =  5.0;
        truth     =  2.5;

        StartGuiUpdateTimer();
        //[/user-Initialize]
    }

    case PolyVoices_Changed:    // longValue is the new number of poly voices
    {
        polyCount     = (int)longValue;
        channelsInUse = Math.min(polyCount, channelCount);
        for(int iDex = 0; iDex < channelsInUse; ++iDex) {
            toggles[iDex].SetEnabled(true);
            led[iDex].SetVisible(false);
        }
        for(int iDex = channelsInUse; iDex < channelCount; ++iDex) {
            toggles[iDex].SetValue(0.0);
            toggles[iDex].SetEnabled(false);
            led[iDex].SetVisible(false);
        }
    }
    break;
Garnet, I'd really think twice about publishing a module with potential bugs, no matter how interesting its behavior. If you really want behavior that changes based on your current activity, look into something like Entropy Pools. The bugs you don't know about will be plenty to keep you busy, without planning any in. :D

Reid
Cyberwerks Heavy Industries -- viewforum.php?f=76
Post Reply

Return to “Module Designer”