[chuck-users] polyphony (what happens after note off)? was: Killing thread from without

Kassen signal.automatique at gmail.com
Sun Apr 26 14:15:04 EDT 2009


I wrote;

> Well, as you are basically using a single STKInstrument per voice here
> I don't think there is a need to have a shred per voice at all in this
> case. Just a array of a dozen or so UGens with some infrastructure to
> keep track of which ones are free and similar voice-cycling
> infrastructure should do the trick. That would mean a single shred and
> no garbage at all.

I'm pasting a edit of the last version Hans send to the list based on
this strategy below.

the basic strategy is starting a few voices, hopefully a amount our
cpu can take. When a new key is pressed it will be assigned to the
voice that already had that voice linked to it. If no such voice is
found it'll take the voice that's not currently playing and gone
longest since the last note off. When all voices are currently playing
it'll steal the one that has gone longest since the last note on.

No sporking and probably no garbage (I didn't verify the second part
of that claim).

There are likely some issues with this; while the strategy should be
sound I wrote this quite quickly. I also changed the indentation style
after getting confused about a "else" in the main loop. This is
roughly the strategy hardware synths take; the amount of cpu used
should be more or less static, clearly something more advanced is in
order when we need to run several instruments on the same computer but
I took this setup to be meant for a solo instrument.

No warranties, no refunds, please mind your speakers and neighbours.

Cheers,
Kas.


//======================8<=============================
 //set this to what your computer can take
5 => int NR_OF_VOICES;

// The device number to open
1 => int deviceNum;

//voice cycling by Kas, everything else by Hans 2009

/*
Example of a generalized diatonic (or extended mentone)
key map. Transposition takes place by keyboard translation,
so each scale and chord need only one fingering pattern.

The keyboard layout is altering pitches as follows:
           ^ #
          /
         . -> M
        / \
     b v   v m
where M (resp. m) is the major (resp. minor) second, and the
the sharp # (resp. flat b) raises (resp lower) with the interval
M - m, that is, the difference between the major and minor seconds.

Resulting key pattern:
      C#  D#  E#
    C   D   E   F#  G#  A#  B#
  Cb  Db  Eb  F   G   A   B
            Fb  Gb  Ab  Bb  C'

M is set to 2^(5/31) and m = 2^(3/31), the E31 approximation of
Renaissance meantone.

Middle A = 440 Hz, and is in US keyboard layout on key "J".
*/



// keyboard
Hid kb;
// hid message
HidMsg msg;

// Open keyboard.
if(!kb.openKeyboard(deviceNum)) me.exit();
// Successful! Print name of device.
<<< "Keyboard '", kb.name(), "' ready." >>>;

// Key x-axis coordinate
int x[256];
 0 => x[100] => x[53];
 1 => x[41] => x[49] => x[30] => x[20] => x[4] => x[29];
 2 => x[58] => x[31] => x[26] => x[22] => x[27];
 3 => x[59] => x[32] => x[8]  => x[7]  => x[6];
 4 => x[60] => x[33] => x[21] => x[9]  => x[25];
 5 => x[61] => x[34] => x[23] => x[10] => x[5];
 6 => x[62] => x[35] => x[28] => x[11] => x[17];
 7 => x[63] => x[36] => x[24] => x[13] => x[16];
 8 => x[64] => x[37] => x[12] => x[14] => x[54];
 9 => x[65] => x[38] => x[18] => x[15] => x[55];
10 => x[66] => x[39] => x[19] => x[51] => x[56];
11 => x[67] => x[45] => x[47] => x[52] => x[229];
12 => x[68] => x[46] => x[48] => x[49];

// Key y-axis coordinate
int y[256];
0 => y[49] 	=> y[58] => y[59] => y[60] => y[61] => y[62] => y[63] =>
y[64] => y[65] => y[66] => y[67] => y[68] => y[69];
1 => y[100]	=> y[30] => y[31] => y[32] => y[33] => y[34] => y[35] =>
y[36] => y[37] => y[38] => y[39] => y[45] => y[46];
2 => y[20] 	=> y[26] => y[8]  => y[21] => y[23] => y[28] => y[24] =>
y[12] => y[18] => y[19] => y[47] => y[48];
3 => y[4] 	=> y[22] => y[7]  => y[9]  => y[10] => y[11] => y[13] =>
y[14] => y[15] => y[51] => y[52] => y[49];
4 => y[53] 	=> y[29] => y[27] => y[6]  => y[25] => y[5]  => y[17] =>
y[16] => y[54] => y[55] => y[56] => y[229];

