TouchOSC and hanging nodes on server

I have a problem with this code. It’s supposed to make it so my TouchOsc on my iPhone works like a keyboard with the sound being generated by Supercollider. What happens though is occasionally the scsynth does not release the node, which I can see on the Node tree. The old nodes usually don’t sound, but they should disappear, and there seems to be some sluggishness when they are many. I think the problem comes from some interaction in Synth between the sclang and the scsynth. The f is set to one of the fmgen’s from 100 FM Synths that came with the Supercollider package, modified to be a sustaining instrument like it says in the file.

(
~oldlatency = s.latency;
s.latency = 0;
// s.latency = ~oldlatency; do this when done

~bus = 0;
)

(
~pushb  = Array.newClear(16);
~synths = Array.newClear(16);

f = ("fmgen_s_" ++ 100.rand);
("f is " ++ f).postln;
~bias = 80; // GS bottom-1

[13, 14, 15, 16, 9, 10, 11, 12, 5, 6, 7, 8, 1, 2, 3, 4]
.do(
  { |button, i|
    ~pushb.put(
      i,
      OSCdef.new(
        'push'++(i+1).asString,
        { |msg, time, addr, port|
          if (
            msg[1] == 1,
            {
              if ((~synths[i].notNil), { ~synths[i].set(\gate, 0)});

              ~synths.put(i, Synth(f, [out: ~bus, freq: (~bias+i).midicps]));
            },
            { ~synths[i].set(\gate, 0); ~synths[i] = nil }
          );
        },
        '/2/push'++button.asString
      )
    )
  }
);

)

One possible situation is that you could be closing the gate (sending the \gate, 0 message) at the exact same time as the synth starts. In that case, the gate never opens – the envelope never starts – if the envelope didn’t start then it can’t end – and if it doesn’t end, then the synth doesn’t stop.

One solution is to write Impulse.kr(0) + gate in the EnvGen, to guarantee that the gate is always open for at least one control cycle.

hjh

I took the above suggestion about how to construct my synth, but couldn’t figure out how the envelope worked (yet) in 100 FM Synths, so wrote my own. I also rewrote the function that instantiated the OSCdefs.

It indeed does work for parameters for thisSynth.release that are at least 0 without accumulating zombie Synths in the NodeTree. However if I put nil as an argument, it does accumulate such zombies, apparently contradicting the manual where it says that such a Synth (Node) will go through its release phase.

As you can see in my new code an old Synth on a particular note supposedly gets detached, and should not affect a new one, but somehow it does. I tried putting debugging statements, but nothing leaps out at me. Does anyone have some insight?

(
SynthDef(\foo, { | out, gate = 1, freq = 440|
    var z = (Impulse.kr(0) + EnvGen.kr(Env.adsr, gate, doneAction: 2)) * SinOsc.ar(freq, 0, 0.3).distort;
    Out.ar(out, z!2)
}).add
);

(
~oldlatency = s.latency;
s.latency = 0;
// s.latency = ~oldlatency;           // do this when done with this program
thisProcess.openUDPPort(8000).postln; // attempt to open port 8000, the one TouchOSC is
                                      // outputting on
~bus = 0;
)

(
~pushb  = Array.newClear(16);
~synths = Array.fill(16, {Array(8)});

f =   \foo;
~bias = 80;    // GS bottom-1

16.do(
  { |i|
    ~pushb.put(
      i,
      OSCdef.new(
        'push'++(i+1).asString,
        { |msg, time, addr, port|
          var thisSynth;

          if (
            ~synths[i].size > 0,
            {
              thisSynth = ~synths[i].pop;
              thisSynth.release(0.1); // odd dependence on parameter
            }
          );

          if (
            (msg[1] == 1),
            {~synths[i].add(Synth(f, [out: ~bus, freq: (~bias+i).midicps]))}
          )
        },
        '/2/push'++(i+1).asString
      )
    )
  }
);
)

I was suggesting like this:

EnvGen.kr(Env.adsr, Impulse.kr(0) + gate, doneAction: 2)

… where I said “Impulse + gate in the EnvGen” but you wrote Impulse + EnvGen, not the same.

hjh

How occasionally does the synth not release? If there is in fact nothing wrong with your code it still might not work.

UDP (which is what osc uses) message are not guaranteed to be sent nor read. If you’re on the same computer this usually isn’t an issue, but if you are using a network (particularly WiFi!) then is can.

Is touch osc constantly streaming the data out, or does it only send the note off message at the exact moment you release the finger?

To test this you could try adding some printing statement in the code reporting what osc messages you are receiving and in what order.

jamshark’s latest suggestion worked, so thanks a lot. I am still a little bit confused as to why, but I think I will have to go into the Class code files and figure that out if I’m interested.

I am aware of UDP not requiring confirmation, and so it can ‘miss’ messages. I had tried various debugging printouts including .dumpOSC but nothing leapt out at me. I think I’ve learned a lot about debugging signal flow, so that’s a good thing.

I don’t think class files will be very helpful for this.

Understand how gates work.

If you have a synth with a gate argument, the synth can begin with either a positive gate (> 0) or a zero/negative gate (<= 0) – there aren’t any other choices in real numbers.

  • Positive gate: the envelope opens immediately and proceeds to the releaseNode. At some point in the future, we expect the gate to become 0. At this time, the EnvGen proceeds past the releaseNode, on to the end. When it reaches the end, doneAction will take effect.

    • Note: doneAction takes effect only if the EnvGen proceeds forward to the end.
  • Zero/negative gate: The envelope is not allowed to start until the gate is positive. So an envelope that begins with a zero gate is in a “waiting” state, holding at the initial level. At some future time, the gate should become positive, running the envelope up to the releaseNode; and later, zero again to let the envelope run past the releaseNode, and on to the end triggering doneAction.

The gate control is typically control rate, meaning that it updates only once per control block.

What if an event schedules a node for time x, and schedules a “set gate to 0” message for a time within the control block at x?

Then both the initial positive gate and the gate=0 update happen in the same control block = simultaneously. It can’t have two values in the same block. So it has to use one of them, and this is the last one.

So the initial synth calculations take place with a zero gate.

The synth does not know that this 0 is overriding a positive initial value.

The synth can’t know that because 0 has totally overridden the initial value.

So this case is indistinguishable from the waiting state.

The envelope never opens. Because it never opens, it can’t close, and it can’t trigger the done action.

The only way to guarantee that a quick release doesn’t enter this condition is to guarantee a positive value at the gate input for at least one control cycle. That’s the purpose of the Impulse. The Impulse must be applied to the gate input, not anywhere else. (This is why it was wrong to do Impulse + EnvGen – because doneAction behavior is unaffected by downstream math ops applied to the EnvGen’s output – it can be affected only by inputs to the EnvGen.)

hjh

Thank you much for the detailed explanation.