Welcome to %s forums

BrainModular Users Forum

Login Register

Question #1: What MIDI Mapper functions would you like to see in V5?

General Discussion about whatever fits..
Post Reply
bsork
Site Admin
Posts: 1334
Location: Asker, Norway
Contact:

Unread post by bsork » 14 Nov 2009, 00:42

I made the MidiMapper patch included in LibraryOthers some time ago. As you're probably aware of, Usine V5 is in the works (but not yet ready for a beta release), and I'm brushing up this patch to amongst other things fit the new FastScript.

When I did this patch I guess I went a bit over the top :rolleyes: and put too many functions into it making it overly complicated, so Olivier has asked me to create a set of smaller patches that cover the different options in it.

I'd like to have some opinions of what kind of patches you think would be useful. If you have some ideas that are not too dissimilar from those in the existing MidiMapper, those might also be included.

Anyone?
Bjørn S

woodslanding
Member
Posts: 1327
Contact:

Unread post by woodslanding » 14 Nov 2009, 12:40

Here's stuff I'm using or could use:

Rechannelize;
Transpose;
Range Limit (ideally with midi learn)
filter Note on and off but not PB;
filter Note on, note off, PB and CC64;
convert CC64 to delayed noteoffs;
send true all notes off;
convert noteoffs to noteons of vel 0 and vice-versa;
velocity scaling with array or curve input;
CC scaling;
AT to CC# and vice versa;
note on to CC# and vice versa;
Convert one CC to another;
Split to multiple outputs by message type
Patch change map

Ideally they would all deal gracefully with other messages--I shouldn't have to filter notes out before a cc scaler.
Filters should just split the output, so you can take the filtered or passed output.

A smart CC scaler for using volume pedals as control pedals would be nice. (typically they have a partial range and a non-linear response....)

I'm sure I've missed a bunch.....
-e
Custom Ryzen 5900x MATX build, Win10, Fireface UFX, touchscreen
Custom 2 manual midi keyboard
Usine, Kontakt, Reaktor, Synthmaster, Byome, Arturia, Soundtoys, Unify

amiga909
Member
Posts: 324
Contact:

Unread post by amiga909 » 15 Nov 2009, 09:06

@bsork: "fit the new FastScript."
any detailed infos on what is different?
i've tried my old scripts with alpha9 and they all dont work anymore. i am sure they all worked before.
one grave difference seems to be new types (single instead of integer)?

@woodslanding: i've done for mostly all the tasks u mention (except 'convert CC64 to delayed noteoffs') a more or less specialized script.
http://www.sensomusic.com/wiki/doku.php ... scripts_V2

however, maybe these scripts aren't worth an update, and/or scripting should be done by internal people..

woodslanding
Member
Posts: 1327
Contact:

Unread post by woodslanding » 16 Nov 2009, 07:50

Well, I've done almost all of these myself, but don't look forward to rewriting them all for the new scripting. Maybe I won't have to, of course..... It seems like existing patches with old scripting work, but I haven't figured out if you can create an 'old' script.....
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 » 16 Nov 2009, 10:20

amiga909 wrote:@bsork: "fit the new FastScript."
any detailed infos on what is different?
There are a lot of differences with the new script module, but coding-wise mostly additions to the old one. The single, most important difference is that the old "main" (actually unnamed) procedure doesn't exist anymore. It has been replaced by two new procedures: Callback and Process. Callback is run once for each change in an input parameter, while Process will run each block. None of them actually has to be in the script though, only the Init procedure.

Most of the old scripts I've encountered, need just two small changes to run as a new one: Change "END." at the bottom to "END;" and add "Procedure Process" before the main "BEGIN". In fact, I've already converted all of amiga909's MIDI add-ons to the new script already, and if I remember correctly this is what I've done in most of them, except removing some semicolons that shouldn't have been there but didn't create any problems with the old script module.

Some of the larger scripts might need some more reworking though. The MidiMapper is an example: When loading the old patch, it compiles as an old script (don't quite remember whether midi output worked though...), but the simple changes described above gave me some compilations errors, so I did a more extensive rewrite. Luckily for me, I had used quite a lot of local procedures instead of putting everything within the main part, so changing it to use Callback and Process was mostly a case of cut'n'paste. The bad thing was that I didn't find a way to do that that worked within the existing script (as with all the add-ons I've converted), so I had to redo all the connections to and from the new script.

Well - back to work I suppose :(... I have to do some thinking and will get back to you all with some suggestions. One thing that have crossed my mind that comes into play if you would string several scripts after another, you'd get increased latency, as I'm pretty sure (but will have to test it!) that script one's output will be picked up in the next execution block by script two's input.

Any more suggestions or opinions?
Bjørn S

amiga909
Member
Posts: 324
Contact:

Unread post by amiga909 » 16 Nov 2009, 13:42

tkx for ur extensive reply, bsork.

wow! quite some drastic changes.
bsork wrote:In fact, I've already converted all of amiga909's MIDI add-ons to the new script already, and if I remember correctly this is what I've done in most of them, except removing some semicolons that shouldn't have been there but didn't create any problems with the old script module.
oh! thanks a lot for this!
hope u didnt convert all my bugs too ;)
bsork wrote:Well - back to work I suppose :(... I have to do some thinking and will get back to you all with some suggestions. One thing that have crossed my mind that comes into play if you would string several scripts after another, you'd get increased latency, as I'm pretty sure (but will have to test it!) that script one's output will be picked up in the next execution block by script two's input.

Any more suggestions or opinions?
<<non-bloated scripts>>
yes. ur most probably right about the latency. there has to be the right balance between powerful but barely useable and trivial but non-standalone designed und thus latency inducing scripts.
still i'd go for small scripts. its also a question of clever patch design: one can use parallel midi processing if latency is a problem - instead of eg. transposing channel 1 and then companding channel 2, one can split midi by channel and do it in parallel.

i second woodslanding insofar:
- every script should work with any midi data.
no prefiltering required. this just means, the script should not cease working if system clock is running thru. dont think its necessary to have a midi transposer that can transpose all kind of message types, rather have a pitchbend transposer, a cc transposer; etc. this allows easy readable, context-sensitive controls (eg. transpose in octaves for notes, transpose in semitones for pitchbend; transpose cc values and/or cc numbers etc.)
- filter processes should always output the filtered data too.
thirdly i'd propose that every script should avoid hanging notes (using ur technique from the note transposer) and every script should have a bypass switch.

<<concerning midi mapper>>
i very much like the idea of having arrays as input control (data mapping). maybe rather instead of an universal scale factor (data scaling). having both ways of mapping/scaling in one script is too much, i find.

woodslanding
Member
Posts: 1327
Contact:

Unread post by woodslanding » 16 Nov 2009, 19:55

In the issue of many small scripts vs. one large one. I wonder if you could have a chaning set of II/O where the text of each of these methods is sent on to the next script instead of being processed, and is then concatenated and processed by the last script in the chain. Then you could have lots of little scripts, and still use a bunch in a row without introducing latency.

Probably too much work to be worthwhile.

Beyond that, parallel processing is the way to go. Not only is it faster, its more readable. That's definitely an important lesson in midi work. That's why filters that give both accepted and rejected outs are really useful.

Another thing I would wish for midi-wise is note-closing on conductor preset changes. But I'm not sure what that entails. Indeed it may be happenning already, but since I have to wait for actual silence between patch changes, because of the buss noise bug, I've never tested it.

The big reason for converting cc64 to note data is to take advantage of note-closing technology. You can send noteoffs until you're blue in the face, and your module will keep sounding if it hasn't gotten a cc64 off.
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 » 16 Nov 2009, 23:10

You guys have given me lots of stuff to digest - why isn't there a headscratching smiley available on this forum? :)

The concatenated strings idea is interesting, but as you said it will probably be too much work, and I feel that it is somewhat the opposite of the whole idea behind this: To create a set of patches and/or scripts where each has a limited functionality and that hopefully should be relatively easy to grasp for newcomers to Usine. The structure of the new script engine should make it possible to write the scripts in such a way that a user could do some very easy editing to merge several functions into one script if he so wishes. Actually I've been thinking that maybe I'll start with just a few, big scripts, and then create several versions of each where I comment most of the code so that each performs one function. That way a user can just remove commenting to introduce more functions. Not sure, though...

OTOH, one thing I think I'm sure of, is that it's a good idea to "group" the functions. For instance:
- rerouting/rechanneling (some kind of matrix?)
- filtering
- scaling
- change message types
...and others...

@amiga909: Is it ok with you if I end up "stealing" some of the ideas and coding from your add-ons?

@woodslanding: Just out of curiosity: What note-closing technology are you referring to? I thought most instruments (with the possible exception of more advanced pianos and such) actually handled notes held and released by a sustain just the same as when the notes where held. BTW, it's a rather simple thing to implement.
Re note-closing on conductor changes: The ClearMidiNotes script in the add-ons should do the trick if triggered by a HasChanged.

...back to the think tank...
Bjørn S

amiga909
Member
Posts: 324
Contact:

Unread post by amiga909 » 17 Nov 2009, 08:47

note-closing technology: not sure if woodslanding referred to this?
i see 2 use cases:
1) a note off gets lost (eg. due to conductor change, cable remove, etc.): here clearmidinotes reacts to a manual close is the solution - as bsork explained
2) a note off doesnt fit anymore: can happen with any midi processor that changes note numbers while a note is held. or that changes output. eg. with 'midi split': if the split value is changed while a note is held, it can happen the corresponding note off is sent to the wrong output.

@bsork: i am ok with that.
i'd be happy of course to hear about if (IF!) u find something clever that gets reused by the script maestro :)

woodslanding
Member
Posts: 1327
Contact:

Unread post by woodslanding » 17 Nov 2009, 20:50

Great stuff! -- I like your code commenting solution.

On clearmidinotes--just trigger it from the conductor object??

I need to integrate it in to my wkp, but since I have to wait for all sounds to decay to silence before every patch change, due to the buss switching bug, I haven't been troubled much by stuck notes. I'd like to be able to play it faster and looser in V5, tho!

Does clearmidinotes treat cc64 messages the same way? I guess that's all you have to do, and you don't even have to worry about transposition. It's just important that note filter objects always treat cc64 like a note, not like a cc, so that cc64 data always moves with the notes.

No real need to hold note-offs, per se.

Also, about grouping. That would be helpful. Right now midi processing stuff is pretty randomly sprinkled around the menus. A lot of times I end up opening old workspaces or patches that I know contain something, because I can't find it in the menus!
Custom Ryzen 5900x MATX build, Win10, Fireface UFX, touchscreen
Custom 2 manual midi keyboard
Usine, Kontakt, Reaktor, Synthmaster, Byome, Arturia, Soundtoys, Unify

woodslanding
Member
Posts: 1327
Contact:

Unread post by woodslanding » 17 Nov 2009, 21:36

one more detail:

when building these scripts, it would be nice to avoid short or programmatic variables like "in", "out", "input", "output","array", etc.

The reason is that if someone is editing the script, they can't do a find and replace on the variable, without changing the code. If you use something more precise like "audioOut", or "MIDIin", there won't be this problem.

It also helps, in copying and pasting between scripts, if everything has a distinctive name......

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

amiga909
Member
Posts: 324
Contact:

Unread post by amiga909 » 18 Nov 2009, 07:49

woodslanding wrote:Does clearmidinotes treat cc64 messages the same way? I guess that's all you have to do, and you don't even have to worry about transposition. It's just important that note filter objects always treat cc64 like a note, not like a cc, so that cc64 data always moves with the notes.
interesting point. but it has to be optional. cc64 isnt always denoted as holding a note (no CC has an universal meaning).
more opinions on this (i never use the sustain pedal command so i cant say)?
woodslanding wrote:No real need to hold note-offs, per se.
if u mean case 2 i mentioned: thats ur perspective.
imho its important if one wants to go nuts with automation. also from a programmer perspective illegal data (a note off that fails to close a note) should not be possible.
woodslanding wrote:The reason is that if someone is editing the script, they can't do a find and replace on the variable, without changing the code. If you use something more precise like "audioOut", or "MIDIin", there won't be this problem
yes, self-explaining variables are a good idea. i'd opt for more documentation too.
ur reason isnt valid thu: one should avoid to name a variable 'i' then?
try notepad++ as external midi script editor. integrates nicely with usine, highlights syntax (pascal) and with 'match whole word only' u can find+replace any variable name.

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

Unread post by bsork » 18 Nov 2009, 08:45

Regarding ClearMidiNotes, when getting a trigger it sends out missing NoteOffs and CC64=0. think I made it as a response to some patching question.

I agree with woodslanding in that sustained notes should be handled as delayed NoteOffs, at least when some transposition or rerouting takes place. The CC number should be a variable, which would have the added benefit that you could use some other controller like the modwheel - normally assigned to CC1 - for holding notes. Don't quite think I can guarantee to come up with something that would link every NoteOff to the right NoteOn though, especially when doing more esoteric transformations.

About the latency (and CPU) problem with several functions after another, I think I'm drifting away from the "comment code" idea and onto something that - if not technically, at least conceptually - is more like woodslanding's script idea. I have to do some more thinking before I present the idea to you though. :) No matter what, it would be centered around a pretty large script, so I find it natural to create different smaller scripts with a limited functionality.

