-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmidiChStrip.jsfx
396 lines (370 loc) · 15 KB
/
midiChStrip.jsfx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
desc:midiChStrip
tags: MIDI
author: Eric Moon
version: .55
changelog: alpha
about:
## Description
Designed to be placed upstream from a VSTi to control the flow of midi data in. Facilitates creating
composite instruments with layers on multiple tracks, all controlled from a single controller.
Includes pitchbend and voice limiting, for converting between MPE and conventional midi.
## Notes
Features:
1.Midi enable/disable,Octave and Semitone transpose (transpose allows dynamic changes without stuck notes,noteoffs sent when midi is disabled
2.Option to convert Semitone control to send CC out, for drum sounds, effects, etc.
3.'Hands' feature moves LH up an octave, and RH down an octave for playing drums or 2-handed melodies
4.Limit midi range, high and low note.
5.High and low 'foldback' transposes by octaves to bring notes within range, ala Hammond organs.
6.Switch to ignore sustain pedal, for drum sounds, etc., or simulate sustain for vsts that don't support it
7.Expression CC# can be converted and rescaled or inverted
7.'Hold' feature, withholds note offs and ignores new notes until released, then sends all necessary note offs.
8.Conversion from conventional midi to MPE. Support for any combination of MPE/conventional controllers and VSTi's.
9.Pitch Bend conversion, so VSTs that cannot support the full MPE spec of +-48 semitones can be used alongside those that do.
10.PB conversion to allow musical (smaller) pitch bends from a traditional wheel, for VSTi's with PB maxed out for MPE.
11.Creation of new MIDI notes from MPE pitch bends, for better behaviour with VSTi's that don't use pitch bends, e.g. organs, pianos.
11.MPE voice reduction so VSTi's that cannot receive on the full 15 channels of MPE (e.g. Omnisphere) can be used with those that do.
12.Simple MIDI ch remapping for conventional VSTs used with a conventional controller.
13.AT->CC conversion, so MPE AT can be mapped to a CC, for VSTi's that don't respond to AT (e.g. many Kontakt orchestral libraries)
12.Option to set AT toggling so that sustained pressure will toggle a value from 0 to 127
/////////////////////////////////////////////////////////////////////////////////
// .55 -- added support for AT toggling
//////////////////////////////// DEFINE SLIDERS ///////////////////////////////
//The following are read only, and written by scripts: NsMute NsMuteHi,NsMuteLo
slider1:s_enable=1<0,1,1{off,on}>Midi Enable
slider4:s_octave=0<-5,5,1>Octave transpose
slider5:s_semi=0<-7,7,1>Semitone transpose
slider6:s_semiToCC=0<0,127,1>Semi->CC# (0 for none)
slider7:s_hands=0<0,1,1{off,on}>Hands (LH up 8va, RH down 8va)
slider8:s_loNote=0<0,127,1>Lo Note (MIDI Note #)
slider9:s_hiNote=127<0,127,1>Hi Note (MIDI Note #)
slider10:s_loFold=0<0,127,1>Fold lo (MIDI Note #)
slider11:s_hiFold=127<0,127,1>Fold hi (MIDI Note #)
//NOTESOURCE
slider13:s_nsMute=0<0,1,1{off,on}>NS Status
slider14:s_nsMuteLo=0<0,127,1>NS Mute Low (MIDI Note #)
slider15:s_nsMuteHi=127<0,127,1>NS Mute High (MIDI Note #)
//PEDALS
slider16:s_susType=0<0,2,0{normal,ignored,faked}>Sus Type
slider17:s_hold=0<0,1,1{off,hold}>Hold
//MPE for Src:Both,
slider20:s_mpeSrc=0<0,3,1{KEYB,MPE,NONE,DUAL}>Controller Type
slider21:s_mpeVST=0<0,1,1{no,yes}>MPE vst
slider22:s_baseCh=1<0,15,1{1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}>ChanOut (base chan for MPE)
slider23:s_mpePoly=5<1,15>MPE poly to vst
slider24:s_ATtoCC=0<0,11,1{off,1,2,3,4,5,6,7,8,9,10,11}>AT->CC
slider25:s_pbNorm=2<0,48,1>Normal pb range (e.g. 2)
slider26:s_pbMPE=48<1,48,1>MPE pb range (e.g. 48)
slider27:s_pbVST=48<1,48,1>VST pb range (e.g. 12)
slider28:s_bendToNotes=0<0,1,1{off,on}>PB->notes
// these lines tell Reaper the effect has no audio input/output,
// which enables processing optimizations.
// MIDI-only FX should always have these lines.
in_pin:none
out_pin:none
options:no_meter
@init
ext_noinit = 1; //don't reset on stop/start
//CONSTANTS
ROLI_CC = 74;
K_PNO = 0;
K_MPE = 1;
K_NON = 2;
K_DBL = 3;
////////////////////////////// MIDI FUNCTIONS ///////////////////////////
NOTE_OFF = 128;
NOTE_ON = 144;
CC = 176;
PG_CH = 192;
CH_AT = 208;
P_BEND = 224;
MOD = 1;
SUSTAIN = 64;
// Aftertouch->toggle
touchToggleState = 0;
toggleThreshold = 100; //value must be continuously above threshold to toggle
toggleDuration = 20000; //samples of sustained pressure to toggle
toggleCount = 0; //reset to zero when pressure released
counting = 0; //are we counting?
sendToggle = 0;
function getStatus (msg) ( msg & $xF0; );
function getChannel (msg) ( msg & $x0F; );
function getLSB (pbValue) ( pbValue % 128; );
function getMSB (pbValue) ( floor(pbValue / 128) ; );
function isMpeMsg(status,msg2,msg3) (
(((status == CC) && (msg2 == ROLI_CC))
|| ((status == CC) && (msg2 == MOD))
|| (status == CH_AT)
|| (status == P_BEND)) ? 1 : 0; //include Sustain here??
);
function isSustain(status,msg2) (
(status == CC) && (msg2 == SUSTAIN) ? 1 : 0;
);
function scalePB (pbMSB,pbLSB,channel) local(val,ratio) (
val = ((pbMSB * 128) + pbLSB) - $x2000; //aka 8192... center about 0
// if the source is non-mpe, or an mpe src is sending pb on ch1
(channel == 0 && s_mpeSrc == K_MPE) || (s_mpeSrc == K_PNO) ?
( // rescale pb from 12 to 2,
ratio = s_pbVST / s_pbNorm; //=6, so reduce to 1/6th
val = val / ratio;
)
:
(
// rescale pb from 12 to 48
ratio = s_pbVST / s_pbMPE; //=1/4
val = val / ratio; // = PB * 4
);
//decenter
val = val + $x2000;
val = val < 0 ? 0 : val;
//trim
val = val > $x4000 ? $x4000 : val;
val;
);
//sends a CC to all relevant channels
function sendCC(num,val) (
(s_semiToCC > 0) ?
(
(s_mpeVST > 0) ?
(
ch = s_baseCh;
loop( s_mpePoly,
midisend(0,CC + ch,num,val);
ch = ch + 1; );
)
: midisend(0,CC + s_baseCh,num,val);
)
);
/*
function computeExp(ccval) (
s_expCurve == 0 ? val = ccval :
s_expCurve == 1 ? val = 127 - ccval :
s_expCurve == 2 ? val = min(ccval*2, 127):
s_expCurve == 3 ? val = min(127-(ccval*2),127);
);*/
function playNote(key) (
play = 1;
(key < s_loNote) || (key > s_hiNote) || (s_hold == 1) ? play = 0;
(s_nsMuting == 1) && (key > s_nsMuteLo) && (key < snsMuteHi) ? play = 0;
play;
);
///////////////////////////////// NOTE STORAGE /////////////////////
noteCount = 0;
heldNotes = 0;
k_size = 1; // 1 entry per note: newNoteOff
v_size = 3; // 3 entries per note: newNoteOff, newChan, origVel
keyInput = 0; //keyDowns coming in from conventional controller
voiceInput = v_size * 100; //voices coming in from the roli
function clearArrays() local(i) (
i = 0;
loop (k_size * 128,
keyInput[i] = -1;
i = i + 1;
);
i = 0;
loop (v_size * 16,
voiceInput[i] = -1;
i = i + 1;
);
);
//get data by key
function k_note(key) ( keyInput[k_size * key]; );
//get data by voice (roli input chan)
function v_note(voice) ( voiceInput[v_size * voice]; );
function v_chan(voice) ( voiceInput[(v_size * voice) + 1]; );
function v_vel(voice) ( voiceInput[(v_size * voice) + 2]; );
function k_store(key,note) (
keyInput[k_size * key] = note;
);
function v_store(voice,note,chan,vel) (
voiceInput[v_size * voice] = note;
voiceInput[(v_size * voice) + 1] = chan;
voiceInput[(v_size * voice) + 2] = vel;
);
function sendNoteOffs() local(key,ch) (
key = 0;
loop( 128,
k_note(key) > -1 ?
(
midisend(0,NOTE_OFF + s_baseCh, k_note(key), 0);
midisend(0,CC + s_baseCh,SUSTAIN,0);//clear sus pedal
k_store(key,-1) //zero the buffer
)
);
ch = 0;
loop( 16,
v_note(ch) > -1 ?
(
midisend(ch,NOTE_OFF + v_chan(ch), v_note(ch), 0); //ch value for offset
midisend(ch,CC + ch,SUSTAIN,0); //clear sus pedal
v_store(ch,-1,-1,-1) //zero the buffer
)
)
);
clearArrays();
// global variables
SENSITIVITY = 100;
holdPrev = 0; // always power up with hold off. TEST: Is this sufficient??
@slider ///@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ SLIDER @@@@@@@@@@@@@@@@@@@@@@@@@@///////
s_mpeSrc == K_DBL ? // DUAL MODE
(s_mpeVST=0; // must be non-mpe vst...
s_bseCh=1; // base chan is 1
s_mpePoly = 15; ); // use all available poly
s_mpeSrc == K_NON ? sendNoteOffs(); //when we disable input, clear notes
s_semiToCC > 0 ? semi = 0 : semi = s_semi;//if converted to CCs, don't use for transpose
tuneVal = (s_semi + 7) * (127/14);//scale +-7 to 0-127
transpose = (s_octave * 12) + semi;
s_loFold > s_hiFold - 12 ? s_hiFold = s_loFold + 12;
s_loNote > s_hiNote ? s_hiNote = s_loNote; //todo: allow 'deadspot' negative ranges....
@block ////@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ BLOCK @@@@@@@@@@@@@@@@@@@@@@@@@@///////
//send semi-> cc values
semiPrev != s_semi && s_semiToCC > 0 ? sendCC(s_semiToCC,tuneVal);
semiPrev = s_semi;
//hold changed to 0 in the last block. clear held notes
holdPrev != s_hold && s_hold == 0 ? sendNoteOffs();
holdPrev = s_hold; //check for change in hold value
//midi disabled in the last block. clear held notes
enablePrev != s_enable && s_enable == 0 ? sendNoteOffs();
enablePrev = s_enable; //check for change in hold value
//if we are counting, increment. If counting has stopped, reset.
counting ? toggleCount = toggleCount + 1 : toggleCount = 0;
/////////////////////////////////////////////////////MIDI RECEIVE//////////////////////////////
while (midirecv(offset,msg1,msg2,msg3) && (s_enable > 0) && (s_mpeSrc != K_NON)) ? //input none really NONE?
(
heldNotes == 0 ? noteCount = 0;
status = getStatus(msg1);
channel = getChannel(msg1);
///////////////////////////////////////////////// NOTE ON ////////////////////////
(status == NOTE_ON) && (msg3 > 0) ?
(
noteCount = noteCount + 1;
heldNotes = heldNotes + 1;
key = msg2;
vel = msg3;
playNote(key) == 1 ?
(
note = (key + transpose); // transposed note
s_hands ? ( (key < 65) ? note = note + 12 : note = note - 12; );
//foldback:: while not in range, transpose up or down by octaves as needed
while ( note < s_loFold ? note = note + 12; );
while ( note > s_hiFold ? note = note - 12; );
//??Can roli send same note on 2 different channels? YES!
//so store roli data by channel, not note
(s_mpeSrc == K_PNO) || ((s_mpeSrc == K_DBL) && (channel == 1)) ?
//store in array by which key was hit on the keyboard
(
k_store(key,note);
midisend(offs, status + s_baseCh, note, vel);
)
: //store in array by which channel the note came in on
(
noteOnOut = (s_mpeSrc == K_DBL) ? 1 : (noteCount % s_mpePoly) + s_baseCh;
v_store(channel,note,noteOnOut,vel);
midisend(offs, status + noteOnOut, note, vel);
)
)
)
: ////////////////////////////////////// NOTE OFF //////////////////////////
( (status == NOTE_ON && msg3 == 0 ) || status == NOTE_OFF) && (s_hold == 0) ?
(
//if faking sustain then do nothing here
heldNotes = heldNotes - 1;
key = msg2;
vel = msg3;
(s_mpeSrc == K_PNO) || ((s_mpeSrc == K_DBL) && (channel == 1)) ?
(
noteOff = k_note(key);
midisend(offs,status + s_baseCh,noteOff,vel);
)
:
(
noteOff = v_note(channel);
midisend(offs,status + v_chan(channel),noteOff,vel);
);
)
: ///////////////////////////////////////// SUSTAIN ///////////////////////
isSustain(status,msg2) ?
(
(s_susType == 0) ?
sendCC(SUSTAIN,msg3)
: (s_susType == 2) && (msg3 < 63) && (s_hold == 0) ? //fake sustain
sendNoteOffs()
//if we're ignoring sustain, don't do anything...
)
: ///////////////////////////////////// PITCH BEND ///////////////////////
isMpeMsg(status,msg2,msg3) && (v_note(channel) > - 1) ? //check for note
(
(status == P_BEND) ?
(
bendAmount = scalePB(msg3,msg2,channel);
(s_bendToNotes == 0) || (s_mpeSrc == K_PNO) ? //don't convert bends from non-mpe sources
(
msg3 = getMSB(bendAmount);
msg2 = getLSB(bendAmount);
midisend(offs,status + v_chan(channel),msg2,msg3);
)
: //convert pb to notes... no pb is sent out...
(
bendAdjust = 8192/s_pbVST; ///# of midi vals per 1/2 step...
rawBendVal = bendAmount/bendAdjust; // 1/2 steps un-rounded
bendVal = floor(bendVal + 0.5);
abs(rawBendVal - bendVal) < SENSITIVITY ? //looking for 'sweet spot'
(
bendInterval = bendVal - s_pbMPE; //center about 0
origPitch = v_note[channel];
origVel = v_vel[channel];
newPitch = bendInterval + origPitch;
(s_mpeVST == 0) ? pbNoteChOut = s_baseCh :
pbNoteChOut = v_chan[channel];
(newPitch - origPitch) != 0 ?
(
midisend(offs,NOTE_OFF + pbNoteChOut,origPitch,10);
v_store(channel,newPitch,pbNoteChOut,origVel);
midisend(offs,NOTE_ON + pbNoteChOut,newPitch,origVel);
)
)
)
)
: /////////////////////////////////////// AFTER TOUCH ////////////////
//if toggling, then we need to see consistent values above the threshold before toggling
//so we should maintain a count, incrementing with each block, see @block
(status == CH_AT) && (s_aTouchLatch > 0) ?
(
(msg2 > toggleThreshold) ?
(
counting = 1;
(toggleCount > toggleDuration) ?
(
//toggle the value!
touchToggleState = 0 ? touchToggleState = 127 : touchToggleState = 0;
msg3 = touchToggleState;
counting = 0;
//reset counting
)
//if we are counting, but not to duration, we should not send out an event!!
: stopMidiEvent = 1
) :
(
counting = 0;
toggleCount = 0;
)
) :
// if we are latching and not over the threshold, don't send out data.
stopMidiEvent = 1;
(s_mpeSrc == K_PNO) ?
(
(stopMidiEvent == 0) ?
midisend(offs,status + s_baseCh,msg2,msg3)
: stopMidiEvent = 0; //reset event withholding.
)
:
(
(stopMidiEvent == 0) ?
(
mpeCtlOut = v_chan(channel);
midisend(offs,status + mpeCtlOut,msg2,msg3);
) : stopMidiEvent = 0;
)
)
:
midisend(offs,msg1,msg2,msg3); //pass all other events
);