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.
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.
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.
//"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.
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]]
I’m in the process of creating it now
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.
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.
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?