You ask, i code :
Logic SCRIPT for N1X, tu use NOTE-OFF velocity set like this:
![]()
![]()
/* Logic Pro Scripter — PolyAT slope -> NoteOff velocity (silent, tuned)
- Collects 2 PolyAT (20..32) samples + timestamps
- slope = |Δvalue| / Δtime (values per millisecond)
- Maps slope -> NoteOff velocity when NoteOff arrives
- If no relevant PolyAT was received: NoteOff velocity = Default OffVel
- Option: Pass-thru PolyAT (Yes/No)
*/
var PluginParameters = [
{ name: "OffVel Min", type: "lin", minValue: 1, maxValue: 127, numberOfSteps: 126, defaultValue: 1 }, // min = 1
{ name: "OffVel Max", type: "lin", minValue: 1, maxValue: 127, numberOfSteps: 126, defaultValue: 50 }, // max = 50
{ name: "Slope Min (x1000)", type: "lin", minValue: 1, maxValue: 200, numberOfSteps: 199, defaultValue: 10 }, // 0.010/ms
{ name: "Slope Max (x1000)", type: "lin", minValue: 5, maxValue: 400, numberOfSteps: 395, defaultValue: 80 }, // 0.080/ms
{ name: "Default OffVel", type: "lin", minValue: 1, maxValue: 127, numberOfSteps: 126, defaultValue: 100 }, // fallback
{ name: "Pass-thru PolyAT", type: "menu", valueStrings: ["No", "Yes"], defaultValue: 0 }
];
var paramValues = [];
for (var i=0;i<PluginParameters.length;i++){
paramValues[i] = PluginParameters[i].defaultValue;
}
function ParameterChanged(index, value){ paramValues[index] = value; }
function getParam(name){
for (var i=0;i<PluginParameters.length;i++){
if (PluginParameters[i].name === name) return paramValues[i];
}
return null;
}
// state per note: key = (channel<<7)+pitch
var noteState = {};
function keyOf(e){ return (e.channel << 7) + e.pitch; }
function nowMs(){ return Date.now(); }
function withinWindowAT(v){ return v >= 20 && v <= 32; }
function rememberATSample(e){
var k = keyOf(e);
var s = noteState[k];
if (!s){ s = {}; noteState[k] = s; }
var t = nowMs();
var v = e.value|0;
if (!withinWindowAT(v)) return false;
if (s.v2 !== undefined){ s.v1 = s.v2; s.t1 = s.t2; }
s.v2 = v; s.t2 = t;
return true;
}
function computeSlopePerMs(s){
if (s && s.v1 !== undefined && s.v2 !== undefined && s.t1 !== undefined && s.t2 !== undefined){
var dv = Math.abs(s.v2 - s.v1);
var dt = (s.t2 - s.t1);
if (dt > 0) return dv / dt; // values per ms
}
return null;
}
function mapSlopeToVelocity(slope){
var vmin = getParam("OffVel Min")|0;
var vmax = getParam("OffVel Max")|0;
var sMin = (getParam("Slope Min (x1000)")|0) / 1000.0;
var sMax = (getParam("Slope Max (x1000)")|0) / 1000.0;
if (slope == null) return getParam("Default OffVel")|0;
var denom = (sMax - sMin);
if (denom <= 0) denom = 1e-9;
var t = (slope - sMin) / denom;
if (t < 0) t = 0;
if (t > 1) t = 1;
var out = Math.round(vmin + t * (vmax - vmin));
if (out < 1) out = 1;
if (out > 127) out = 127;
return out;
}
function HandleMIDI(e){
if (e instanceof NoteOn){
noteState[keyOf(e)] = {};
e.send();
return;
}
if (e instanceof PolyPressure){
rememberATSample(e);
if (getParam("Pass-thru PolyAT") === 1){ e.send(); }
return;
}
if (e instanceof NoteOff){
var k = keyOf(e);
var s = noteState[k];
var slope = computeSlopePerMs(s);
var vel = mapSlopeToVelocity(slope);
e.velocity = vel;
e.send();
delete noteState[k];
return;
}
e.send();
}