Welcome to %s forums

BrainModular Users Forum

Login Register

Questions about Processing overhead, MIDI and scripting

General Discussion about whatever fits..
Post Reply
woodslanding
Member
Posts: 1327
Contact:

Unread post by woodslanding » 19 Oct 2008, 08:21

Does usine automatically desist processing unused paths? If so, under what
conditions? will 'bypass' stop processing a vst?

I'm used to Reaktor which has an 'active' light in each module. I'm concerned because I'm running at 30% with less than one incomplete channel of midi input processing, and no VST's at all yet. That's what my entire bidule and reaktor setup runs on my other computer....

So I'm also wondering about other processor drains.... do subgroups have a processing hit? Am I better off nesting deeper, or not? Are scripts 'cheaper' than graphic processing?

Also wondering: is there an API for the scripting language around? I think I'd like to try and use it if it's more efficient. One module I'd like to write would be one that keeps track of midi note-ons that have not had a note-off (as well as sustain pedal events that haven't had a release) and send them out on cue, so that when I transpose an instrument, or stop controlling it from a given keyboard, I don't get stuck notes. More efficient and more musical than sending out 128 note-offs on every channel (and I've found very few VSTi's that respond to the 'all notes off' command...) which I used to do in Reaktor. Is this something well suited to scripting?

If possible, I'd like to create my whole midi input processing setup with a script if I can. I think it would end up being easier, even with the learning curve.

I'm also wondering if there are more economical ways to do certain midi tasks. For instance--if I just want to stop midi events from going through a cable, the only way I've figured out how to do this is to use a midi filter, and filter out the channel I have events coming in on. But that seems so messy! Is there just a simple midi on-off? How about a midi version of dispatcher or selector?

And don't those 16 ins and outs take up processing? For my needs, I rarely need more than 4 sockets for either....

I'm also concerned that there doesn't seem to be a simple audio on-off switch. I'm using panners and crossfaders, when all I want to do is send audio one place or another. That seems expensive too....

I guess I need to try to set up some stress tests, and see if I'm going to be able to run anything like my old setup on my computer under Usine.

Any thoughts welcome, as always!

cheers,
-eric
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 » 19 Oct 2008, 22:04

Only Olivier has a good answer on a lot of these questions I believe, but I'll have a go at some of them:

As far as I've understood, your MIDI setup is quite complex, but 30% before any audio?!? That seems VERY strange... Without knowing any details, it's of course hard to tell why this is so. Jus for the record, you can turn off processing on every (sub-)patch when it's not needed.

If you mean sub-patches (not subgroups), there shouldn't be much extra drain using those instead of having everything exposed within the (top) patch. Of course a tiny bit of extra CPU is used since there are more connections, but I've never found that I've noted any difference. As using sub-patches also makes complex patches tidier, it might even give some CPU gains because of a better constructed program. In most cases, scripts are using more CPU than modules, but it isn't always so. Most modules are "cheap", but with very complex calculations, script might be the better way od doing things. Not only will it often be simpler to create a script when you have a lot of IF..THEN...ELSE type of things to consider, but with a script it's easier to make sure that only the needed calculations are being performed.

I'm not quite sure what you mean by an API for the scripts. There's an API if you want to create you're own compiled user modules, otherwise the script language is sort of an API in itself. Check out some of supplied scripts, and I suppose you soon get the hang of it. (And I know: It's about time that a manual or quick-start for scripts was made...)

The idea of accumulating NoteOns without NoteOffs is a good one and clearly something best done with a script. In fact I've had in the back of my mind to create an add-on similar to what you describe. If you want to have a go at it, you could do worse than take a look at the script you'll find in http://www.sensomusic.com/forums/viewtopic.php?id=1027. Also so take a look at the various MIDI scripts that's included with Usine.

You can send MIDI through the same wires as everything else within Usine, only that it doesn't always make sense because of MIDI's variable data structures... As long as there are MIDI specific varieties of a module (like buses), you should use those, though. To stop a MIDI stream, you can use the Event Control/Stop Event Flow module.

You don't have to turn on all the in- or outputs, neither audio nor MIDI, if you don't want to. Go to setup - it's quite easy. You might also want to check whether the dual core optimization has been turned on, and that the right ASIO driver is selected.

You can use the dispatcher module to select where to send your audio (or any other data). If it's audio, you should turn on the reset to 0 option as explained in the manual.

Hope this helps!
Bjørn S

User avatar
nay-seven
Site Admin
Posts: 5684
Location: rennes France
Contact:

Unread post by nay-seven » 19 Oct 2008, 22:44

I'm concerned because I'm running at 30% with less than one incomplete channel of midi input processing, and no VST's at all yet.
not totally sure , but i think cpu indicator in Usine is not only Usine but all processus running...so maybe something else running in the background...?

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

Unread post by bsork » 20 Oct 2008, 07:38