I also agree that the MIDI extras in the distro could be organized better.

About naming conventions, I'll do my best to balance laziness (= short and undescriptive) with readability. :)

Come to think of it, this topic hasn't really been dealing much with the old MidiMapper patch, which of course is quite ok. However, that makes me wonder whether someone out there actually uses it?
Bjørn S

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

Unread post by bsork » 18 Nov 2009, 10:32

Without bothering about technicalities, I think I've come up with a skeleton of an idea:

1) 1 midi in, 2 outs.
2) Two internal data streams; A and B.
3) A couple of selectors/filters for each stream, with the option of excluding the selected data from the "thru" (=input) in 5)
4) Some transformers for each stream (transpose, change message type, swap data1/data2, scale, ...)
5) Output section where "thru", "A", and "B" separately can be routed to either of the outputs.
Bjørn S

amiga909
Member
Posts: 324
Contact:

Unread post by amiga909 » 18 Nov 2009, 11:39

bsork wrote:Come to think of it, this topic hasn't really been dealing much with the old MidiMapper patch, which of course is quite ok. However, that makes me wonder whether someone out there actually uses it?
dont think we didnt discuss it. for me, its actually quite difficult to sort out what can be done with midimapper.
bsork wrote:Don't quite think I can guarantee to come up with something that would link every NoteOff to the right NoteOn though, especially when doing more esoteric transformations.
why not? i tried this and it works (not sure for really EVERY situation).
dont forget that in a (regular) input stream its always clear which 2 note messages belong together. so, principally it should just be a matter of keeping a record what has been done to the input notes. in ur design there are 2 things to consider: output routing + value transformation. well possible i am forgetting something substantial.
i admit its a personal thing i am following here, grounding in an interest in experimental midi fx. afaik no (vst) midi processor really cares about that.


the generic wood is imaging (concatenate strings), is appealing.
i'll eagerly wait to see if something like this is possible :)


hope i am not bothering u too much, bsork.
in the end, i'll maybe make the stuff i need myself anyway. also i have to confess that i am strongly biased by plogue bidule where there are loads of tiny, single-purpose midi modules.

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

Unread post by bsork » 18 Nov 2009, 12:52

amiga090 wrote:its actually quite difficult to sort out what can be done with midimapper
Well, that's one of the reasons for this, isn't it? ;) As I've written before, I have mostly converted the patch to V5, but there are still some bugs in there. I don't think I bother getting the whole thing working as it should, but I think I will make a simplified standalone patch of the left, "range" part of the patch. If I do, however, it won't be part of the idea I presented in my previous post as it will be a much too complicated.

About the NoteOffs, I have given it a bit more thought and I actually think it's feasible without too much coding.
amiga090 wrote:hope i am not bothering u too much, bsork
Not at all! What I'm missing in this thread is some opinions from other users than yourself and woodslanding, both of which I'm certain can create pretty wild and complex midi scripts yourselves. What I hope to cough up in the end is, after all, a set of midi tools for rather simple, "everyday" midi manipulations that shouldn't be complicated to understand.
Bjørn S

woodslanding
Member
Posts: 1327
Contact:

Unread post by woodslanding » 19 Nov 2009, 02:25

Okay, okay, I'll pipe down..... ;)

I also come from bidule, and was initially dismayed at usine's limited midi manipulation options. However, I think it has 1) improved a great deal with user modules and 2) was never as bad as I thought--just very difficult to find things in menus.

But you are right--I will probably end up making my own scripts anyway, but good starting points will continue to be very important.

I was taught OO, and variable naming and scope as a source of bugs was beat into my head! But when you have less control over scope, naming seems even more important. For myself, I refactor constantly to keep my variable names as precise as possible. The more readable my code is, the easier it is to debug--and I am not a good enough programmer that typing is my rate-limiting factor-- I learned programming too late in life for that!! So I typically replace 'i' and with a word that explains what's going on like "midiByteNumber".

And yes, I use notepad++, but a public library for inexperienced end users should do what it can to simplify things for those who aren't likely to invest the time to learn about, download, and learn to use more sophisticated external tools.

These are the folks Bjorn is talking about, and writing for, but unfortunately they are less likely to weigh in on a forum like this.....

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

woodslanding
Member
Posts: 1327
Contact:

Unread post by woodslanding » 20 Nov 2009, 05:09

I just found another reason for converting sustain pedal. I've got a great new free vst I am using in my rig, from the kvr developers challenge. Unfortunately, it's not a terribly professional product, and didn't implement sustain pedal..... I suppose there are a few out there like that.

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

woodslanding
Member
Posts: 1327
Contact:

Unread post by woodslanding » 20 Nov 2009, 05:48

one thing I just realized.....

Go ahead and use those short variable names--and then de-obfuscate them in notepad before you post them. I'll help....

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

amiga909
Member
Posts: 324
Contact:

Unread post by amiga909 » 20 Nov 2009, 13:49

woodslanding wrote:I just found another reason for converting sustain pedal. I've got a great new free vst I am using in my rig, from the kvr developers challenge. Unfortunately, it's not a terribly professional product, and didn't implement sustain pedal..... I suppose there are a few out there like that.

-e
nice idea.
maybe u could do a dedicated script for us that emulates sustain pedal functionality?
u could reuse the script 'keyboard hold' - should save some time.

slightly off topic again ;)

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

Unread post by bsork » 20 Nov 2009, 16:14

HI, I will try to use descriptive variable and procedure names all the way. That doesn't mean you won't see any integers i, j, k.... - especially in FOR-loops. ;) I will also do my best to make it relatively simple to add new functions to the script.

The convert sustain idea will be implemented, and I also plan to create standalone versions of all the single functions I'm going to implement. But it will take time...

BTW, which VST are you referring to? I don't have time to check them all out - the only one I've downloaded so far is the Stolon pitchshifter as I've been a sucker for harmonizers and pitchshifters after using an Eventide H949 in studio a veeery long time ago.
Bjørn S

woodslanding
Member
Posts: 1327
Contact:

Unread post by woodslanding » 21 Nov 2009, 11:02

well, I realize you always set the value of a var in a for loop, so there is not really any danger of scope issues..... you can have lots of i's in a program, and they won't get in each other's way---so long as they're not nested ;)

I just wrote a version of the concatenation script with multiple input strings, where I realized 'i' was way more readable than the long variable name, so I changed it..... so much for grand policies!

the vst is transistorhead. Just what I needed for a bit of automated background chaos.....

The automated sus script is on my list--but where is this 'keyboard hold' script???

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

amiga909
Member
Posts: 324
Contact:

Unread post by amiga909 » 21 Nov 2009, 11:59

woodslanding wrote:The automated sus script is on my list--but where is this 'keyboard hold' script???

-e
midi addons: some_midi scripts_V2 (does not work with fast script)
its not the same as u want. however, i'd go from that script to make a midi sustain.

woodslanding
Member
Posts: 1327
Contact:

Unread post by woodslanding » 22 Nov 2009, 03:36

Wow, Amiga, that is nice work!

It shouldn't take much modification.....
Custom Ryzen 5900x MATX build, Win10, Fireface UFX, touchscreen
Custom 2 manual midi keyboard
Usine, Kontakt, Reaktor, Synthmaster, Byome, Arturia, Soundtoys, Unify

woodslanding
Member
Posts: 1327
Contact:

Unread post by woodslanding » 22 Nov 2009, 08:16

Okay, it took a little more modification than I thought ;)

Now I am trying to integrate it with Bsork's smart transpose script. Very similar structure. Not so similar that I can use the same array--

or can I???

Can't quite get my head around this yet.....
Custom Ryzen 5900x MATX build, Win10, Fireface UFX, touchscreen
Custom 2 manual midi keyboard
Usine, Kontakt, Reaktor, Synthmaster, Byome, Arturia, Soundtoys, Unify

woodslanding
Member
Posts: 1327
Contact:

Unread post by woodslanding » 22 Nov 2009, 08:38

okay, I think not..... seems like the two arrays are kind of independent. At least my brain likes them that way ;)

now I'm trying to figure out how transposition and note limiting interact, such that there are still no orphan notes....

maybe this should all be moved to a patching thread. I realize I've hijacked Bsorks original post....

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

amiga909
Member
Posts: 324
Contact:

Unread post by amiga909 » 23 Nov 2009, 12:23

about the hanging-note thing:
there actually is a vst series that takes care of it:
"•most plugins include a sort of hanging-note-protection system (where applicable) to enable making changes during playback (e.g. when a MIDI thru option is toggled) without the risk of not releasing notes which are played at the same time the changes happen "

about a sustain effect:
S-CC-Sustain-Lite
http://www.s-production.de/
haven't tested it but i dont think a usine script would be much more effective.

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

Unread post by bsork » 23 Nov 2009, 13:14

woodslanding wrote:maybe this should all be moved to a patching thread. I realize I've hijacked Bsorks original post....
No worries, mate!
Bjørn S

woodslanding
Member
Posts: 1327
Contact:

Unread post by woodslanding » 23 Nov 2009, 20:25

In that case, I have a question:

As far as I can tell, in the new scripts, I should be putting midi processing in the process thread, and respond to changes from other (non-audio) inputs in the callback thread. My question is: If I want a button input to clearNotes, which involves sending out a bunch of midi messages, but is triggered by a callback, how do I do this??

Process is always looping, right?? So if the callback adds some values to the process midiArray and increments its size--process can take that in stride? Or is there better way to do it?

Is it possible to put the 'release notes' code in a method that can be called by either thread?? Does the callback need to have its own midi array?

Thanks
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 » 23 Nov 2009, 21:52

Callback is called once for a change in an input parameter, so as long as a script isn't supposed to do anything unless something happens at the input(s) and that can be done within the same block, you actually don't need Process. For something like a delay, however, you would have to use Process. For any midi script with an output, you would also have to use Process, as you will need to set the length of the midi output in the next block.

As long as you're defining your variables globally you can access them from anywhere in the script. (Well, I suppose they have to be declared before getting used. ;)) Within the scripts themselves, there are no differences between the Init, Callback and Process methods and the procedures and/or functions you declare yourself, except for when they are called. Regarding Callback you have to remember that it's called once for each parameter that's changed within the block, so if you have a patch where several parameters may be updated simultaneously (on a recall of preset for instance), and you do a lot of computing with these parameters, it can be wise to use a boolean (doUpdate := TRUE) in Callback, and check that boolean in Process. As far as I can tell, all the Callback calls happen before Process is called.

Eg:

Code: Select all

VAR pParam1, pParam2, pParam3 &#58; tParameter;
VAR param1, param2, param3 &#58; single;
VAR doUpdate &#58; boolean;
....
PROCEDURE Init;
BEGIN
   pParam1 &#58;= CreateParam&#40;'param 1', ...
   pParam2 &#58;= CreateParam&#40;'param 2', ...
   pParam3 &#58;= CreateParam&#40;'param 3', ...
   doUpdate &#58;= FALSE;
END;

PROCEDURE GetParams;
BEGIN
    param1 &#58;= GetValue&#40;pParam1&#41;;
    param2 &#58;= GetValue&#40;pParam2&#41;;
    param3 &#58;= GetValue&#40;pParam3&#41;;
 END;

PROCEDURE Callback&#40;n ; integer&#41;;
BEGIN
   // You can of course use IF-statements instead of CASE
   CASE n OF
       pParam1 &#58; doUpdate &#58;= TRUE;
       pParam2 &#58; doUpdate &#58;= TRUE;
       pParam3 &#58; doUpdate &#58;= TRUE;
   END;
END;

PROCEDURE Process;
BEGIN
   IF &#40;doUpdate&#41; THEN BEGIN
      GetParams;
      doUpdate &#58;= FALSE;
   END;
   ...
END;
To sum up:
Init is called only when compiling, that means only once, so as well defining input/output parameters, put all code that initializes variables (values, array lengths, ...) in here.
Callback is called every time an input parameter is changed, and can be called several times within the same block.
Process is called once for each block.
You can create a script with only the Init procedure, bot mostly you would want to use either Callback or Process or both as well.

Did that help?
Bjørn S

woodslanding
Member
Posts: 1327
Contact:

Unread post by woodslanding » 24 Nov 2009, 07:13

well, that's all very helpful.... I'm going to do some more debugging and see if I can get this script to work. I'm seeing midi out activity on the script object, but nothing is going down the cable. Curious....
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 » 24 Nov 2009, 08:14

Oh, that was strange, can't say I've experienced that. Maybe I have to check some more the scripts I've already converted?
Bjørn S

woodslanding
Member
Posts: 1327
Contact:

Unread post by woodslanding » 24 Nov 2009, 09:00

try this. I've got the code where most of the funtionality seems to be working. All the midi LOOKS so nice!!!

Code: Select all

&#40;*/////////////////////////////////////////////////////
// CHANNEL  
// Version 2009-11-21; author&#58; eric moon
//
// based on work by Bsork and amiga909
//////////////////////////////////////////////////////
*&#41;

//  This takes a number of midi inputs &#40;on separate cables, for visual clarity&#41;
//  and performs the following operations on each.
//  enable, disable--per input
//  strip CCs and AT and send them out a separate output
//  read CC64 values &#40;or a CC of your choice&#41; to withhold noteoffs until the sus pedal is released.
//  limit to minimum and maximum notes--one range affects all inputs, as it's assumed you'll typically be using one input at a time
//  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 new notesOns as long as it is held
//  It is designed to function as part of a mixer channel strip, running between several keyboards and a vsti.


CONST SUS_PED= 64;    //change if you use an unorthodox CC for sustaining.
CONST INPUT_COUNT = 2;  //number of midi note sources

CONST NOTE_ON        = 144;	
CONST NOTE_OFF       = 128;
CONST CONTROL        = 176;	
CONST PITCHBEND      = 224;


VAR   noteList                              &#58; ARRAY OF boolean;
VAR   transList                             &#58; ARRAY OF integer;
VAR   midi,noteOff                          &#58; TMidi; 

var midiIns                                 &#58;  ARRAY OF Tparameter;
var enables                                 &#58;  ARRAY OF TParameter;

var notesOut  &#58; Tparameter;
var otherOut  &#58; Tparameter;       // all CCs except the sustain value go out this output.  So does AT and PgCh
var octave    &#58; TParameter;
var semi      &#58; TParameter;
var hiNote    &#58; TParameter;
var lowNote   &#58; TParameter;
var drone     &#58; TParameter;       // sustains, but ignores new noteons
var outChan   &#58; TParameter;       // everything goes out one channel.  It's a channel strip. Use more if you need other channels!
var instBypass&#58; TParameter; 


VAR   octVal, semiVal, transpose            &#58; integer;
VAR   hiVal, lowVal                         &#58; integer; 
VAR   midiInCount                           &#58; integer;
VAR   otherOutCount, notesOutCount          &#58; integer;
VAR   key, inputNum, byteNum                &#58; integer; 
VAR   minChn, maxChn, minKey, maxKey        &#58; integer;
VAR   outputChannel                         &#58; integer;
VAR   isSustained,noInput                   &#58; boolean;
VAR   isEnabled                             &#58; Array of boolean;

//////////////////////////////////////////////////////
// initialize
//////////////////////////////////////////////////////
PROCEDURE init;    
BEGIN  


    minChn &#58;= INPUT_COUNT - 1;   // these are initially set to their opposite extremes....
    maxChn &#58;= 0; 
    minKey &#58;= 128; 
    maxKey &#58;= 0;
    
    isSustained &#58;= FALSE;	
          
    setArrayLength&#40;noteList, 128&#41;;  
    FOR key&#58;=0 TO 127 DO BEGIN 
        noteList&#91;key&#93;&#58;=FALSE;
    END;
    SetArrayLength&#40;transList, 128&#41;;
    FOR key&#58;=0 TO 127 DO BEGIN 
        transList&#91;key&#93;&#58;=0;
    END;  
     
    setArrayLength&#40;midiIns, INPUT_COUNT&#41;;
    setArrayLength&#40;enables, INPUT_COUNT&#41;;
    FOR inputNum &#58;= 0 TO INPUT_COUNT - 1 DO  BEGIN
        enables&#91;inputNum&#93;  &#58;= CreateParam&#40;'enable ' + intToStr&#40;inputNum + 1&#41;,ptSwitch&#41;; 
        SetValue&#40;enables&#91;inputNum&#93;, 1&#41;;       
        SetIsOutPut&#40;enables&#91;inputNum&#93;,false&#41;;        
    END;
       
    octave      &#58;= CreateParam&#40;'octave',ptDataFader&#41;;          SetIsOutPut&#40;octave,false&#41;;
    semi        &#58;= CreateParam&#40;'semi',ptDataFader&#41;;            SetIsOutPut&#40;semi,false&#41;;

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

    hiNote        &#58;= CreateParam&#40;'high note',ptMidiNoteFader&#41;;   SetIsOutPut&#40;hiNote,false&#41;;
    lowNote       &#58;= CreateParam&#40;'low note',ptMidiNoteFader&#41;;    SetIsOutPut&#40;lowNote,false&#41;;  
    outChan       &#58;= CreateParam&#40;'output chan', ptDataFader&#41;;     SetIsOutPut&#40;outchan,false&#41;; 
    SetFormat&#40;outChan,'%.0f'&#41;; SetMin&#40;outChan,1&#41;;  SetMax&#40;outChan,16&#41;;
    SetValue&#40;lowNote, 10&#41;;
    SetValue&#40;hiNote, 115&#41;;
  
    drone         &#58;= CreateParam&#40;'drone', ptSwitch&#41;;              SetIsOutput&#40;drone,false&#41;;
    instBypass  &#58;= CreateParam&#40;'inst bypass', ptSwitch&#41;;       SetIsInput&#40;instBypass,false&#41;;  

    FOR inputNum &#58;= 0 TO INPUT_COUNT - 1 DO  BEGIN
        midiIns&#91;inputNum&#93;  &#58;= CreateParam&#40;'MIDIin ' + intToStr&#40;inputNum + 1&#41; ,ptMidi&#41;;       
        SetIsOutPut&#40;midiIns&#91;inputNum&#93;,false&#41;;
    END;
    
    notesOut      &#58;= CreateParam&#40;'Notes Out',ptMidi&#41;;             SetIsInput&#40;notesOut,false&#41;;  
    otherOut      &#58;= CreateParam&#40;'other Out',ptMidi&#41;;             SetIsInput&#40;otherOut,false&#41;;  


    SetArrayLength&#40;isEnabled, INPUT_COUNT&#41;;    
END;

// <F> isNoteOn&#58;  
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^// 
FUNCTION isNoteOn&#40;midi&#58; tMidi&#41;&#58; boolean;
BEGIN 
  
    IF &#40;midi.msg = NOTE_ON&#41; AND &#40;midi.data2>0&#41; THEN BEGIN 
        result &#58;= true;
    END
    ELSE BEGIN 
        result &#58;= false; 
    END;
END;

// <F> isNoteOff&#58;  
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^// 
FUNCTION isNoteOff&#40;midi&#58; tMidi&#41;&#58; boolean;
BEGIN 
     
    IF &#40;midi.msg = NOTE_OFF&#41; OR &#40;&#40;midi.msg = NOTE_ON&#41; AND &#40;midi.data2 = 0&#41;&#41; THEN BEGIN 
        result &#58;= true;
    END
    ELSE BEGIN 
        result &#58;= false;
    END;
END; 

// <F> isSusPed&#58;  
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^// 
FUNCTION isSusPed&#40;midi&#58; tMidi&#41; &#58; boolean;
BEGIN
    IF &#40;midi.msg = CONTROL&#41; AND &#40;midi.data1 = SUS_PED&#41; THEN BEGIN
        result &#58;= true;
    END
    ELSE BEGIN
        result &#58;= false;
    END;
END; 

// <F> isPitchBend&#58;  
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^// 
FUNCTION isPitchBend&#40;midi&#58; tMidi&#41;&#58; boolean;
BEGIN 
    IF &#40;midi.msg = PITCHBEND&#41; THEN BEGIN 
        result &#58;= true;
    END
    ELSE BEGIN 
        result &#58;= false; 
    END;
END;

// <F> global function inRange--eliminates only noteons outside the range limits 
FUNCTION inRange&#40;midi&#58; tMidi&#41; &#58; boolean;
BEGIN
    IF &#40;isNoteOn&#40;midi&#41;&#41; AND &#40;midi.data1 > lowVal&#41; AND &#40;midi.data1 < hiVal&#41; THEN BEGIN
        result &#58;= TRUE;
    END
    ELSE BEGIN
        result &#58;= FALSE;
    END;
 
END;



// <F> isClear
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^//
FUNCTION isClear&#40;&#41;  &#58; boolean;   // can we stop this loop as soon as a key = true??  Didn't like 'while'
VAR clear &#58; boolean;
BEGIN
    clear &#58;= TRUE;
    BEGIN
        FOR key&#58;= minKey TO maxKey DO BEGIN    
            IF &#40;noteList&#91;key&#93; = TRUE&#41; THEN BEGIN 
                clear &#58;= FALSE;           
            END;                                                          
        END; 
    END;
    result &#58;= clear;     
END;



// <F> ReleaseNotes  --when you release the sustain, or release the drone, or change the output channel
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^//
PROCEDURE ReleaseNotes;
BEGIN
    FOR key&#58;= minKey TO maxKey DO BEGIN    
        IF &#40;noteList&#91;key&#93; = TRUE&#41; THEN BEGIN 
            noteOff.msg &#58;= NOTE_OFF;
            noteOff.channel &#58;= outputChannel; // byte is a conversion--is this needed?
            noteOff.data1 &#58;= key;
            noteOff.data2 &#58;= 0;
            setMidiArrayValue&#40;notesOut, notesOutCount, noteOff&#41;;  //danger when called from callback?
            notesOutCount &#58;= notesOutCount + 1;
            noteList&#91;key&#93; &#58;= FALSE;
        END;                                                          
    END; 
END;
    



// Global Procedure StoreNoteOffs--just de-inlined for readability
//----------------------------------------------//
PROCEDURE StoreNoteOffs;
BEGIN
				
    IF &#40;midi.data1 > maxKey&#41; THEN BEGIN   
        maxKey &#58;= midi.data1;
    END;
    IF &#40;midi.data1 < minKey&#41; THEN BEGIN 
        minKey &#58;= midi.data1; 
    END; 
    noteList&#91;midi.data1&#93; &#58;= TRUE;
END;

// Callback
//----------------------------------------------// 
procedure Callback&#40;n&#58;integer&#41;;

BEGIN

    // get input values    
    hiVal &#58;= trunc&#40;getValue&#40;hiNote&#41;&#41;;
    lowVal &#58;= trunc&#40;getValue&#40;lowNote&#41;&#41;;
    octVal &#58;= trunc&#40;getValue&#40;octave&#41;&#41;;
    semival &#58;= trunc&#40;getValue&#40;semi&#41;&#41;;
    transpose &#58;= &#40;octVal * 12&#41; + semiVal;
writeln&#40;'transpose = ' + intToStr&#40;transpose&#41;&#41;;
    outputChannel &#58;= trunc&#40;getValue&#40;outChan&#41;&#41;;
    // see if any input is enabled....
    noInput &#58;= TRUE;                                         
    FOR inputNum &#58;= 0 to INPUT_COUNT - 1 DO 
    BEGIN
        IF getValue&#40;enables&#91;inputNum&#93;&#41; = 1 THEN BEGIN
            isEnabled&#91;inputNum&#93; &#58;= TRUE; 
            noInput &#58;= FALSE;
        END
        ELSE BEGIN
            isEnabled&#91;inputNum&#93; &#58;= FALSE;
        END;

    END;
        
    // release notes if the drone has just been turned off, or if all inputs and notes are off, or if the channel is changed
    IF &#40;&#40;n = drone&#41; and &#40;getValue&#40;drone&#41; = 0&#41;&#41; OR 
       &#40;&#40;noInput = TRUE&#41; AND &#40;isClear&#40;&#41; = TRUE&#41;&#41;
       OR &#40;n = outChan&#41;                
    THEN 
    BEGIN
        releaseNotes;               // can I call this from callback?
        setValue&#40;instBypass, 1&#41;;
    END
    ELSE BEGIN
        setValue&#40;instBypass, 0&#41;;
    END;

END;
 
 //////////////////////////////////////////////////////
