Here is the latest version of my midi processing script, which you might find useful.... It gives control over transposition, noterange, etc, so it functions like a master keyboard:
Code: Select all
(*/////////////////////////////////////////////////////
// CHANNEL
// Version 2010-03-28; 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
// 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 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 pTransposeIN, 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 isLearning, isDroning, doRelease, bypassReady : boolean;
/////////////////////////// INITIALIZE /////////////////////////////////////
PROCEDURE init;
VAR i : integer;
BEGIN
transpose := 0; hiVal:= 120; lowVal := 12;
passCount := 0; rejectCount := 0; learnCount := 0; minKey := 128; maxKey := 0; outChan := 1;
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);
pTransposeIN := CreateParam('Transpose',ptDataFader); SetIsOutPut(pTransposeIN,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(pTransposeIN,'%.0f'); SetMin(pTransposeIN,-48); SetMax(pTransposeIN,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;
////////////////////////// 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
// pass PB and AT with notes
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;
IF (midi.data1 > maxKey) THEN maxKey := midi.data1;
IF (midi.data1 < minKey) THEN minKey := midi.data1;
END;
END;
PROCEDURE ProcessNoteOff(midi : Tmidi);
BEGIN
midi.data1 := midi.data1 + transList[midi.data1];
heldNotes[midi.data1] := FALSE;
PassOut(midi);
IF bypassReady AND isClear() THEN BEGIN
setValue(pBypassOut, 1);
isDroning := FALSE;
bypassReady := FALSE;
END;
END;
PROCEDURE ProcessDrone(n : integer);
BEGIN
IF n = 0 THEN SendNoteOffs(TRUE);
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)));
pTransposeIN : 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;