Yes nay-seven - I forgot about that one! Contrary to a lot of other "CPU meters" in DAWs, Usine is showing the total utilization so you shouldn't get figures far from what you get from the task manager. In for instance Cubase, the percentage is related to the time available to fill up the next ASIO buffer. (Iif I have understood correctly, that is...)
Bjørn S

woodslanding
Member
Posts: 1327
Contact:

Unread post by woodslanding » 21 Oct 2008, 18:31

Thanks for all the help!

Yes, by API, I actually meant 'documentation'. Bad word choice! Thanks for tips on more example scripts. I've done scripts in 'kontakt' and it wasn't too hard, although they did have a good chunk of manual on it. Is there even a list of commands somewhere??

The CPU tip is also helpful--I'm less worried now. I'll see what I can turn off in the background. Eventually, if I'm using this computer live, I'll be dual-booting, but my live computer is super clean, so those figures should go down on that machine.

I found out where to disable processing on subgroups. I'm still not clear if 'bypass' on a vst bypasses processing as well as signal, but I should be able to figure that out.

I may try replacing most of my midi management with a script, as I think I could replace an awful lot of modules with it. It would have a lot of data ins and outs though!

Thanks as always for the support!
-e
Custom Ryzen 5900x MATX build, Win10, Fireface UFX, touchscreen
Custom 2 manual midi keyboard
Usine, Kontakt, Reaktor, Synthmaster, Byome, Arturia, Soundtoys, Unify

antwan
Member
Posts: 164
Contact:

Unread post by antwan » 21 Oct 2008, 21:48

Hi there!

In the scripting window there's a tab ("help" I think) with most of the available commands. That list together with all the examples an user add-on scripts around (and of course help here at the forum) has gotten me quite far personally. Anyways, I've been following the thread and will be glad to join in on helping you out in the process. I've just been very busy lately. I too am interested in similar scripts. Bsork is the proven master of scripting (and patching for that matter) but as mentioned, I too will do my best to help you out.

Best regards,

antwan

woodslanding
Member
Posts: 1327
Contact:

Unread post by woodslanding » 22 Oct 2008, 05:52

Thanks Antwan!

I have successfully modified and compiled a script, and read the helpfile, and I am starting to understand. Here's a question:

I am assuming (okay, I haven't checked!) that in the midi transpose script, if you change the transpose value while holding down notes, they will stick. I'm trying to figure out how the main event loop is going to recognise that the transpose value has changed, in order to fire off the requisite note-offs. Do I just get the value of transpose with each loop, and compare it with a stored version of the value? What happens if it changes during the loop? Or does the script not see any changes until the next loop?

Also, I'm wondering: does Usine use noteons of vel 0, or true noteoffs? Or seamlessly respond to both??

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

antwan
Member
Posts: 164
Contact:

Unread post by antwan » 22 Oct 2008, 07:33

Eric,

I'm not in front of my laptop/usine so can't check on the midi transpose script specifically but in general wherever you see a GetValue (or GetMidiArrayValue etc.) is when the input is read. If the value is assigned to a variable that's when the variable gets updated. I'll have a look at the script in question later today unless someone else makes it first.

Concerning note offs, I believe both work alright (code 128 and vel 0).

antwan

antwan
Member
Posts: 164
Contact:

Unread post by antwan » 23 Oct 2008, 13:36

Hi Eric,

Here's a modified (in a big way) version of the midi transpose script that takes care of note off's even if the transposition has changed since the note on. Seems to work alright after a quick test.

Code: Select all

// ADVANCED MIDI TRANSPOSE

// Parameters declaration

VAR input   : Tparameter;
VAR output  : Tparameter;
VAR transpo : TParameter;

// Global variables

VAR i, j, x          : integer;
VAR nbOfMidi         : integer;
VAR transpoVal       : integer;
VAR arrLength        : integer;
VAR ReceivedMidi     : TMidi;
VAR notesArray       : Array of integer;    // This holds the note numbers that are waiting for note offs
VAR transpoArray     : Array of integer;    // This holds the transposition value that was in place when each of the notes in notesArray was added
VAR tempNotesArray   : Array of integer;    // A temp array used in proc DelArrayIndices
VAR tempTranspoArray : Array of integer;    // A temp array used in proc DelArrayIndices

// Initialisation - create parameters

PROCEDURE init;
BEGIN  
 Input := CreateParam('In',ptMidi);
 Output := CreateParam('Out',ptMidi);
 transpo := CreateParam('transpo',ptDataFader);
 
 SetIsInput(Output,false);
 SetIsOutPut(Input,false);
 SetIsOutPut(transpo,false);
 SetFormat(transpo,'%.0f');
 SetMin(transpo,-24);
 SetMax(transpo,24);
END;    // init

PROCEDURE DelArrayIndices;
BEGIN
  SetArrayLength(tempNotesArray, (arrLength-1));
  SetArrayLength(tempTranspoArray, (arrLength-1));
  FOR x := 0 TO j-1
  DO BEGIN
    tempNotesArray[x] := notesArray[x];
    tempTranspoArray[x] := transpoArray[x];
  END;
  IF j <> arrLength-1 THEN BEGIN
    FOR x &#58;= j+1 TO arrLength-1
    DO BEGIN
      tempNotesArray&#91;x-1&#93; &#58;= notesArray&#91;x&#93;;
      tempTranspoArray&#91;x-1&#93; &#58;= transpoArray&#91;x&#93;;
    END;
  END;
  SetArrayLength&#40;notesArray, &#40;arrLength - 1&#41;&#41;;
  SetArrayLength&#40;transpoArray, &#40;arrLength - 1&#41;&#41;;
  notesArray &#58;= tempNotesArray;
  transpoArray &#58;= tempTranspoArray;
  SetArrayLength&#40;tempNotesArray, 0&#41;;
  SetArrayLength&#40;tempTranspoArray, 0&#41;;
 END;    // DelArrayIndices 

 // Main

BEGIN
  nbOfMidi &#58;= GetLength&#40;input&#41;;  // get the number of incoming midi codes  
  IF nbOfMidi > 0 THEN BEGIN
    SetLength&#40;outPut,nbOfMidi&#41;;      // set the number of output codes
    transpoVal &#58;= trunc&#40;getValue&#40;transpo&#41;&#41;; // get the transpo value
    FOR i &#58;= 0 TO nbOfMidi-1         // loop for all input codes, for polyphonic data &#40;chords&#41;
    DO BEGIN
      GetMidiArrayValue&#40;input,i,ReceivedMidi&#41;; // get each code
      arrLength &#58;= GetArrayLength&#40;notesArray&#41;; // Refresh how many notes are waiting for note off

      // If received midi is NOTE ON then add note to array and current transposition to another array
      IF &#40;ReceivedMidi.msg = 144&#41; THEN BEGIN

        SetArrayLength&#40;notesArray, &#40;arrLength + 1&#41;&#41;; // Expand note array for new note
        SetArrayLength&#40;transpoArray, &#40;arrLength + 1&#41;&#41;; // Expand transposition value array for new note
        notesArray&#91;arrLength&#93; &#58;= &#40;ReceivedMidi.data1 + transpoVal&#41;;  // Add note number to newest slot
        transpoArray&#91;arrLength&#93; &#58;= transpoVal;  // Add current transposition to newest slot
        // FOR DEBUGGING
        // writeln&#40;'ADD&#58; ' + intToStr&#40;ReceivedMidi.data1&#41; + ' WITH&#58; ' + intToStr&#40;transpoVal&#41;&#41;;
        ReceivedMidi.data1 &#58;= ReceivedMidi.data1 + transpoVal; // Adjust midi with transposition value
        SetMidiArrayValue&#40;output,i,ReceivedMidi&#41;; // Set transformed midi to output   
      END;

      // If received is NOTE OFF then take current transposition into account, find the note in array and remove from array
      IF &#40;ReceivedMidi.msg = 128&#41; AND &#40;arrLength > 0&#41;  THEN BEGIN
        FOR j &#58;= 0 TO arrLength-1  // Go through all stored notes
        DO BEGIN
          IF notesArray&#91;j&#93; = &#40;ReceivedMidi.data1 + transpoArray&#91;j&#93;&#41; THEN BEGIN                                     
            ReceivedMidi.data1 &#58;= &#40;ReceivedMidi.data1 + transpoArray&#91;j&#93;&#41;;
            SetMidiArrayValue&#40;output, i, ReceivedMidi&#41;;  // Send note off to midi outlet
            // FOR DEBUGGING
            // writeln&#40;'REMOVE&#58; ' + intToStr&#40;notesArray&#91;j&#93;&#41;&#41;;
            DelArrayIndices; // call proc to remove note from arrays
            BREAK;  // Break for-loop
          END;
        END;
      END;		
    END;
  END
  ELSE SetLength&#40;outPut,0&#41;; // nothing received, set out length to 0
END.    // main
I can elaborate more on this if you're interested but I put in quite a lot of comments.
Also, since I'm not a pro, perhaps bsork for instance might be able to point out things that aren't optimal, but I did my best!

antwan

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

Unread post by bsork » 23 Oct 2008, 14:19

I've had a VERY quick look at the script, and it looks OK to me. Some clever stuff there.

However, instead of using lots of temporary arrys, loops etc, I think it's a lot easier to use the MIDI data for array lookups. Eg when a NoteOff is received, store the current transposition value together with the channel and note number and send the transposed note out. When a NoteOff is received, use ch/note to retrieve the original transposition value. I can't be sure, but I think it not only makes for less programming, but will in most cases be more efficient. The drawback in most cases is a bit more RAM used, I suppose.
Bjørn S

antwan
Member
Posts: 164
Contact:

Unread post by antwan » 23 Oct 2008, 16:33

Hi bsork,

Thanks for comments. I'm not sure I understand this part though:
bsork wrote:However, instead of using lots of temporary arrys, loops etc, I think it's a lot easier to use the MIDI data for array lookups.
Hence, I'm having trouble understanding the rest of it :)
But I sure would like to understand! Could you elaborate?

Thanks a million,

antwan

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

Unread post by bsork » 24 Oct 2008, 01:07

Well, I coded a version using the channels and note numbers to store and retrieve the transpose values:

Code: Select all

VAR pIn, pOut, pTransp &#58; tParameter;

TYPE tTransp = ARRAY OF integer;
VAR transp &#58; ARRAY OF tTransp;

VAR tmp &#58; tMidi;
VAR len, transpose, i &#58; integer;

PROCEDURE Init;
BEGIN
   pIn &#58;= CreateParam&#40;'midi in', ptMidi&#41;; SetIsOutput&#40;pIn, FALSE&#41;;
   pOut &#58;= CreateParam&#40;'midi out', ptMidi&#41;; SetIsInput&#40;pOut, FALSE&#41;;

   pTransp &#58;= CreateParam&#40;'transpose', ptDataFader&#41;; SetIsOutput&#40;pTransp, FALSE&#41;;
   SetFormat&#40;pTransp, '%.0f'&#41;; SetMin&#40;pTransp, -24&#41;; SetMax&#40;pTransp, 24&#41;;

   SetArrayLength&#40;transp, 16&#41;;
   FOR i &#58;= 0 TO 15 DO // A two-dimensional array; &#91;16 channels&#93;&#91;128 transpose values&#93;
      SetArrayLength&#40;transp&#91;i&#93;, 128&#41;;
END; // Init

// main
BEGIN
   len &#58;= GetLength&#40;pIn&#41;;
   IF &#40;len > 0&#41; THEN BEGIN
      transpose &#58;= trunc&#40;GetValue&#40;pTransp&#41;&#41;;
      SetLength&#40;pOut, len&#41;;
      FOR i &#58;= 0 TO &#40;len - 1&#41; DO BEGIN
         GetMidiArrayValue&#40;pIn, i, tmp&#41;;
         IF &#40;&#40;tmp.msg = 144&#41; AND &#40;tmp.data2 > 0&#41;&#41; THEN BEGIN // NoteOn
            transp&#91;tmp.channel - 1&#93;&#91;tmp.data1&#93; &#58;= transpose; // Store transpose value used for NoteOn
            tmp.data1 &#58;= tmp.data1 + transpose;
         END
         ELSE IF &#40;   &#40;tmp.msg = 128&#41;
                  OR &#40;&#40;tmp.msg = 144&#41; AND &#40;tmp.data2 = 0&#41;&#41;&#41; THEN BEGIN // NoteOff
            tmp.data1 &#58;= tmp.data1 + transp&#91;tmp.channel - 1&#93;&#91;tmp.data1&#93;; // Retrieve stored transpose and add to NoteOff
         END;
         SetMidiArrayValue&#40;pOut, i, tmp&#41;;
      END;
   END
   ELSE BEGIN
      SetLength&#40;pOut, 0&#41;;
   END;
END.
Running test with three RandomMidi modules spitting out notes didn't really give any definitive "winner" in CPU usage. Your script was in fact generally a tiny bit lower, but the occasional spikes that I could see for both was on the other hand generally more pronounced with your script. If I had restricted the script to ignore the MIDI channel, I guess I could have saved a bit on the CPU.

If you have any questions, feel free to ask. I'm not as good as you to write comments. :)
Bjørn S

woodslanding
Member
Posts: 1327
Contact:

Unread post by woodslanding » 24 Oct 2008, 06:45

wow, thanks everybody. I have loads to chew on now!

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

antwan
Member
Posts: 164
Contact:

Unread post by antwan » 24 Oct 2008, 11:12

Ah yes, now I understand.
A 2D array with a slot for every possible note. I'd say that's pretty genius!
It's definately a cleaner script, that's for sure. Plus I had forgotten to take care of note on with 0 velocity.

But incase anyone ever needs a script for "delete index from array", they can find that hacked within my mess!
Well this made for another interesting thread :)