// process
//////////////////////////////////////////////////////
PROCEDURE PROCESS;
BEGIN 

    FOR inputNum &#58;= 0 to INPUT_COUNT - 1 DO BEGIN     
       
        midiInCount &#58;= getLength&#40;midiIns&#91;inputNum&#93;&#41;;	
    	IF &#40;midiInCount > 0&#41; THEN BEGIN

            notesOutCount &#58;= 0;
            otherOutCount &#58;= 0;        
        	FOR byteNum &#58;= 0 TO &#40;midiInCount - 1&#41; DO BEGIN   // process input                           
        	    getMidiArrayValue&#40;midiIns&#91;inputNum&#93;, byteNum, midi&#41;;
                midi.channel &#58;= outputChannel; 
                If isSusPed&#40;midi&#41; THEN BEGIN          
                    if &#40;midi.data2 > 64&#41; THEN BEGIN
                        isSustained &#58;= true;
                    END
                    ELSE BEGIN                         
                        isSustained &#58;= false;  
                        ReleaseNotes;          // send noteoffs when the sus pedal is released
                    END;                              
                END; 
                IF isNoteOn&#40;midi&#41; AND &#40;inRange&#40;midi&#41;&#41; AND &#40;isEnabled&#91;inputNum&#93;&#41; THEN BEGIN	 //note on, sus on, store and send	
                    IF &#40;isSustained = TRUE&#41; THEN BEGIN           
                        StoreNoteOffs;              
                        setMidiArrayValue&#40;notesOut, notesOutCount, midi&#41;;
                        notesOutCount &#58;= notesOutCount + 1;
                    END                      
                    ELSE BEGIN                                        // valid noteon, sus off, send it out
                        transList&#91;midi.data1&#93; &#58;= transpose; 
                        midi.data1 &#58;= midi.data1 + transpose;                
                        setMidiArrayValue&#40;notesOut, notesOutCount, midi&#41;;
                        notesOutCount &#58;= notesOutCount + 1;
                     END;
                END
                ELSE IF isNoteOff&#40;midi&#41; AND &#40;isSustained = FALSE&#41;  THEN BEGIN // valid noteoff, sus off, send it out
                        midi.data1 &#58;= midi.data1 + transList&#91;midi.data1&#93;;                 
                        setMidiArrayValue&#40;notesOut, notesOutCount, midi&#41;;
                        notesOutCount &#58;= notesOutCount + 1;                 
                END                     
                ELSE IF isPitchBend&#40;midi&#41; THEN BEGIN                        // send PB along with the notes                             
                    midi.channel &#58;= outputChannel;                
                    setMidiArrayValue&#40;notesOut, notesOutCount, midi&#41;;
                    notesOutCount &#58;= notesOutCount + 1;
                END
                ELSE IF &#40;NOT&#40;isNoteOn&#40;midi&#41;&#41;&#41; AND &#40;NOT&#40;isNoteOff&#40;midi&#41;&#41;&#41; AND &#40;NOT&#40;isPitchBend&#40;midi&#41;&#41;&#41; THEN BEGIN  //not a note, pb or sus pedal event
                    setMidiArrayValue&#40;otherOut, otherOutCount, midi&#41;;
                    otherOutCount &#58;= otherOutCount + 1;
                END;                   
            END;                        // process input
            setLength&#40;notesOut, notesOutCount&#41;;		
            setLength&#40;otherOut, otherOutCount&#41;;
            
        END
        ELSE BEGIN 
            setLength&#40;notesOut,0&#41;;
            setLength&#40;otherOut, 0&#41;; 
        END;
    END;  
END;
Does it do this for you???
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 » 24 Nov 2009, 09:32

At work now - will have to take a look tonight at home.
Bjørn S

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

Unread post by bsork » 24 Nov 2009, 14:31

I took a break! :)

Since you're adding stuff to the outputs from Callback, you have to move the resetting of notesOutCount and otherOutCount to the end of Process. (I think I have done the same mistake myself, but my script isn't yet finished enough for the problem to occur. Will have to check later.)

I've only tested with a couple of CreateMidi modules without bothering with any transposing etc and I get the expected data out of the script with the exception of NoteOffs.
Bjørn S

amiga909
Member
Posts: 324
Contact:

Unread post by amiga909 » 24 Nov 2009, 20:57

tkx a lot for the script woodslanding. also thanks to bsork. learnt quite a lot about the new callback thing.

woodslanding
Member
Posts: 1327
Contact:

Unread post by woodslanding » 25 Nov 2009, 04:56

okay, so set them to zero in init, and again at the very end of the process loop?? Okay, I'll try it!

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

woodslanding
Member
Posts: 1327
Contact:

Unread post by woodslanding » 25 Nov 2009, 05:24

Well, I did that, which doubtless will help down the line, but it's not helping the current problem. I'm still seeing really correct-looking data in the midi output window for note limit, transpose and enable but no actual midi data is being sent. Drone is clearly not working correctly and I haven't tried out the sus pedal yet.

Probably should have started with a smaller script, but I got all excited! ;)

-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 » 25 Nov 2009, 08:53

Did you move the lines from the top of Process, or did you copy them? If you copied, the problem will persist as you fill the outputs and increment the counters in a call from Callback which is executed before Process. That's the reason you can see some activity on the outputs: You put some midi data in there and "flashes by" on the outputs, but then you set the lengths to zero, which means that nothing is sent. BTW, as you said you should also initialize the variables in Init. Most probably you won't get any problems without that, but it's good programming practice, and you have better control over anything strange the compiler might do.

In a way it behaves the opposite way of what you get by filling the array and setting the length within a block and then forget to zero the length in the next block. In that case the same data will be sent each block until something else in the script overwrites it.

I didn't have time yesterday for a closer look at your script as I was struggling with some other issues in some of the "normal" Usine modules, but as I said I managed to get data some simple data through just by moving the counter initializations down to the bottom. I did however notice that the alpha seemed more unstable running your script than most others I've tried. You better take a close look at how variables are treated etc, and remember that the new script engine seems less forgiving about minor incompatibilities and errors. You might have to quit and restart once in a while also.

The flip side of more possibilities is that you also get more possibilities for doing something wrong... ;)
Bjørn S

woodslanding
Member
Posts: 1327
Contact:

Unread post by woodslanding » 26 Nov 2009, 08:26

I took the test for midLength > 0 out. Why was that in there?? It doesn't seem like it saves a step. If the length is zero, it's just not going to run the for loop anyway, right?? or was the script engine not able to handle 'for i = 0 to 0' correctly before? It seems to be working now.

So it's functioning, and the sustain pedal is sustaining, but I am getting stuck notes, so I'll have to think carefully about the code. Also, my code as it stands seems to not always update transpose correctly. Sometimes the writeln test value changes, but the note out doesn't, and other times neither is synched up with the values on the sliders.

nevertheless it is an encouraging start.

cheers!
-eric

The code as it stands:

Code: Select all

&#40;*/////////////////////////////////////////////////////
// CHANNEL  
// Version 2009-11-21; author&#58; eric moon
//
// based on work by Bsork and amiga909
//////////////////////////////////////////////////////
*&#41;

//  This takes a number of midi inputs &#40;on separate cables, for visual clarity&#41;
//  and performs the following operations on each.
//  enable, disable--per input
//  strip CCs and AT and send them out a separate output
//  read CC64 values &#40;or a CC of your choice&#41; to withhold noteoffs until the sus pedal is released.
//  limit to minimum and maximum notes--one range affects all inputs, as it's assumed you'll typically be using one input at a time
//  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 new notesOns as long as it is held
//  It is designed to function as part of a mixer channel strip, running between several keyboards and a vsti.


CONST SUS_PED= 64;    //change if you use an unorthodox CC for sustaining.
CONST INPUT_COUNT = 2;  //allows up to 16 inputs, one per channel

CONST NOTE_ON        = 144;	
CONST NOTE_OFF       = 128;
CONST CONTROL        = 176;	
CONST PITCHBEND      = 224;


VAR   noteList                              &#58; ARRAY OF boolean;
VAR   transList                             &#58; ARRAY OF integer;
VAR   midi,noteOff                          &#58; TMidi; 

var midiIns                                 &#58;  ARRAY OF Tparameter;
var enables                                 &#58;  ARRAY OF TParameter;

var notesOut  &#58; Tparameter;
var otherOut  &#58; Tparameter;       // all CCs except the sustain value go out this output.  So does AT and PgCh
var octave    &#58; TParameter;
var semi      &#58; TParameter;
var hiNote    &#58; TParameter;
var lowNote   &#58; TParameter;
var drone     &#58; TParameter;       // sustains, but ignores new noteons
var outChan   &#58; TParameter;       // everything goes out one channel.  It's a channel strip. Use more if you need other channels!
var instBypass&#58; TParameter; 


VAR   octVal, semiVal, transpose            &#58; integer;
VAR   hiVal, lowVal                         &#58; integer; 
VAR   midiInCount                           &#58; integer;
VAR   otherOutCount, notesOutCount          &#58; integer;
VAR   key, inputNum, byteNum                &#58; integer; 
VAR   minChn, maxChn, minKey, maxKey        &#58; integer;
VAR   outputChannel                         &#58; integer;
VAR   isSustained,noInput                   &#58; boolean;
VAR   isEnabled                             &#58; Array of boolean;

//////////////////////////////////////////////////////
// initialize
//////////////////////////////////////////////////////
PROCEDURE init;    
BEGIN  


    minChn &#58;= INPUT_COUNT - 1;   // these are initially set to their opposite extremes....
    maxChn &#58;= 0; 
    minKey &#58;= 128; 
    maxKey &#58;= 0;
    
    isSustained &#58;= FALSE;	
          
    setArrayLength&#40;noteList, 128&#41;;  
    FOR key&#58;=0 TO 127 DO BEGIN 
        noteList&#91;key&#93;&#58;=FALSE;
    END;
    SetArrayLength&#40;transList, 128&#41;;
    FOR key&#58;=0 TO 127 DO BEGIN 
        transList&#91;key&#93;&#58;=0;
    END;  
     
    setArrayLength&#40;midiIns, INPUT_COUNT&#41;;
    setArrayLength&#40;enables, INPUT_COUNT&#41;;
    FOR inputNum &#58;= 0 TO INPUT_COUNT - 1 DO  BEGIN
        enables&#91;inputNum&#93;  &#58;= CreateParam&#40;'enable ' + intToStr&#40;inputNum + 1&#41;,ptSwitch&#41;; 
        SetValue&#40;enables&#91;inputNum&#93;, 1&#41;;       
        SetIsOutPut&#40;enables&#91;inputNum&#93;,false&#41;;        
    END;
       
    octave      &#58;= CreateParam&#40;'octave',ptDataFader&#41;;          SetIsOutPut&#40;octave,false&#41;;
    semi        &#58;= CreateParam&#40;'semi',ptDataFader&#41;;            SetIsOutPut&#40;semi,false&#41;;

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

    hiNote        &#58;= CreateParam&#40;'high note',ptMidiNoteFader&#41;;   SetIsOutPut&#40;hiNote,false&#41;;
    lowNote       &#58;= CreateParam&#40;'low note',ptMidiNoteFader&#41;;    SetIsOutPut&#40;lowNote,false&#41;;  
    outChan       &#58;= CreateParam&#40;'output chan', ptDataFader&#41;;     SetIsOutPut&#40;outchan,false&#41;; 
    SetFormat&#40;outChan,'%.0f'&#41;; SetMin&#40;outChan,1&#41;;  SetMax&#40;outChan,16&#41;;
    SetValue&#40;lowNote, 10&#41;;
    SetValue&#40;hiNote, 115&#41;;
  
    drone         &#58;= CreateParam&#40;'drone', ptSwitch&#41;;              SetIsOutput&#40;drone,false&#41;;
    instBypass  &#58;= CreateParam&#40;'inst bypass', ptSwitch&#41;;       SetIsInput&#40;instBypass,false&#41;;  

    FOR inputNum &#58;= 0 TO INPUT_COUNT - 1 DO  BEGIN
        midiIns&#91;inputNum&#93;  &#58;= CreateParam&#40;'MIDIin ' + intToStr&#40;inputNum + 1&#41; ,ptMidi&#41;;       
        SetIsOutPut&#40;midiIns&#91;inputNum&#93;,false&#41;;
    END;
    
    notesOut      &#58;= CreateParam&#40;'Notes Out',ptMidi&#41;;             SetIsInput&#40;notesOut,false&#41;;  
    otherOut      &#58;= CreateParam&#40;'other Out',ptMidi&#41;;             SetIsInput&#40;otherOut,false&#41;;  
    notesOutCount &#58;= 0;
    otherOutCount &#58;= 0;   

    SetArrayLength&#40;isEnabled, INPUT_COUNT&#41;; 


END;

// <F> isNoteOn&#58;  
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^// 
FUNCTION isNoteOn&#40;midi&#58; tMidi&#41;&#58; boolean;
BEGIN 
  
    IF &#40;midi.msg = NOTE_ON&#41; AND &#40;midi.data2>0&#41; THEN BEGIN 
        result &#58;= true;
    END
    ELSE BEGIN 
        result &#58;= false; 
    END;
END;

// <F> isNoteOff&#58;  
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^// 
FUNCTION isNoteOff&#40;midi&#58; tMidi&#41;&#58; boolean;
BEGIN 
     
    IF &#40;midi.msg = NOTE_OFF&#41; OR &#40;&#40;midi.msg = NOTE_ON&#41; AND &#40;midi.data2 = 0&#41;&#41; THEN BEGIN 
        result &#58;= true;
    END
    ELSE BEGIN 
        result &#58;= false;
    END;
END; 

// <F> isSusPed&#58;  
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^// 
FUNCTION isSusPed&#40;midi&#58; tMidi&#41; &#58; boolean;
BEGIN
    IF &#40;midi.msg = CONTROL&#41; AND &#40;midi.data1 = SUS_PED&#41; THEN BEGIN
        result &#58;= true;
    END
    ELSE BEGIN
        result &#58;= false;
    END;