// Key serving as origin
7 => int x0;
3 => int y0;

int init[256];

// Tuning frequency
440.0 => float f0;

Voice voices[NR_OF_VOICES];

JCRev r => dac;
r => Echo e => Echo e2 => dac;

// set delays
240::ms => e.max => e.delay;
480::ms => e2.max => e2.delay;
// set gains
.6 => e.gain;
.3 => e2.gain;
.05 => r.mix;

for(int n; n < NR_OF_VOICES; n++)
	{
	voices[n].signal => r;
	1 => voices[n].signal.noteOff;
	}

while(true)
{
  kb => now;

  while(kb.recv(msg))
  {
    // Print.
	/* not printing as writing to the screen can cause a delay as debated
on the list
	
    if(msg.isButtonDown())
      <<< " key", msg.which, "down (", x[msg.which], ",", y[msg.which], ")" >>>;
    else
      <<< " key", msg.which, "up (", x[msg.which], ",", y[msg.which], ")" >>>;
	*/
    // Play a note.
    if (!((x[msg.which] == 0) && (y[msg.which] == 0)))
		{
		if(msg.isButtonDown())
			{
			int assigned;
			
			//check whether one of our voices already had this pitch assigned
			for(int n; n< NR_OF_VOICES; n++)
				{
				if (voices[n].key == msg.which)
					{
					f0 * Math.pow(2, (5*(x[msg.which] - x0) + 3*(y[msg.which] -
y0))/31.0) => voices[n].signal.freq;
					1 			=> voices[n].playing;
					msg.which 	=> voices[n].key;
					now 		=> voices[n].last_NoteOn;
					1 			=> voices[n].signal.noteOn;
					1 => assigned;
					break;
					}
				}
				
			//otherwise take the voice that has been silent for longest.
			if(!assigned)
				{
				now => time voice_age;
				int oldest_voice;
				
				for (int n; n< NR_OF_VOICES; n++)
					{
					if ( !voices[n].playing && voices[n].last_NoteOff < voice_age)
						{
						voices[n].last_NoteOff => voice_age;
						n => oldest_voice;
						}
					}
				f0 * Math.pow(2, (5*(x[msg.which] - x0) + 3*(y[msg.which] -
y0))/31.0) => voices[oldest_voice].signal.freq;
				1 			=> voices[oldest_voice].playing;
				msg.which 	=> voices[oldest_voice].key;
				now 		=> voices[oldest_voice].last_NoteOn;
				1 			=> voices[oldest_voice].signal.noteOn;
				1 => assigned;
				}
				
			//if that too fails we'll need to "steal" a voice
			//we'll take the one that has been playing for the longest time
			//"real" synths tend to reserve the highest and/or lowest pitch
			//such cleverness is left as a excersise for the reader
			if(!assigned)
				{
				now => time voice_age;
				int oldest_voice;
				
				for (int n; n< NR_OF_VOICES; n++)
					{
					if ( voices[n].last_NoteOff < voice_age)
						{
						voices[n].last_NoteOff => voice_age;
						n => oldest_voice;
						}
					}
				f0 * Math.pow(2, (5*(x[msg.which] - x0) + 3*(y[msg.which] -
y0))/31.0) => voices[oldest_voice].signal.freq;
				1 			=> voices[oldest_voice].playing;
				msg.which 	=> voices[oldest_voice].key;
				now 		=> voices[oldest_voice].last_NoteOn;
				1 			=> voices[oldest_voice].signal.noteOn;
				1 => assigned;
				}
			}
		else
			{
			for (int n; n< NR_OF_VOICES; n++)
				{
				if(voices[n].playing && voices[n].key == msg.which)
					{
					1 => voices[n].signal.noteOff;
					now => voices[n].last_NoteOff;
					0 => voices[n].playing;
					}
				}	
			}
		}
	else
		{
		<<<"presumably keys reserved for control go here?", "">>>;
		}
	}
}

class Voice
	{
	BeeThree signal;
	int playing;
	int key;
	time last_NoteOn;
	time last_NoteOff;
	}


More information about the chuck-users mailing list