Thanks bsork!

antwan

amiga909
Member
Posts: 324
Contact:

Unread post by amiga909 » 24 Oct 2008, 14:57

tkx a lot for the scripts.

got 3 newbie questions regarding scripting:

1- writeln works? where is the line being written to? to the scripting window?
only very roughly tested it yesterday. but its also possible I did not realize assignements in pascal script have "pass-by-value" instead of "pass-by-reference" (like Java) (so writeln(array[0] returned nothing cause I read the variable out before)?

2- can smby recommend a full reference website/pdf for the script module?

3- both of you ask for noteOns with 'IF (ReceivedMidi.msg = 144)' and for noteOffs with 'IF (ReceivedMidi.msg = 128)'
looking at this table http://www.midi.org/about-midi/table2.shtml , is it possible you will only take care of midi notes on channel 1, channels 2-16 will be passed?

antwan
Member
Posts: 164
Contact:

Unread post by antwan » 24 Oct 2008, 15:23

Hi amiga909

1 - writeln works, yes. It writes to the trace window.

2 - I don't think there exists a full reference for the Usine scripting environment. Most basic pascal stuff seems to work. And the help-tab gives an idea (but not a full idea) of which commands it can take. Olivier will be better at answering this one.

3 - Absolutely. Basically try something like:
IF (ReceivedMidi.msg = 144) AND (ReceivedMidi.channel = 0) THEN...
Note that in the scripting environment midi channels go from 0-15, not from 1-16.

Others can elaborate more on the parts I missed.

Have fun!

antwan

amiga909
Member
Posts: 324
Contact:

Unread post by amiga909 » 24 Oct 2008, 15:55

antwan wrote:3 - Absolutely. Basically try something like:
IF (ReceivedMidi.msg = 144) AND (ReceivedMidi.channel = 0) THEN...
Note that in the scripting environment midi channels go from 0-15, not from 1-16.
why do you expect no noteOn's on channels 2-16 (erg. 1-15) with code 145 to 159?

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

Unread post by bsork » 24 Oct 2008, 22:12

The channels within the scripts are just the same as used in the modules: 1-16, not 0-15. If you look in my script where I use the channel field for accessing the arrays, you can see that I subtract 1 from the channel because the array indexes goes from 0 to 15.

The reason that all channels are recognized, is that the first (status) byte is being handled before it enters Usine. A typical channel MIDI message like a NoteOn going down a MIDI cable consists of three bytes: 1) channel and message type NoteOn, 2) note number, and 3) velocity.
Bjørn S

amiga909
Member
Posts: 324
Contact:

Unread post by amiga909 » 25 Oct 2008, 14:47

about the first byte you guys are right.

made a little test script and fed midi notes on various channels into it.

Code: Select all

//////////////////////////////////////////////////////
// TEST&#58; midi message 1st byte + midi channel filtering
//////////////////////////////////////////////////////

// Paramters declaration
var input, txt &#58; Tparameter;
var tmp &#58; TMidi;
var len, i&#58; integer; 

// initialisation procedure
procedure init;
begin  
 input &#58;= CreateParam&#40;'input',ptMidi&#41;;
 SetIsOutput&#40;input,false&#41;;
 txt &#58;= CreateParam&#40;'txt',ptTextField&#41;;
 SetIsInput&#40;txt,false&#41;;
end;

procedure writeMidi&#40;m&#58; TMidi&#41;;
var s&#58; string;
begin
setLength&#40;txt,50&#41;;
s&#58;= '&#91;msg&#58; ' + intToStr&#40;m.msg&#41; +'&#93; &#91;chn&#58;'+ intToStr&#40;m.channel&#41; + '&#93;';
setStringValue&#40;txt,s&#41;;
writeln&#40;s&#41;;
end; 

// Main Loop procedure

begin
 len &#58;= getLength&#40;input&#41;;
 if len > 0 then begin
     for i&#58;=0 to len-1 do begin
     getMidiArrayValue&#40;input,i,tmp&#41;;
     if &#40;tmp.msg = 144&#41; or &#40;tmp.msg = 128&#41; then 
         writeMidi&#40;tmp&#41;;
     if &#40;&#40;tmp.msg > 144&#41; and &#40;tmp.msg < 160&#41;&#41;
             or &#40;&#40;tmp.msg > 128&#41; and &#40;tmp.msg < 144&#41;&#41; then
         writeMidi&#40;tmp&#41;;

     end;
 end;
end.
how did you know this? thought this to be right http://www.midi.org/about-midi/table2.shtml
thanks.

woodslanding
Member
Posts: 1327
Contact:

Unread post by woodslanding » 26 Oct 2008, 02:01

Okay, I've written my first iteration of the smart splitter, based around Bsork's smart transpose code. This is AWESOME!

It's turned the transpose (especially the octave control) into a performance tool!

So here it is if anyone is interested.... rather specific application, but useful stuff maybe.

Code: Select all

//// take input from two keyboards and assign one or both to a vst.  also split and transpose.


TYPE tNoteArr = ARRAY OF boolean;
TYPE tStatus = RECORD
   low  &#58; integer;
   max  &#58; integer;
   sus  &#58; boolean;
   bend &#58; boolean;
END;

TYPE tTransp = ARRAY OF integer;
VAR transpositions &#58; ARRAY OF tTransp;

// parameters declaration
var input    &#58; Tparameter;   // midi input
var output   &#58; Tparameter;   // midi output

var key1     &#58; Tparameter;   // output notes from keyboard 1
var key2     &#58; Tparameter;   // output notes from keyboard 2

