Skip to content

Instantly share code, notes, and snippets.

@kerrishotts
Last active December 18, 2022 20:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kerrishotts/9a31dd4d884ba60d0e4410ccd78f1d62 to your computer and use it in GitHub Desktop.
Save kerrishotts/9a31dd4d884ba60d0e4410ccd78f1d62 to your computer and use it in GitHub Desktop.
Loud Note Correction Script for Logic Pro X / Mainstage Midi FX Scripter plugin

Loud Note Correction Script for Logic Pro X / Mainstage Midi FX Scripter plugin.

Logic Pro Script to detect and apply velocity corrections to load notes. This is particularly useful for the Yamaha NU1X hybrid piano, which can sometimes register a key press with high velocity by accident, usually during rapid trilling or repeating of notes (in this case a regular acoustic would simply play a silent or much quieter note).

Original Kontact code by herqX at https://github.com/herqX/YamahaNU1LoudNoteFix. I took the liberty to add a few more parameters.

LICENSE: GNU General Public License V3 (herqX's is also GPL V3)


Parameters

  • Correction Mode: Play, with gain applied (Default) | Silent
    • "Play, with gain applied": The offending note is reduced in volume by a given correction percentage (see Velocity Correction Gain)
    • "Silent": The offending note is silenced (velocity set to zero)
  • Maximum time interval (ms): Default 120ms
    • This is the look-behind interval when comparing velocities for the same note over time. If a new note velocity exceeds a given limit, the correction is applied, but the note must be seen within this timeframe.
  • Minimum velocity interval: Default 15
    • If a repeated note's new velocity is greater than this amount, the velocity will be corrected or silenced, depending on the correction mode.
  • Maximum allowed velocity: Default 127
    • Any MIDI velocities greater than this amount will be capped to this velocity.
  • Velocity Correction Gain (%): Default 80%
    • This value controls how much velocity correction is applied to the repeated note's velocity. A value of 0% indicates that no correction is applied, whereas 100% would use the previous velocity (with no change).
  • Fix NU1(X) Loud Notes: Default Checked
    • If checked, this script will apply velocity correction to MIDI data based upon the correction mode.
  • Apply Velocity Limit: Defaut Unchecked
    • If checked, this script will limit MIDI velocities to the maxmimum allowed velocity specified above.
  • Log
    • When checked, modifications to played velocities are logged to the Script Editor console.
/* Logic Pro Script to detect and apply velocity corrections to load notes. This is particularly useful for the Yamaha NU1X
* hybrid piano, which can sometimes register a key press with high velocity by accident, usually during rapid trilling or repeating of notes (in this case a regular acoustic
* would simply play a silent or much quieter note).
*
* This script is based on the Kontakt script by herqX located at https://github.com/herqX/YamahaNU1LoudNoteFix. I took the
* liberty to add a few more parameters (see README)
*
* LICENSE: GNU General Public License V3
*
*/
// this script works by comparing velocities of the same note over time; therefore we need timing data
var NeedsTimingInfo = true; // yes, var; Logic Pro won't read this if it's "let" or "const"
// The original script played a quieter note, but here we have the option to make the note silent if you'd rather
const MODES = {
PLAY: "Play, with gain applied",
MUTE: "Silent note"
};
const MODE_MAP = {
PLAY: 0,
MUTE: 1
};
const PARAMS = {
MODE: "Correction Mode",
MAX_DT: "Maximum time interval (ms)",
MIN_DV: "Minimum velocity interval",
MAX_V: "Maximum allowed velocity",
GAIN: "Velocity correction gain (%)",
ENABLED: "Fix NU1(X) Loud Notes",
LIMIT: "Apply velocity limit",
LOG: "Log"
}
// yes, var; Logic Pro won't read this if it's "let"
var PluginParameters = [
{
name: PARAMS.MODE, type: "menu",
valueStrings: Object.values(MODES), defaultValue: 0
},
{
name: PARAMS.MAX_DT, type: "lin",
defaultValue: 120, minValue: 0, maxValue: 2000, numberOfSteps: 500
},
{
name: PARAMS.MIN_DV, type: "lin",
defaultValue: 15, minValue: 0, maxValue: 127, numberOfSteps: 127
},
{
name: PARAMS.MAX_V, type: "lin",
defaultValue: 127, minValue: 0, maxValue: 127, numberOfSteps: 127
},
{
name: PARAMS.GAIN, type: "lin",
defaultValue: 80, minValue: 0, maxValue: 100, numberOfSteps: 100
},
{
name: PARAMS.ENABLED, type: "checkbox",
defaultValue: 1
},
{
name: PARAMS.LIMIT, type: "checkbox",
defaultValue: 0
},
{
name: PARAMS.LOG, type: "checkbox",
defaultValue: 1
}
]
let options = {};
let noteVelocities = Array.from({length: 128}, _ => 0);
let noteIntervals = Array.from({length: 128}, _ => 0);
// Called whenever a UI parameter is changed. We don't use param or value here; instead I just
// loop over the parameters (PARAMS) and create a new object from them by calling GetParameter.
function ParameterChanged(param, value) {
options = Object.entries(PARAMS).reduce( (acc, cur, idx) => {
let [k, v] = cur;
acc[k] = GetParameter(v);
return acc;
}, {});
noteVelocities = Array.from({length: 128}, _ => 0);
noteIntervals = Array.from({length: 128}, _ => 0);
}
// Called for every MIDI event
function HandleMIDI(event)
{
// beatPos is in seconds, but our parameter is in milliseconds, so multiply it up
const timestamp = Math.floor(event.beatPos * 1000);
// record the time each note is released (we'll compare this value the next time we see the note played)
if (event instanceof NoteOff) {
noteIntervals[event.pitch] = timestamp;
}
if (event instanceof NoteOn) {
let newVelocity = event.velocity;
const originalVelocity = event.velocity;
const previousVelocity = noteVelocities[event.pitch];
const deltaT = timestamp - noteIntervals[event.pitch];
const deltaV = event.velocity - previousVelocity;
// store the original MIDI velocity data
// TODO: I'm ambivalent on whether this is good (or if we should store the _corrected_ velocity)
noteVelocities[event.pitch] = originalVelocity;
// If we're applying a limit, cap the velocity to the limit
if (options.LIMIT) {
newVelocity = Math.min(options.MAX_V, originalVelocity);
event.velocity = newVelocity;
}
// if velocity correction is enabled, compare with the previous velocity. If it's in the
// specified time frame (MAX_DT) _AND_ the change in velocity is greater than our cutoff (MIN_DV)
// we'll apply some correction. (We don't use absolute values here, becase we're not trying to
// smooth quieter notes)
if (options.ENABLED) {
if (deltaT < options.MAX_DT && deltaV > options.MIN_DV && previousVelocity > 0) {
switch (options.MODE) {
case MODE_MAP.PLAY:
// apply the specified gain to the correction -- this will cause it to be quieter than the
// current MIDI velocity, but it will still be louder than the old velocity -- since the
// player may actually be intending to play louder, unless the correction gain is 100%
newVelocity = Math.min(options.MAX_V,
Math.floor(previousVelocity + (((100 - options.GAIN) / 100) * deltaV))
);
break;
case MODE_MAP.MUTE:
default:
// silence the note in this mode.
newVelocity = 0;
}
event.velocity = newVelocity;
}
}
// log out velocity corrections to the console
if (options.LOG && originalVelocity != newVelocity) {
Trace(`${timestamp} [${MIDI.noteName(event.pitch).padEnd(3)} on] ov:${originalVelocity} nv:${newVelocity} ${originalVelocity != newVelocity ? "*" : " "} pv:${previousVelocity}`);
}
}
// send the new MIDI information
event.send();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment