Welcome to %s forums

BrainModular Users Forum

Login Register

script efficiencies....

I need help on a Patch
Post Reply
woodslanding
Member
Posts: 1327
Contact:

Unread post by woodslanding » 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!

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 &#58;= &#40;noteNumber < hiVal&#41;  OR  &#40;noteNumber > lowVal&#41;
                ELSE isInRange &#58;= &#40;noteNumber <= hiVal&#41; AND &#40;noteNumber >= lowVal&#41;; 
END;


////////////////////////       SETTERS       ////////////////////////////////////////////
PROCEDURE SetLowVal&#40;n &#58; integer&#41;;
BEGIN
    setValue&#40;pLowNoteOut, n&#41;;
    lowVal &#58;= n;
END;

PROCEDURE SetHiVal&#40;n &#58; integer&#41;;
BEGIN
    setValue&#40;pHiNoteOut, n&#41;;
    hiVal &#58;= n;
END;

////////////////////////     PROCESS MIDI    ////////////////////////////////////////////
FUNCTION bstr&#40;b &#58; boolean&#41; &#58; string;
BEGIN if b THEN bstr &#58;= 'TRUE' else bstr &#58;= 'FALSE'; END;

PROCEDURE ProcessMidi&#40;midi &#58; tMidi; enabled &#58; boolean&#41;;
BEGIN
    IF isLearning THEN processLearn&#40;midi&#41; 
    ELSE IF not&#40;isDroning&#41; THEN BEGIN
        IF isSustain&#40;midi&#41; THEN BEGIN
            isSustaining &#58;= midi.data2 > 64;
            if NOT&#40;isSustaining&#41; THEN doRelease &#58;= TRUE;        
        END           
        // pass PB and AT with notes
        ELSE IF &#40;midi.msg = PITCHBEND&#41; OR &#40;midi.msg = AFTERTOUCH&#41; THEN passOut&#40;midi&#41;
        ELSE IF NOT&#40;IsNote&#40;midi&#41;&#41; THEN rejectOut&#40;midi&#41;
        // everything else deals with note data....
        ELSE IF IsInRange&#40;midi.data1&#41;and enabled THEN BEGIN
            IF isNoteOn&#40;midi&#41; THEN BEGIN
                ProcessNoteOn&#40;midi, enabled&#41;;
            END ELSE BEGIN       
                ProcessNoteOff&#40;midi&#41;
            END;
        END ELSE rejectOut&#40;midi&#41;; //notes that are out of range
        IF doRelease THEN sendNoteOffs&#40;false&#41;;
    END;  
END;

PROCEDURE ProcessLearn&#40;midi &#58; tMidi&#41;;
BEGIN
    IF &#40;isNoteOn&#40;midi&#41;&#41; and &#40;learnCount = 0&#41; THEN BEGIN
        lowVal &#58;= midi.data1;
        setValue&#40;pLowNoteOUT, lowVal&#41;;
        learnCount &#58;= 1;                   
    END 
    ELSE IF &#40;isNoteOn&#40;midi&#41;&#41; and &#40;learnCount = 1&#41;  THEN BEGIN
        hiVal &#58;= midi.data1;
        setValue&#40;pHiNoteOUT, hiVal&#41;;
        learnCount &#58;= 0;
        isLearning &#58;= FALSE;  // we've set our outs....
    END;
END;

PROCEDURE ProcessNoteOn&#40;midi &#58; tMidi; enabled &#58; boolean&#41;;
BEGIN
    IF enabled THEN BEGIN
        transList&#91;midi.data1&#93; &#58;= transpose; 
        midi.data1 &#58;= midi.data1 + transpose;
        PassOut&#40;midi&#41;;
        heldNotes&#91;midi.data1&#93; &#58;= TRUE;
    END;
END;

PROCEDURE ProcessNoteOff&#40;midi &#58; Tmidi&#41;;
BEGIN
    midi.data1 &#58;= midi.data1 + transList&#91;midi.data1&#93;;
    heldNotes&#91;midi.data1&#93; &#58;= FALSE;
    IF isSustaining THEN  storeNoteOff&#40;midi&#41; ELSE  PassOut&#40;midi&#41;;
    IF bypassReady AND isClear&#40;&#41; THEN  BEGIN
        setValue&#40;pBypassOut, 1&#41;;
        isSustaining &#58;= FALSE;
        isDroning &#58;= FALSE;
        bypassReady &#58;= FALSE;
    END;
END;

PROCEDURE storeNoteOff&#40;midi &#58; tMidi&#41;;
BEGIN         // is this all we're doing here?  should this be in the method above??				
    IF &#40;midi.data1 > maxKey&#41; THEN maxKey &#58;= midi.data1;
    IF &#40;midi.data1 < minKey&#41; THEN minKey &#58;= midi.data1;  
END;


PROCEDURE ProcessDrone&#40;n &#58; integer&#41;;
BEGIN
    IF n = 0 THEN SendNoteOffs&#40;TRUE&#41;;
    isSustaining &#58;= FALSE;
    isDroning &#58;= n > 0;   
END;