// these are not implemented yet....soon.
var volKey1  &#58; Tparameter;   // send out a volume control signal for keyboard 1, if enabled as a source
var volKey2  &#58; Tparameter;   // send out a volume control signal for keyboard 2, if enabled as a source
var enable   &#58; Tparameter;   // boolean control out for disabling VST if no keyboard is controlling it
var outCH    &#58; Tparameter;   // rechannelize input.  Value of 0 leaves output channel unchanged

var semi     &#58; TParameter;   // transpose by semi-tone
var octave   &#58; TParameter;   // transpose by octave
var split    &#58; Tparameter;   // pitch below or above which notes are not sent
var upper    &#58; Tparameter;   // send pitches above the split if on.  otherwise send pitches below.

var bytecount    &#58; integer;
var midiLen      &#58; integer;
var midi         &#58; TMidi;
var transpose    &#58; integer;
var splitVal     &#58; integer;
var upperVal     &#58; integer;
var key1Val      &#58; single;
var key2Val      &#58; single;
var key1ch       &#58; integer;
var key2ch       &#58; integer;
var i            &#58; integer;
var channel      &#58; integer;


// initialisation 
PROCEDURE init;
BEGIN  
 
 output &#58;= CreateParam&#40;'MIDIout',ptMidi&#41;;           SetIsInput&#40;Output,false&#41;; 
 volKey1 &#58;= CreateParam&#40;'volK1',ptMidi&#41;;            SetIsInput&#40;volKey1,false&#41;;
 volKey2 &#58;= CreateParam&#40;'volK2',ptMidi&#41;;            SetIsInput&#40;volKey2,false&#41;;
 enable &#58;= CreateParam&#40;'vstEnable',ptDataField&#41;;    SetIsInput&#40;enable,false&#41;;
 input &#58;= CreateParam&#40;'MIDIin',ptMidi&#41;;             SetIsOutput&#40;Input,false&#41;;
 semi &#58;= CreateParam&#40;'semi',ptDataField&#41;;           SetIsOutput&#40;semi,false&#41;;
 octave &#58;= CreateParam&#40;'8va',ptDataField&#41;;          SetIsOutput&#40;octave,false&#41;;  
 split &#58;= CreateParam&#40;'split',ptMidiNoteFader&#41;;     SetIsOutput&#40;split,false&#41;;
 upper &#58;= CreateParam&#40;'upper',ptSwitch&#41;;            SetIsOutput&#40;upper,false&#41;;
 key1 &#58;= CreateParam&#40;'key1', ptSwitch&#41;;             SetIsOutput&#40;key1,false&#41;;
 key2 &#58;= CreateParam&#40;'key2', ptSwitch&#41;;             SetIsOutput&#40;key2,false&#41;;
 outCH &#58;= CreateParam&#40;'outCH', ptDataField&#41;;        SetIsOutput&#40;outCH,false&#41;;

 key1ch &#58;= 1;   
 key2ch &#58;= 2;
 
 SetArrayLength&#40;transpositions, 2&#41;;
 FOR i &#58;= 0 TO 1 DO // we are going to ignore midi data on any but the 2 keyboard channels...
    SetArrayLength&#40;transpositions&#91;i&#93;, 128&#41;;
 
 //default values and formats
 SetDefaultValue&#40;upper,1&#41;;
 SetDefaultValue&#40;key1,1&#41;;
 SetDefaultValue&#40;key2,1&#41;;

 SetFormat&#40;split,'%.0f'&#41;;
 SetMin&#40;split,1&#41;;
 SetMax&#40;split,127&#41;;
 SetDefaultValue&#40;split,1&#41;;

 SetFormat&#40;semi,'%.0f'&#41;;
 SetMin&#40;semi,-7&#41;;
 SetMax&#40;semi,7&#41;;
 SetDefaultValue&#40;semi,0&#41;;

 SetFormat&#40;octave,'%.0f'&#41;;
 SetMin&#40;octave,-4&#41;;
 SetMax&#40;octave,4&#41;;
 SetDefaultValue&#40;octave,0&#41;

end;

PROCEDURE processNoteOns;
BEGIN
    writeln&#40;'in noteon loop'&#41;;
    IF &#40;&#40;&#40;channel = key1ch&#41; AND &#40;key1Val = 1&#41;&#41; OR &#40;&#40;channel = key2ch&#41; AND &#40;key2Val = 1&#41;&#41;&#41; 
       AND &#40;&#40;&#40;midi.data1 >= splitVal&#41; AND &#40;upperVal = 1&#41;&#41; OR &#40;&#40;midi.data1 < splitVal&#41; AND &#40;upperVal = 0&#41;&#41;&#41;
    THEN BEGIN        
        transpositions&#91;channel - 1&#93;&#91;midi.data1&#93; &#58;= transpose;              // only works if keyboard ch's are 1 and 2....
        midi.data1 &#58;= midi.data1 + transpose;
        SetMidiArrayValue&#40;output, bytecount, midi&#41;;
    END;  
        
