Best way to trigger a sample to play

Thanks @jamshark70 getting of the trigger solves my problem though without a nice fade-out. But it seems to be working perfectly now, and I can tap my foot as fast as I need to.

I think the crazy NumSynths value is due to a bug in my code. I’m sending/receiving OSC from my code through sockets, but in a very rudimentary way that only works with the simple commands to setup synths. But the extra stress of sending continuous trigger OSC commands most likely caused intermixing of commands and responses. My solution for that is to have a thread store socket responses in a circular queue, but that will probably take some time to implement. It was always a TODO, but not important until now. In the end @jordan solution is nicer because, as you mention, the sound can fade out because it cuts it off with this current solution you mentioned of getting rid of the trigger. But perhaps having a server side way to call a synthdef as @jordon mentioned might also be useful in the future.

Your advice about using scopes and simple automated routines is precisely what I learned, so very important for any beginners. It seems that trying to print values isn’t a reliable way to understand what is happening when everything is a signal.

Thanks so much for everybody’s help!
-Paul

1 Like

Then:

b = Buffer.read(s, Platform.resourceDir +/+ "sounds/a11wlk01.wav");

(
c = Bus.control(s, 1);

a = { |gate = 1, amp = 0.1|
    var main_eg = EnvGen.kr(Env.asr(0.01, 1, 0.1), gate, doneAction: 2);
    var trig = NamedControl.tr(\trig, 0);
    var toggle = ToggleFF.kr(trig);
    var gates = [toggle, toggle <= 0];
    var egs = EnvGen.kr(Env.asr(0.01, 1, 0.01), gates);
    var players = PlayBuf.ar(1, b, BufRateScale.kr(b),
        gates,
        startPos: TRand.kr(0, BufFrames.kr(b) * 0.5, gates)
    );
    Out.ar(0, ((players * egs).sum * (main_eg * amp)).dup)
}.play(args: [trig: c.asMap]);

// automated trigger for testing: any kr trigger-y signal will work
// (which you already have)
t = {
    var trigFreq = SinOsc.kr(0.1).exprange(1, 12);
    Impulse.kr(trigFreq)
}.play(outbus: c);
)

hjh

1 Like

@jamshark70 what does the trig: c.asMap do. And are var trig and /trig the same? Guess I don’t understand NamedControl either. Staring at the documentation now, but not yet sinking in.

You can ignore that, because you already have a trigger signal coming from the bela pins.

See Node:map help for info on synth control bus mapping. But you don’t strictly speaking need bus mapping for your final version. This was just a convenient way to provide a trigger signal without requiring user interaction.

No. Alternately:

a = { |gate = 1, amp = 0.1|
    var main_eg = EnvGen.kr(Env.asr(0.01, 1, 0.1), gate, doneAction: 2);
    var trigVariableToProvideAccessToTheSynthControlInput = NamedControl.tr(\trigNameOfTheSynthControlInput, 0);
    var toggle = ToggleFF.kr(trigVariableToProvideAccessToTheSynthControlInput);
    var gates = [toggle, toggle <= 0];
    var egs = EnvGen.kr(Env.asr(0.01, 1, 0.01), gates);
    var players = PlayBuf.ar(1, b, BufRateScale.kr(b),
        gates,
        startPos: TRand.kr(0, BufFrames.kr(b) * 0.5, gates)
    );
    Out.ar(0, ((players * egs).sum * (main_eg * amp)).dup)
}.play(args: [trigNameOfTheSynthControlInput: c.asMap]);

Within a SynthDef function, we can use variables to refer to parts of the DSP graph. (Arguments are, practically speaking, variables – the only difference is that arguments receive values from outside when the function is called, while variables are completely local, 100% under control of the scope in which they are defined.)

When a Synth runs in the server, you can get control information into it using buses (In.kr) or control inputs. It’s inconvenient to allocate buses for everything, so SynthDefs have named control inputs. We use control input a lot, and buses somewhat less. (Buses are good for control values that are shared across many nodes; control inputs are good for per-synth values.)

SynthDef function arguments are converted into control inputs, and control input channels are passed into the function at SynthDef build time (which is how UGen inputs get connected to these control inputs). So there are already two elements to gate = 1, for instance: the control input named gate with default = 1, and the argument/variable gate which receives a handle to that control input.

For demo purposes, I wanted to be sure that the trigger signal is a true trigger (1 only momentarily). For control inputs, this can be done by defining it as a trigger. This can be done with argument names, but it’s a bit icky. It’s more clear to define the control explicitly as “trigger rate”: .tr. .tr exists basically only for the NamedControl class, where it produces a TrigControl control input. (Note that \someName.kr(...) is just a shortcut for NamedControl – and that the shortcut syntax also confused you earlier in this thread.)

