I randomly mentioned this idea at a party once, when chatting about music stuff, and real vs digital pianos. I commented as to the kind of weird training exercises you can set up with a MIDI instrument. One was the idea of a simple 'touch trainer'. I played around in Max MSP for a bit, and then realised a short js script would also do the trick.
Here, the idea is that the incoming noteon velocity is divided by some number, and rounded to the nearest integer, so as to subdivide the velocity into zones (as happens in a sampler). Then you possibly map to a semitone in a scale (i.e. [0,1,2,3,...] -> [0,2,4,5,...] for a major scale), add that to the incoming pitch, and output that pitch, with a fixed velocity.
Basically, the harder you press, the higher the pitch goes above the normal pitch. The use in practice is that you can do a number of exercises where the pitches only sound right if you play with the desired velocity.
For example:
play a scale (and if the velocities are all consistently in the same zone, you'll hear a scale, and if not, it will sound messy);
play a 'paradiddle' between two velocity zones (e.g. if you divide the velocities 1..127 into 4 zones, playing a L-R-L-L-R-L-R-R pattern between, say, zones 1 and 3, or 3 and 4).
I've yet to see how well this works as a practice aid, having just written it, but I thought I'd share the script and the rather crazy idea for a practice aid.
As it is, it may still have issues with stuck notes and so on. But the other point of sharing this here is as an example of the idea of using things like plugins and scripting as practice aids in some way. That you can knock something functional out in about half an hour (while waiting for my dinner to cook), and with only 129 lines to type, is why I love to have a decent textual scripting language in my DAW. (The major[3]=5; stuff was generated by a 5-line python script.)
Code:
desc: TouchTrainer1
// One slider for number of notes to divide into, max 12.
// One slider for major, minor, and chromatic scales, 0,1,2
slider1:5<2,12,1>Subdivisions;
slider2:0<0,2,1>Major/Minor/Chromatic;
slider3:0<0,15,1>Channel;
slider4:80<10,127,1>Output velocity;
slider5:0<-12,12,1>Pitch offset;
@init
ext_noinit = 1.0;
// allocate arrays
noteouts = 100; // maps note in to current (pitch adjusted note)
notesheld = 300; // keeps track of how many notes held map to each pitch
// midi pitch 0 is ignored.
scales = 500;
major = scales+0;
minor = scales+100;
chrom = scales+200;
major[0] = 0;
major[1] = 2;
major[2] = 4;
major[3] = 5;
major[4] = 7;
major[5] = 9;
major[6] = 11;
major[7] = 12;
major[8] = 14;
major[9] = 16;
major[10] = 17;
major[11] = 19;
minor[0] = 0;
minor[1] = 2;
minor[2] = 3;
minor[3] = 5;
minor[4] = 7;
minor[5] = 8;
minor[6] = 10;
minor[7] = 12;
minor[8] = 14;
minor[9] = 15;
minor[10] = 17;
minor[11] = 19;
chrom[0] = 0;
chrom[1] = 1;
chrom[2] = 2;
chrom[3] = 3;
chrom[4] = 4;
chrom[5] = 5;
chrom[6] = 6;
chrom[7] = 7;
chrom[8] = 8;
chrom[9] = 9;
chrom[10] = 10;
chrom[11] = 11;
subdivs = slider1;
scaletype = slider2;
scale = scales+100*scaletype;
divisor = 128/subdivs; // we then divide incoming nonzero velocity by this value
inchannel = slider3;
outvel = slider4; // fixed output velocity -- user a slider later
pitchoffset = slider5;
// notes held during channel change will stick
@slider
subdivs = slider1;
scaletype = slider2;
scale = scales+100*scaletype;
divisor = 128/subdivs; // we then divide incoming nonzero velocity by this value
// later versions can do more clever mapping
inchannel = slider3;
outvel = slider4; // fixed output velocity -- user a slider later
pitchoffset = slider5;
// if channel changes, loop through and release all held notes
// later
@block
// will NOT work with multiple channel input
// could add a slider for which channel
while(
midirecv(ts,msg1,msg23) ?
(
m = msg1&240; // message type
ch = msg1&15;
ch == inchannel ? (
note = msg23&127;
note += pitchoffset;
vel = msg23/256;
(m == 128 || (m == 144 && vel == 0)) ? (
// note off
boink = 0;
noteoff1 = note;
noteout = noteouts[note];
notesheld[noteout] > 0 ? (
notesheld[noteout] -= 1;
);
notesheld[noteout] <= 0 ? (
midisend(ts,msg1,noteout);
notesheld[noteout] = 0;
);
) : (m == 144 && vel > 0) ? (
boink = 1;
// note on
veld = vel/divisor; // select subdivision
pitchoffset = scale[veld];
noteout = note+pitchoffset;
noteouts[note] = noteout;
notesheld[noteout] += 1;
midisend(ts,msg1,noteout + 256*outvel);
) : (
boink = 2;
midisend(ts,msg1,msg23); // else echo
);
) : (
boink = 3;
midisend(ts,msg1,msg23);
);
);
);