END;   //process Noteons

PROCEDURE processNoteOffs;
BEGIN
    midi.data1 &#58;= midi.data1 + transpositions&#91;channel - 1&#93;&#91;midi.data1&#93;; // Retrieve stored transpose and add to NoteOff
    SetMidiArrayValue&#40;output, bytecount, midi&#41;;
END;  //processNoteoffs

PROCEDURE processSustain;   // untested
BEGIN
    IF &#40;midi.data1 < 64&#41; THEN   //send sus up on all instruments, selected or not...
        BEGIN
            SetMidiArrayValue&#40;output, bytecount, midi&#41;;
        END
    ELSE IF &#40;&#40;&#40;channel = 1&#41; AND &#40;key1Val = 1&#41;&#41; OR &#40;&#40;channel = 2&#41; AND &#40;key2Val = 1&#41;&#41;&#41; THEN
    BEGIN
        IF &#40;midi.data1 >= 64&#41;  THEN  //send sus down only on selected instruments
        BEGIN    
            SetMidiArrayValue&#40;output, bytecount, midi&#41;;
        END;
    END;
END;   // process sustain
        
// main
BEGIN
   midiLen &#58;= GetLength&#40;input&#41;;
   IF &#40;midiLen > 0&#41;  THEN
   BEGIN
        SetLength&#40;output, midiLen&#41;;    
      
        // set variables from inputs
        transpose &#58;= trunc&#40;GetValue&#40;semi&#41; + &#40;&#40;getValue&#40;octave&#41; * 12&#41;&#41;&#41;;
        splitVal  &#58;= trunc&#40;getValue&#40;split&#41;&#41;;
        upperVal  &#58;= trunc&#40;getValue&#40;upper&#41;&#41;;
        key1Val   &#58;= trunc&#40;getValue&#40;key1&#41;&#41;;
        key2Val   &#58;= trunc&#40;getValue&#40;key2&#41;&#41;;
        
        // process midi
        
        FOR bytecount &#58;= 0 TO &#40;midiLen - 1&#41; DO 
        BEGIN
            GetMidiArrayValue&#40;input, bytecount, midi&#41;;
            channel &#58;= midi.channel;
            IF &#40;&#40;midi.msg = 144&#41; AND &#40;midi.data2 > 0&#41;&#41; 
            THEN BEGIN           // Noteon
                processNoteons;       
            END
            ELSE IF &#40;&#40;midi.msg = 128&#41;
                OR  &#40;&#40;midi.msg = 144&#41; AND &#40;midi.data2 = 0&#41;&#41;&#41; THEN
            BEGIN      // NoteOff
                processNoteoffs;
            END
            ELSE IF &#40;midi.msg = 176&#41; THEN
            BEGIN
                processSustain;
            END;                         ///ELSE IF &#40;&#40;midi.msg = 224&#41;  pitchbend, etc.
        END;      
    END

    // no midi in this block....
    ELSE BEGIN
        SetLength&#40;output, 0&#41;;
    END;
END.
Thanks everybody for your help. I'm so excited by the options here....
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 » 26 Oct 2008, 08:37

amiga909 wrote:how did you know this? thought this to be right http://www.midi.org/about-midi/table2.shtml
thanks.
Of course the MIDI table is right, it's just that it specifies the messages as it is transmitted. In practical use, the first (status) byte of channel messages can be called a compound message since it contains both the channel and the message type. As the byte has to be "split" into two parts, one call just as well add 1 to the channel part of the message as the MIDI channels are defined as 1 to 16, even when it's mathematically 0 to 15.

To be honest, I have always thought the definition of channels as 1 to 16 has been silly. I guess the explanation is a "psychological" or "political" one in that a lot of people actually find counting from 0 instead of 1 hard to swallow.
woodslanding wrote:It's turned the transpose (especially the octave control) into a performance tool!
I'd like to add a little "party trick" if someone else is testing your rig: Add an LFO or random generator to the transpose value as I did when testing, it's quite confusing when the same key changes notes all the time! :D
Bjørn S

amiga909
Member
Posts: 324
Contact:

Unread post by amiga909 » 26 Oct 2008, 15:26

here an extended version of midi transpose:

Code: Select all

//////////////////////////////////////////////////////
// transpose midi note
// 2008-10-24&#58; @Bsork, first version 
// 2008-10-26&#58; @amiga909, 'wrap around' added 
//////////////////////////////////////////////////////

VAR pIn, pOut, pTransp, pWrapAround &#58; tParameter;

TYPE tTransp = ARRAY OF integer;
VAR transp &#58; ARRAY OF tTransp;

VAR tmp &#58; tMidi;
VAR len, transpose, i, tempTransp &#58; integer;

