Single KRate Pulse

I need to send a single KRate pulse to a custom UGen I’m writing whenever a MIDI note on or off message is received, and can’t work out how to do it.

I envisage the UGen receiving a value of 1.0 when a note on is received, then a value of 0 the next KRate cycle.

Apologies if this is a Really Stupid question. It’s been a while since I last used SuperCollider.

You can use a trigger rate control, like

(
x = {
  var trig = \trig.tr(0);
  ....
}.play;
)

x.set(\trig, 1);

Then when you set it to 1 it will be for one control cycle, then back to 0.

Great, thank you very much @Eric_Sluyter!

A question – if your custom plugin receives two 1.0 samples in a row, do you want it to trigger twice or only once? That is, is “1.0 1.0” a valid input, or invalid?

If it’s valid, then controlling the trigger at the input is the only way.

If invalid, then you could steal the trigger-handling logic from TriggerUGens.cpp, basically if(prevtrig <= 0.f && trig > 0.f) to prevent double triggers without requiring the user to be extra careful about the input.

hjh

Perfect, thank you @jamshark70 !

I’m doing basically this in the custom UGen itself, ie

if(notealloff == 1.0 && lastnotealloff == 0.0) {
   // All notes OFF
}
// Update previous all-note-off
lastnotealloff = notealloff;

The trigger input has to go low eventually though, or the next rising edge won’t be detected.

Yep – it’s less fragile that way. tr controls are the easiest way to get it to go low automatically.

hjh

Sorry @jamshark70 I realise I didn’t read you earlier message properly. Apologies.

That would be valid. I want the trigger to fire every time. So I need the SynthDef parameter to toggle high on reception of a MIDI note (on or off), then somehow automatically toggle low on the next cycle.

But how…?
It seems like SuperCollider isn’t setup to send signals like this.

I’ve thought up a hacky workaround: send a random value to my synth every note-on/off, then compare the new value to the previous one. That way I can trigger an event in my custom UGen only when the value changes.

I guess I’ll have to do the comparison in my UGen itself though, as I don’t know if it’s possible to store the old value between cycles in the SynthDef itself.

Although… there Has to be a better way…

Hm, I’m a bit confused by this. “I want the trigger to fire every time” sounds like, if the trigger input is positive in this control block, and positive in the next control block, then you’d trigger twice. But that conflicts with “automatically toggle low on the next cycle,” where requiring low-then-high would make it impossible to trigger twice in a row.

If you want every positive value at the input to be a distinct trigger, then you could delete the previous-trigger check in the C++ code, and use tr control inputs to keep the triggers as short as possible. (Or, if the trigger is generated as a signal, Trig.kr(trig, ControlDur.ir) but you wouldn’t need that with a tr input.)

If you require a low value before high, then the fastest possible trigger rate is half control rate.

hjh

Apologies, I’m not explaining myself very well.

Here’s the code I have at the moment:

//"https://doc.sccode.org/Guides/UsingMIDI.html"
MIDIClient.init;
MIDIIn.connectAll;

s.boot;

(
// Init bassline synth object
var bassline;
var m_on, m_off;

SynthDef.new("AcidBass", { arg out = 0,
	noteevent = 0,
	note = 60.0,
	velocity = 100.0;

	var result;

    result = Open303.ar(noteevent, note, velocity);

	Out.ar(out, result);

}).add;

bassline.free;

// Instantiate Open303 synth
bassline = Synth("AcidBass");


m_on = MIDIFunc.noteOn({ |vel, num, chan, src|
	"NOTEON note % @ velocity %\n".postf(num, vel);
	bassline.set(\note, num);
	bassline.set(\velocity, vel);
	bassline.set(\noteevent, 1.0); // Needs to fall back to 0 after one cycle
});

m_off = MIDIFunc.noteOff({ |vel, num, chan, src|
	"NOTEOFF note %\n".postf(num);
	bassline.set(\note, num);
	bassline.set(\velocity, 0.0);
	bassline.set(\noteevent, 1.0); // Needs to fall back to 0 after one cycle
});

)

What I need is for the value of the “noteevent” parameter of my “bassline” synth instance to toggle to 1.0 for one single KRate cycle then drop back to 0.0, every time a NoteOn or NoteOff event is received.

