Yes, that was my plan. I don’t track gates at all at the moment, just send note and velocity of every note event to the plugin (setting velocity to 0 for note-offs).
The plugin is meant to handle a note queue, and determine which notes are still held, and slide to the next or previous note where notes overlap.
Here’s my SuperCollider plugin-testing script:
//"https://doc.sccode.org/Guides/UsingMIDI.html"
MIDIClient.init;
MIDIIn.connectAll;
s.boot;
(
// Init bassline synth object
var bassline;
var m_on, m_off, m_waveform, m_cutoff, m_resonance, m_envmod, m_decay, m_accent, m_volume;
SynthDef.new("AcidBass", {
arg out,
notenum = 60.0,
notevel = 64.0,
notealloff = 0.0,
waveform = 0.85,
cutoff = 0.229,
resonance = 0.5,
envmod = 0.25,
decay = 0.5,
accent = 0.5,
volume = 0.9;
// Declare vars
var noteevent = NamedControl.tr(\noteevent); // Ensures 'noteevent' is only ever positive for 1 k-rate cycle!
var result;
// Create output
result = Open303.ar(noteevent, notenum, notevel, notealloff, waveform, cutoff, resonance, envmod, decay, accent, volume);
// Output output
Out.ar(out, result);
}).add;
// Instantiate Open303 synth
bassline = Synth("AcidBass");
// MIDI functions
m_on = MIDIFunc.noteOn({ |vel, num, chan, src|
//"NOTEON note % @ velocity %\n".postf(num, vel);
bassline.set(\notenum, num);
bassline.set(\notevel, vel);
bassline.set(\noteevent, 1.0);
});
m_off = MIDIFunc.noteOff({ |vel, num, chan, src|
//"NOTEOFF note %\n".postf(num);
bassline.set(\notenum, num);
bassline.set(\notevel, 0.0);
bassline.set(\noteevent, 1.0);
});
m_waveform = MIDIFunc.cc({ |val, ccNum, chan, src|
bassline.set(\waveform, val / 127);
}, ccNum:21);
m_cutoff = MIDIFunc.cc({ |val, ccNum, chan, src|
bassline.set("cutoff", val / 127);
}, ccNum:22);
m_resonance = MIDIFunc.cc({ |val, ccNum, chan, src|
bassline.set(\resonance, val / 127);
}, ccNum:23);
m_envmod = MIDIFunc.cc({ |val, ccNum, chan, src|
bassline.set(\envmod, val / 127);
}, ccNum:24);
m_decay = MIDIFunc.cc({ |val, ccNum, chan, src|
bassline.set(\decay, val / 127);
}, ccNum:25);
m_accent = MIDIFunc.cc({ |val, ccNum, chan, src|
bassline.set(\accent, val / 127);
}, ccNum:26);
m_volume = MIDIFunc.cc({ |val, ccNum, chan, src|
bassline.set(\volume, val / 127);
}, ccNum:27);
)
s.free;
I’ve noticed some note-on events haven’t been getting through, too.
Here’s the plugin implementation file:
// PluginOpen303.cpp
// toneburst (the_voder@yahoo.co.uk)
#include "SC_PlugIn.hpp"
// Include Open303 DSP header
#include "lib/Open303/Source/DSPCode/rosic_Open303.h"
// Global functions (for parameter scaling)
#include "lib/Open303/Source/DSPCode/GlobalFunctions.h"
#include "Open303.hpp"
static InterfaceTable* ft;
namespace Open303 {
// Instantiate Open303 object
rosic::Open303 o303;
// CONSTRUCTOR
Open303::Open303() {
// Initialize the state of member variables that depend on input aruguments
m_waveform = in0(WAVEFORM);
m_cutoff = in0(CUTOFF);
m_resonance = in0(RESONANCE);
m_envmod = in0(ENVMOD);
m_decay = in0(DECAY);
m_accent = in0(ACCENT);
m_volume = in0(VOLUME);
// Get Sample-Rate
srate = fullSampleRate();
// Set Open303 sample-rate
o303.setSampleRate(srate);
mCalcFunc = make_calc_function<Open303, &Open303::next>();
next(1);
}
// Control-rate loop
void Open303::next(int nSamples) {
// Control parameters
// (at Audio rate, the input values are supplied in a block of nSamples values (as floats).
// "in0" is the first value in the block (although all other values are probably the same))
// Note parameters. Synth expects ints
const int noteevent = static_cast<int>(in0(NOTEEVENT));
const int notenum = static_cast<int>(in0(NOTENUM));
const int notevel = static_cast<int>(in0(NOTEVEL));
const int notealloff = static_cast<int>(in0(NOTEALLOFF));
// Interpolated synth control parameters. Synth expects doubles, inputs are floats.
// Conversion functions from Globalfunctions.h
// Conversion function args: double linToLin(double in, double inMin, double inMax, double outMin, double outMax);
// Param conversion values copied from Open303VST.cpp
// All params 0.0 - 1.0 range
const float waveformParam = in0(WAVEFORM); // No scaling required (already in 0-1 range)
const float cutoffParam = linToExp(in0(CUTOFF), 0.0, 1.0, 314.0, 2394.0);
const float resonanceParam = linToLin(in0(RESONANCE), 0.0, 1.0, 0.0, 100.0);
const float envmodParam = linToLin(in0(ENVMOD), 0.0, 1.0, 0.0, 100.0);
const float decayParam = linToExp(in0(DECAY), 0.0, 1.0, 200.0, 2000.0);
const float accentParam = linToLin(in0(ACCENT), 0.0, 1.0, 0.0, 100.0);
const float volumeParam = linToLin(in0(VOLUME), 0.0, 1.0, -60.0, 0.0);
// Create interpolation slopes
SlopeSignal<float> slopedWaveform = makeSlope(waveformParam, m_waveform);
SlopeSignal<float> slopedCutoff = makeSlope(cutoffParam, m_cutoff);
SlopeSignal<float> slopedResonance = makeSlope(resonanceParam, m_resonance);
SlopeSignal<float> slopedEnvmod = makeSlope(envmodParam, m_envmod);
SlopeSignal<float> slopedDecay = makeSlope(decayParam, m_decay);
SlopeSignal<float> slopedAccent = makeSlope(accentParam, m_accent);
SlopeSignal<float> slopedVolume = makeSlope(volumeParam, m_volume);
///////////////////
// Note-Handling //
///////////////////
if(noteevent == 1 && lastnoteevent == 0) {
// Check note velocity to determine if this is a note-on or note-off event
if(notevel > 0) {
// Note-ON
// 3rd arg is 'detune'
o303.noteOn(notenum, notevel, 0.0);
} else {
// Note-OFF (note-on event with velocity 0, slightly confusingly)
o303.noteOn(notenum, 0, 0.0);
}
}
// Update previous note-event
lastnoteevent = noteevent;
// Detect all-notes-off trigger
if(notealloff == 1 && lastnotealloff == 0) {
o303.allNotesOff();
}
// Update previous all-note-off
lastnotealloff = notealloff;
//////////////////
// Audio Render //
//////////////////
// Create output buffer
float* outbuf = out(0);
// Fill output buffer with nSamples Open303 synth samples
for (int i = 0; i < nSamples; ++i) {
// Update synth params with interpolated value
// Cast floats to doubles
o303.setWaveform( static_cast<double>(slopedWaveform.consume()));
o303.setCutoff( static_cast<double>(slopedCutoff.consume()));
o303.setResonance( static_cast<double>(slopedResonance.consume()));
o303.setEnvMod( static_cast<double>(slopedEnvmod.consume()));
o303.setDecay( static_cast<double>(slopedDecay.consume()));
o303.setAccent( static_cast<double>(slopedAccent.consume()));
o303.setVolume( static_cast<double>(slopedVolume.consume()));
// Call Open303 render function
outbuf[i] = o303.getSample();
}
////////////////////////
// Update Param State //
////////////////////////
m_waveform = slopedWaveform.value;
m_cutoff = slopedCutoff.value;
m_resonance = slopedResonance.value;
m_envmod = slopedEnvmod.value;
m_decay = slopedDecay.value;
m_accent = slopedAccent.value;
m_volume = slopedVolume.value;
}
} // Close namespace Open303
PluginLoad(Open303UGens) {
// Plugin magic
ft = inTable;
registerUnit<Open303::Open303>(ft, "Open303", false);
}
And the SuperCollider class file:
Open303 : UGen {
// Required parameters:
// Note number: int 0-127 (MIDI note number)
// Note velocity: int 0-127 (MIDI note velocity. Sending velocity = 0 is note-off)
// 'noteevent' parameter going positive will trigger a new Open303 note event (noteOn or noteOff)
// noteevent must go positive for only one k-rate cycle for each note-on/off received!!
// Defaults values from Open303VST.cpp starting at line 14
*ar { | noteevent = 0.0, notenum = 60.0, notevel = 64.0, notealloff = 0.0, waveform = 0.85, cutoff = 0.229, resonance = 0.5, envmod = 0.25, decay = 0.5, accent = 0.5, volume = 0.9 |
^this.multiNew('audio', noteevent, notenum, notevel, notealloff, waveform, cutoff, resonance, envmod, decay, accent, volume);
}
checkInputs {
^this.checkValidInputs;
}
}