END; 

// <F> isPitchBend&#58;  
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^// 
FUNCTION isPitchBend&#40;midi&#58; tMidi&#41;&#58; boolean;
BEGIN 
    IF &#40;midi.msg = PITCHBEND&#41; THEN BEGIN 
        result &#58;= true;
    END
    ELSE BEGIN 
        result &#58;= false; 
    END;
END;

// <F> global function inRange--eliminates only noteons outside the range limits 
FUNCTION inRange&#40;midi&#58; tMidi&#41; &#58; boolean;
BEGIN
    IF &#40;isNoteOn&#40;midi&#41;&#41; AND &#40;midi.data1 > lowVal&#41; AND &#40;midi.data1 < hiVal&#41; THEN BEGIN
        result &#58;= TRUE;
    END
    ELSE BEGIN
        result &#58;= FALSE;
    END;
 
END;



// <F> isClear
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^//
FUNCTION isClear&#40;&#41;  &#58; boolean;   // can we stop this loop as soon as a key = true??  Didn't like 'while'
VAR clear &#58; boolean;
BEGIN
    clear &#58;= TRUE;
    BEGIN
        FOR key&#58;= minKey TO maxKey DO BEGIN    
            IF &#40;noteList&#91;key&#93; = TRUE&#41; THEN BEGIN 
                clear &#58;= FALSE;           
            END;                                                          
        END; 
    END;
    result &#58;= clear;     
END;



// <F> ReleaseNotes  --when you release the sustain, or release the drone, or change the output channel
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^//
PROCEDURE ReleaseNotes;
BEGIN
    FOR key&#58;= minKey TO maxKey DO BEGIN    
        IF &#40;noteList&#91;key&#93; = TRUE&#41; THEN BEGIN 
            noteOff.msg &#58;= NOTE_OFF;
            noteOff.channel &#58;= outputChannel; // byte is a conversion--is this needed?
            noteOff.data1 &#58;= key;
            noteOff.data2 &#58;= 0;
            setMidiArrayValue&#40;notesOut, notesOutCount, noteOff&#41;;  //danger when called from callback?
            notesOutCount &#58;= notesOutCount + 1;
            noteList&#91;key&#93; &#58;= FALSE;
        END;                                                          
    END; 
END;
    



// Global Procedure StoreNoteOffs--just de-inlined for readability
//----------------------------------------------//
PROCEDURE StoreNoteOffs;
BEGIN
				
    IF &#40;midi.data1 > maxKey&#41; THEN BEGIN   
        maxKey &#58;= midi.data1;
    END;
    IF &#40;midi.data1 < minKey&#41; THEN BEGIN 
        minKey &#58;= midi.data1; 
    END; 
    noteList&#91;midi.data1&#93; &#58;= TRUE;
END;

// Callback
//----------------------------------------------// 
procedure Callback&#40;n&#58;integer&#41;;

BEGIN

    // get input values    
    hiVal &#58;= trunc&#40;getValue&#40;hiNote&#41;&#41;;
    lowVal &#58;= trunc&#40;getValue&#40;lowNote&#41;&#41;;
    octVal &#58;= trunc&#40;getValue&#40;octave&#41;&#41;;
    semival &#58;= trunc&#40;getValue&#40;semi&#41;&#41;;
    transpose &#58;= &#40;octVal * 12&#41; + semiVal;
writeln&#40;'transpose = ' + intToStr&#40;transpose&#41;&#41;;
    outputChannel &#58;= trunc&#40;getValue&#40;outChan&#41;&#41;;
    // see if any input is enabled....
    noInput &#58;= TRUE;                                         
    FOR inputNum &#58;= 0 to INPUT_COUNT - 1 DO 
    BEGIN
        IF getValue&#40;enables&#91;inputNum&#93;&#41; = 1 THEN BEGIN
            isEnabled&#91;inputNum&#93; &#58;= TRUE; 
            noInput &#58;= FALSE;
        END
        ELSE BEGIN
            isEnabled&#91;inputNum&#93; &#58;= FALSE;
        END;

    END;
      
    // release notes if the drone has just been turned off, or if all inputs and notes are off, or if the channel is changed
    IF &#40;&#40;n = drone&#41; and &#40;getValue&#40;drone&#41; = 0&#41;&#41; OR 
       &#40;&#40;noInput = TRUE&#41; AND &#40;isClear&#40;&#41; = TRUE&#41;&#41;
       OR &#40;n = outChan&#41;                
    THEN 
    BEGIN
        releaseNotes;               // can I call this from callback?
        setValue&#40;instBypass, 1&#41;;
    END
    ELSE BEGIN
        setValue&#40;instBypass, 0&#41;;
    END;
END;
 
 //////////////////////////////////////////////////////
// process
//////////////////////////////////////////////////////
PROCEDURE PROCESS;
BEGIN 
    notesOutCount &#58;= 0;
    otherOutCount &#58;= 0;
    FOR inputNum &#58;= 0 to INPUT_COUNT - 1 DO BEGIN     
       
        midiInCount &#58;= getLength&#40;midiIns&#91;inputNum&#93;&#41;;	
       
        	FOR byteNum &#58;= 0 TO &#40;midiInCount - 1&#41; DO BEGIN   // process input                           
        	    getMidiArrayValue&#40;midiIns&#91;inputNum&#93;, byteNum, midi&#41;;
                midi.channel &#58;= outputChannel; 
                If isSusPed&#40;midi&#41; THEN BEGIN          
                    if &#40;midi.data2 > 64&#41; THEN BEGIN
                        isSustained &#58;= true;
                    END
                    ELSE BEGIN                         
                        isSustained &#58;= false;  
                        ReleaseNotes;          // send noteoffs when the sus pedal is released
                    END;                              
                END; 
                IF isNoteOn&#40;midi&#41; AND &#40;inRange&#40;midi&#41;&#41; AND &#40;isEnabled&#91;inputNum&#93;&#41; THEN BEGIN	 //note on, sus on, store and send	
                    transList&#91;midi.data1&#93; &#58;= transpose; 
                    midi.data1 &#58;= midi.data1 + transpose; 
                    IF &#40;isSustained = TRUE&#41; THEN BEGIN           
                        StoreNoteOffs;              
                        setMidiArrayValue&#40;notesOut, notesOutCount, midi&#41;;
                        notesOutCount &#58;= notesOutCount + 1;
                    END                      
                    ELSE BEGIN                                        // valid noteon, sus off, send it out               
                        setMidiArrayValue&#40;notesOut, notesOutCount, midi&#41;;
                        notesOutCount &#58;= notesOutCount + 1;
                     END;
                END
                ELSE IF isNoteOff&#40;midi&#41; AND &#40;isSustained = FALSE&#41;  THEN BEGIN // valid noteoff, sus off, send it out
                        midi.data1 &#58;= midi.data1 + transList&#91;midi.data1&#93;;                 
                        setMidiArrayValue&#40;notesOut, notesOutCount, midi&#41;;
                        notesOutCount &#58;= notesOutCount + 1;                 
                END                     
                ELSE IF isPitchBend&#40;midi&#41; THEN BEGIN                        // send PB along with the notes                             
                    midi.channel &#58;= outputChannel;                
                    setMidiArrayValue&#40;notesOut, notesOutCount, midi&#41;;
                    notesOutCount &#58;= notesOutCount + 1;
                END
                ELSE IF &#40;NOT&#40;isNoteOn&#40;midi&#41;&#41;&#41; AND &#40;NOT&#40;isNoteOff&#40;midi&#41;&#41;&#41; AND &#40;NOT&#40;isPitchBend&#40;midi&#41;&#41;&#41; THEN BEGIN  //not a note, pb or sus pedal event
                    setMidiArrayValue&#40;otherOut, otherOutCount, midi&#41;;
                    otherOutCount &#58;= otherOutCount + 1;
                END;                   
   
        END;
    END;
    setLength&#40;notesOut, notesOutCount&#41;;		
    setLength&#40;otherOut, otherOutCount&#41;; 
 
END;
Custom Ryzen 5900x MATX build, Win10, Fireface UFX, touchscreen
Custom 2 manual midi keyboard
Usine, Kontakt, Reaktor, Synthmaster, Byome, Arturia, Soundtoys, Unify

woodslanding
Member
Posts: 1327
Contact:

Unread post by woodslanding » 26 Nov 2009, 08:56

amiga909 wrote:about the hanging-note thing:
there actually is a vst series that takes care of it:
"•most plugins include a sort of hanging-note-protection system (where applicable) to enable making changes during playback (e.g. when a MIDI thru option is toggled) without the risk of not releasing notes which are played at the same time the changes happen "

about a sustain effect:
S-CC-Sustain-Lite
http://www.s-production.de/
haven't tested it but i dont think a usine script would be much more effective.
Missed this the first time. I'm going to check it out. Looks like good stuff, and if it's programmed in C, should be fast.

Thanks!
-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 » 26 Nov 2009, 09:41

woodslanding wrote:I took the test for midLength > 0 out. Why was that in there?? It doesn't seem like it saves a step. If the length is zero, it's just not going to run the for loop anyway, right?? or was the script engine not able to handle 'for i = 0 to 0' correctly before?
Beware that FOR i := 0 TO 0 will execute the loop once! So you should either check length 0 with an IF before the loop, or use a WHILE loop. Something like:

Code: Select all

 i &#58;= 0;
WHILE i < cnt DO BEGIN
   do_something...
   i &#58;= i + 1;
END;
Bjørn S

woodslanding
Member
Posts: 1327
Contact:

Unread post by woodslanding » 26 Nov 2009, 10:28

hmmm. okay.

Wow, just looked at the code, and in reality it would be 'for i = 0 to -1'

doesn't sound like a good thing, but it's not crashing the script.... but I guess the important thing is that I can bypass the FOR loop without specifically setting the output array length to zero--if it startes out at size zero, and nothing is added to it, it's still size zero.

Any clues why my transpose value isn't tracking? What I'm doing in callback() seems very simple and straighforward. It is the only place the value of transpose is changed......

Oh--maybe I need to be getting parameters in the process loop, ala your earlier suggestion!! I will try that.

thanks!
-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 » 26 Nov 2009, 11:50

'FOR i := 0 TO -1 DO' is OK, it means no looping.

You still have to move the 'notesOutCount := 0; otherOutCount := 0;' down below where you set the lengths though, as you're calling releaseNotes from Callback. Remember that Callback is executed once for each change in an input parameter before Process is called, so what happens when drone is turned off you first fill the outputs in releaseNotes, then in Process the counters are zeroed which means that the data from Callback->releaseNotes is never sent.

BTW, I'm thinking of implementing these varieties of sustain within the script I'm working on:
Sustain: Like your script (without drone); delays NoteOffs until sustain is deactivated.
Sustenuto: Delays NoteOffs only for notes held when activating sustain.
Hold: Like amiga909's script; accumulates notes until all have been released, and send NoteOffs when a new NoteOn is received or sustain is deactivated.
There will also be a trigger/button input to send all missing NoteOffs + CC123/AllNotesOff.
Bjørn S

woodslanding
Member
Posts: 1327
Contact:

Unread post by woodslanding » 26 Nov 2009, 20:46

Ahhh, I understand about resetting the count now. But I did need to put it outside the 'for' loop to solve my original problem.

Sostenuto is clever--I've never seen that in the midi world!
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 Nov 2009, 21:11

It's actually a part of the (original?) midi spec, defined as CC66. Why portamento = CC65 is squeezed between sustain and sostenuto is strange, though. Go figure...
Bjørn S

woodslanding
Member
Posts: 1327
Contact:

Unread post by woodslanding » 27 Nov 2009, 05:56

putting portamento on a pedal is not a bad idea, though!
Custom Ryzen 5900x MATX build, Win10, Fireface UFX, touchscreen
Custom 2 manual midi keyboard
Usine, Kontakt, Reaktor, Synthmaster, Byome, Arturia, Soundtoys, Unify

gurulogic
Member
Posts: 1019
Contact:

Unread post by gurulogic » 03 Dec 2009, 19:19

Built in functionality similar to this VST would be great!---> http://www.asseca.com/nicfit/mungrack.html

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

Unread post by bsork » 03 Dec 2009, 21:44

Hi gurulogic, and thanks for directing me to MungRack. It seems to have a lot in common with the ideas I'm working. The interface will obviously look a lot different, in some ways (IMO) more user friendly, in other probably less. And since this will be Usine patches, you can allways redesign the UI if you want to. :)

There are some things that MungRack do that I have no plans of implementing, but there are also stuff that I - from reading the docs - don't think it will do; like the sustain/sustenotu/hold stuff mentioned before in this topic. I will also try to do my best to avoid hanging notes, even when changing channel/note range/transpose/output parameters when notes are sounding.

Just to recap a little; what I'm working on is a patch with a script to handle various filters and transformers, and I also plan to create smaller, single function version patches with most, if not all, the transformers etc.