But doing it this way exposes the two elements, which, in an argument list, are collapsed into one expression. The NamedControl needs… well… a name, which is provided as a symbol \trig. And to access the control elsewhere, it needs a variable, which is declared: var trig. But the name by which the control is exposed to the outside world, and the variable name used locally to access the control, are conceptually not the same thing.

(It’s also possible to dispense with the var and just write ToggleFF.kr(\trig.tr(0)). This is a matter of taste. Personally, I find it bothersome to have to scan through the entire function to find control input definitions, which may then be anywhere in a line, not only near the left. Others very much prefer not to be bothered with variable declarations. But, the problem in that case is that you have to be sure every time \trig.tr is written in the same SynthDef that it has the same default value. I’ve actually argued elsewhere that neither arguments nor \name.kr(default) syntax are good solutions to the problem, and that what’s needed is a bespoke syntax. I prototyped such a thing, but it hasn’t come into wide use. At the end of the day, it’s largely a matter of preference. For some, SynthDef function arguments are more transparent and intuitive at least for “normal” kr single-valued controls, but others feel that there is unpleasant magic lurking behind them and they prefer writing inline \symbol.kr style. I know which way I like better, but I’m not going to tell you what you should do.)

hjh

@jamshark70 I finally had a chance to test this out, and it’s not working as expected. Actually, I’m getting better results with the real application, but I don’t know why. To explain the difference, bela.io has a UI in a web browser where I can copy in the code below. But my app uses OSC and only defines the synths in sclang. So maybe there’s a difference there, but in any case it doesn’t work in the web UI, so I want to understand why.

I did make some changes, so I’ll start there. I still didn’t understand the /trig stuff so if my alternate version is ok, then perhaps we can start with that, since I do understand that method. If your version is critical, then I need to ask more questions, so let me know.

My version turns sends a bus as an input called trigBus, which replaces. So again, if that’s the issue, we can discuss that part. But also I want the value of the trigger to be change the volume based on it’s 0-1 value. I didn’t see how your code took that into account. But I’m obviously no expert.

The other change I made was to the startPos, which you had a TRand. I could not understand why this would be important so I made it start at zero each time, which is what I want.

Also for testing I created an autoTrig synthDef which sends random values to simulate what the digital inputs do. And that does reproduce the issue as well, but I think it’s not working the same. The first image below is from autoTrig, and the second using the digital ins, which also send a 0.003 pulse.

Also, the bela scope seems to show some strangeness when I added the output signal but. I’m hoping it’s accurate for this test. Or perhaps you have some explanation.

Here’s the code I’m using now, hoping you are somebody can help as I do feel I’m getting closer. Meanwhile I will try the supercollider UI directly, as I just thought about that tshooting step.


s = Server.default;

s.options.numAnalogInChannels = 8;
s.options.numAnalogOutChannels = 2;
s.options.numDigitalChannels = 16;

s.options.belaMaxScopeChannels = 3;

s.options.numMultiplexChannels = 0; // do not enable multiplexer channels

s.options.blockSize = 128;

