got a problem with this (unfinished) script here.
when I compile it the midi in input pin is not generated. (using v.4.01)
further I get some cpu lags and freezes but this could be caused by my shitty office computer.
and yes, I hope this script will have some practical use
thaumat.org/tmp/_transfer/MEP.0.037.script
Code: Select all
(* MEP 0.037
// 2008-11-29: @amiga909
// credits to: bSork, senso (www.sensomusic.com/forums)
// <Description>
// MEP: midi tool roughly inspired by Yamaha MEP-4 Midi Event Processor.
download the original MEP-4 manual here:
http://www2.yamaha.co.jp/manual/pdf/emi/english/synth/MEP4E.PDF
// Features: prevent hanging notes, swap data bytes, convert to notes or system messages, ..
// Processing chain:
<input> ->
select -> // filter input (if midi thru on: pass filtered data)
inverse -> swapBytes -> // modify input bytes
expand -> transpose -> // calculate input bytes
wrap -> step(x%y) -> limit -> // limit/rescale input bytes
affectByte -> // modify output bytes
convert -> // change output type
<output> <-
// <Parameters>
// select event : select an event type for processing
// midi thru : output all other event types.
!{sysex, clock and activesensing are always filtered}
// affect byte : process first or second data byte or both. if only one
byte is selected, then the other byte is output unprocessed.
// inv. byte1/2 : inverse incoming data byte. high value -> low, low -> high.
// swap bytes : swap first and second byte (eg. convert velocity to noteOn).
// expand : choose a multiplication factor between 0.0625 to 16
// transpose : add or subtract value
// wrap around : if a value as a result of expanding or transposing is not between 1 and 128,
and if wrap around is off: result is 128 or 1,
if wrap around is on: (1-1=128;128+1=1;).
// step : only messages with a data byte that is a multiple of the Step
will be allowed through.
// limit low/hi : specify the output data range.
use a range fader (interface design) for control.
// limit mode : limit hard limits the output and filters values out of range
scale rescales to desired range: value/128 => value/newRange
// convert : transform input event to CC, PC, PitchBend or Aftertouch
// <Notes>
Aftertouch, PC and other messages are not tested.
*)
VAR pIn, pOut, pMult, pEvent, pDest, pWrap,pThru, pSwap,
pInv1, pInv2, pTransp, pNotesCount,
pStep, pLimitHi, pLimitLo, pLimit, pConv : TParameter;
TYPE TMult = ARRAY OF integer;
VAR mult : ARRAY OF TMult;
VAR pMultAr: ARRAY OF double;
TYPE TBool = ARRAY OF boolean; // convert -> notes
VAR notesList: ARRAY OF TBool;
VAR tmp: TMidi;
VAR multiply: double;
VAR len, i, tmpX ,tmpY, tmpXX, tmpDest,tmpMsg, transpose, limit, convert,
tmpEvent,limitLo, limitHi, tmpLimitLo, step, notesCount,filterByte,lenCount: integer;
VAR isNoteOff, isNoteOn, isSwap, isThru,isWrap, isStepActive,
isInv1,isInv2,isConvert,isLimit,isFirstByte,isSecondByte: boolean;
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^//
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^//
PROCEDURE Init;
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^//
VAR f_c1,f_c2,f_c3: integer;
BEGIN
pIn := createParam('midi in', ptMidi);
pOut := createParam('midi out', ptMidi);
pEvent := createParam('select', ptListBox);
setListBoxString(pEvent,
'"ALL","Note On+Off", "Control Change", "Program Change", "Pitch Bend", "Aftertouch"');
pInv1 := createParam('inverse byte1', ptSwitch);
pInv2 := createParam('inverse byte2', ptSwitch);
pSwap := createParam('swap bytes', ptSwitch);
pMult := createParam('expand', ptListBox);
setListBoxString(pMult,
' "-bypass-", "1/16", "1/8", "1/4", "1/2","1","2","4","8","16"');
pMultAr:=[1, 0.0625, 0.125, 0.25, 0.5, 1, 2, 4, 8, 16];
pTransp := createParam('transpose',ptDataFader);setMin(pTransp,-128);setMax(pTransp,127);
setSymbol(pTransp, '');setFormat(pTransp,'-bypass-');
pStep := createParam('step', ptDataFader);setMin(pStep,1);setMax(pStep, 16);
setSymbol(pStep,'');setFormat(pStep,'-bypass-');
pWrap := createParam('wrap around', ptSwitch);
pLimitLo := createParam('lo limit', ptMidiNoteFader); setMax(pLimitLo, 126); setMin(pLimitLo, 0);
pLimitHi := createParam('hi limit', ptMidiNoteFader); setMin(pLimitHi, 1);setMax(pLimitHi, 127);
pLimit := createParam('limit mode', ptListBox);
setListBoxString(pLimit,'"-bypass-","filter","rescale","compress"');// if compress: faders = ratio, threshold
setValue(pLimit,1); setDefaultValue(pLimit,0);
pDest := createParam('affect byte', ptListBox);
setListBoxString(pDest,'"1(note,cc.num,..)", "2(velo,cc.val,..)", "1 + 2"');
pConv := createParam('convert', ptListBox); // take all from 'Create Midi?'
setListBoxString(pConv,
'"-bypass-","Note Legato","Note Poly (mode1)","Note Poly (mode2)","Control Change","Program Change", "Pitch Bend","Aftertouch Channel","Aftertouch Poly", "Timing Clock","Start","Stop","Continue","Active Sensing", "NoteOn !","NoteOff !"');
pThru := createParam('midi thru', ptSwitch);
pNotesCount := createParam('open notes', ptDataField); setReadOnly(pNotesCount,true);
setMin(pNotesCount,0);setMax(pNotesCount,999);
f_c1:= 90099;f_c2:= 60099;f_c3:= 6666666;
setColor(pLimitHi,f_c1); setColor(pLimitLo,f_c1);setColor(pTransp,f_c1);setColor(pStep,f_c1);
setColor(pWrap,f_c2);setColor(pThru,f_c2);setColor(pInv1,f_c2);setColor(pInv2,f_c2);setColor(pSwap,f_c2);
//setColor(pNotesCount,f_c3);
setIsInput(pOut,false);
setIsInput(NotesCount,false);
setIsOutput(pIn,false);
setIsOutput(pEvent,false);
setIsOutput(pThru,false);
setIsOutput(pDest,false);
setIsOutput(pInv1,false);
setIsOutput(pInv2,false);
setIsOutput(pSwap,false);
setIsOutput(pMult,false);//setIsOutput(pMultSw,false);
setIsOutput(pTransp,false);//setIsOutput(pTranspSw,false);
setIsOutput(pStep,false);//setIsOutput(pStepSw,false);
setIsOutput(pWrap,false);
setIsOutput(pLimit,false);
setIsOutput(pLimitLo,false);
setIsOutput(pLimitHi,false);
setIsOutput(pConv,false);
setValue(pEvent,1);
setValue(pDest,0);
setValue(pInv1,0);setValue(pInv2,0);
setValue(pMult,GetLength(pMult)-1);setDefaultValue(pMult,0);
setValue(pTransp,-128);
setValue(pStep,1);
setValue(pWrap,0);
setValue(pLimitLo,0);setValue(pLimitHi,127); setValue(pLimit,0);
setValue(pConv,0);
setArrayLength(mult, 16);
setArrayLength(notesList, 16);
FOR i := 0 TO 15 DO BEGIN
setArrayLength(mult[i], 128); // init [16 channels][128 multiply values]
SetArrayLength(notesList[i], 128);
END;
notesCount:=0;
END;
// <F> isMidiMsg: 'event' is a list of pEvent (0=ALL). filters sysex, clock and system messages
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^//
FUNCTION isMidiMsg(msg: integer; e: integer): boolean;
BEGIN
IF ((e=1)OR(e=0)) AND ((msg=144)OR(msg=128)) THEN BEGIN
result := true; END // Notes
ELSE IF ((e=2)OR(e=0)) AND (msg=176) THEN BEGIN
result := true; END //CtrlChg
ELSE IF ((e=3)OR(e=0)) AND (msg=192) THEN BEGIN
result := true; END //PrgrChg
ELSE IF ((e=4)OR(e=0)) AND (msg=224) THEN BEGIN
result := true; END //PitchBend
ELSE IF ((e=5)OR(e=0)) AND ((msg=208) OR (msg=160)) THEN BEGIN
result := true; END //Aftertouch (Channel, Poly)
ELSE BEGIN
result:=false;
END;
END;
// <F> setWrap: wrap or limit byte range
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^//
FUNCTION setWrap (t: integer; wrap: boolean): integer; // 777777 rename 'setWrap'
BEGIN
IF t < 0 THEN BEGIN t:=t*(-1); END;
IF t > 127 THEN BEGIN
IF isWrap THEN BEGIN
result := round((t+1) MOD 128); // TEST
END
ELSE BEGIN
result:= 127;
END;
END
ELSE IF t < 0 THEN BEGIN /// ADD for transpose!
END
ELSE BEGIN
result:=t;
END;
END;
// : setTransp:
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^//
FUNCTION setTransp(val: integer; trsp: integer; wrap: boolean):integer;
BEGIN
val:=val+trsp;
IF ((val>127)OR(val<0)) THEN BEGIN
IF isWrap THEN BEGIN
IF val>127 THEN BEGIN
result:=val-127;
END
ELSE BEGIN
result:=val+127;
END;
END
ELSE BEGIN
IF val>127 THEN BEGIN
result:=127;
END
ELSE BEGIN // val<0
result:=0;
END;
END;
END
ELSE BEGIN
result:=0;
END;
END;
// <F> calcLimited: returns multiplication value for limiting, or return 1 (x*1=x; x>0)
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^//
FUNCTION calcLimited (val:integer; lo:integer; hi:integer; mode:integer):double;
BEGIN
IF mode=0 THEN BEGIN result:=1 END
//IF (val<=hi)AND(val>=lo)
ELSE IF mode=1 THEN BEGIN
result:= (hi-lo)/128;
//result:= (lo + round(val*r) - 1);
END
ELSE IF mode = 2 THEN BEGIN
// compress
END
ELSE BEGIN
result:=1
END;
END;
// <F> isStep: step limit data: filter out (? or compress->max, or compress->min)
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^//
FUNCTION isStep(val: integer; stp: integer):boolean;
BEGIN
IF ((val+1) MOD stp=0) THEN BEGIN
result:=true;
END
ELSE BEGIN
result:=false;
END;
END;
// <F> convert
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^//
// setConvert: (why not convert to: clock , start, stop, cont?)
FUNCTION setConvert(srcMsg:byte;typeId:integer):byte;
BEGIN
(*
IF = notes:
Note Legato","Note Poly (mode1)","Note Poly (mode2) 1-3; CC = 4
generate events! if replace with current, loose data...
Legato:
if new event arrives, last event is closed:
if not first event generate noteOff for the previous event
structures: noteConvertTmpMidi:= tmp // just before FOR loop ends.
Mode1: if new event arrives, add_output noteOFF for oldest even noteOn,
or if it is the only note present. remove closed note from array
structures: event array: boolean[chn][note] for currently open notes //
..already got this?
Mode2: if new event arrives, add_output noteOFF for oldest odd noteOn,
or if it there are only even notes. remove closed note from array
-> even notes will endure until all odd notes are finished
*)
IF typeId=4 THEN BEGIN
result:=176; // CC
END
ELSE IF typeId=14 THEN BEGIN
result:=144; // leg play: send noteOff Value if next noteOn value arrives tmp.Msg var holding last value
END
ELSE IF typeId=5 THEN BEGIN
result:=192; // PC (no 2nd byte)
END
ELSE IF typeId=6 THEN BEGIN
result:=224; // PitchBend (Modwheel)
END
ELSE IF typeId=7 THEN BEGIN
result:=208; // Aftert Channel
END
ELSE IF typeId=8 THEN BEGIN
result:=160; // Aftert Poly
END
ELSE BEGIN
result:=srcMsg;
END;
END;
(*
// <F>getOpenNotes
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^//
FUNCTION getOpenNotes():integer;
// global variables!!
BEGIN
//boolNotesArray[][]
END;
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^//
// <P> writeNoteOff: for convert to Notes
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^//
PROCEDURE writeNoteOff;
// global variables!!
VAR extra: TMidi;
BEGIN
extra.msg:=byte(128);
extra.data1:=tmp.data1;
extra.data2:=0;
extra.channel:=tmp.channel;
// write out
//setLength(pOut):= getLength(pOut)+1;
// ?? TEST!! ?? setMidiArrayData(pOut,trunc(getLength(pOut))-1),extraMidi)
// remove from array
//NotesArray[aVal]:= false;
//countNotes(false);
END;
*)
// <P> countNotes:
PROCEDURE countNotes(isANoteAndON:boolean);
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^//
BEGIN
IF isANoteAndON THEN BEGIN
notesCount:=notesCount+1
END
ELSE BEGIN
notesCount:=notesCount-1;
END;
END;
// <P> initLimitRange: expect: limitHi and limitLo are getValue(p)
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^//
PROCEDURE initLimitRange;
BEGIN
IF limitHi-limitLo< 0 THEN BEGIN
tmpLimitLo:=limitLo;
limitLo:=limitHi;
limitHi:=tmpLimitLo
END
ELSE IF limitHi-limitLo=0 THEN BEGIN
IF limitHi=128 THEN BEGIN
limitLo:=127;
END
ELSE BEGIN
limitHi:=limitHi+1;
END;
END;
END;
// <P> setDataSymbols:
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^//
PROCEDURE setDataSymbols;
VAR tr: integer;
BEGIN
IF (trunc(getValue(pStep))<= 1) THEN BEGIN
setSymbol(pStep,'');setFormat(pStep,'-bypass-');
END
ELSE BEGIN
setSymbol(pStep,'%'); setFormat(pStep,'%.0f')
END;
tr:=trunc(getValue(pTransp));
IF tr>0 THEN BEGIN
setSymbol(pTransp,'+'); setFormat(pTransp,'%.0f');
END
ELSE IF (tr=(-128))OR(tr=0) THEN BEGIN
setSymbol(pTransp,''); setFormat(pTransp,'-bypass-');
END
ELSE BEGIN
setSymbol(pTransp,'');setFormat(pTransp,'%.0f');
END;
(*
IF trunc(getValue(pLimitHi)) = 127 THEN BEGIN
setColor(pLimitHi,81099);
END
ELSE BEGIN
setColor(pLimitHi,90099);
END;
IF trunc(getValue(pLimitLo)) = 0 THEN BEGIN
setOffColor(pLimitLo,90099);
END
ELSE BEGIN
// setColor(pLimitLo,90099);
END;
*)
END;
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^|
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^|
// main ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^|
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^|
BEGIN
len := getLength(pIn);
lenCount:= len;
IF len > 0 THEN BEGIN
multiply:=pMultAr[trunc(getValue(pMult))];
transpose:= trunc(getValue(pTransp));
IF (transpose=(-128)) THEN BEGIN // control: bypass at the left end for convenience
transpose:=0;
END;
tmpEvent:=trunc(getValue(pEvent));
tmpDest:=trunc(getValue(pDest));
isFirstByte:=(tmpDest=2)OR(tmpDest=0);
isSecondByte:=(tmpDest=2)OR(tmpDest=1);
limitLo:=trunc(getValue(pLimitLo));
limitHi:=trunc(getValue(pLimitHi));
limit:=trunc(getValue(pLimit));
isLimit:=(limit>0) AND ((limitLo>=1)AND(limitHi<=126));
initLimitRange();
convert:=trunc(getValue(pConv));
step:=trunc(getValue(pStep));
isStepActive:=step>1;
isInv1:=getValue(pInv1)>0;
isInv2:=getValue(pInv2)>0;
isSwap:=getValue(pSwap)>0;
isWrap:=trunc(getValue(pWrap))>0;
isThru:=trunc(getValue(pThru))>0;
FOR i := 0 TO (len - 1) DO BEGIN
getMidiArrayValue(pIn, i, tmp);
IF isMidiMsg(tmp.msg, tmpEvent) THEN BEGIN // process on IF. if pThru is 1, process on ELSE
tmpMsg :=tmp.msg;
tmpX := tmp.data1;
tmpY := tmp.data2;
tmpXX := tmpX; // store global for note off array
IF isInv1 THEN BEGIN tmpX:= 127 - tmpX;END;
IF isInv2 THEN BEGIN tmpY:= 127 - tmpY;END;
IF isSwap THEN BEGIN
tmpX := tmpY;
tmpY := tmpXX;
END;
// affect byte 1
IF isFirstByte THEN BEGIN // 1st byte: process 1st+2nd byte if tmpDest=2
tmpX := trunc(multiply*tmpX) + (transpose);
tmpX := setWrap(tmpX, isWrap);
IF isLimit THEN BEGIN
tmpX := trunc(tmpX * calcLimited(tmpX,limitLo,limitHi,limit));
END;
tmp.data1 := byte(tmpX);
END;
// affect byte 2
IF isSecondByte THEN BEGIN // 2nd byte: process 1st+2nd byte if tmpDest=2
tmpY := trunc(multiply*tmpY) + (transpose);
tmpY := setWrap(tmpY, isWrap);
IF isLimit THEN BEGIN
tmpY := trunc(tmpY * calcLimited(tmpY,limitLo,limitHi,limit));
END;
tmp.data2 := byte(tmpY);
END;
// affect msg: convert event
IF isConvert THEN BEGIN
tmp.msg := setConvert(tmp.msg,convert);
END;
isNoteOff:= (tmpMsg=128) OR ((tmpMsg=144)AND(tmpY=0));
isNoteOn := (isNoteOff=false)AND((tmpMsg=144)AND(tmpY>0));
// only when Notes are output
// prevent hanging notes (tmpXX for swapped note values); ! if convert=>Notes:
IF isNoteOn THEN BEGIN
mult[tmp.channel - 1][tmpXX] := tmp.data1; // tmpXX(old 1st byte) needed for noteOff value
IF notesList[tmp.channel - 1][tmpXX]=false THEN BEGIN
notesList[tmp.channel - 1][tmpXX]:= true;
END;
//IF ((convert<=3)AND(convert>0)) THEN BEGIN
//addToBoolNotes(tmp.data1, tmp.channel, tmpXX);
// END
//ELSE BEGIN
//END;
END
ELSE IF isNoteOff THEN BEGIN // ! if convert=>Notes:
tmp.data1 := mult[tmp.channel - 1][tmpXX]; // set NoteOff value from original NoteOn value
IF notesList[tmp.channel - 1][tmpXX]=true THEN BEGIN
notesList[tmp.channel - 1][tmpXX]:=false;
END;
END;
IF isFirstByte OR (isFirstByte AND isSecondByte) THEN BEGIN
filterByte:= tmpX;
END //(tempDest=1)
ELSE BEGIN
filterByte:= tmpY;
END;
IF ((limit=1) AND ((limitLo>filterByte) OR (limitHi<filterByte))) OR
(isStepActive AND(isStep(filterByte,step)=true))
THEN BEGIN // filter message
lenCount:=lenCount-1;
END
ELSE BEGIN
IF isNoteOn THEN BEGIN
countNotes(true);
END
ELSE IF isNoteOff THEN BEGIN
countNotes(false);
END;
setLength(pOut,lenCount);
setMidiArrayValue(pOut, i, tmp); // write modified message
END;
END
// ! isMidiMsg()
ELSE BEGIN
IF isThru THEN BEGIN
setMidiArrayValue(pOut, i, tmp);
END
ELSE BEGIN
setLength(pOut, 0);
END;
END;
END; // (FOR i := 0 ..
END
ELSE BEGIN
setLength(pOut, 0);
END; // len = 0
setValue(pNotesCount, notesCount);
setDataSymbols();
END.