Well, I feel like I’ve wasted everyone’s time a bit, because I’ve got it working (finally!!), but the setup is very similar to the monosynth example waaay upthread.
I wasn’t quite happy with the way the example responded to released legato notes though, so I created this variation, which seems to work perfectly with the updated note-handling setup in my plugin, similar to that suggested by @Spacechild1 above.
Here’s the latest SCLang script:
( // SynthDef
s.waitForBoot {
SynthDef.new("AcidBass", {
arg out,
gate = 0.0,
notenum = 60.0,
notevel = 64.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 notealloff = NamedControl.tr(\notealloff);
// Create output
var result;
result = Open303.ar(gate, notenum, notevel, notealloff, waveform, cutoff, resonance, envmod, decay, accent, volume);
// Output output
Out.ar(out, result);
}).add;
};
)
( // Start it
// Create note-stack list
~notestack = List[ ];
// Create bassline synth object
~bassline = Synth(\AcidBass);
// Start MIDI
MIDIClient.init;
MIDIIn.connectAll;
// MIDI functions
MIDIFunc.noteOn({ |vel, num|
// Add new note to end of note-stack
~notestack.add(num);
// If note-stack size is now 1, this is a non-legato note
if (~notestack.size == 1) {
// Switch gate high and update synth MIDI note index and velocity. Synth will play note
//postf("SCLANG NOTEON % STACK SIZE % STACK % \n", num, ~notestack.size, ~notestack);
~bassline.set(\gate, 1.0, \notenum, num, \notevel, vel);
} {
// ...else this is a legato note
// Hold gate high and update synth note number and velocity. Synth will slide to new note
//postf("SCLANG SLIDETO % STACK SIZE % STACK % \n", num, ~notestack.size, ~notestack);
~bassline.set(\gate, 1.0, \notenum, num, \notevel, vel);
}
});
MIDIFunc.noteOff({ |vel, num|
// Seach for note index in note-stack and remove
~notestack.do({ arg item, i; if (item == num) { ~notestack.removeAt(i); }});
// note-stack could sorted at this point to add lowest/highest-note priority legato release response
// Check if this we've just released the last held note
if (~notestack.size == 0) {
// ...we have. Pull gate low and send note index to synth (velocity not required). Synth will release note
//postf("SCLANG LAST NOTE OFF % STACK SIZE % STACK % \n", num, ~notestack.size, ~notestack);
~bassline.set(\gate, 0.0, \notenum, num);
} {
// Update synth with most recent note index remaining in note-stack. Synth will slide back to note
//postf("SCLANG SLIDETO % STACK SIZE % STACK % \n", ~notestack.last, ~notestack.size, ~notestack);
~bassline.set(\gate, 1.0, \notenum, ~notestack.last);
}
});
MIDIFunc.cc({ |val, ccNum, chan, src|
~bassline.set(\waveform, val / 127);
}, ccNum:21);
MIDIFunc.cc({ |val, ccNum, chan, src|
~bassline.set(\cutoff, val / 127);
}, ccNum:22);
MIDIFunc.cc({ |val, ccNum, chan, src|
~bassline.set(\resonance, val / 127);
}, ccNum:23);
MIDIFunc.cc({ |val, ccNum, chan, src|
~bassline.set(\envmod, val / 127);
}, ccNum:24);
MIDIFunc.cc({ |val, ccNum, chan, src|
~bassline.set(\decay, val / 127);
}, ccNum:25);
MIDIFunc.cc({ |val, ccNum, chan, src|
~bassline.set(\accent, val / 127);
}, ccNum:26);
MIDIFunc.cc({ |val, ccNum, chan, src|
~bassline.set(\volume, val / 127);
}, ccNum:27);
)
// Stop it
~bassline.free;
MIDIdef.freeAll;
I’ve implemented a dynamic note-stack where the most recent note is always at the last index, which I think is how Pichenettes’ Shruthi-1 legato note-handling works. If a key is still being held when a note is released, the synth slides to the note at the top of the stack (the most recent remaining note).
The list could probably be sorted such that released legato notes slide back to the highest or lowest remaining notes. Using a SortedList instead of the standard List might achieve that quite easily.
I’m going to test this against my MB-33 and T-8 when I get a chance, but for the moment, I’m just happy it’s working reliably without stuck notes!
Here’s the relevant chunk of my Plugin code:
// New gate
if(gate && !lastgate) {
//cout << "PLUGIN NOTEON " << notenum << "\n";
o303.triggerNote(notenum, accent);
}
// Gate still high but note changed. Slide to new note
if((notenum != lastnotenum) && (gate && lastgate)) {
//cout << "PLUGIN SLIDETO " << notenum << "\n";
o303.slideToNote(notenum, accent);
}
// Last note off
if(lastgate && !gate) {
//cout << "PLUGIN LAST NOTE OFF " << notenum << "\n";
o303.releaseNote(notenum);
}
// Detect all-notes-off trigger
if(notealloff == 1 && lastnotealloff == 0) {
// Trigger synth all-notes-off
//cout << "PLUGIN ALL NOTES OFF\n"
o303.allNotesOff();
}
Thank you so much for all your advice, guys. I’ve learnt a lot! I’m sure I’ll be back soon with more Stupid Questions.
If anyone is interested in following this project, the repo is here.
There will eventually be an updated version of my bline Norns script featuring this plugin as an alternative internal sound-engine (assuming I can get this thing compiled on Norns’ Raspberry Pi hardware).