|
| 1 | +desc: MIDI hold notes range |
| 2 | +author: tomaszpio |
| 3 | +version: 1.0.0 |
| 4 | +about: |
| 5 | + This JSFX plugin implements a "note hold" function, active only within |
| 6 | + a specified MIDI note range. It is intended for live playing or MIDI |
| 7 | + processing where one key at a time is held, regardless of Note Off messages. |
| 8 | + |
| 9 | + HOW IT WORKS: |
| 10 | + - The plugin reacts only to Note On / Note Off messages. |
| 11 | + - It works on: |
| 12 | + • a selected MIDI channel (1–16), or |
| 13 | + • all channels if Channel = 0 (omni mode). |
| 14 | + - It is active only for notes within the [Low note .. High note] range. |
| 15 | + - For any Note On with velocity > 0 in that range: |
| 16 | + • The previously held note is turned off (Note Off is sent). |
| 17 | + • The new note is turned on (Note On is sent) and stored as the |
| 18 | + currently held note. |
| 19 | + - Note Off messages and Note On with velocity = 0 in that range are ignored, |
| 20 | + so they do NOT release the held note. |
| 21 | + - All other MIDI messages (outside the note range, or on other channels, |
| 22 | + or non-note messages) pass through unchanged. |
| 23 | + |
| 24 | + SLIDERS: |
| 25 | + - Channel (0=omni, 1–16 = specific MIDI channel) |
| 26 | + • 0 => process all channels |
| 27 | + • 1–16 => only process that MIDI channel |
| 28 | + - Low note – lower bound of the active note range (0–127) |
| 29 | + - High note – upper bound of the active note range (0–127) |
| 30 | + If Low note > High note, the plugin automatically swaps them. |
| 31 | + |
| 32 | + TYPICAL USE CASES: |
| 33 | + - Use a low octave on your keyboard as a "hold" controller area, |
| 34 | + while higher notes play normally. |
| 35 | + - Restrict hold behavior to a specific region (e.g., bass notes or keyswitches) |
| 36 | + without affecting the rest of the keyboard. |
| 37 | + |
| 38 | +slider1:0<0,16,1>Channel (0=omni) |
| 39 | +slider2:36<0,127,1>Low note |
| 40 | +slider3:84<0,127,1>High note |
| 41 | + |
| 42 | +in_pin:none |
| 43 | +out_pin:none |
| 44 | + |
| 45 | +@init |
| 46 | +held_note = -1; |
| 47 | +held_chan = -1; |
| 48 | + |
| 49 | +@slider |
| 50 | +// Channel mapping: |
| 51 | +// slider1 = 0 -> omni (chan = -1, so all channels match) |
| 52 | +// slider1 = 1–16 -> MIDI channels 0–15 |
| 53 | +s1 = slider1|0; |
| 54 | +chan = s1 ? (s1 - 1) : -1; |
| 55 | + |
| 56 | +low = slider2|0; |
| 57 | +high = slider3|0; |
| 58 | + |
| 59 | +// If user sets Low > High, swap them |
| 60 | +low > high ? ( |
| 61 | + tmp = low; |
| 62 | + low = high; |
| 63 | + high = tmp; |
| 64 | +); |
| 65 | + |
| 66 | +@block |
| 67 | +while ( |
| 68 | + midirecv(ts, msg, msg23) ? ( |
| 69 | + status = msg & $xf0; // 0x80 = Note Off, 0x90 = Note On |
| 70 | + ch = msg & $x0f; // MIDI channel 0–15 |
| 71 | + note = msg23 & $xff; // note number |
| 72 | + vel = (msg23/256) & $xff; // velocity |
| 73 | + |
| 74 | + is_note = (status == $x80) || (status == $x90); |
| 75 | + in_chan = (chan < 0) || (ch == chan); |
| 76 | + in_range = (note >= low) && (note <= high); |
| 77 | + |
| 78 | + is_note && in_chan && in_range ? ( |
| 79 | + // Hold logic only for Note On with vel > 0 |
| 80 | + status == $x90 && vel ? ( |
| 81 | + // Turn off previously held note (if any) |
| 82 | + held_note >= 0 ? ( |
| 83 | + midisend(ts, $x80 + held_chan, held_note); |
| 84 | + ); |
| 85 | + |
| 86 | + // Store new held note |
| 87 | + held_note = note; |
| 88 | + held_chan = ch; |
| 89 | + |
| 90 | + // Send Note On for new note |
| 91 | + midisend(ts, $x90 + ch, note | (vel << 8)); |
| 92 | + ); |
| 93 | + // Note Off and Note On with vel=0 in the active range are ignored |
| 94 | + ) : ( |
| 95 | + // Pass everything else unchanged |
| 96 | + midisend(ts, msg, msg23); |
| 97 | + ); |
| 98 | + |
| 99 | + 1; |
| 100 | + ); |
| 101 | +); |
0 commit comments