A trigger aiming at trigger when a audio in raise above a treshold triggers also when the signal goes below the treshold

Hi,

I’m trying to detect when an incoming sound goes above a certain threshold with the following code.

I’m able to detect when the sound’s amplitude is above the threshold, but I fail to transform this into a valid trigger.

The trigger I’ve built triggers when the signal goes above the threshold (this is what I expect) but also when it goes below the threshold. And this is puzzling me a lot…

(
{
    // AMPLITUDE ANALYSER
    SynthDef(\amplTrig, {
        var treshold=\treshold.kr(0.1);
        var in=\in.kr(0);
        
        var sig=In.ar(in);
        var ampl=PeakFollower.ar(sig);
        var start=InRange.ar(ampl,treshold,1);
        
        // Trigger construction
        // build a [-1,1] signal from a [0,1] signal 
        var t=Trig.ar(start*2-1); // building a trigger when a new in signal is detected
        
        Poll.ar(t,start,"TRIG");
        
    }).add;
    
    SynthDef(\dummy, 	{
        var freq=\freq.kr(220);
        var out=\out.kr(0);
        var amp=\amp.kr(0.5);
        var env=EnvGen.kr(Env.new([0,1,0.5],[0.01,4],releaseNode:2)) * EnvGate.new(fadeTime: 1);
        var sig=SinOsc.ar(freq)*env;
        
        sig=sig*amp;
        Out.ar(out,sig);
    }).add();
    
    s.sync;
    ~trig=Synth(\amplTrig,[\in, 0],addAction: \addToTail);
}.fork;

)


// DEBUG
(
// ~mic.release;
Pbind(\instrument, \dummy,
    \dur, 8,
    \amp, 0.5,
    \legato, 0.25,
    \note, Pseq([2,0])
    // \note, Pseq([2])
).play;
)

Anyone can point me my mistake ?

I have not tested your code yet but I have been through a lot of ways of trying to reliably create triggers from coming audio. I found the best way to be Onsets.kr so far. I am using the synthdef below to simultaneously detect onsets (above a threshold) and trigger envelopes.

SynthDef(\touch, {
	var trigsig = In.ar(\in.kr(0));
	var chain = FFT(LocalBuf(512), trigsig);
	var intrig = Onsets.kr(chain, \thresh.kr(0.1));
	var trig = Trig.kr(intrig, \t1.kr(0.1) + \t2.kr(0.1) * \minOn.kr(0));
	var sig = Env(
		[\lo.kr(120), \hi.kr(3000), \lo.kr(120)],
		[\t1.kr(0.1), \t2.kr(0.1)],
		[\cur1.kr(4), \cur2.kr(-4)]
	).kr(0, trig);
	Line.kr(dur:\dur.kr(3), doneAction:2);
	Out.kr(\out.kr(0), sig);
}).add;

This synthdef is non sustaining and frees itself after \dur seconds. Note that if \minOn is set to 1, the envelope will finish before allowing a new trigger to come through.

Hope this can be helpful.

1 Like

Thanks for the suggestion.

However I’d really like to understand what’s my error. I guess I don’t understand how are working Trig.ar and Trig1.ar
The documentation says for Trig1.ar: “When a nonpositive to positive transition occurs at the input, Trig outputs the level of the triggering input for the specified duration, otherwise it outputs zero.”/

In this basic example:

(
{
	var base=LFPulse.kr(2,0.3).lag(0.1);
	var up=(Slope.kr(base)*0.002).max(0); // filtering raising slopes
	var trig=Trig.kr(up); // should only trigger when the signal goes up.
	[base,up,Env.perc(0,0.2).kr(0,trig)]; // Evn.perc is used to visualise the trigger
}.plot(2);
)

the next plot is made. It shows a trigger when the signal goes down…

Why ?

I can’t really say why that is not working, when I plot ‘trig’ it looks fine. However this slightly mod’ed version seem to work, using round instead of max(0).

