Question #1: What MIDI Mapper functions would you like to see in V5?
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?
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:
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
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
Custom 2 manual midi keyboard
Usine, Kontakt, Reaktor, Synthmaster, Byome, Arturia, Soundtoys, Unify
@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..
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:
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
Custom 2 manual midi keyboard
Usine, Kontakt, Reaktor, Synthmaster, Byome, Arturia, Soundtoys, Unify
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.amiga909 wrote:@bsork: "fit the new FastScript."
any detailed infos on what is different?
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
Any more suggestions or opinions?
Bjørn S
tkx for ur extensive reply, bsork.
wow! quite some drastic changes.
hope u didnt convert all my bugs too
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.
wow! quite some drastic changes.
oh! thanks a lot for this!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.
hope u didnt convert all my bugs too
<<non-bloated scripts>>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?
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:
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.
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
Custom 2 manual midi keyboard
Usine, Kontakt, Reaktor, Synthmaster, Byome, Arturia, Soundtoys, Unify
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...
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
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
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:
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!
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
Custom 2 manual midi keyboard
Usine, Kontakt, Reaktor, Synthmaster, Byome, Arturia, Soundtoys, Unify
-
woodslanding
- Member
- Posts: 1327
- Contact:
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
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
Custom 2 manual midi keyboard
Usine, Kontakt, Reaktor, Synthmaster, Byome, Arturia, Soundtoys, Unify
interesting point. but it has to be optional. cc64 isnt always denoted as holding a note (no CC has an universal meaning).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.
more opinions on this (i never use the sustain pedal command so i cant say)?
if u mean case 2 i mentioned: thats ur perspective.woodslanding wrote:No real need to hold note-offs, per se.
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.
yes, self-explaining variables are a good idea. i'd opt for more documentation too.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
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.
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?
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.
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
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.
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
dont think we didnt discuss it. for me, its actually quite difficult to sort out what can be done with midimapper.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?
why not? i tried this and it works (not sure for really EVERY situation).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.
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.
Well, that's one of the reasons for this, isn't it?amiga090 wrote:its actually quite difficult to sort out what can be done with midimapper
About the NoteOffs, I have given it a bit more thought and I actually think it's feasible without too much coding.
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.amiga090 wrote:hope i am not bothering u too much, bsork
Bjørn S
-
woodslanding
- Member
- Posts: 1327
- Contact:
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
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
Custom 2 manual midi keyboard
Usine, Kontakt, Reaktor, Synthmaster, Byome, Arturia, Soundtoys, Unify
-
woodslanding
- Member
- Posts: 1327
- Contact:
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
-e
Custom Ryzen 5900x MATX build, Win10, Fireface UFX, touchscreen
Custom 2 manual midi keyboard
Usine, Kontakt, Reaktor, Synthmaster, Byome, Arturia, Soundtoys, Unify
Custom 2 manual midi keyboard
Usine, Kontakt, Reaktor, Synthmaster, Byome, Arturia, Soundtoys, Unify
-
woodslanding
- Member
- Posts: 1327
- Contact:
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
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
Custom 2 manual midi keyboard
Usine, Kontakt, Reaktor, Synthmaster, Byome, Arturia, Soundtoys, Unify
nice idea.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
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
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.
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:
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
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
Custom 2 manual midi keyboard
Usine, Kontakt, Reaktor, Synthmaster, Byome, Arturia, Soundtoys, Unify
midi addons: some_midi scripts_V2 (does not work with fast script)woodslanding wrote:The automated sus script is on my list--but where is this 'keyboard hold' script???
-e
its not the same as u want. however, i'd go from that script to make a midi sustain.
-
woodslanding
- Member
- Posts: 1327
- Contact:
Wow, Amiga, that is nice work!
It shouldn't take much modification.....
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
Custom 2 manual midi keyboard
Usine, Kontakt, Reaktor, Synthmaster, Byome, Arturia, Soundtoys, Unify
-
woodslanding
- Member
- Posts: 1327
- Contact:
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.....
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
Custom 2 manual midi keyboard
Usine, Kontakt, Reaktor, Synthmaster, Byome, Arturia, Soundtoys, Unify
-
woodslanding
- Member
- Posts: 1327
- Contact:
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
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
Custom 2 manual midi keyboard
Usine, Kontakt, Reaktor, Synthmaster, Byome, Arturia, Soundtoys, Unify
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.
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.
No worries, mate!woodslanding wrote:maybe this should all be moved to a patching thread. I realize I've hijacked Bsorks original post....
Bjørn S
-
woodslanding
- Member
- Posts: 1327
- Contact:
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
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
Custom 2 manual midi keyboard
Usine, Kontakt, Reaktor, Synthmaster, Byome, Arturia, Soundtoys, Unify
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:
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?
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.
Eg:
Code: Select all
VAR pParam1, pParam2, pParam3 : tParameter;
VAR param1, param2, param3 : single;
VAR doUpdate : boolean;
....
PROCEDURE Init;
BEGIN
pParam1 := CreateParam('param 1', ...
pParam2 := CreateParam('param 2', ...
pParam3 := CreateParam('param 3', ...
doUpdate := FALSE;
END;
PROCEDURE GetParams;
BEGIN
param1 := GetValue(pParam1);
param2 := GetValue(pParam2);
param3 := GetValue(pParam3);
END;
PROCEDURE Callback(n ; integer);
BEGIN
// You can of course use IF-statements instead of CASE
CASE n OF
pParam1 : doUpdate := TRUE;
pParam2 : doUpdate := TRUE;
pParam3 : doUpdate := TRUE;
END;
END;
PROCEDURE Process;
BEGIN
IF (doUpdate) THEN BEGIN
GetParams;
doUpdate := FALSE;
END;
...
END;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:
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
Custom 2 manual midi keyboard
Usine, Kontakt, Reaktor, Synthmaster, Byome, Arturia, Soundtoys, Unify
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:
try this. I've got the code where most of the funtionality seems to be working. All the midi LOOKS so nice!!!
Does it do this for you???
Code: Select all
(*/////////////////////////////////////////////////////
// CHANNEL
// Version 2009-11-21; author: eric moon
//
// based on work by Bsork and amiga909
//////////////////////////////////////////////////////
*)
// This takes a number of midi inputs (on separate cables, for visual clarity)
// and performs the following operations on each.
// enable, disable--per input
// strip CCs and AT and send them out a separate output
// read CC64 values (or a CC of your choice) to withhold noteoffs until the sus pedal is released.
// limit to minimum and maximum notes--one range affects all inputs, 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 : ARRAY OF boolean;
VAR transList : ARRAY OF integer;
VAR midi,noteOff : TMidi;
var midiIns : ARRAY OF Tparameter;
var enables : ARRAY OF TParameter;
var notesOut : Tparameter;
var otherOut : Tparameter; // all CCs except the sustain value go out this output. So does AT and PgCh
var octave : TParameter;
var semi : TParameter;
var hiNote : TParameter;
var lowNote : TParameter;
var drone : TParameter; // sustains, but ignores new noteons
var outChan : TParameter; // everything goes out one channel. It's a channel strip. Use more if you need other channels!
var instBypass: TParameter;
VAR octVal, semiVal, transpose : integer;
VAR hiVal, lowVal : integer;
VAR midiInCount : integer;
VAR otherOutCount, notesOutCount : integer;
VAR key, inputNum, byteNum : integer;
VAR minChn, maxChn, minKey, maxKey : integer;
VAR outputChannel : integer;
VAR isSustained,noInput : boolean;
VAR isEnabled : Array of boolean;
//////////////////////////////////////////////////////
// initialize
//////////////////////////////////////////////////////
PROCEDURE init;
BEGIN
minChn := INPUT_COUNT - 1; // these are initially set to their opposite extremes....
maxChn := 0;
minKey := 128;
maxKey := 0;
isSustained := FALSE;
setArrayLength(noteList, 128);
FOR key:=0 TO 127 DO BEGIN
noteList[key]:=FALSE;
END;
SetArrayLength(transList, 128);
FOR key:=0 TO 127 DO BEGIN
transList[key]:=0;
END;
setArrayLength(midiIns, INPUT_COUNT);
setArrayLength(enables, INPUT_COUNT);
FOR inputNum := 0 TO INPUT_COUNT - 1 DO BEGIN
enables[inputNum] := CreateParam('enable ' + intToStr(inputNum + 1),ptSwitch);
SetValue(enables[inputNum], 1);
SetIsOutPut(enables[inputNum],false);
END;
octave := CreateParam('octave',ptDataFader); SetIsOutPut(octave,false);
semi := CreateParam('semi',ptDataFader); SetIsOutPut(semi,false);
SetFormat(octave,'%.0f'); SetMin(octave,-4); SetMax(octave,4);
SetFormat(semi,'%.0f'); SetMin(semi,-7); SetMax(semi,7);
hiNote := CreateParam('high note',ptMidiNoteFader); SetIsOutPut(hiNote,false);
lowNote := CreateParam('low note',ptMidiNoteFader); SetIsOutPut(lowNote,false);
outChan := CreateParam('output chan', ptDataFader); SetIsOutPut(outchan,false);
SetFormat(outChan,'%.0f'); SetMin(outChan,1); SetMax(outChan,16);
SetValue(lowNote, 10);
SetValue(hiNote, 115);
drone := CreateParam('drone', ptSwitch); SetIsOutput(drone,false);
instBypass := CreateParam('inst bypass', ptSwitch); SetIsInput(instBypass,false);
FOR inputNum := 0 TO INPUT_COUNT - 1 DO BEGIN
midiIns[inputNum] := CreateParam('MIDIin ' + intToStr(inputNum + 1) ,ptMidi);
SetIsOutPut(midiIns[inputNum],false);
END;
notesOut := CreateParam('Notes Out',ptMidi); SetIsInput(notesOut,false);
otherOut := CreateParam('other Out',ptMidi); SetIsInput(otherOut,false);
SetArrayLength(isEnabled, INPUT_COUNT);
END;
// <F> isNoteOn:
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^//
FUNCTION isNoteOn(midi: tMidi): boolean;
BEGIN
IF (midi.msg = NOTE_ON) AND (midi.data2>0) THEN BEGIN
result := true;
END
ELSE BEGIN
result := false;
END;
END;
// <F> isNoteOff:
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^//
FUNCTION isNoteOff(midi: tMidi): boolean;
BEGIN
IF (midi.msg = NOTE_OFF) OR ((midi.msg = NOTE_ON) AND (midi.data2 = 0)) THEN BEGIN
result := true;
END
ELSE BEGIN
result := false;
END;
END;
// <F> isSusPed:
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^//
FUNCTION isSusPed(midi: tMidi) : boolean;
BEGIN
IF (midi.msg = CONTROL) AND (midi.data1 = SUS_PED) THEN BEGIN
result := true;
END
ELSE BEGIN
result := false;
END;
END;
// <F> isPitchBend:
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^//
FUNCTION isPitchBend(midi: tMidi): boolean;
BEGIN
IF (midi.msg = PITCHBEND) THEN BEGIN
result := true;
END
ELSE BEGIN
result := false;
END;
END;
// <F> global function inRange--eliminates only noteons outside the range limits
FUNCTION inRange(midi: tMidi) : boolean;
BEGIN
IF (isNoteOn(midi)) AND (midi.data1 > lowVal) AND (midi.data1 < hiVal) THEN BEGIN
result := TRUE;
END
ELSE BEGIN
result := FALSE;
END;
END;
// <F> isClear
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^//
FUNCTION isClear() : boolean; // can we stop this loop as soon as a key = true?? Didn't like 'while'
VAR clear : boolean;
BEGIN
clear := TRUE;
BEGIN
FOR key:= minKey TO maxKey DO BEGIN
IF (noteList[key] = TRUE) THEN BEGIN
clear := FALSE;
END;
END;
END;
result := clear;
END;
// <F> ReleaseNotes --when you release the sustain, or release the drone, or change the output channel
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^//
PROCEDURE ReleaseNotes;
BEGIN
FOR key:= minKey TO maxKey DO BEGIN
IF (noteList[key] = TRUE) THEN BEGIN
noteOff.msg := NOTE_OFF;
noteOff.channel := outputChannel; // byte is a conversion--is this needed?
noteOff.data1 := key;
noteOff.data2 := 0;
setMidiArrayValue(notesOut, notesOutCount, noteOff); //danger when called from callback?
notesOutCount := notesOutCount + 1;
noteList[key] := FALSE;
END;
END;
END;
// Global Procedure StoreNoteOffs--just de-inlined for readability
//----------------------------------------------//
PROCEDURE StoreNoteOffs;
BEGIN
IF (midi.data1 > maxKey) THEN BEGIN
maxKey := midi.data1;
END;
IF (midi.data1 < minKey) THEN BEGIN
minKey := midi.data1;
END;
noteList[midi.data1] := TRUE;
END;
// Callback
//----------------------------------------------//
procedure Callback(n:integer);
BEGIN
// get input values
hiVal := trunc(getValue(hiNote));
lowVal := trunc(getValue(lowNote));
octVal := trunc(getValue(octave));
semival := trunc(getValue(semi));
transpose := (octVal * 12) + semiVal;
writeln('transpose = ' + intToStr(transpose));
outputChannel := trunc(getValue(outChan));
// see if any input is enabled....
noInput := TRUE;
FOR inputNum := 0 to INPUT_COUNT - 1 DO
BEGIN
IF getValue(enables[inputNum]) = 1 THEN BEGIN
isEnabled[inputNum] := TRUE;
noInput := FALSE;
END
ELSE BEGIN
isEnabled[inputNum] := 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 ((n = drone) and (getValue(drone) = 0)) OR
((noInput = TRUE) AND (isClear() = TRUE))
OR (n = outChan)
THEN
BEGIN
releaseNotes; // can I call this from callback?
setValue(instBypass, 1);
END
ELSE BEGIN
setValue(instBypass, 0);
END;
END;
//////////////////////////////////////////////////////
// process
//////////////////////////////////////////////////////
PROCEDURE PROCESS;
BEGIN
FOR inputNum := 0 to INPUT_COUNT - 1 DO BEGIN
midiInCount := getLength(midiIns[inputNum]);
IF (midiInCount > 0) THEN BEGIN
notesOutCount := 0;
otherOutCount := 0;
FOR byteNum := 0 TO (midiInCount - 1) DO BEGIN // process input
getMidiArrayValue(midiIns[inputNum], byteNum, midi);
midi.channel := outputChannel;
If isSusPed(midi) THEN BEGIN
if (midi.data2 > 64) THEN BEGIN
isSustained := true;
END
ELSE BEGIN
isSustained := false;
ReleaseNotes; // send noteoffs when the sus pedal is released
END;
END;
IF isNoteOn(midi) AND (inRange(midi)) AND (isEnabled[inputNum]) THEN BEGIN //note on, sus on, store and send
IF (isSustained = TRUE) THEN BEGIN
StoreNoteOffs;
setMidiArrayValue(notesOut, notesOutCount, midi);
notesOutCount := notesOutCount + 1;
END
ELSE BEGIN // valid noteon, sus off, send it out
transList[midi.data1] := transpose;
midi.data1 := midi.data1 + transpose;
setMidiArrayValue(notesOut, notesOutCount, midi);
notesOutCount := notesOutCount + 1;
END;
END
ELSE IF isNoteOff(midi) AND (isSustained = FALSE) THEN BEGIN // valid noteoff, sus off, send it out
midi.data1 := midi.data1 + transList[midi.data1];
setMidiArrayValue(notesOut, notesOutCount, midi);
notesOutCount := notesOutCount + 1;
END
ELSE IF isPitchBend(midi) THEN BEGIN // send PB along with the notes
midi.channel := outputChannel;
setMidiArrayValue(notesOut, notesOutCount, midi);
notesOutCount := notesOutCount + 1;
END
ELSE IF (NOT(isNoteOn(midi))) AND (NOT(isNoteOff(midi))) AND (NOT(isPitchBend(midi))) THEN BEGIN //not a note, pb or sus pedal event
setMidiArrayValue(otherOut, otherOutCount, midi);
otherOutCount := otherOutCount + 1;
END;
END; // process input
setLength(notesOut, notesOutCount);
setLength(otherOut, otherOutCount);
END
ELSE BEGIN
setLength(notesOut,0);
setLength(otherOut, 0);
END;
END;
END;Custom Ryzen 5900x MATX build, Win10, Fireface UFX, touchscreen
Custom 2 manual midi keyboard
Usine, Kontakt, Reaktor, Synthmaster, Byome, Arturia, Soundtoys, Unify
Custom 2 manual midi keyboard
Usine, Kontakt, Reaktor, Synthmaster, Byome, Arturia, Soundtoys, Unify
At work now - will have to take a look tonight at home.
Bjørn S
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.
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
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:
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
thanks,
-e
Custom Ryzen 5900x MATX build, Win10, Fireface UFX, touchscreen
Custom 2 manual midi keyboard
Usine, Kontakt, Reaktor, Synthmaster, Byome, Arturia, Soundtoys, Unify
Custom 2 manual midi keyboard
Usine, Kontakt, Reaktor, Synthmaster, Byome, Arturia, Soundtoys, Unify
-
woodslanding
- Member
- Posts: 1327
- Contact:
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
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
Custom 2 manual midi keyboard
Usine, Kontakt, Reaktor, Synthmaster, Byome, Arturia, Soundtoys, Unify
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...
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:
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:
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
(*/////////////////////////////////////////////////////
// CHANNEL
// Version 2009-11-21; author: eric moon
//
// based on work by Bsork and amiga909
//////////////////////////////////////////////////////
*)
// This takes a number of midi inputs (on separate cables, for visual clarity)
// and performs the following operations on each.
// enable, disable--per input
// strip CCs and AT and send them out a separate output
// read CC64 values (or a CC of your choice) to withhold noteoffs until the sus pedal is released.
// limit to minimum and maximum notes--one range affects all inputs, 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 : ARRAY OF boolean;
VAR transList : ARRAY OF integer;
VAR midi,noteOff : TMidi;
var midiIns : ARRAY OF Tparameter;
var enables : ARRAY OF TParameter;
var notesOut : Tparameter;
var otherOut : Tparameter; // all CCs except the sustain value go out this output. So does AT and PgCh
var octave : TParameter;
var semi : TParameter;
var hiNote : TParameter;
var lowNote : TParameter;
var drone : TParameter; // sustains, but ignores new noteons
var outChan : TParameter; // everything goes out one channel. It's a channel strip. Use more if you need other channels!
var instBypass: TParameter;
VAR octVal, semiVal, transpose : integer;
VAR hiVal, lowVal : integer;
VAR midiInCount : integer;
VAR otherOutCount, notesOutCount : integer;
VAR key, inputNum, byteNum : integer;
VAR minChn, maxChn, minKey, maxKey : integer;
VAR outputChannel : integer;
VAR isSustained,noInput : boolean;
VAR isEnabled : Array of boolean;
//////////////////////////////////////////////////////
// initialize
//////////////////////////////////////////////////////
PROCEDURE init;
BEGIN
minChn := INPUT_COUNT - 1; // these are initially set to their opposite extremes....
maxChn := 0;
minKey := 128;
maxKey := 0;
isSustained := FALSE;
setArrayLength(noteList, 128);
FOR key:=0 TO 127 DO BEGIN
noteList[key]:=FALSE;
END;
SetArrayLength(transList, 128);
FOR key:=0 TO 127 DO BEGIN
transList[key]:=0;
END;
setArrayLength(midiIns, INPUT_COUNT);
setArrayLength(enables, INPUT_COUNT);
FOR inputNum := 0 TO INPUT_COUNT - 1 DO BEGIN
enables[inputNum] := CreateParam('enable ' + intToStr(inputNum + 1),ptSwitch);
SetValue(enables[inputNum], 1);
SetIsOutPut(enables[inputNum],false);
END;
octave := CreateParam('octave',ptDataFader); SetIsOutPut(octave,false);
semi := CreateParam('semi',ptDataFader); SetIsOutPut(semi,false);
SetFormat(octave,'%.0f'); SetMin(octave,-4); SetMax(octave,4);
SetFormat(semi,'%.0f'); SetMin(semi,-7); SetMax(semi,7);
hiNote := CreateParam('high note',ptMidiNoteFader); SetIsOutPut(hiNote,false);
lowNote := CreateParam('low note',ptMidiNoteFader); SetIsOutPut(lowNote,false);
outChan := CreateParam('output chan', ptDataFader); SetIsOutPut(outchan,false);
SetFormat(outChan,'%.0f'); SetMin(outChan,1); SetMax(outChan,16);
SetValue(lowNote, 10);
SetValue(hiNote, 115);
drone := CreateParam('drone', ptSwitch); SetIsOutput(drone,false);
instBypass := CreateParam('inst bypass', ptSwitch); SetIsInput(instBypass,false);
FOR inputNum := 0 TO INPUT_COUNT - 1 DO BEGIN
midiIns[inputNum] := CreateParam('MIDIin ' + intToStr(inputNum + 1) ,ptMidi);
SetIsOutPut(midiIns[inputNum],false);
END;
notesOut := CreateParam('Notes Out',ptMidi); SetIsInput(notesOut,false);
otherOut := CreateParam('other Out',ptMidi); SetIsInput(otherOut,false);
notesOutCount := 0;
otherOutCount := 0;
SetArrayLength(isEnabled, INPUT_COUNT);
END;
// <F> isNoteOn:
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^//
FUNCTION isNoteOn(midi: tMidi): boolean;
BEGIN
IF (midi.msg = NOTE_ON) AND (midi.data2>0) THEN BEGIN
result := true;
END
ELSE BEGIN
result := false;
END;
END;
// <F> isNoteOff:
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^//
FUNCTION isNoteOff(midi: tMidi): boolean;
BEGIN
IF (midi.msg = NOTE_OFF) OR ((midi.msg = NOTE_ON) AND (midi.data2 = 0)) THEN BEGIN
result := true;
END
ELSE BEGIN
result := false;
END;
END;
// <F> isSusPed:
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^//
FUNCTION isSusPed(midi: tMidi) : boolean;
BEGIN
IF (midi.msg = CONTROL) AND (midi.data1 = SUS_PED) THEN BEGIN
result := true;
END
ELSE BEGIN
result := false;
END;
END;
// <F> isPitchBend:
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^//
FUNCTION isPitchBend(midi: tMidi): boolean;
BEGIN
IF (midi.msg = PITCHBEND) THEN BEGIN
result := true;
END
ELSE BEGIN
result := false;
END;
END;
// <F> global function inRange--eliminates only noteons outside the range limits
FUNCTION inRange(midi: tMidi) : boolean;
BEGIN
IF (isNoteOn(midi)) AND (midi.data1 > lowVal) AND (midi.data1 < hiVal) THEN BEGIN
result := TRUE;
END
ELSE BEGIN
result := FALSE;
END;
END;
// <F> isClear
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^//
FUNCTION isClear() : boolean; // can we stop this loop as soon as a key = true?? Didn't like 'while'
VAR clear : boolean;
BEGIN
clear := TRUE;
BEGIN
FOR key:= minKey TO maxKey DO BEGIN
IF (noteList[key] = TRUE) THEN BEGIN
clear := FALSE;
END;
END;
END;
result := clear;
END;
// <F> ReleaseNotes --when you release the sustain, or release the drone, or change the output channel
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^//
PROCEDURE ReleaseNotes;
BEGIN
FOR key:= minKey TO maxKey DO BEGIN
IF (noteList[key] = TRUE) THEN BEGIN
noteOff.msg := NOTE_OFF;
noteOff.channel := outputChannel; // byte is a conversion--is this needed?
noteOff.data1 := key;
noteOff.data2 := 0;
setMidiArrayValue(notesOut, notesOutCount, noteOff); //danger when called from callback?
notesOutCount := notesOutCount + 1;
noteList[key] := FALSE;
END;
END;
END;
// Global Procedure StoreNoteOffs--just de-inlined for readability
//----------------------------------------------//
PROCEDURE StoreNoteOffs;
BEGIN
IF (midi.data1 > maxKey) THEN BEGIN
maxKey := midi.data1;
END;
IF (midi.data1 < minKey) THEN BEGIN
minKey := midi.data1;
END;
noteList[midi.data1] := TRUE;
END;
// Callback
//----------------------------------------------//
procedure Callback(n:integer);
BEGIN
// get input values
hiVal := trunc(getValue(hiNote));
lowVal := trunc(getValue(lowNote));
octVal := trunc(getValue(octave));
semival := trunc(getValue(semi));
transpose := (octVal * 12) + semiVal;
writeln('transpose = ' + intToStr(transpose));
outputChannel := trunc(getValue(outChan));
// see if any input is enabled....
noInput := TRUE;
FOR inputNum := 0 to INPUT_COUNT - 1 DO
BEGIN
IF getValue(enables[inputNum]) = 1 THEN BEGIN
isEnabled[inputNum] := TRUE;
noInput := FALSE;
END
ELSE BEGIN
isEnabled[inputNum] := 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 ((n = drone) and (getValue(drone) = 0)) OR
((noInput = TRUE) AND (isClear() = TRUE))
OR (n = outChan)
THEN
BEGIN
releaseNotes; // can I call this from callback?
setValue(instBypass, 1);
END
ELSE BEGIN
setValue(instBypass, 0);
END;
END;
//////////////////////////////////////////////////////
// process
//////////////////////////////////////////////////////
PROCEDURE PROCESS;
BEGIN
notesOutCount := 0;
otherOutCount := 0;
FOR inputNum := 0 to INPUT_COUNT - 1 DO BEGIN
midiInCount := getLength(midiIns[inputNum]);
FOR byteNum := 0 TO (midiInCount - 1) DO BEGIN // process input
getMidiArrayValue(midiIns[inputNum], byteNum, midi);
midi.channel := outputChannel;
If isSusPed(midi) THEN BEGIN
if (midi.data2 > 64) THEN BEGIN
isSustained := true;
END
ELSE BEGIN
isSustained := false;
ReleaseNotes; // send noteoffs when the sus pedal is released
END;
END;
IF isNoteOn(midi) AND (inRange(midi)) AND (isEnabled[inputNum]) THEN BEGIN //note on, sus on, store and send
transList[midi.data1] := transpose;
midi.data1 := midi.data1 + transpose;
IF (isSustained = TRUE) THEN BEGIN
StoreNoteOffs;
setMidiArrayValue(notesOut, notesOutCount, midi);
notesOutCount := notesOutCount + 1;
END
ELSE BEGIN // valid noteon, sus off, send it out
setMidiArrayValue(notesOut, notesOutCount, midi);
notesOutCount := notesOutCount + 1;
END;
END
ELSE IF isNoteOff(midi) AND (isSustained = FALSE) THEN BEGIN // valid noteoff, sus off, send it out
midi.data1 := midi.data1 + transList[midi.data1];
setMidiArrayValue(notesOut, notesOutCount, midi);
notesOutCount := notesOutCount + 1;
END
ELSE IF isPitchBend(midi) THEN BEGIN // send PB along with the notes
midi.channel := outputChannel;
setMidiArrayValue(notesOut, notesOutCount, midi);
notesOutCount := notesOutCount + 1;
END
ELSE IF (NOT(isNoteOn(midi))) AND (NOT(isNoteOff(midi))) AND (NOT(isPitchBend(midi))) THEN BEGIN //not a note, pb or sus pedal event
setMidiArrayValue(otherOut, otherOutCount, midi);
otherOutCount := otherOutCount + 1;
END;
END;
END;
setLength(notesOut, notesOutCount);
setLength(otherOut, otherOutCount);
END;Custom Ryzen 5900x MATX build, Win10, Fireface UFX, touchscreen
Custom 2 manual midi keyboard
Usine, Kontakt, Reaktor, Synthmaster, Byome, Arturia, Soundtoys, Unify
Custom 2 manual midi keyboard
Usine, Kontakt, Reaktor, Synthmaster, Byome, Arturia, Soundtoys, Unify
-
woodslanding
- Member
- Posts: 1327
- Contact:
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.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.
Thanks!
-e
Custom Ryzen 5900x MATX build, Win10, Fireface UFX, touchscreen
Custom 2 manual midi keyboard
Usine, Kontakt, Reaktor, Synthmaster, Byome, Arturia, Soundtoys, Unify
Custom 2 manual midi keyboard
Usine, Kontakt, Reaktor, Synthmaster, Byome, Arturia, Soundtoys, Unify
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: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?
Code: Select all
i := 0;
WHILE i < cnt DO BEGIN
do_something...
i := i + 1;
END;Bjørn S
-
woodslanding
- Member
- Posts: 1327
- Contact:
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
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
Custom 2 manual midi keyboard
Usine, Kontakt, Reaktor, Synthmaster, Byome, Arturia, Soundtoys, Unify
'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.
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:
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!
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
Custom 2 manual midi keyboard
Usine, Kontakt, Reaktor, Synthmaster, Byome, Arturia, Soundtoys, Unify
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:
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
Custom 2 manual midi keyboard
Usine, Kontakt, Reaktor, Synthmaster, Byome, Arturia, Soundtoys, Unify
Built in functionality similar to this VST would be great!---> http://www.asseca.com/nicfit/mungrack.html
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...
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
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!
Definately more tools like this are needed in Usine!
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.
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:
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!
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
(*/////////////////////////////////////////////////////
// CHANNEL
// Version 2009-11-21; author: eric moon
//
// based on work by Bsork and amiga909
//////////////////////////////////////////////////////
*)
// This takes a number of midi inputs (on separate cables, for visual clarity)
// and performs the following operations on each.
// enable, disable--per input
// strip CCs and AT and send them out a separate output
// read CC64 values (or a CC of your choice) to withhold noteoffs until the sus pedal is released.
// limit to minimum and maximum notes--one range affects all inputs, 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 : ARRAY OF boolean;
VAR transList : ARRAY OF integer;
VAR midi,noteOff : TMidi;
var midiIns : ARRAY OF Tparameter;
var enables : ARRAY OF TParameter;
var notesOut : Tparameter;
var otherOut : Tparameter; // all CCs except the sustain value go out this output. So does AT and PgCh
var octave : TParameter;
var semi : TParameter;
var hiNote : TParameter;
var lowNote : TParameter;
var drone : TParameter; // sustains, but ignores new noteons
var outChan : TParameter; // everything goes out one channel. It's a channel strip. Use more if you need other channels!
var instEnable: TParameter;
var lower : Tparameter;
var upper : Tparameter;
var learn : TParameter;
VAR octVal, semiVal, transpose : integer;
VAR hiVal, lowVal : integer;
VAR midiInCount : integer;
VAR otherOutCount, notesOutCount : integer;
VAR key, inputNum, byteNum : integer;
VAR minChn, maxChn, minKey, maxKey : integer;
VAR outputChannel : integer;
VAR isSustained,noInput : boolean;
VAR isEnabled : Array of boolean;
VAR learning : boolean;
VAR droning : boolean;
var learnCount : integer;
//////////////////////////////////////////////////////
// initialize
//////////////////////////////////////////////////////
PROCEDURE init;
BEGIN
minChn := INPUT_COUNT - 1; // these are initially set to their opposite extremes....
maxChn := 0;
minKey := 128;
maxKey := 0;
isSustained := FALSE;
setArrayLength(noteList, 128);
FOR key:=0 TO 127 DO BEGIN
noteList[key]:=FALSE;
END;
SetArrayLength(transList, 128);
FOR key:=0 TO 127 DO BEGIN
transList[key]:=0;
END;
setArrayLength(midiIns, INPUT_COUNT);
setArrayLength(enables, INPUT_COUNT);
FOR inputNum := 0 TO INPUT_COUNT - 1 DO BEGIN
enables[inputNum] := CreateParam('enable ' + intToStr(inputNum + 1),ptSwitch);
SetValue(enables[inputNum], 1);
SetIsOutPut(enables[inputNum],false);
END;
octave := CreateParam('octave',ptDataFader); SetIsOutPut(octave,false);
semi := CreateParam('semi',ptDataFader); SetIsOutPut(semi,false);
SetFormat(octave,'%.0f'); SetMin(octave,-4); SetMax(octave,4);
SetFormat(semi,'%.0f'); SetMin(semi,-7); SetMax(semi,7);
hiNote := CreateParam('high note',ptMidiNoteFader); SetIsOutPut(hiNote,false);
lowNote := CreateParam('low note',ptMidiNoteFader); SetIsOutPut(lowNote,false);
outChan := CreateParam('output chan', ptDataFader); SetIsOutPut(outchan,false);
SetFormat(outChan,'%.0f'); SetMin(outChan,1); SetMax(outChan,16);
SetValue(lowNote, 10);
SetValue(hiNote, 115);
drone := CreateParam('drone', ptSwitch); SetIsOutput(drone,false);
instEnable := CreateParam('inst enable', ptSwitch); SetIsInput(instEnable,false);
FOR inputNum := 0 TO INPUT_COUNT - 1 DO BEGIN
midiIns[inputNum] := CreateParam('MIDIin ' + intToStr(inputNum + 1) ,ptMidi);
SetIsOutPut(midiIns[inputNum],false);
END;
notesOut := CreateParam('NoteMIDI',ptMidi); SetIsInput(notesOut,false);
otherOut := CreateParam('otherMIDI',ptMidi); SetIsInput(otherOut,false);
learn := CreateParam('learn',ptButton); SetIsOutPut(learn,false);
lower := CreateParam('lower',ptMidiNoteFader); SetIsInPut(lower,false);
upper := CreateParam('upper',ptMidiNoteFader); SetIsInPut(upper,false);
notesOutCount := 0;
otherOutCount := 0;
learnCount := 0;
SetArrayLength(isEnabled, INPUT_COUNT);
END;
// <F> isNoteOn:
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^//
FUNCTION isNoteOn(midi: tMidi): boolean;
BEGIN
IF (midi.msg = NOTE_ON) AND (midi.data2>0) THEN BEGIN
result := true;
END
ELSE BEGIN
result := false;
END;
END;
// <F> isNoteOff:
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^//
FUNCTION isNoteOff(midi: tMidi): boolean;
BEGIN
IF (midi.msg = NOTE_OFF) OR ((midi.msg = NOTE_ON) AND (midi.data2 = 0)) THEN BEGIN
result := true;
END
ELSE BEGIN
result := false;
END;
END;
// <F> isSusPed:
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^//
FUNCTION isSusPed(midi: tMidi) : boolean;
BEGIN
IF (midi.msg = CONTROL) AND (midi.data1 = SUS_PED) THEN BEGIN
result := true;
END
ELSE BEGIN
result := false;
END;
END;
// <F> isPitchBend:
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^//
FUNCTION isPitchBend(midi: tMidi): boolean;
BEGIN
IF (midi.msg = PITCHBEND) THEN BEGIN
result := true;
END
ELSE BEGIN
result := false;
END;
END;
// <F> global function inRange--eliminates only noteons outside the range limits
FUNCTION inRange(midi: tMidi) : boolean;
BEGIN
IF (isNoteOn(midi)) AND (midi.data1 > lowVal) AND (midi.data1 < hiVal) THEN BEGIN
result := TRUE;
END
ELSE BEGIN
result := FALSE;
END;
END;
// <F> isClear
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^//
FUNCTION isClear() : boolean; // can we stop this loop as soon as a key = true?? Didn't like 'while'
VAR clear : boolean;
BEGIN
clear := TRUE;
BEGIN
FOR key:= minKey TO maxKey DO BEGIN
IF (noteList[key] = TRUE) THEN BEGIN
clear := FALSE;
END;
END;
END;
result := clear;
END;
// <F> ReleaseNotes --when you release the sustain, or release the drone, or change the output channel
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^//
PROCEDURE ReleaseNotes;
BEGIN
FOR key:= minKey TO maxKey DO BEGIN
IF (noteList[key] = TRUE) THEN BEGIN
noteOff.msg := NOTE_OFF;
noteOff.channel := outputChannel; // byte is a conversion--is this needed?
noteOff.data1 := key;
noteOff.data2 := 0;
setMidiArrayValue(notesOut, notesOutCount, noteOff);
strace('sending noteOff for ' + intToStr(key));
notesOutCount := notesOutCount + 1;
noteList[key] := FALSE;
END;
END;
END;
// Global Procedure StoreNoteOffs--just de-inlined for readability
//----------------------------------------------//
PROCEDURE StoreNoteOffs;
BEGIN
IF (midi.data1 > maxKey) THEN BEGIN
maxKey := midi.data1;
END;
IF (midi.data1 < minKey) THEN BEGIN
minKey := midi.data1;
END;
noteList[midi.data1] := TRUE;
END;
// Callback
//----------------------------------------------//
procedure Callback(n:integer);
BEGIN
if (n = hiNote) then begin
hiVal := round(getValue(hiNote));
setValue(upper, hiVal);
end
else if (n = lowNote) then begin
lowVal := round(getValue(lowNote));
setValue(lower, lowVal);
end
else if (n = learn) and (getValue(n) = 1) then begin
learning:= TRUE;
strace('learning...');
learnCount := 0;
end
else if (learnCount = 2) then begin
learning := FALSE;
learnCount := 0;
strace(' finishedlearning');
end
else begin
// get input values
octVal := trunc(getValue(octave));
semival := trunc(getValue(semi));
transpose := (octVal * 12) + semiVal;
outputChannel := trunc(getValue(outChan));
droning := (getValue(drone) = 1);
// see if any input is enabled....
noInput := TRUE;
FOR inputNum := 0 to INPUT_COUNT - 1 DO
BEGIN
IF getValue(enables[inputNum]) = 1 THEN BEGIN
isEnabled[inputNum] := TRUE;
noInput := FALSE;
END
ELSE BEGIN
isEnabled[inputNum] := 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 ((n = drone) and (not(Droning))) OR
((noInput = TRUE) AND (isClear() = TRUE))
OR (n = outChan)
THEN BEGIN
strace('releasing notes');
releaseNotes;
setValue(instEnable, 0);
END
ELSE BEGIN
setValue(instEnable, 1);
END;
end;
END;
//////////////////////////////////////////////////////
// process
//////////////////////////////////////////////////////
PROCEDURE PROCESS;
BEGIN
notesOutCount := 0;
otherOutCount := 0;
FOR inputNum := 0 to INPUT_COUNT - 1 DO BEGIN
midiInCount := getLength(midiIns[inputNum]);
FOR byteNum := 0 TO (midiInCount - 1) DO BEGIN // process input
getMidiArrayValue(midiIns[inputNum], byteNum, midi);
midi.channel := outputChannel;
if (learning) then begin
IF (isNoteOn(midi)) and (learnCount = 0) THEN BEGIN
lowVal := midi.data1;
setValue(lower, lowVal );
learnCount := 1;
setMidiArrayValue(otherOut, otherOutCount, midi);
otherOutCount := otherOutCount + 1;
strace('learnCount = ' + intToStr(learnCount));
END
ELSE IF (isNoteOn(midi)) and (learnCount = 1) THEN BEGIN
hiVal := midi.data1;
setValue(upper, hiVal);
learnCount := 2;
learning := FALSE; // we've set our outs....
setMidiArrayValue(otherOut, otherOutCount, midi);
otherOutCount := otherOutCount + 1;
END;
end
else if (droning) then begin // basically ignore all midi when droning
setMidiArrayValue(otherOut, otherOutCount, midi);
otherOutCount := otherOutCount + 1;
end
else begin
If isSusPed(midi) THEN BEGIN
if (midi.data2 > 64) THEN BEGIN
isSustained := true;
END
ELSE BEGIN
isSustained := false;
ReleaseNotes; // send noteoffs when the sus pedal is released
END;
END;
IF isNoteOn(midi) AND (inRange(midi)) AND (isEnabled[inputNum]) THEN BEGIN //note on, sus on, store and send
transList[midi.data1] := transpose;
midi.data1 := midi.data1 + transpose;
IF (isSustained = TRUE) THEN BEGIN
StoreNoteOffs;
setMidiArrayValue(notesOut, notesOutCount, midi);
notesOutCount := notesOutCount + 1;
END
ELSE BEGIN // valid noteon, sus off, send it out
setMidiArrayValue(notesOut, notesOutCount, midi);
notesOutCount := notesOutCount + 1;
END;
END
ELSE IF isNoteOff(midi) AND (isSustained = FALSE) THEN BEGIN // valid noteoff, sus off, send it out
midi.data1 := midi.data1 + transList[midi.data1];
setMidiArrayValue(notesOut, notesOutCount, midi);
notesOutCount := notesOutCount + 1;
END
ELSE IF isPitchBend(midi) THEN BEGIN // send PB along with the notes
midi.channel := outputChannel;
setMidiArrayValue(notesOut, notesOutCount, midi);
notesOutCount := notesOutCount + 1;
END
ELSE IF (NOT(isNoteOn(midi))) AND (NOT(isNoteOff(midi))) AND (NOT(isPitchBend(midi))) THEN BEGIN //not a note, pb or sus pedal event
setMidiArrayValue(otherOut, otherOutCount, midi);
otherOutCount := otherOutCount + 1;
END;
end;
END;
END;
setLength(notesOut, notesOutCount);
setLength(otherOut, otherOutCount);
END;Custom Ryzen 5900x MATX build, Win10, Fireface UFX, touchscreen
Custom 2 manual midi keyboard
Usine, Kontakt, Reaktor, Synthmaster, Byome, Arturia, Soundtoys, Unify
Custom 2 manual midi keyboard
Usine, Kontakt, Reaktor, Synthmaster, Byome, Arturia, Soundtoys, Unify
-
woodslanding
- Member
- Posts: 1327
- Contact:
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:
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
(*/////////////////////////////////////////////////////
// CHANNEL
// Version 2009-11-21; author: eric moon
//
// based on work by Bsork and amiga909
//////////////////////////////////////////////////////
*)
// This takes a number of midi inputs (on separate cables, for visual clarity)
// and performs the following operations on each.
// enable, disable--per input
// strip CCs and AT and send them out a separate output
// read CC64 values (or a CC of your choice) to withhold noteoffs until the sus pedal is released.
// limit to minimum and maximum notes--one range affects all inputs, 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 : ARRAY OF boolean;
VAR transList : ARRAY OF integer;
VAR midi,noteOff : TMidi;
var midiIns : ARRAY OF Tparameter;
var enables : ARRAY OF TParameter;
var notesOut : Tparameter;
var otherOut : Tparameter; // all CCs except the sustain value go out this output. So does AT and PgCh
var octave : TParameter;
var semi : TParameter;
var hiNote : TParameter;
var lowNote : TParameter;
var drone : TParameter; // sustains, but ignores new noteons
var outChan : TParameter; // everything goes out one channel. It's a channel strip. Use more if you need other channels!
var instEnable: TParameter;
var lower : Tparameter;
var upper : Tparameter;
var learn : TParameter;
VAR octVal, semiVal, transpose : integer;
VAR hiVal, lowVal : integer;
VAR midiInCount : integer;
VAR otherOutCount, notesOutCount : integer;
VAR key, inputNum, byteNum : integer;
VAR minChn, maxChn, minKey, maxKey : integer;
VAR outputChannel : integer;
VAR isSustained,noInput : boolean;
VAR isEnabled : Array of boolean;
VAR learning : boolean;
VAR droning : boolean;
VAR release : boolean;
VAR disablable : boolean;
var learnCount : integer;
//////////////////////////////////////////////////////
// initialize
//////////////////////////////////////////////////////
PROCEDURE init;
BEGIN
minChn := INPUT_COUNT - 1; // these are initially set to their opposite extremes....
maxChn := 0;
minKey := 128;
maxKey := 0;
isSustained := FALSE;
setArrayLength(noteList, 128);
FOR key:=0 TO 127 DO BEGIN
noteList[key]:=FALSE;
END;
SetArrayLength(transList, 128);
FOR key:=0 TO 127 DO BEGIN
transList[key]:=0;
END;
setArrayLength(midiIns, INPUT_COUNT);
setArrayLength(enables, INPUT_COUNT);
FOR inputNum := 0 TO INPUT_COUNT - 1 DO BEGIN
enables[inputNum] := CreateParam('enable ' + intToStr(inputNum + 1),ptSwitch);
SetValue(enables[inputNum], 1);
SetIsOutPut(enables[inputNum],false);
END;
octave := CreateParam('octave',ptDataFader); SetIsOutPut(octave,false);
semi := CreateParam('semi',ptDataFader); SetIsOutPut(semi,false);
SetFormat(octave,'%.0f'); SetMin(octave,-4); SetMax(octave,4);
SetFormat(semi,'%.0f'); SetMin(semi,-7); SetMax(semi,7);
hiNote := CreateParam('high note',ptMidiNoteFader); SetIsOutPut(hiNote,false);
lowNote := CreateParam('low note',ptMidiNoteFader); SetIsOutPut(lowNote,false);
outChan := CreateParam('output chan', ptDataFader); SetIsOutPut(outchan,false);
SetFormat(outChan,'%.0f'); SetMin(outChan,1); SetMax(outChan,16);
SetValue(lowNote, 10);
SetValue(hiNote, 115);
drone := CreateParam('drone', ptSwitch); SetIsOutput(drone,false);
instEnable := CreateParam('inst enable', ptSwitch); SetIsInput(instEnable,false);
FOR inputNum := 0 TO INPUT_COUNT - 1 DO BEGIN
midiIns[inputNum] := CreateParam('MIDIin ' + intToStr(inputNum + 1) ,ptMidi);
SetIsOutPut(midiIns[inputNum],false);
END;
notesOut := CreateParam('NoteMIDI',ptMidi); SetIsInput(notesOut,false);
otherOut := CreateParam('otherMIDI',ptMidi); SetIsInput(otherOut,false);
learn := CreateParam('learn',ptButton); SetIsOutPut(learn,false);
lower := CreateParam('lower',ptMidiNoteFader); SetIsInPut(lower,false);
upper := CreateParam('upper',ptMidiNoteFader); SetIsInPut(upper,false);
notesOutCount := 0;
otherOutCount := 0;
learnCount := 0;
disablable := FALSE;
droning := FALSE;
release := FALSE;
SetArrayLength(isEnabled, INPUT_COUNT);
END;
// <F> isNoteOn:
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^//
FUNCTION isNoteOn(midi: tMidi): boolean;
BEGIN
IF (midi.msg = NOTE_ON) AND (midi.data2>0) THEN BEGIN
result := true;
END
ELSE BEGIN
result := false;
END;
END;
// <F> isNoteOff:
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^//
FUNCTION isNoteOff(midi: tMidi): boolean;
BEGIN
IF (midi.msg = NOTE_OFF) OR ((midi.msg = NOTE_ON) AND (midi.data2 = 0)) THEN BEGIN
result := true;
END
ELSE BEGIN
result := false;
END;
END;
// <F> isSusPed:
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^//
FUNCTION isSusPed(midi: tMidi) : boolean;
BEGIN
IF (midi.msg = CONTROL) AND (midi.data1 = SUS_PED) THEN BEGIN
result := true;
END
ELSE BEGIN
result := false;
END;
END;
// <F> isPitchBend:
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^//
FUNCTION isPitchBend(midi: tMidi): boolean;
BEGIN
IF (midi.msg = PITCHBEND) THEN BEGIN
result := true;
END
ELSE BEGIN
result := false;
END;
END;
// <F> global function inRange--eliminates only noteons outside the range limits
FUNCTION inRange(midi: tMidi) : boolean;
BEGIN
IF (isNoteOn(midi)) AND (midi.data1 > lowVal) AND (midi.data1 < hiVal) THEN BEGIN
result := TRUE;
END
ELSE BEGIN
result := FALSE;
END;
END;
// <F> isClear
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^//
FUNCTION isClear() : boolean; // can we stop this loop as soon as a key = true?? Didn't like 'while'
VAR clear : boolean;
BEGIN
clear := TRUE;
BEGIN
FOR key:= minKey TO maxKey DO BEGIN
IF (noteList[key] = TRUE) THEN BEGIN
clear := FALSE;
END;
END;
END;
result := clear;
END;
// <F> ReleaseNotes --when you release the sustain, or release the drone, or change the output channel
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^//
PROCEDURE ReleaseNotes;
BEGIN
FOR key:= minKey TO maxKey DO BEGIN
IF (noteList[key] = TRUE) THEN BEGIN
noteOff.msg := NOTE_OFF;
noteOff.channel := outputChannel; // byte is a conversion--is this needed?
noteOff.data1 := key;
noteOff.data2 := 0;
setMidiArrayValue(notesOut, notesOutCount, noteOff);
strace('sending noteOff for ' + intToStr(key));
notesOutCount := notesOutCount + 1;
noteList[key] := FALSE;
END;
END;
END;
// Global Procedure StoreNoteOffs--just de-inlined for readability
//----------------------------------------------//
PROCEDURE StoreNoteOffs;
BEGIN
IF (midi.data1 > maxKey) THEN BEGIN
maxKey := midi.data1;
END;
IF (midi.data1 < minKey) THEN BEGIN
minKey := midi.data1;
END;
noteList[midi.data1] := TRUE;
END;
// Callback
//----------------------------------------------//
procedure Callback(n:integer);
BEGIN
if (n = hiNote) then begin
hiVal := round(getValue(hiNote));
setValue(upper, hiVal);
end
else if (n = lowNote) then begin
lowVal := round(getValue(lowNote));
setValue(lower, lowVal);
end
else if (n = learn) and (getValue(n) = 1) then begin
learning:= TRUE;
strace('learning...');
learnCount := 0;
end
else if (learnCount = 2) then begin
// we've just learned, so we want to send out lower and upper
learning := FALSE;
learnCount := 0;
strace(' finishedlearning');
end
else begin
// get input values
octVal := trunc(getValue(octave));
semival := trunc(getValue(semi));
transpose := (octVal * 12) + semiVal;
outputChannel := trunc(getValue(outChan));
droning := (getValue(drone) = 1);
// see if any input is enabled....
noInput := TRUE;
FOR inputNum := 0 to INPUT_COUNT - 1 DO
BEGIN
IF getValue(enables[inputNum]) = 1 THEN BEGIN
isEnabled[inputNum] := TRUE;
noInput := FALSE;
END
ELSE BEGIN
isEnabled[inputNum] := 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 ((n = drone) and (not(Droning))) OR
((noInput = TRUE) AND (isClear() = TRUE))
OR (n = outChan)
THEN BEGIN
strace('releasing notes');
release := true; // booleans to check in process
disablable := true;
END
ELSE BEGIN
setValue(instEnable, 1);
END;
end;
END;
//////////////////////////////////////////////////////
// process
//////////////////////////////////////////////////////
PROCEDURE PROCESS;
BEGIN
notesOutCount := 0;
otherOutCount := 0;
if release then releaseNotes;
FOR inputNum := 0 to INPUT_COUNT - 1 DO BEGIN
midiInCount := getLength(midiIns[inputNum]);
FOR byteNum := 0 TO (midiInCount - 1) DO BEGIN // process input
getMidiArrayValue(midiIns[inputNum], byteNum, midi);
midi.channel := outputChannel;
if (learning) then begin
IF (isNoteOn(midi)) and (learnCount = 0) THEN BEGIN
lowVal := midi.data1;
setValue(lower, lowVal );
learnCount := 1;
setMidiArrayValue(otherOut, otherOutCount, midi);
otherOutCount := otherOutCount + 1;
strace('learnCount = ' + intToStr(learnCount));
END
ELSE IF (isNoteOn(midi)) and (learnCount = 1) THEN BEGIN
hiVal := midi.data1;
setValue(upper, hiVal);
learnCount := 2;
learning := FALSE; // we've set our outs....
setMidiArrayValue(otherOut, otherOutCount, midi);
otherOutCount := otherOutCount + 1;
END;
end
else if (droning) then begin // basically ignore all midi when droning
setMidiArrayValue(otherOut, otherOutCount, midi);
otherOutCount := otherOutCount + 1;
end
else begin
If isSusPed(midi) THEN BEGIN
if (midi.data2 > 64) THEN BEGIN
isSustained := true;
END
ELSE BEGIN
isSustained := false;
ReleaseNotes; // send noteoffs when the sus pedal is released
END;
END;
IF isNoteOn(midi) AND (inRange(midi)) AND (isEnabled[inputNum]) THEN BEGIN //note on, sus on, store and send
transList[midi.data1] := transpose;
midi.data1 := midi.data1 + transpose;
IF (isSustained = TRUE) THEN BEGIN
StoreNoteOffs;
setMidiArrayValue(notesOut, notesOutCount, midi);
notesOutCount := notesOutCount + 1;
END
ELSE BEGIN // valid noteon, sus off, send it out
setMidiArrayValue(notesOut, notesOutCount, midi);
notesOutCount := notesOutCount + 1;
END;
END
ELSE IF isNoteOff(midi) AND (isSustained = FALSE) THEN BEGIN // valid noteoff, sus off, send it out
midi.data1 := midi.data1 + transList[midi.data1];
setMidiArrayValue(notesOut, notesOutCount, midi);
notesOutCount := notesOutCount + 1;
END
ELSE IF isPitchBend(midi) THEN BEGIN // send PB along with the notes
midi.channel := outputChannel;
setMidiArrayValue(notesOut, notesOutCount, midi);
notesOutCount := notesOutCount + 1;
END
ELSE IF (NOT(isNoteOn(midi))) AND (NOT(isNoteOff(midi))) AND (NOT(isPitchBend(midi))) THEN BEGIN //not a note, pb or sus pedal event
setMidiArrayValue(otherOut, otherOutCount, midi);
otherOutCount := otherOutCount + 1;
END;
end;
END;
END;
setLength(notesOut, notesOutCount);
setLength(otherOut, otherOutCount);
if disablable then setValue(instEnable, 0);
END;Custom Ryzen 5900x MATX build, Win10, Fireface UFX, touchscreen
Custom 2 manual midi keyboard
Usine, Kontakt, Reaktor, Synthmaster, Byome, Arturia, Soundtoys, Unify
Custom 2 manual midi keyboard
Usine, Kontakt, Reaktor, Synthmaster, Byome, Arturia, Soundtoys, Unify
-
woodslanding
- Member
- Posts: 1327
- Contact:
crap, this still isn't working.... and it's getting late.
tomorrow....
tomorrow....
Custom Ryzen 5900x MATX build, Win10, Fireface UFX, touchscreen
Custom 2 manual midi keyboard
Usine, Kontakt, Reaktor, Synthmaster, Byome, Arturia, Soundtoys, Unify
Custom 2 manual midi keyboard
Usine, Kontakt, Reaktor, Synthmaster, Byome, Arturia, Soundtoys, Unify
Who is online
Users browsing this forum: Google [Bot] and 106 guests