s.waitForBoot({
	var digitalPins = [ 0, 1, 2, 3, 4, 5, 6, 7];
	var triggerCount = 1;

  	SynthDef(\trigDetector, {
		arg ctlBus, deviceId;

		var all = Array.fill(digitalPins.size, { |i|
		    DigitalIn.ar(digitalPins[i]);
		});
		
		// Convert all the bit array to a binary value
		var allId = all.reverse.reduce({ arg total, bit; total * 2 + bit }, 0);
		
		// Make sure they are set by delaying a little bit
		var delay_allId = TDelay.ar(in: allId, dur: 0.001);

		// Create bitmasks.  
		//  velMask is how many bit used for velocity
		//  devMask used for devices, of which there are only 2
		var velMask = pow(2, digitalPins.size - triggerCount) -1;
		var devMask = pow(2, triggerCount) - 1;

		// Use the masks to get the velocity and device that was activated
		var velocity = allId.bitAnd(velMask);
		var device = (allId >> (digitalPins.size - triggerCount)).bitAnd(devMask);

		// scale button between 0 and 1 based on velMask
		var button = velocity / velMask;

		var signal = Trig.ar( Select.ar(abs(device - deviceId) < 0.1, [K2A.ar(0), delay_allId * button]), 0.05);

		Out.ar(ctlBus, signal);
		Out.kr(ctlBus, signal);
		
		button.belaScope(2);
		SendTrig.ar(HPZ1.ar(delay_allId), 20, allId);
		// SendTrig.ar(HPZ1.ar(delay_allId), 21, deviceId);
	}).send(s);
	
  SynthDef("sh1", {
  	
  });

    SynthDef("sh",{ 
		arg buf=0, gate = 1, scale = 1.0, trigBus = -1, wetBus, vol = 0.6, loop = 0, t_reset = 0;
		
	    var main_eg = EnvGen.kr(Env.asr(0.01, 1, 0.1), gate, doneAction: 2);
		var trigVal = In.ar(trigBus);
	    // var trigVariableToProvideAccessToTheSynthControlInput = NamedControl.tr(\trigNameOfTheSynthControlInput, 0);
	    // var toggle = ToggleFF.kr(trigVariableToProvideAccessToTheSynthControlInput);
	    var toggle = ToggleFF.kr(trigVal > 0.01);
		var latch = SetResetFF.ar(trigVal, t_reset) ;
	    var gates = [toggle, toggle <= 0];
	    var egs = EnvGen.kr(Env.asr(0.01, 1, 0.01), gates);
	    var players = PlayBuf.ar(1, buf, BufRateScale.kr(buf),
	        gates,
	        startPos: 0 /*TRand.kr(
	        	0, 
	        	BufFrames.kr(buf) * 0.5, 
	        	gates
	        )*/
	        ,
			loop: loop * latch
	    );
	    
	    var signal = ((players * egs).sum * (main_eg * vol * ((1.0 - scale) + (trigVal*scale))  )).dup;
	    Out.ar(wetBus, signal);
	    
      	//signal.belaScope(1);
      	trigVal.belaScope(0);
    }).send(s);  
  
  SynthDef("autoTrig",
  {
  	arg trigBus;
  	
  	var trigger = Impulse.ar(1);
  	var randTrigger = Trig.ar(TRand.ar(0.3, 0.8, trigger) * trigger, 0.003);
  	randTrigger.belaScope(0);
  	
  	Out.ar(trigBus, randTrigger);
  }).send(s);
  
  SynthDef("scope2", {
  	arg ctlBus;

  	In.ar(ctlBus).belaScope(2);
  }).send(s);

  s.sync;

  b = Bus.audio(s, 1);
  c = Bus.audio(s, 1);
	
  Synth.new("scope2", [\ctlBus, b]);
  //k = Buffer.read(s, "/home/paul/src/HarmProject/data/samples/Ride.wav");
  //j = Buffer.read(s, "/home/paul/src/HarmProject/data/samples/Kick2.wav");
  k = Buffer.read(s, "/root/src/HarmProject/data/samples/Ride.wav");
  j = Buffer.read(s, "/root/src/HarmProject/data/samples/Kick2.wav");

  //k = Buffer.read(s, "/root/src/HarmProject/data/samples/Snare.wav");
  //Synth.new("simpleTrig", [\buf: k]);

  Synth.new("sh", [\buf: k, \trigBus: b]);
  Synth.new("sh", [\buf: j, \trigBus: c]);
  Synth.new("trigDetector", [\deviceId, 0.0, \ctlBus, b]);
  Synth.new("trigDetector", [\deviceId, 1.0, \ctlBus, c]);
  
  Synth.new("autoTrig", [\trigBus: b]);

 // o = OSCFunc({ 
 // 	arg msg, time;
	
	// //[time, msg].postln; 
 // 	if(msg[2] == -1 && msg[3] == 10, { ~sendFootTrigger.value(msg[5], msg[4] ) });
 // },'/trg');

  o = OSCFunc({ 
  	arg msg, time;
	
	[time, msg].postln; 
  },'/tr');
  
	k.postln;
	j.postln;
  "Ready ".postln;
  s.sync;

});

  "Waiting for quit ".postln;
ServerQuit.add({ 0.exit }); // quit if the button is pressed

I have to admit here that I don’t have bandwidth right now to understand your code thoroughly.

One way to proceed (not the only way, and this probably isn’t going to sound very appealing, but I think it’s a good way to clarify the issues) is to treat this first version as a learning experience, and rebuild it, one step at a time.

First, make absolutely sure that you understand the behavior of the digital pins, and that the triggers you’re deriving from the pins are behaving in the way that you need. There are a couple of comments in your post about simulated triggers not behaving exactly the same as the real ones, and some strangeness on the bela side, which suggests to me that maybe the trigger signal behavior is not completely under control. If the trigger signal is not behaving in the way you expect, then everything downstream is at risk of running into trouble. So I think it’s critically important to spend more time on this, without the audio.

Then, try triggering a simple SinOsc + envelope, and make sure this is really doing something sensible. (Since you know you want “play until finished, or until the next trigger, whichever is first,” you could use two SinOscs at different frequencies to test the toggling behavior.)

Then you might be ready to play the samples.

hjh