FUNCTION isClear&#40;&#41; &#58; boolean;
VAR key &#58; integer; 
VAR clear &#58; boolean;
BEGIN
    clear &#58;= TRUE;
    FOR key&#58;= minKey TO maxKey DO IF heldNotes&#91;key&#93; = TRUE THEN clear &#58;= FALSE;
    isClear &#58;= clear;
END;

PROCEDURE SendNoteOffs&#40;closeAll &#58; boolean&#41;;
VAR key &#58; integer;
VAR midi &#58; tMidi;
BEGIN
    FOR key&#58;= minKey TO maxKey DO BEGIN    
        IF &#40;heldNotes&#91;key&#93; = FALSE&#41; OR closeAll THEN BEGIN 
            midi.msg &#58;= NOTEOFF;
            midi.channel &#58;= Byte&#40;outChan&#41;;
            midi.data1 &#58;= key;
            midi.data2 &#58;= 0;
            passOut&#40;midi&#41;;
            heldNotes&#91;key&#93; &#58;= FALSE;
        END;                                                          
    END; 
    doRelease &#58;= FALSE;
END;

////////////////////////       CALLBACK      /////////////////////////////////////////////

PROCEDURE Callback&#40;n&#58; integer&#41;;
VAR i &#58; integer;

BEGIN


    FOR i &#58;= 0 to INPUT_COUNT - 1 DO BEGIN
        CASE n OF 
            pEnableINS&#91;i&#93; &#58;  BEGIN
                                 isEnabled&#91;i&#93; &#58;= getValue&#40;n&#41; > 0;
                                 UpdateBypass&#40;&#41;;
                             END;   
            pMidiINS&#91;i&#93;   &#58;  midiInCounts&#91;i&#93; &#58;= GetLength&#40;n&#41;;
        END;
    END;
    
    CASE n OF
        pLearnIN      &#58;     isLearning     &#58;= TRUE;        
        pOutChanIN    &#58;     outChan        &#58;= Byte&#40;trunc&#40;getValue&#40;n&#41;&#41;&#41;;
        pTranspIN     &#58;     transpose      &#58;=&#40;trunc&#40;getValue&#40;n&#41;&#41;&#41;;
        pHiNoteIN     &#58;     setHiVal&#40;trunc&#40;getValue&#40;n&#41;&#41;&#41;;
        pLowNoteIN    &#58;     setLowVal&#40;trunc&#40;getValue&#40;n&#41;&#41;&#41;;        
        pDroneIN      &#58;     ProcessDrone&#40;trunc&#40;getValue&#40;n&#41;&#41;&#41;;     
    END;
END;

/////////////////////////      PROCESS        //////////////////////////////////////////

PROCEDURE Process;
VAR i, inputNum &#58; Integer;
VAR currMsg     &#58; tMidi;
BEGIN
    FOR inputNum &#58;= 0 TO &#40;INPUT_COUNT - 1&#41; DO BEGIN
            FOR i &#58;= 0 TO &#40;midiInCounts&#91;inputNum&#93; - 1&#41; DO BEGIN
                GetMidiArrayValue&#40;pMidiINS&#91;inputNum&#93;, i, currMsg&#41;;
                ProcessMidi&#40;currMsg, isEnabled&#91;inputNum&#93;&#41;;
            END;
    END;
    SetLength&#40;pPassOut, passCount&#41;;
    SetLength&#40;pRejectOut, rejectCount&#41;;
    passCount   &#58;= 0;
    rejectCount &#58;= 0;
END;
Custom Ryzen 5900x MATX build, Win10, Fireface UFX, touchscreen
Custom 2 manual midi keyboard
Usine, Kontakt, Reaktor, Synthmaster, Byome, Arturia, Soundtoys, Unify

23fx23
Member
Posts: 2545
Contact:

Unread post by 23fx23 » 06 Mar 2010, 08:09

cool thanks for sharing.

gurulogic
Member
Posts: 1019
Contact:

Unread post by gurulogic » 06 Mar 2010, 08:54

yeah thanks. This looks very usefull!

User avatar
senso
Site Admin
Posts: 4425
Location: France
Contact:

Unread post by senso » 06 Mar 2010, 12:23

could be a great add-on?

woodslanding
Member
Posts: 1327
Contact:

Unread post by woodslanding » 07 Mar 2010, 03:03

I can upload it. I'd like to make sure it's really working correctly, so let me test for a week or two, and then I'll post.

-e
Custom Ryzen 5900x MATX build, Win10, Fireface UFX, touchscreen
Custom 2 manual midi keyboard
Usine, Kontakt, Reaktor, Synthmaster, Byome, Arturia, Soundtoys, Unify

bsork
Site Admin
Posts: 1334
Location: Asker, Norway
Contact:

Unread post by bsork » 07 Mar 2010, 11:54

Hi, I've had a look at the code, and it's looking just fine to me. I can't see anything that strikes me as an obvious candidate for code tuning - all nice and tidy! It definitely belongs in an add-on.
Bjørn S

Post Reply

Who is online

Users browsing this forum: No registered users and 20 guests