Still got a lot of work to do, I'm afraid...
Bjørn S

gurulogic
Member
Posts: 1019
Contact:

Unread post by gurulogic » 04 Dec 2009, 03:17

Hmm..yeah, I just discovered MungRack today. I was in desparate need of a way to convert note off with a velocity of 0 and note on with a velocity of 64 into a midi CC per note on and off with a low value of 0 and a high value of 127 and MungRack did the trick perfectly ...I could find no way in Usine.
Definately more tools like this are needed in Usine!

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

Unread post by bsork » 04 Dec 2009, 16:15

If I understand you correctly, it wouldn't be too complicated to create something like that with a handful of modules in Usine. Some midi filters and midi create messages coupled with some logical checks would probably do the job.

I will try to implement stuff like this, but the more straight-forward stuff like filtering, transposition, rerouting and value scaling will have the priority.
Bjørn S

woodslanding
Member
Posts: 1327
Contact:

Unread post by woodslanding » 10 Jan 2010, 11:18

Okay, I am finally back at this. I have the script working now. I added a midi learn function for setting the range.

And I finally got around to checking the drone out, and it actually works. Sort of. Problem is, the noteOffs are not going out the output, even though my strace shows the code is getting called with correct values. So I'm wondering if this is somehow related to the problems above.

I tried moving the noteOut count resetting to the beginning of process, but that just gave me a bunch of stuck notes.

So thanks for any tips. This is pretty close to working!

Code: Select all

&#40;*/////////////////////////////////////////////////////
// CHANNEL  
// Version 2009-11-21; author&#58; eric moon
//
// based on work by Bsork and amiga909
//////////////////////////////////////////////////////
*&#41;

//  This takes a number of midi inputs &#40;on separate cables, for visual clarity&#41;
//  and performs the following operations on each.
//  enable, disable--per input
//  strip CCs and AT and send them out a separate output
//  read CC64 values &#40;or a CC of your choice&#41; to withhold noteoffs until the sus pedal is released.
//  limit to minimum and maximum notes--one range affects all inputs, as it's assumed you'll typically be using one input at a time
//  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 new notesOns as long as it is held
//  It is designed to function as part of a mixer channel strip, running between several keyboards and a vsti.


//  When the 'learn' function is enabled, notes are sent out output 2
//  the first note sets the lower range, the second note sets the upper limit.
//  when both notes are sent, learn is disabled, and notes go to output 1 as usual.

CONST SUS_PED= 64;    //change if you use an unorthodox CC for sustaining.
CONST INPUT_COUNT = 2;  //allows up to 16 inputs, one per channel

CONST NOTE_ON        = 144;	
CONST NOTE_OFF       = 128;
CONST CONTROL        = 176;	
CONST PITCHBEND      = 224;


VAR   noteList                              &#58; ARRAY OF boolean;
VAR   transList                             &#58; ARRAY OF integer;
VAR   midi,noteOff                          &#58; TMidi; 

var midiIns                                 &#58;  ARRAY OF Tparameter;
var enables                                 &#58;  ARRAY OF TParameter;

var notesOut  &#58; Tparameter;
var otherOut  &#58; Tparameter;       // all CCs except the sustain value go out this output.  So does AT and PgCh
var octave    &#58; TParameter;
var semi      &#58; TParameter;
var hiNote    &#58; TParameter;
var lowNote   &#58; TParameter;
var drone     &#58; TParameter;       // sustains, but ignores new noteons
var outChan   &#58; TParameter;       // everything goes out one channel.  It's a channel strip. Use more if you need other channels!
var instEnable&#58; TParameter;
var lower    &#58; Tparameter;
var upper   &#58; Tparameter; 
var learn     &#58; TParameter;

VAR   octVal, semiVal, transpose            &#58; integer;
VAR   hiVal, lowVal                         &#58; integer; 
VAR   midiInCount                           &#58; integer;
VAR   otherOutCount, notesOutCount          &#58; integer;
VAR   key, inputNum, byteNum                &#58; integer; 
VAR   minChn, maxChn, minKey, maxKey        &#58; integer;
VAR   outputChannel                         &#58; integer;
VAR   isSustained,noInput                   &#58; boolean;
VAR   isEnabled                             &#58; Array of boolean;
VAR   learning                              &#58; boolean;
VAR   droning                               &#58; boolean;
var   learnCount                            &#58; integer;

//////////////////////////////////////////////////////
// initialize
//////////////////////////////////////////////////////
PROCEDURE init;    
BEGIN  


    minChn &#58;= INPUT_COUNT - 1;   // these are initially set to their opposite extremes....
    maxChn &#58;= 0; 
    minKey &#58;= 128; 
    maxKey &#58;= 0;
    
    isSustained &#58;= FALSE;	
          
    setArrayLength&#40;noteList, 128&#41;;  
    FOR key&#58;=0 TO 127 DO BEGIN 
        noteList&#91;key&#93;&#58;=FALSE;
    END;
    SetArrayLength&#40;transList, 128&#41;;
    FOR key&#58;=0 TO 127 DO BEGIN 
        transList&#91;key&#93;&#58;=0;
    END;  
     
    setArrayLength&#40;midiIns, INPUT_COUNT&#41;;
    setArrayLength&#40;enables, INPUT_COUNT&#41;;
    FOR inputNum &#58;= 0 TO INPUT_COUNT - 1 DO  BEGIN
        enables&#91;inputNum&#93;  &#58;= CreateParam&#40;'enable ' + intToStr&#40;inputNum + 1&#41;,ptSwitch&#41;; 
        SetValue&#40;enables&#91;inputNum&#93;, 1&#41;;       
        SetIsOutPut&#40;enables&#91;inputNum&#93;,false&#41;;        
    END;
       
    octave      &#58;= CreateParam&#40;'octave',ptDataFader&#41;;          SetIsOutPut&#40;octave,false&#41;;
    semi        &#58;= CreateParam&#40;'semi',ptDataFader&#41;;            SetIsOutPut&#40;semi,false&#41;;

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

    hiNote        &#58;= CreateParam&#40;'high note',ptMidiNoteFader&#41;;   SetIsOutPut&#40;hiNote,false&#41;;
    lowNote       &#58;= CreateParam&#40;'low note',ptMidiNoteFader&#41;;    SetIsOutPut&#40;lowNote,false&#41;;  
    outChan       &#58;= CreateParam&#40;'output chan', ptDataFader&#41;;     SetIsOutPut&#40;outchan,false&#41;; 
    SetFormat&#40;outChan,'%.0f'&#41;; SetMin&#40;outChan,1&#41;;  SetMax&#40;outChan,16&#41;;
    SetValue&#40;lowNote, 10&#41;;
    SetValue&#40;hiNote, 115&#41;;
  
    drone         &#58;= CreateParam&#40;'drone', ptSwitch&#41;;              SetIsOutput&#40;drone,false&#41;;
    instEnable  &#58;= CreateParam&#40;'inst enable', ptSwitch&#41;;          SetIsInput&#40;instEnable,false&#41;; 

    FOR inputNum &#58;= 0 TO INPUT_COUNT - 1 DO  BEGIN
        midiIns&#91;inputNum&#93;  &#58;= CreateParam&#40;'MIDIin ' + intToStr&#40;inputNum + 1&#41; ,ptMidi&#41;;       
        SetIsOutPut&#40;midiIns&#91;inputNum&#93;,false&#41;;
    END;
    
    notesOut      &#58;= CreateParam&#40;'NoteMIDI',ptMidi&#41;;             SetIsInput&#40;notesOut,false&#41;;  
    otherOut      &#58;= CreateParam&#40;'otherMIDI',ptMidi&#41;;             SetIsInput&#40;otherOut,false&#41;;

    learn &#58;= CreateParam&#40;'learn',ptButton&#41;;                       SetIsOutPut&#40;learn,false&#41;;
    lower &#58;= CreateParam&#40;'lower',ptMidiNoteFader&#41;;                SetIsInPut&#40;lower,false&#41;;
    upper &#58;= CreateParam&#40;'upper',ptMidiNoteFader&#41;;                SetIsInPut&#40;upper,false&#41;;  
 
  
    notesOutCount &#58;= 0;
    otherOutCount &#58;= 0;
    learnCount    &#58;= 0;   

    SetArrayLength&#40;isEnabled, INPUT_COUNT&#41;; 


END;

// <F> isNoteOn&#58;  
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^// 
FUNCTION isNoteOn&#40;midi&#58; tMidi&#41;&#58; boolean;
BEGIN 
  
    IF &#40;midi.msg = NOTE_ON&#41; AND &#40;midi.data2>0&#41; THEN BEGIN 
        result &#58;= true;
    END
    ELSE BEGIN 
        result &#58;= false; 
    END;
END;

// <F> isNoteOff&#58;  
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^// 
FUNCTION isNoteOff&#40;midi&#58; tMidi&#41;&#58; boolean;
BEGIN 
     
    IF &#40;midi.msg = NOTE_OFF&#41; OR &#40;&#40;midi.msg = NOTE_ON&#41; AND &#40;midi.data2 = 0&#41;&#41; THEN BEGIN 
        result &#58;= true;
    END
    ELSE BEGIN 
        result &#58;= false;
    END;
END; 

// <F> isSusPed&#58;  
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^// 
FUNCTION isSusPed&#40;midi&#58; tMidi&#41; &#58; boolean;
BEGIN
    IF &#40;midi.msg = CONTROL&#41; AND &#40;midi.data1 = SUS_PED&#41; THEN BEGIN
        result &#58;= true;
    END
    ELSE BEGIN
        result &#58;= false;
    END;
END; 

// <F> isPitchBend&#58;  
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^// 
FUNCTION isPitchBend&#40;midi&#58; tMidi&#41;&#58; boolean;
BEGIN 
    IF &#40;midi.msg = PITCHBEND&#41; THEN BEGIN 
        result &#58;= true;
    END
    ELSE BEGIN 
        result &#58;= false; 
    END;
END;

// <F> global function inRange--eliminates only noteons outside the range limits 
FUNCTION inRange&#40;midi&#58; tMidi&#41; &#58; boolean;
BEGIN
    IF &#40;isNoteOn&#40;midi&#41;&#41; AND &#40;midi.data1 > lowVal&#41; AND &#40;midi.data1 < hiVal&#41; THEN BEGIN
        result &#58;= TRUE;
    END
    ELSE BEGIN
        result &#58;= FALSE;
    END;
 
END;



// <F> isClear
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^//
FUNCTION isClear&#40;&#41;  &#58; boolean;   // can we stop this loop as soon as a key = true??  Didn't like 'while'
VAR clear &#58; boolean;
BEGIN
    clear &#58;= TRUE;
    BEGIN
        FOR key&#58;= minKey TO maxKey DO BEGIN    
            IF &#40;noteList&#91;key&#93; = TRUE&#41; THEN BEGIN 
                clear &#58;= FALSE;           
            END;                                                          
        END; 
    END;
    result &#58;= clear;     
END;



// <F> ReleaseNotes  --when you release the sustain, or release the drone, or change the output channel
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^//
PROCEDURE ReleaseNotes;
BEGIN
    FOR key&#58;= minKey TO maxKey DO BEGIN    
        IF &#40;noteList&#91;key&#93; = TRUE&#41; THEN BEGIN 
            noteOff.msg &#58;= NOTE_OFF;
            noteOff.channel &#58;= outputChannel; // byte is a conversion--is this needed?
            noteOff.data1 &#58;= key;
            noteOff.data2 &#58;= 0;
            setMidiArrayValue&#40;notesOut, notesOutCount, noteOff&#41;;

strace&#40;'sending noteOff for ' + intToStr&#40;key&#41;&#41;;

            notesOutCount &#58;= notesOutCount + 1;
            noteList&#91;key&#93; &#58;= FALSE;
        END;                                                          
    END; 
END;
    



// Global Procedure StoreNoteOffs--just de-inlined for readability
//----------------------------------------------//
PROCEDURE StoreNoteOffs;
BEGIN
				
    IF &#40;midi.data1 > maxKey&#41; THEN BEGIN   
        maxKey &#58;= midi.data1;
    END;
    IF &#40;midi.data1 < minKey&#41; THEN BEGIN 
        minKey &#58;= midi.data1; 
    END; 
    noteList&#91;midi.data1&#93; &#58;= TRUE;
END;

// Callback
//----------------------------------------------// 
procedure Callback&#40;n&#58;integer&#41;;