PROCEDURE Init;
BEGIN

   pIn &#58;= CreateParam&#40;'midi in', ptMidi&#41;; SetIsOutput&#40;pIn, FALSE&#41;;
   pOut &#58;= CreateParam&#40;'midi out', ptMidi&#41;; SetIsInput&#40;pOut, FALSE&#41;;

   pTransp &#58;= CreateParam&#40;'transpose', ptDataFader&#41;; SetIsOutput&#40;pTransp, FALSE&#41;;
   SetFormat&#40;pTransp, '%.0f'&#41;; SetMin&#40;pTransp, -127&#41;; SetMax&#40;pTransp, 127&#41;;

   pWrapAround &#58;= CreateParam&#40;'wrap around', ptSwitch&#41;; SetIsOutput&#40;pWrapAround, FALSE&#41;;

   SetArrayLength&#40;transp, 16&#41;;
   FOR i &#58;= 0 TO 15 DO // A two-dimensional array; &#91;16 channels&#93;&#91;128 transpose values&#93;
      SetArrayLength&#40;transp&#91;i&#93;, 128&#41;;
END; 


// expects a noteOn msg with velocity > 0; outputs the transposed note value &#91;0-127&#93;
FUNCTION calcTranspose &#40;t&#58; integer; wrap&#58; single&#41;&#58; integer;
BEGIN
 IF wrap > 0 THEN BEGIN // 
    IF &#40;t > 127&#41; THEN 
        result &#58;= t - 128
    ELSE IF &#40;t < 0&#41; THEN 
        result &#58;= t + 128;  
 END
 ELSE BEGIN
    IF t > 127 THEN 
        result&#58;= 127
    ELSE IF t < 0 THEN 
        result&#58;=0
    ELSE  
        result&#58;=t; 
 END;  
END;

// main
BEGIN
   len &#58;= GetLength&#40;pIn&#41;;
   IF &#40;len > 0&#41;  THEN BEGIN
      transpose &#58;= trunc&#40;GetValue&#40;pTransp&#41;&#41;;
      SetLength&#40;pOut, len&#41;;
      FOR i &#58;= 0 TO &#40;len - 1&#41; DO BEGIN
         GetMidiArrayValue&#40;pIn, i, tmp&#41;; 
         tempTransp &#58;= transpose + tmp.data1; 
         IF  &#40;tmp.msg = 144&#41; AND &#40;tmp.data2 > 0&#41; THEN BEGIN // NoteOn
             tempTransp&#58;= calcTranspose&#40;tempTransp, GetValue&#40;pWrapAround&#41;&#41;;
             transp&#91;tmp.channel - 1&#93;&#91;tmp.data1&#93; &#58;= tempTransp;
             tmp.data1 &#58;= tempTransp;  
             SetMidiArrayValue&#40;pOut, i, tmp&#41;;
         END
         ELSE IF &#40;tmp.msg = 128&#41; or &#40;&#40;tmp.data2 = 144&#41; and &#40;tmp.data2 = 0&#41;&#41; THEN // NoteOff
             tmp.data1 &#58;= transp&#91;tmp.channel - 1&#93;&#91;tmp.data1&#93;;  
             SetMidiArrayValue&#40;pOut, i, tmp&#41;;
         END;  
   END
   ELSE BEGIN
      SetLength&#40;pOut, 0&#41;;
   END;
END.
bsork wrote:I'd like to add a little "party trick" if someone else is testing your rig: Add an LFO or random generator to the transpose value as I did when testing, it's quite confusing when the same key changes notes all the time! :D
:)

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

Unread post by bsork » 26 Oct 2008, 21:48

Nice one! It had also struck me that the script needed something to handle transposed notes outside the legal range!
Bjørn S

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

Unread post by bsork » 28 Oct 2008, 13:00

It has just struck me that a possibly useful extension of the ideas in this topic would be a key switcher script: Assign a number of notes as switcher keys and create the same number of outputs with separate transpose values and MIDI channels.

With for example three outputs and C3 selected as the first switcher key, hitting D3 will send the following NoteOns out through the third MIDI output, but using the some logic as in the transposer any NoteOffs received after key switching could be routed to the same output as the preceding NoteOn.

Anyone interested?
Bjørn S

woodslanding
Member
Posts: 1327
Contact:

Unread post by woodslanding » 28 Oct 2008, 16:45

That sounds like fun!

Another thought I had, was to save pitches of note-ons, and send them when a trigger key was hit--so you could do quick rhythms on full chords like a guitarist. I guess if you really wanted to emulate guitar, you could have one trigger delay notes as they went from low to high, and another delay them high to low, to simulate strumming.
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 » 28 Oct 2008, 22:04

Hi, funny that you mentioned a save-noteons-script. As I'm guitarist myself, I haven't thought of guitar playing emulation, more a way of accumulating notes into chords when using monophonic MIDI sources; for instance audio-to-MIDI.
Bjørn S

Post Reply

Who is online

Users browsing this forum: No registered users and 16 guests