Mono Legato Voice Mechanism Sanity-Check

I’ve been trying to work out a basic last-note-priority legato voicing setup, and wondered if someone could take a moment to check this chunk of code makes some kind of sense…

(
var counter = 0;
var notes = Array.fill(128, {-1});

var voicer = { arg gate, note;
	var previousnote,
	result = Dictionary[\note -> 0, \gate -> false, \slide -> 0];

	// Find array index containing highest value (most recent note)
	var highestval = {
		var tmp = -1;
		notes.do({ arg item, i;
			if(item > tmp, {tmp = i});
		});
		tmp;
	};

	if( gate === 1, {
	// Note-on. We need to know if there is already one of more notes playing

		// Gate is always going to be high on a note-on
		result[\gate] = 1;

		// Always set note to incoming note no.
		result[\note] = note;

		// Now we need to determine if the incoming note is legato

		// Get previous note index
		// This corresponds to the array index containing the highest value in the notes array
		previousnote = highestval.value();

		// Highest note index will be -1 if no other notes are playing
		if( previousnote === -1, {
			// Set gate slide low
			result[\slide] = 0;
		}, {
		// Else this is a legato note, hold gate high and set slide high
			result[\slide] = 1;
		});

		// Increment note-counter
		counter = counter + 1;

		// Set note-counter value at index note-number in notes array
		notes[note] = counter;
    }, {
	// Note-off. We need to find if other notes are still being held here, too

		// Unset just-released note in notes array
		notes[note] = -1;

		// Get previous note
		previousnote = highestval.value();

		// Highest note index will be -1 if no other notes are held
		if( previousnote === -1, {
			// Set gate and slide low
			result[\gate] = 0;
			result[\slide] = 0;
			// Keep note at previous value to prevent weirdness on release
			result[\note] = note;
		}, {
		// Else this is a legato note, hold gate high and set slide high
			result[\gate] = 1;
			result[\slide] = 1;
			// Slide to next most recent note
			result[\note] = previousnote;
		});
    });

	result.postln;
};

// MIDI setup
MIDIClient.init;
MIDIIn.connectAll;

MIDIdef.noteOn(\noteon, { |vel, num|
	voicer.value(1, num);
});

MIDIdef.noteOff(\noteoff, { |vel, num|
	voicer.value(0, num);
});

)

I’d love to know if there’s a better way to do it. Keeping a 128-element array seems a bit OTT, and maybe some kind of ring-buffer type setup with a much shorter array would make more sense. I’m not sure how I’d approach that, however.

Also, a more basic SCLang question: is it possible to declare persistent variables within a function?

I feel it would be neater to tuck my notes array and counter inside the function declaration, but their state doesn’t seem to persist between calls to the function, if I do that.

I realise this isn’t how functions work in traditional functional programming, but since “everything’s an object” in SC, I thought maybe there was some way to make functions work in a more “object-ey” way.

Any/all comment appreciated.

When I want functions with some persistent state I usually use an Event as a pseudoObject…

Of course you could just write a class already…

1 Like

If it’s mono, then there’s only one synth, so a 128-element array is overkill.

I would:

(
var synth;
var activeFreqs = Array.new;

MIDIdef.noteOn({ |vel, note|
	var freq = note.midicps;
	if(activeFreqs.isEmpty) {
		synth = Synth(\defname, [freq: freq, ... other args ...]);
	} {
		synth.set(\freq, freq);
	};
	activeFreqs = activeFreqs.add(freq);
});

MIDIdef.noteOff({ |vel, note|
	var freq = note.midicps;
	activeFreqs.remove(freq);
	if(activeFreqs.isEmpty) {
		synth.release;
	} {
		synth.set(\freq, activeFreqs.last);
	};
});
)

Nope. Functions are functions; object instances are object instances.

hjh

1 Like

Thanks both!

@jamshark70 your method is certainly simpler! I’ll give it a go! Thank you.