BEGIN
    if &#40;n = hiNote&#41; then begin
         hiVal &#58;= round&#40;getValue&#40;hiNote&#41;&#41;;
         setValue&#40;upper, hiVal&#41;;
    end
    else if &#40;n = lowNote&#41; then begin
         lowVal &#58;= round&#40;getValue&#40;lowNote&#41;&#41;;
         setValue&#40;lower, lowVal&#41;;
    end
    else if &#40;n = learn&#41; and &#40;getValue&#40;n&#41; = 1&#41; then begin 
        learning&#58;= TRUE;
        strace&#40;'learning...'&#41;; 
        learnCount &#58;= 0;
    end
    else if &#40;learnCount = 2&#41; then begin 
        learning &#58;= FALSE;
        learnCount &#58;= 0;
        strace&#40;' finishedlearning'&#41;;
    end 
    else begin
        // get input values    
        octVal &#58;= trunc&#40;getValue&#40;octave&#41;&#41;;
        semival &#58;= trunc&#40;getValue&#40;semi&#41;&#41;;
        transpose &#58;= &#40;octVal * 12&#41; + semiVal;
        outputChannel &#58;= trunc&#40;getValue&#40;outChan&#41;&#41;;
        droning &#58;= &#40;getValue&#40;drone&#41; = 1&#41;;
        // see if any input is enabled....
        noInput &#58;= TRUE;                                         
        FOR inputNum &#58;= 0 to INPUT_COUNT - 1 DO 
        BEGIN
            IF getValue&#40;enables&#91;inputNum&#93;&#41; = 1 THEN BEGIN
                isEnabled&#91;inputNum&#93; &#58;= TRUE; 
                noInput &#58;= FALSE;
            END
            ELSE BEGIN
                isEnabled&#91;inputNum&#93; &#58;= FALSE;
            END;

        END;
          
        // release notes if the drone has just been turned off, or if all inputs and notes are off, or if the channel is changed
      
        IF &#40;&#40;n = drone&#41; and &#40;not&#40;Droning&#41;&#41;&#41; OR 
           &#40;&#40;noInput = TRUE&#41; AND &#40;isClear&#40;&#41; = TRUE&#41;&#41;
           OR &#40;n = outChan&#41;                
        THEN BEGIN
            strace&#40;'releasing notes'&#41;;
            releaseNotes;   
            setValue&#40;instEnable, 0&#41;;
        END
        ELSE BEGIN
            setValue&#40;instEnable, 1&#41;;
        END;
    end;
END;
 
 //////////////////////////////////////////////////////
// process
//////////////////////////////////////////////////////
PROCEDURE PROCESS;
BEGIN 
    notesOutCount &#58;= 0;
    otherOutCount &#58;= 0;   
    
    FOR inputNum &#58;= 0 to INPUT_COUNT - 1 DO BEGIN     
       
        midiInCount &#58;= getLength&#40;midiIns&#91;inputNum&#93;&#41;;
        
        
       
        FOR byteNum &#58;= 0 TO &#40;midiInCount - 1&#41; DO BEGIN   // process input                           
            getMidiArrayValue&#40;midiIns&#91;inputNum&#93;, byteNum, midi&#41;;
            midi.channel &#58;= outputChannel;
            if &#40;learning&#41; then begin
                IF &#40;isNoteOn&#40;midi&#41;&#41; and &#40;learnCount = 0&#41; THEN BEGIN
                    lowVal &#58;= midi.data1;
                    setValue&#40;lower, lowVal &#41;;
                    learnCount &#58;= 1;
                    setMidiArrayValue&#40;otherOut, otherOutCount, midi&#41;;
                    otherOutCount &#58;= otherOutCount + 1; 
                    strace&#40;'learnCount = ' + intToStr&#40;learnCount&#41;&#41;;              
                   
                END 
                ELSE IF &#40;isNoteOn&#40;midi&#41;&#41; and &#40;learnCount = 1&#41;  THEN BEGIN
                    hiVal &#58;= midi.data1;
                    setValue&#40;upper, hiVal&#41;;
                    learnCount &#58;= 2;
                    learning &#58;= FALSE;  // we've set our outs....
                    setMidiArrayValue&#40;otherOut, otherOutCount, midi&#41;;
                    otherOutCount &#58;= otherOutCount + 1;
                END;
            end 
            else if &#40;droning&#41; then begin  // basically ignore all midi when droning
                    setMidiArrayValue&#40;otherOut, otherOutCount, midi&#41;;
                    otherOutCount &#58;= otherOutCount + 1;
            end
            else begin
                If isSusPed&#40;midi&#41; THEN BEGIN          
                    if &#40;midi.data2 > 64&#41; THEN BEGIN
                        isSustained &#58;= true;
                    END
                    ELSE BEGIN                         
                        isSustained &#58;= false;  
                        ReleaseNotes;          // send noteoffs when the sus pedal is released
                    END;                              
                END; 
                IF isNoteOn&#40;midi&#41; AND &#40;inRange&#40;midi&#41;&#41; AND &#40;isEnabled&#91;inputNum&#93;&#41; THEN BEGIN	 //note on, sus on, store and send	
                    transList&#91;midi.data1&#93; &#58;= transpose; 
                    midi.data1 &#58;= midi.data1 + transpose; 
                    IF &#40;isSustained = TRUE&#41; THEN BEGIN           
                        StoreNoteOffs;              
                        setMidiArrayValue&#40;notesOut, notesOutCount, midi&#41;;
                        notesOutCount &#58;= notesOutCount + 1;
                    END                      
                    ELSE BEGIN                                        // valid noteon, sus off, send it out               
                        setMidiArrayValue&#40;notesOut, notesOutCount, midi&#41;;
                        notesOutCount &#58;= notesOutCount + 1;
                     END;
                END
                ELSE IF isNoteOff&#40;midi&#41; AND &#40;isSustained = FALSE&#41;  THEN BEGIN // valid noteoff, sus off, send it out
                        midi.data1 &#58;= midi.data1 + transList&#91;midi.data1&#93;;                 
                        setMidiArrayValue&#40;notesOut, notesOutCount, midi&#41;;
                        notesOutCount &#58;= notesOutCount + 1;                 
                END                     
                ELSE IF isPitchBend&#40;midi&#41; THEN BEGIN                        // send PB along with the notes                             
                    midi.channel &#58;= outputChannel;                
                    setMidiArrayValue&#40;notesOut, notesOutCount, midi&#41;;
                    notesOutCount &#58;= notesOutCount + 1;
                END
                ELSE IF &#40;NOT&#40;isNoteOn&#40;midi&#41;&#41;&#41; AND &#40;NOT&#40;isNoteOff&#40;midi&#41;&#41;&#41; AND &#40;NOT&#40;isPitchBend&#40;midi&#41;&#41;&#41; THEN BEGIN  //not a note, pb or sus pedal event
                    setMidiArrayValue&#40;otherOut, otherOutCount, midi&#41;;
                    otherOutCount &#58;= otherOutCount + 1;
                END;                   
            end;
        END;
    END;
    setLength&#40;notesOut, notesOutCount&#41;;		
    setLength&#40;otherOut, otherOutCount&#41;;

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

woodslanding
Member
Posts: 1327
Contact:

Unread post by woodslanding » 10 Jan 2010, 11:32

okay, I figured out what was going on. Just what Bsork said--the callback loop was getting called before the process loop had a chance to respond.

So I put in a couple of booleans to decide whether to send the note offs and disable the instrument, but then I called the actual methods from within process. Seems to work! Here's the code:

Code: Select all

&#40;*/////////////////////////////////////////////////////
// CHANNEL  
// Version 2009-11-21; author&#58; eric moon
//
// based on work by Bsork and amiga909
//////////////////////////////////////////////////////
*&#41;

//  This takes a number of midi inputs &#40;on separate cables, for visual clarity&#41;
//  and performs the following operations on each.
//  enable, disable--per input
//  strip CCs and AT and send them out a separate output
//  read CC64 values &#40;or a CC of your choice&#41; to withhold noteoffs until the sus pedal is released.
//  limit to minimum and maximum notes--one range affects all inputs, as it's assumed you'll typically be using one input at a time
//  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 new notesOns as long as it is held
//  It is designed to function as part of a mixer channel strip, running between several keyboards and a vsti.


//  When the 'learn' function is enabled, notes are sent out output 2
//  the first note sets the lower range, the second note sets the upper limit.
//  when both notes are sent, learn is disabled, and notes go to output 1 as usual.

CONST SUS_PED= 64;    //change if you use an unorthodox CC for sustaining.
CONST INPUT_COUNT = 2;  //allows up to 16 inputs, one per channel

CONST NOTE_ON        = 144;	
CONST NOTE_OFF       = 128;
CONST CONTROL        = 176;	
CONST PITCHBEND      = 224;


VAR   noteList                              &#58; ARRAY OF boolean;
VAR   transList                             &#58; ARRAY OF integer;
VAR   midi,noteOff                          &#58; TMidi; 

var midiIns                                 &#58;  ARRAY OF Tparameter;
var enables                                 &#58;  ARRAY OF TParameter;

var notesOut  &#58; Tparameter;
var otherOut  &#58; Tparameter;       // all CCs except the sustain value go out this output.  So does AT and PgCh
var octave    &#58; TParameter;
var semi      &#58; TParameter;
var hiNote    &#58; TParameter;
var lowNote   &#58; TParameter;
var drone     &#58; TParameter;       // sustains, but ignores new noteons
var outChan   &#58; TParameter;       // everything goes out one channel.  It's a channel strip. Use more if you need other channels!
var instEnable&#58; TParameter;
var lower    &#58; Tparameter;
var upper   &#58; Tparameter; 
var learn     &#58; TParameter;

VAR   octVal, semiVal, transpose            &#58; integer;
VAR   hiVal, lowVal                         &#58; integer; 
VAR   midiInCount                           &#58; integer;
VAR   otherOutCount, notesOutCount          &#58; integer;
VAR   key, inputNum, byteNum                &#58; integer; 
VAR   minChn, maxChn, minKey, maxKey        &#58; integer;
VAR   outputChannel                         &#58; integer;
VAR   isSustained,noInput                   &#58; boolean;
VAR   isEnabled                             &#58; Array of boolean;
VAR   learning                              &#58; boolean;
VAR   droning                               &#58; boolean;
VAR   release                               &#58; boolean;
VAR   disablable                            &#58; boolean;
var   learnCount                            &#58; integer;

//////////////////////////////////////////////////////
// initialize
//////////////////////////////////////////////////////
PROCEDURE init;    
BEGIN  


    minChn &#58;= INPUT_COUNT - 1;   // these are initially set to their opposite extremes....
    maxChn &#58;= 0; 
    minKey &#58;= 128; 
    maxKey &#58;= 0;
    
    isSustained &#58;= FALSE;	
          
    setArrayLength&#40;noteList, 128&#41;;  
    FOR key&#58;=0 TO 127 DO BEGIN 
        noteList&#91;key&#93;&#58;=FALSE;
    END;
    SetArrayLength&#40;transList, 128&#41;;
    FOR key&#58;=0 TO 127 DO BEGIN 
        transList&#91;key&#93;&#58;=0;
    END;  
     
    setArrayLength&#40;midiIns, INPUT_COUNT&#41;;
    setArrayLength&#40;enables, INPUT_COUNT&#41;;
    FOR inputNum &#58;= 0 TO INPUT_COUNT - 1 DO  BEGIN
        enables&#91;inputNum&#93;  &#58;= CreateParam&#40;'enable ' + intToStr&#40;inputNum + 1&#41;,ptSwitch&#41;; 
        SetValue&#40;enables&#91;inputNum&#93;, 1&#41;;       
        SetIsOutPut&#40;enables&#91;inputNum&#93;,false&#41;;        
    END;
       
    octave      &#58;= CreateParam&#40;'octave',ptDataFader&#41;;          SetIsOutPut&#40;octave,false&#41;;
    semi        &#58;= CreateParam&#40;'semi',ptDataFader&#41;;            SetIsOutPut&#40;semi,false&#41;;

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

    hiNote        &#58;= CreateParam&#40;'high note',ptMidiNoteFader&#41;;   SetIsOutPut&#40;hiNote,false&#41;;
    lowNote       &#58;= CreateParam&#40;'low note',ptMidiNoteFader&#41;;    SetIsOutPut&#40;lowNote,false&#41;;  
    outChan       &#58;= CreateParam&#40;'output chan', ptDataFader&#41;;     SetIsOutPut&#40;outchan,false&#41;; 
    SetFormat&#40;outChan,'%.0f'&#41;; SetMin&#40;outChan,1&#41;;  SetMax&#40;outChan,16&#41;;
    SetValue&#40;lowNote, 10&#41;;
    SetValue&#40;hiNote, 115&#41;;
  
    drone         &#58;= CreateParam&#40;'drone', ptSwitch&#41;;              SetIsOutput&#40;drone,false&#41;;
    instEnable  &#58;= CreateParam&#40;'inst enable', ptSwitch&#41;;          SetIsInput&#40;instEnable,false&#41;; 

    FOR inputNum &#58;= 0 TO INPUT_COUNT - 1 DO  BEGIN
        midiIns&#91;inputNum&#93;  &#58;= CreateParam&#40;'MIDIin ' + intToStr&#40;inputNum + 1&#41; ,ptMidi&#41;;       
        SetIsOutPut&#40;midiIns&#91;inputNum&#93;,false&#41;;
    END;
    
    notesOut      &#58;= CreateParam&#40;'NoteMIDI',ptMidi&#41;;             SetIsInput&#40;notesOut,false&#41;;  
    otherOut      &#58;= CreateParam&#40;'otherMIDI',ptMidi&#41;;             SetIsInput&#40;otherOut,false&#41;;

    learn &#58;= CreateParam&#40;'learn',ptButton&#41;;                       SetIsOutPut&#40;learn,false&#41;;
    lower &#58;= CreateParam&#40;'lower',ptMidiNoteFader&#41;;                SetIsInPut&#40;lower,false&#41;;
    upper &#58;= CreateParam&#40;'upper',ptMidiNoteFader&#41;;                SetIsInPut&#40;upper,false&#41;;  
 
  
    notesOutCount &#58;= 0;
    otherOutCount &#58;= 0;
    learnCount    &#58;= 0;
    disablable   &#58;= FALSE;
    droning       &#58;= FALSE;
    release       &#58;= FALSE;   

    SetArrayLength&#40;isEnabled, INPUT_COUNT&#41;; 


