Open giuliomoro opened 5 years ago
1) yes, polyphonic voice stealing algorithm (in https://github.com/grame-cncm/faust/blob/master-dev/architecture/faust/dsp/poly-dsp.h) is not enough sophisticated to handle correctly this monophonic case. I'll try to have a look ASAP, feel free to hack something.
2) declare options "[midi:on][nvoices:1];
stuff is already documented, but not yet fully implemented ))-; (Romain is too fast here...)
Yes, Faust just does basic voice stealing, which with nvoices=1 turns off any sounding note, which is consistent with the behavior for nvoices > 1.
The voice allocation might be improved, but that will require more sophisticated data structures than what we have now. Can you point to any online sources about the voice allocation methods that you mention? I'd be willing to look into this some time, since I'd also like to add proper multi-channel support to the polyphony component in the (hopefully) not-too-distant future.
hmm, I naively implemented it in Pd once, storing one value per note in an array, assigning a count++
to the note at NoteOn, and -1 at NoteOff. Then every time there was a noteOff coming in, I'd check the array for the largest value. This requires finding the max of the array for each noteoff, so it may carry a bit of memory overhead (we are talking of an array of 128 ints, so not much stuff). Additionally, if your count
variable overflows, you are screwed. This is unlikely to ever happen (and it could be reset to 0 any time there is no NoteOn), but still not very elegant.
A doubly linked list is probably the way to go?
A doubly linked list is probably the way to go?
I think that this is too slow. Linear search might be ok with a small amount of input events, but that's not guaranteed here. Remember that the voice allocation usually runs in a real-time callback under very tight timing constraints, and AFAIK the Grame implementation also does sample-accurate timing of MIDI events, so the callback might be invoked any number of times for each dsp tick. So we probably need some kind of priority queue data structure which can be accessed and updated in O(1) or at worst O(log n) time in order to not bog down the real-time thread when a lot of notes are received at the same time.
It would be great to have this. Any chance?
completely agree!
@giuliomoro Thanks for the description of your pd algorithm. I implemented it in faust, also using 2 lines of code by @josmithiii :
declare author "Bart Brouns";
declare license "GPLv3";
declare name "lastNote";
declare options "[midi:on]";
import("stdfaust.lib");
///////////////////////////////////////////////////////////////////////////////
// give the number of the last note played //
///////////////////////////////////////////////////////////////////////////////
process =
os.osc(lastNote:ba.pianokey2hz)
// increases the cpu-usage, from 7% to 11%
// * (vel(lastNote)/127)
// no velocity:
* (nrNotesPlaying>0)
;
nrNotesPlaying = 0: seq(i, nrNotes, noteIsOn(i),_:+);
noteIsOn(i) = velocity(i)>0;
vel(x) = par(i, nrNotes, velocity(i)*(i==x)):>_ ;
velocity(i) = hslider("velocity of note %i [midi:key %i ]", 0, 0, nrNotes, 1);
nrNotes = 127; // nr of midi notes
// nrNotes = 32; // for block diagram
lastNote = par(i, nrNotes, i,index(i)):find_max_index(nrNotes):(_,!)
with {
// an index to indicate the order of the note
// it adds one for every additional note played
// it resets to 0 when there are no notes playing
// assume multiple notes can start at once
orderIndex = ((_+((nrNotesPlaying-nrNotesPlaying'):max(0))) * (nrNotesPlaying>1))~_;
// the order index of note i
// TODO: when multiple notes start at the same time, give each a unique index
index(i) = orderIndex:(select2(noteStart(i),_,_)
:select2(noteEnd(i)+(1:ba.impulsify),_,-1))~_;
// we use this instead of:
// hslider("frequency[midi:keyon 62]",0,0,nrNotes,1)
// because keyon can come multiple times, and we only want the first
noteStart(i) = noteIsOn(i):ba.impulsify;
noteEnd(i) = (noteIsOn(i)'-noteIsOn(i)):max(0):ba.impulsify;
//or do we?
// noteStart(i) = (hslider("keyon[midi:keyon %i]",0,0,nrNotes,1)>0) :ba.impulsify;
// at the very least, the first implementation of noteStart(i) doesn't add another 127 sliders
// from Julius Smith's acor.dsp:
index_comparator(n,x,m,y) = select2((x>y),m,n), select2((x>y),y,x); // compare integer-labeled signals
// take N number-value pairs and give the number with the maximum value
find_max_index(N) = seq(i,N-2, (index_comparator,si.bus(2*(N-i-2)))) : index_comparator;
};
It works great, though it's a bit CPU-hungry. Oddly enough the velocity function (commented out) increases the CPU usage quite a lot too.
Another downside is that there are 127 sliders on screen. Any chance of this, or something like it, being included in the compiler?
Or maybe it should go in the libraries, but in that case I think the compiler should add the option of [hidden]
metadata to the gui-elements, so you can hide them.
Thanks for developing and testing, but I think it should go in a improve voice stealing algo in the C++ code...
Yeah, that is what I meant by "including it in the compiler". This is just a workaround / proof of concept.
I hope you'll get around to it soon! :)
use
faust2xxx
(e.g.:bela
,caqt
) with-midi -nvoices 1
and you will get the following behaviour:This may be expected behaviour, as the docs say
but I'd argue this is not desirable behaviour: who would want to find themselves still holding down a note and not hearing anything? Analog monosynths typically had "high" or "low" note priority, meaning the currently pressed highest (or lowest) note would be the one you'd hear. Later on, digital keyboard scanning came into play and some synthesizers started providing the "most recent" note on: the latest note being pressed (B) would be the one you'd hear, and when releasing it, while still holding down an old note A, you'd go back to hearing A. Are any of these behaviours available in FAUST?
example file:
built with
faust2caqt -midi -nvoices 1 poly.dsp
note that the above file (mostly taken from https://faust.grame.fr/doc/manual/index.html#midi-polyphony-support ), does actually require the
-nvoices
command-line switch to build, even though the page says it shouldn't need it.