That solution was given – delete noteevent from the argument list, then add:

var noteevent = NamedControl.tr(\noteevent);

hjh

Aha…!
Thank you!!

Is the trigger signal sent to the UGen exactly 1 cycle long, in this case?

The syntax is… counter-intuitive but it does sort of work. My custom UGen seems to be missing note-offs quite a lot, but it may be a problem at the plugin end (maybe my check for duplicate triggers is causing problems).

Yes, the definition of a trigger rate control is a control-rate input which, when set to a nonzero value, will pass that value through for exactly one control cycle, and then immediately go to zero.

NamedControl was introduced because the other syntax to create a trigger control is even less intuitive, and harder to explain (and there’s a weird thing about that in the default event prototype).

Arguments about synth arguments (pun intended) are kind of a whole thing… It’s even been argued that nobody should ever use function arguments for controls at all, and you’ll see some SynthDefs where all control inputs are defined as \freq.kr(440). My view:

  • For normal, single-value controls:
    • Function arguments pro: convenient syntax to list all/most controls at the top; con: magically breaks if you calculate a default value from an expression.
    • \freq.kr way pro: consistent syntax; con: syntactic redundancy if you group them at the top, or, you have to take care with multiple references’ default values if you don’t group them at the top.
    • IMO neither way has a clear advantage, pure matter of preference.
  • Special cases (tr or arrayed controls): Function arguments are the clear loser here.

Awhile back, I wrote up a synth arg preprocessor to implement a consistent syntax for synth inputs that avoids the problems of the existing approaches. But nobody uses it. (BTW I just pushed a couple of bug fixes – but unfortunately, I had to remove support for LagControl temporarily and fixed LagControl shortly after that.)

Quarks.install("https://github.com/jamshark70/ddwSynthArgPreprocessor.git");

thisProcess.recompile;

SynthArgPreprocessor.install;

(
SynthDef(\AcidBass, { 
	## out = 0;
	## noteevent = 0: t;  // 't' is the trig-control specifier
	## note = 60.0;
	## velocity = 100.0;

	var result = Open303.ar(noteevent, note, velocity);

	Out.ar(out, result);

}).add;
)

SynthDescLib.at(\AcidBass)

Controls:
ControlName  P 0 out control 0.0
ControlName  P 1 note control 60.0
ControlName  P 2 velocity control 100.0
ControlName  P 3 noteevent control 0.0  -- "out of order" because it's tr and the others are kr

SynthDescLib.at(\AcidBass).def.dumpUGens

AcidBass
[0_Control, control, nil]
[1_TrigControl, control, nil]   <<-- here's noteevent
[2_Open303, audio, [1_TrigControl[0], 0_Control[1], 0_Control[2]]]
[3_Out, audio, [0_Control[0], 2_Open303]]

hjh

o/t but where does one find this Open303 extension?

I think one doesn’t at this point: the OP says “send a single KRate pulse to a custom UGen I’m writing…”

hjh

1 Like

I’m in the process of creating it now :slight_smile:
As you probably guessed, it’s a SuperCollider extension wrapper around the DSP code of the Open303 TB303 emulator.

It’s producing sound, and responds to notes. Need to add support for the basic 303 parameters, and fix the frequent stuck notes. At the moment I’m not sure if they’re down to SuperCollider not sending triggers on every note-on/off, or the synth not responding properly to note-offs, particularly when more than 2 notes overlap.

1 Like

want! (and some characters…)

20 character of working on it :slight_smile:

Now have all basic synth params working (with interpolation).

Still lots of stuck notes though. And still not sure why they’re happening. It sounds great, apart from that, and I think it’s stable, unless you hit too many overlapping notes.

That’s really interesting background.

I think my perpetual confusion stems from the fact I have no formal background in programming, most of my experience doing it has been with “C-like” languages, plus I tend only to use SuperCollider for specific projects (Monome Norns “Engines”), so whenever I think I’ve started to get my head around SC, I don’t use it for a while, and everything I’d learned disappears from my head…

Having a little knowledge, but no grounding makes learning unfamiliar languages trickier, I think.

Quick question: bit OT, but with an audio-rate plugin, are non-audio parameter inputs actually updated at audio rate, or are all nSample values in the block identical?