(
{
	var base = LFPulse.kr(2,0.3).lag(0.1);
	var up = (Slope.kr(base)*0.02).round; // filtering raising slopes
	var trig = Trig.kr(up, 0); // should only trigger when the signal goes up.
	[base, up, Env.perc(0,0.2).kr(0, trig)]; // Evn.perc is used to visualise the trigger
}.plot(2);
)

Look closely… the unwanted triggers are occurring before the signal goes down.

Therefore the cause of the unwanted triggers cannot have anything to do with the signal going down. It is completely impossible for the value change at 0.6 sec to go back and retroactively cause something to happen at 0.55xxxx sec.

OK, there’s an unwanted trigger between 0.5 and 0.6 sec.

If you assign the plotter object to a variable (instead of just displaying it and throwing it away), then you can get access to the data.

// also added spaces for readability
(
p = {
	var base = LFPulse.kr(2, 0.3).lag(0.1);
	var up = (Slope.kr(base) * 0.002).max(0); // filtering raising slopes
	var trig = Trig.kr(up); // should only trigger when the signal goes up.
	[base, up, Env.perc(0, 0.2).kr(0, trig)]; // Evn.perc is used to visualise the trigger
}.plot(2);
)

// extract 0.5-0.6 sec from the slope plot
// (this was my 3rd try: needed asInteger, and it's a kr plot --> / 64)
a = p.value[1][(0.5 * s.sampleRate / 64).asInteger .. (0.6 * s.sampleRate / 64).asInteger];

As quoted, the key is “nonpositive to positive transition” – we’ve got the data, so we can query the data for that condition.

// look for nonpos --> pos
(
z = Array.new;
a.doAdjacentPairs { |a, b, i|
	if(a <= 0 and: { b > 0 }) {
		z = z.add(i);
	}
};
z
)
-> [ 46, 48, 50, 53, 56, 62 ]

// look at the slope data for this segment
a[46 .. 62]

-> [ 0.0, 8.9406974268513e-08, 0.0, 8.9406974268513e-08, 0.0, 8.9406974268513e-08, 0.0, 0.0, 8.9406974268513e-08, 0.0, 0.0, 8.9406974268513e-08, 0.0, 0.0, 0.0, 0.0, 0.0 ]

// and, for giggles, go back to the original LFPulse
(
var offset = (0.5 * s.sampleRate / 64).asInteger;
p.value[0][46 + offset .. 62 + offset];
)

-> [ 0.99999958276749, 0.99999964237213, 0.99999964237213, 0.99999970197678, 0.99999970197678, 0.99999976158142, 0.99999976158142, 0.99999976158142, 0.99999982118607, 0.99999982118607, 0.99999982118607, 0.99999988079071, 0.99999988079071, 0.99999988079071, 0.99999988079071, 0.99999988079071, 0.99999988079071 ]

So we find, when we zoom in on the data, that Lag, as it approaches the target value, slows down so much that some successive samples are equal, producing slope == 0 momentarily. If you are then basing a trigger directly off of the slope, then you will get spurious triggers immediately following these 0s.

One take away here is that it hurts your ability to diagnose a problem by just making a big plot and saying “well, the plot looks OK.” The plot does not have enough visual resolution for some problems. In this case, if there’s a trigger, then there must be a nonpositive to positive transition, even if you don’t see it.

… implicitly raising the slope threshold to 0.5… but in that case, one could just write the threshold directly: (Slope.kr(base) * 0.02) >= 0.01 – the offending slope values are many orders of magnitude smaller than this. round obscures what is really going on.

In any case, for the original problem: A Schmitt trigger (which SC misspells “Schmidt,” well, we’re stuck with that typo now) triggers when the signal goes above a set level, and it will not trigger again until the signal goes below a different threshold and then above the first threshold again. This “debounces” the triggers when the signal is close to the threshold. You might try that.

hjh

3 Likes

Amazing analyse technique !!! I’ll reuse it for sure.
And Schmidt is the perfect tool of this once correctly calibrated for the incoming signal.