END;

// <F> isNoteOn&#58;  
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^// 
FUNCTION isNoteOn&#40;midi&#58; tMidi&#41;&#58; boolean;
BEGIN 
  
    IF &#40;midi.msg = NOTE_ON&#41; AND &#40;midi.data2>0&#41; THEN BEGIN 
        result &#58;= true;
    END
    ELSE BEGIN 
        result &#58;= false; 
    END;
END;

// <F> isNoteOff&#58;  
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^// 
FUNCTION isNoteOff&#40;midi&#58; tMidi&#41;&#58; boolean;
BEGIN 
     
    IF &#40;midi.msg = NOTE_OFF&#41; OR &#40;&#40;midi.msg = NOTE_ON&#41; AND &#40;midi.data2 = 0&#41;&#41; THEN BEGIN 
        result &#58;= true;
    END
    ELSE BEGIN 
        result &#58;= false;
    END;
END; 

// <F> isSusPed&#58;  
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^// 
FUNCTION isSusPed&#40;midi&#58; tMidi&#41; &#58; boolean;
BEGIN
    IF &#40;midi.msg = CONTROL&#41; AND &#40;midi.data1 = SUS_PED&#41; THEN BEGIN
        result &#58;= true;
    END
    ELSE BEGIN
        result &#58;= false;
    END;
END; 

// <F> isPitchBend&#58;  
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^// 
FUNCTION isPitchBend&#40;midi&#58; tMidi&#41;&#58; boolean;
BEGIN 
    IF &#40;midi.msg = PITCHBEND&#41; THEN BEGIN 
        result &#58;= true;
    END
    ELSE BEGIN 
        result &#58;= false; 
    END;
END;

// <F> global function inRange--eliminates only noteons outside the range limits 
FUNCTION inRange&#40;midi&#58; tMidi&#41; &#58; boolean;
BEGIN
    IF &#40;isNoteOn&#40;midi&#41;&#41; AND &#40;midi.data1 > lowVal&#41; AND &#40;midi.data1 < hiVal&#41; THEN BEGIN
        result &#58;= TRUE;
    END
    ELSE BEGIN
        result &#58;= FALSE;
    END;
 
END;



// <F> isClear
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^//
FUNCTION isClear&#40;&#41;  &#58; boolean;   // can we stop this loop as soon as a key = true??  Didn't like 'while'
VAR clear &#58; boolean;
BEGIN
    clear &#58;= TRUE;
    BEGIN
        FOR key&#58;= minKey TO maxKey DO BEGIN    
            IF &#40;noteList&#91;key&#93; = TRUE&#41; THEN BEGIN 
                clear &#58;= FALSE;           
            END;                                                          
        END; 
    END;
    result &#58;= clear;     
END;



// <F> ReleaseNotes  --when you release the sustain, or release the drone, or change the output channel
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^//
PROCEDURE ReleaseNotes;
BEGIN
    FOR key&#58;= minKey TO maxKey DO BEGIN    
        IF &#40;noteList&#91;key&#93; = TRUE&#41; THEN BEGIN 
            noteOff.msg &#58;= NOTE_OFF;
            noteOff.channel &#58;= outputChannel; // byte is a conversion--is this needed?
            noteOff.data1 &#58;= key;
            noteOff.data2 &#58;= 0;
            setMidiArrayValue&#40;notesOut, notesOutCount, noteOff&#41;;
            strace&#40;'sending noteOff for ' + intToStr&#40;key&#41;&#41;;
            notesOutCount &#58;= notesOutCount + 1;
            noteList&#91;key&#93; &#58;= FALSE;
        END;                                                          
    END; 
END;
    



// Global Procedure StoreNoteOffs--just de-inlined for readability
//----------------------------------------------//
PROCEDURE StoreNoteOffs;
BEGIN
				
    IF &#40;midi.data1 > maxKey&#41; THEN BEGIN   
        maxKey &#58;= midi.data1;
    END;
    IF &#40;midi.data1 < minKey&#41; THEN BEGIN 
        minKey &#58;= midi.data1; 
    END; 
    noteList&#91;midi.data1&#93; &#58;= TRUE;
END;

// Callback
//----------------------------------------------// 
procedure Callback&#40;n&#58;integer&#41;;

BEGIN
    if &#40;n = hiNote&#41; then begin
         hiVal &#58;= round&#40;getValue&#40;hiNote&#41;&#41;;
         setValue&#40;upper, hiVal&#41;;
    end
    else if &#40;n = lowNote&#41; then begin
         lowVal &#58;= round&#40;getValue&#40;lowNote&#41;&#41;;
         setValue&#40;lower, lowVal&#41;;
    end
    else if &#40;n = learn&#41; and &#40;getValue&#40;n&#41; = 1&#41; then begin 
        learning&#58;= TRUE;
        strace&#40;'learning...'&#41;; 
        learnCount &#58;= 0;
    end
    else if &#40;learnCount = 2&#41; then begin
        // we've just learned, so we want to send out lower and upper  
        learning &#58;= FALSE;
        learnCount &#58;= 0;
        strace&#40;' finishedlearning'&#41;;
    end 
    else begin
        // get input values    
        octVal &#58;= trunc&#40;getValue&#40;octave&#41;&#41;;
        semival &#58;= trunc&#40;getValue&#40;semi&#41;&#41;;
        transpose &#58;= &#40;octVal * 12&#41; + semiVal;
        outputChannel &#58;= trunc&#40;getValue&#40;outChan&#41;&#41;;
        droning &#58;= &#40;getValue&#40;drone&#41; = 1&#41;;
        // see if any input is enabled....
        noInput &#58;= TRUE;                                         
        FOR inputNum &#58;= 0 to INPUT_COUNT - 1 DO 
        BEGIN
            IF getValue&#40;enables&#91;inputNum&#93;&#41; = 1 THEN BEGIN
                isEnabled&#91;inputNum&#93; &#58;= TRUE; 
                noInput &#58;= FALSE;
            END
            ELSE BEGIN
                isEnabled&#91;inputNum&#93; &#58;= FALSE;
            END;

        END;
          
        // release notes if the drone has just been turned off, or if all inputs and notes are off, or if the channel is changed
      
        IF &#40;&#40;n = drone&#41; and &#40;not&#40;Droning&#41;&#41;&#41; OR 
           &#40;&#40;noInput = TRUE&#41; AND &#40;isClear&#40;&#41; = TRUE&#41;&#41;
           OR &#40;n = outChan&#41;                
        THEN BEGIN
            strace&#40;'releasing notes'&#41;;
            release &#58;= true;               // booleans to check in process
            disablable &#58;= true;
        END
        ELSE BEGIN
            setValue&#40;instEnable, 1&#41;;
        END;
    end;
END;
 
 //////////////////////////////////////////////////////
// process
//////////////////////////////////////////////////////
PROCEDURE PROCESS;
BEGIN 
    notesOutCount &#58;= 0;
    otherOutCount &#58;= 0;   
    if release then releaseNotes;
    FOR inputNum &#58;= 0 to INPUT_COUNT - 1 DO BEGIN     
       
        midiInCount &#58;= getLength&#40;midiIns&#91;inputNum&#93;&#41;;
        
        
       
        FOR byteNum &#58;= 0 TO &#40;midiInCount - 1&#41; DO BEGIN   // process input                           
            getMidiArrayValue&#40;midiIns&#91;inputNum&#93;, byteNum, midi&#41;;
            midi.channel &#58;= outputChannel;
            if &#40;learning&#41; then begin
                IF &#40;isNoteOn&#40;midi&#41;&#41; and &#40;learnCount = 0&#41; THEN BEGIN
                    lowVal &#58;= midi.data1;
                    setValue&#40;lower, lowVal &#41;;
                    learnCount &#58;= 1;
                    setMidiArrayValue&#40;otherOut, otherOutCount, midi&#41;;
                    otherOutCount &#58;= otherOutCount + 1; 
                    strace&#40;'learnCount = ' + intToStr&#40;learnCount&#41;&#41;;              
                   
                END 
                ELSE IF &#40;isNoteOn&#40;midi&#41;&#41; and &#40;learnCount = 1&#41;  THEN BEGIN
                    hiVal &#58;= midi.data1;
                    setValue&#40;upper, hiVal&#41;;
                    learnCount &#58;= 2;
                    learning &#58;= FALSE;  // we've set our outs....
                    setMidiArrayValue&#40;otherOut, otherOutCount, midi&#41;;
                    otherOutCount &#58;= otherOutCount + 1;
                END;
            end 
            else if &#40;droning&#41; then begin  // basically ignore all midi when droning
                    setMidiArrayValue&#40;otherOut, otherOutCount, midi&#41;;
                    otherOutCount &#58;= otherOutCount + 1;
            end
            else begin
                If isSusPed&#40;midi&#41; THEN BEGIN          
                    if &#40;midi.data2 > 64&#41; THEN BEGIN
                        isSustained &#58;= true;
                    END
                    ELSE BEGIN                         
                        isSustained &#58;= false;  
                        ReleaseNotes;          // send noteoffs when the sus pedal is released
                    END;                              
                END; 
                IF isNoteOn&#40;midi&#41; AND &#40;inRange&#40;midi&#41;&#41; AND &#40;isEnabled&#91;inputNum&#93;&#41; THEN BEGIN	 //note on, sus on, store and send	
                    transList&#91;midi.data1&#93; &#58;= transpose; 
                    midi.data1 &#58;= midi.data1 + transpose; 
                    IF &#40;isSustained = TRUE&#41; THEN BEGIN           
                        StoreNoteOffs;              
                        setMidiArrayValue&#40;notesOut, notesOutCount, midi&#41;;
                        notesOutCount &#58;= notesOutCount + 1;
                    END                      
                    ELSE BEGIN                                        // valid noteon, sus off, send it out               
                        setMidiArrayValue&#40;notesOut, notesOutCount, midi&#41;;
                        notesOutCount &#58;= notesOutCount + 1;
                     END;
                END
                ELSE IF isNoteOff&#40;midi&#41; AND &#40;isSustained = FALSE&#41;  THEN BEGIN // valid noteoff, sus off, send it out
                        midi.data1 &#58;= midi.data1 + transList&#91;midi.data1&#93;;                 
                        setMidiArrayValue&#40;notesOut, notesOutCount, midi&#41;;
                        notesOutCount &#58;= notesOutCount + 1;                 
                END                     
                ELSE IF isPitchBend&#40;midi&#41; THEN BEGIN                        // send PB along with the notes                             
                    midi.channel &#58;= outputChannel;                
                    setMidiArrayValue&#40;notesOut, notesOutCount, midi&#41;;
                    notesOutCount &#58;= notesOutCount + 1;
                END
                ELSE IF &#40;NOT&#40;isNoteOn&#40;midi&#41;&#41;&#41; AND &#40;NOT&#40;isNoteOff&#40;midi&#41;&#41;&#41; AND &#40;NOT&#40;isPitchBend&#40;midi&#41;&#41;&#41; THEN BEGIN  //not a note, pb or sus pedal event
                    setMidiArrayValue&#40;otherOut, otherOutCount, midi&#41;;
                    otherOutCount &#58;= otherOutCount + 1;
                END;                   
            end;
        END;
    END;
    setLength&#40;notesOut, notesOutCount&#41;;		
    setLength&#40;otherOut, otherOutCount&#41;;
    if disablable then setValue&#40;instEnable, 0&#41;;
 
END;
Custom Ryzen 5900x MATX build, Win10, Fireface UFX, touchscreen
Custom 2 manual midi keyboard
Usine, Kontakt, Reaktor, Synthmaster, Byome, Arturia, Soundtoys, Unify

woodslanding
Member
Posts: 1327
Contact:

Unread post by woodslanding » 10 Jan 2010, 11:47

crap, this still isn't working.... and it's getting late.

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

Post Reply

Who is online

Users browsing this forum: No registered users and 17 guests