Posted: 06 Mar 2010, 04:19
I've finally got my midi channel strip script pretty solid, and the code is here for anyone who would like it.
I'm wondering, though (and this is mostly directed at Bsork) whether there are any obvious inefficiencies in it, as I'm trying to save processing power wherever I can....
Any tips appreciated as always!
I'm wondering, though (and this is mostly directed at Bsork) whether there are any obvious inefficiencies in it, as I'm trying to save processing power wherever I can....
Any tips appreciated as always!
Code: Select all
(*/////////////////////////////////////////////////////
// CHANNEL
// Version 2009-11-21; author: eric moon
//
// based on work by Bsork and amiga909
//////////////////////////////////////////////////////
*)
// MIDI Channel Strip:
// it is designed to function as part of a mixer channel strip, running between several keyboards and a vsti.
// takes a number of midi inputs (on separate cables, for visual clarity)
// and performs the following operations on each.
// enable, disable--per input
// strip CCs and send them out a separate output
// read CC64 values (or a CC of your choice) to withhold noteoffs until the sus pedal is released.
// limit to minimum and maximum notes--one range affects all inputs
// transpose--again, one range for all inputs.
// output rechannelizing--all outs are a single channel. Use multiple strips to control multi-timbral modules.
// bypass output for turning off unused instruments. waits for all notes to be released....
// Drone switch that keeps current notes sounding, but discards all note input as long as it is held
// Midi learn for note range:
// When learn is turned on, midi note output is suspended
// the first noteNumber sets the low end of the range, the second noteNumber sets the upper limit.
// once both notes are sent, notes go to main output as usual.
// support for inverse range, if low note is above high note
CONST SUS_PED= 64; //change if you use an unorthodox CC for sustaining.
CONST INPUT_COUNT = 2; // could handle any number, I think!
CONST NOTEON = Byte(144);
CONST NOTEOFF = Byte(128);
CONST CONTROL = Byte(176);
CONST PITCHBEND = Byte(224);
CONST AFTERTOUCH = Byte(208);
VAR heldNotes : ARRAY OF boolean;
VAR transList : ARRAY OF integer;
VAR isEnabled : Array of boolean;
VAR pMidiINS,pEnableINS : ARRAY OF TParameter;
VAR midiInCounts : ARRAY OF integer;
VAR pTranspIN, pDroneIN,pOutChanIN, pLearnIN, pLowNoteIn, pHiNoteIn : TParameter;
VAR pPassOut,pRejectOut,pBypassOut, pLowNoteOut, pHiNoteOut : Tparameter;
VAR transpose, hiVal, lowVal : integer;
VAR passCount, rejectCount, learnCount, minKey, maxKey, outChan : integer;
VAR isSustaining, isLearning, isDroning, doRelease, bypassReady : boolean;
/////////////////////////// INITIALIZE /////////////////////////////////////
PROCEDURE init;
VAR i : integer;
BEGIN
transpose := 0; transpose := 0; hiVal:= 120; lowVal := 12;
passCount := 0; rejectCount := 0; learnCount := 0; minKey := 128; maxKey := 0; outChan := 1;
isSustaining := FALSE; isLearning := FALSE; isDroning := FALSE; doRelease := FALSE; bypassReady := FALSE;
setArrayLength(pMidiINS, INPUT_COUNT);
setArrayLength(pEnableINS, INPUT_COUNT);
setArrayLength(isEnabled, INPUT_COUNT);
setArrayLength(midiInCounts, INPUT_COUNT); FOR i:=0 TO INPUT_COUNT DO midiInCounts[i]:=0;
setArrayLength(heldNotes, 128); FOR i:=0 TO 127 DO heldNotes[i]:=FALSE;
SetArrayLength(transList, 128); FOR i:=0 TO 127 DO transList[i]:=0;
FOR i := 0 TO INPUT_COUNT - 1 DO BEGIN
pEnableINS[i] := CreateParam('enable ' + intToStr(i + 1),ptSwitch);
SetIsOutPut(pEnableINS[i],false);
END;
pBypassOut := CreateParam('inst bypass', ptSwitch); SetIsInput(pBypassOut,false);
pTranspIN := CreateParam('trans',ptDataFader); SetIsOutPut(pTranspIN,false);
pOutChanIN := CreateParam('out ch',ptMidiNoteFader); SetIsOutPut(pOutChanIN,false);
pLearnIN := CreateParam('learn',ptButton); SetIsOutPut(pLearnIN,false);
pHiNoteIN := CreateParam('high note',ptMidiNoteFader); SetIsOutPut(pHiNoteIN,false);
pHiNoteOUT := CreateParam('hi note', ptDataField); SetIsInPut(pHiNoteOut,false);
pLowNoteIN := CreateParam('low note',ptMidiNoteFader); SetIsOutPut(pLowNoteIN,false);
pLowNoteOUT := CreateParam('low note',ptDataField); SetIsInPut(pLowNoteOut,false);
pDroneIN := CreateParam('drone', ptSwitch); SetIsOutput(pDroneIN,false);
SetFormat(pTranspIN,'%.0f'); SetMin(pTranspIN,-48); SetMax(pTranspIN,48);
SetFormat(pOutChanIN,'%.0f'); SetMin(pOutChanIN,1); SetMax(pOutChanIN,16);
FOR i := 0 TO INPUT_COUNT - 1 DO BEGIN
pMidiINS[i] := CreateParam('MIDIin ' + intToStr(i + 1) ,ptMidi);
SetIsOutPut(pMidiINS[i],false);
END;
pPassOut := CreateParam('passed MIDI',ptMidi); SetIsInput(pPassOut,false);
pRejectOut := CreateParam('rejected MIDI',ptMidi); SetIsInput(pRejectOut,false);
END;
/////////////////////////// MIDI OUTPUT METHODS ////////////////////////////////////
PROCEDURE PassOut(midi : tMidi);
BEGIN
midi.channel := BYTE(outChan);
SetMidiArrayValue(pPassOut, passCount, midi);
passCount := passCount + 1;
END;
PROCEDURE RejectOut(midi : tMidi);
BEGIN
midi.channel := BYTE(outChan);
SetMidiArrayValue(pRejectOut, rejectCount, midi);
rejectCount := rejectCount + 1;
END;
///////////////////////// MIDI MESSAGE TESTS ///////////////////////////////////////
FUNCTION IsNote (midi : tMidi) : Boolean;
BEGIN
IsNote := ((midi.msg = NOTEON) OR (midi.msg = NOTEOFF));
END;
FUNCTION IsNoteOn (midi : tMidi) : Boolean;
BEGIN
IsNoteOn := ((midi.msg = NOTEON) AND (midi.data2 > 0));
END;
FUNCTION IsNoteOff (midi : tMidi) : Boolean;
BEGIN
IsNoteOff := (midi.msg = NOTEOFF) OR ((midi.msg = NOTEON) AND (midi.data2 = 0));
END;
FUNCTION IsSustain (midi : tMidi) : Boolean;
BEGIN
IsSustain := (midi.msg = CONTROL) AND (midi.data1 = SUS_PED);
END;
////////////////////////// OTHER TESTS ////////////////////////////////////////
FUNCTION UpdateBypass() : INTEGER;
VAR i : integer;
BEGIN
bypassReady := TRUE;
FOR i := 0 to (INPUT_COUNT - 1) DO BEGIN
if isEnabled[i] THEN BEGIN
bypassReady := FALSE;
setValue(pBypassOut, 0);
END;
END;
IF bypassReady AND isClear() THEN setValue(pBypassOUT, 1);
END;
FUNCTION isInRange(noteNumber : integer ) : Boolean;
VAR inRange : Boolean;
VAR reversed : Boolean;
BEGIN
reversed := (lowVal >= hiVal);
IF reversed THEN isInRange := (noteNumber < hiVal) OR (noteNumber > lowVal)
ELSE isInRange := (noteNumber <= hiVal) AND (noteNumber >= lowVal);
END;
//////////////////////// SETTERS ////////////////////////////////////////////
PROCEDURE SetLowVal(n : integer);
BEGIN
setValue(pLowNoteOut, n);
lowVal := n;
END;
PROCEDURE SetHiVal(n : integer);
BEGIN
setValue(pHiNoteOut, n);
hiVal := n;
END;
//////////////////////// PROCESS MIDI ////////////////////////////////////////////
FUNCTION bstr(b : boolean) : string;
BEGIN if b THEN bstr := 'TRUE' else bstr := 'FALSE'; END;
PROCEDURE ProcessMidi(midi : tMidi; enabled : boolean);
BEGIN
IF isLearning THEN processLearn(midi)
ELSE IF not(isDroning) THEN BEGIN
IF isSustain(midi) THEN BEGIN
isSustaining := midi.data2 > 64;
if NOT(isSustaining) THEN doRelease := TRUE;
END
// pass PB and AT with notes
ELSE IF (midi.msg = PITCHBEND) OR (midi.msg = AFTERTOUCH) THEN passOut(midi)
ELSE IF NOT(IsNote(midi)) THEN rejectOut(midi)
// everything else deals with note data....
ELSE IF IsInRange(midi.data1)and enabled THEN BEGIN
IF isNoteOn(midi) THEN BEGIN
ProcessNoteOn(midi, enabled);
END ELSE BEGIN
ProcessNoteOff(midi)
END;
END ELSE rejectOut(midi); //notes that are out of range
IF doRelease THEN sendNoteOffs(false);
END;
END;
PROCEDURE ProcessLearn(midi : tMidi);
BEGIN
IF (isNoteOn(midi)) and (learnCount = 0) THEN BEGIN
lowVal := midi.data1;
setValue(pLowNoteOUT, lowVal);
learnCount := 1;
END
ELSE IF (isNoteOn(midi)) and (learnCount = 1) THEN BEGIN
hiVal := midi.data1;
setValue(pHiNoteOUT, hiVal);
learnCount := 0;
isLearning := FALSE; // we've set our outs....
END;
END;
PROCEDURE ProcessNoteOn(midi : tMidi; enabled : boolean);
BEGIN
IF enabled THEN BEGIN
transList[midi.data1] := transpose;
midi.data1 := midi.data1 + transpose;
PassOut(midi);
heldNotes[midi.data1] := TRUE;
END;
END;
PROCEDURE ProcessNoteOff(midi : Tmidi);
BEGIN
midi.data1 := midi.data1 + transList[midi.data1];
heldNotes[midi.data1] := FALSE;
IF isSustaining THEN storeNoteOff(midi) ELSE PassOut(midi);
IF bypassReady AND isClear() THEN BEGIN
setValue(pBypassOut, 1);
isSustaining := FALSE;
isDroning := FALSE;
bypassReady := FALSE;
END;
END;
PROCEDURE storeNoteOff(midi : tMidi);
BEGIN // is this all we're doing here? should this be in the method above??
IF (midi.data1 > maxKey) THEN maxKey := midi.data1;
IF (midi.data1 < minKey) THEN minKey := midi.data1;
END;
PROCEDURE ProcessDrone(n : integer);
BEGIN
IF n = 0 THEN SendNoteOffs(TRUE);
isSustaining := FALSE;
isDroning := n > 0;
END;
FUNCTION isClear() : boolean;
VAR key : integer;
VAR clear : boolean;
BEGIN
clear := TRUE;
FOR key:= minKey TO maxKey DO IF heldNotes[key] = TRUE THEN clear := FALSE;
isClear := clear;
END;
PROCEDURE SendNoteOffs(closeAll : boolean);
VAR key : integer;
VAR midi : tMidi;
BEGIN
FOR key:= minKey TO maxKey DO BEGIN
IF (heldNotes[key] = FALSE) OR closeAll THEN BEGIN
midi.msg := NOTEOFF;
midi.channel := Byte(outChan);
midi.data1 := key;
midi.data2 := 0;
passOut(midi);
heldNotes[key] := FALSE;
END;
END;
doRelease := FALSE;
END;
//////////////////////// CALLBACK /////////////////////////////////////////////
PROCEDURE Callback(n: integer);
VAR i : integer;
BEGIN
FOR i := 0 to INPUT_COUNT - 1 DO BEGIN
CASE n OF
pEnableINS[i] : BEGIN
isEnabled[i] := getValue(n) > 0;
UpdateBypass();
END;
pMidiINS[i] : midiInCounts[i] := GetLength(n);
END;
END;
CASE n OF
pLearnIN : isLearning := TRUE;
pOutChanIN : outChan := Byte(trunc(getValue(n)));
pTranspIN : transpose :=(trunc(getValue(n)));
pHiNoteIN : setHiVal(trunc(getValue(n)));
pLowNoteIN : setLowVal(trunc(getValue(n)));
pDroneIN : ProcessDrone(trunc(getValue(n)));
END;
END;
///////////////////////// PROCESS //////////////////////////////////////////
PROCEDURE Process;
VAR i, inputNum : Integer;
VAR currMsg : tMidi;
BEGIN
FOR inputNum := 0 TO (INPUT_COUNT - 1) DO BEGIN
FOR i := 0 TO (midiInCounts[inputNum] - 1) DO BEGIN
GetMidiArrayValue(pMidiINS[inputNum], i, currMsg);
ProcessMidi(currMsg, isEnabled[inputNum]);
END;
END;
SetLength(pPassOut, passCount);
SetLength(pRejectOut, rejectCount);
passCount := 0;
rejectCount := 0;
END;