That feature was implemented at the same time as logging.
globalThis.OnTimerElapsed() should work.
That feature was implemented at the same time as logging.
globalThis.OnTimerElapsed() should work.
It works but it seems that globalThis.OnTimerElapsed() consume all timer elapsed events and that if I have OnTimerElapsed() elsewhere (to manage another timer on the same button) it is not triggered.
For instance, if I have this code :
/// <reference path="C:/Users/LPA/AppData/Roaming/Elgato/StreamDeck/Plugins/se.trevligaspel.midi.sdPlugin/ScriptJint/streamdeck-midi.d.ts" />
const{registerButton, ButtonHandler, handleTimerElapsed} = require("./PressHandler.js");
//const{exportedIncrement} = require("./SharedCounter.js");
const bName = "PressTest";
const bName_dt = bName + "_dt";
const DISP_MS = 1500;
var longActive = false;
ui.text("Start");
function OnInit() {ui.text("Init")}
// Register callbacks for button, returns the timer name
const bName_pt = registerButton(bName, singlePress, doublePress, longPress);
// Stream Deck events : Pass to PressHandler
function OnKeyPressed() {
ButtonHandler("press");
//exportedIncrement();
}
function OnKeyReleased() {
ButtonHandler("release");
if (longActive) {
longActive = false;
//timer.reset(bName_dt);
timer.restart(bName_dt, DISP_MS);
}
}
// Callbacks for single Press, Double Press or Long Press
function singlePress() { ui.text("Single Press"); timer.restart(bName_dt, DISP_MS);
console.warn("PressTest: Timer started, , name=", bName_dt);
}
function doublePress() { ui.text("Double Press"); timer.restart(bName_dt, DISP_MS);}
function longPress() {
ui.text("Long Press");
longActive = true; // mark long press active
}
function OnTimerElapsed(tname, isGlobal, time) {
console.warn("PressTest: Timer elapsed, name=", tname, ", isGlobal=", isGlobal);
switch(tname) {
// case bName_pt: {handleTimerElapsed(tname, isGlobal, time); break;}
case bName_dt: {
ui.text("Idle");
timer.reset(bName_dt);
break;
}
default: return;
}
}
and this exported module :
/// <reference path="C:/Users/LPA/AppData/Roaming/Elgato/StreamDeck/Plugins/se.trevligaspel.midi.sdPlugin/ScriptJint/streamdeck-midi.d.ts" />
const tSuffix = "_pt";
const LONG_PRESS_MS = 400;
// Internal state for the single button
let clickCount = 0;
let isPressed = false;
let longFired = false;
// Callback references
let singlePressFn = null;
let doublePressFn = null;
let longPressFn = null;
// Store the last registered button name
let buttonNameGlobal = "";
/**
* Register the single button
* Returns the _pt timer name
*/
function registerButton(buttonName, singlePress, doublePress, longPress) {
buttonNameGlobal = buttonName;
singlePressFn = singlePress;
doublePressFn = doublePress;
longPressFn = longPress;
// â
return the press timer name
return buttonName + tSuffix;
}
/**
* Handle press/release events
*/
function ButtonHandler(event) {
if (!buttonNameGlobal) return;
const tname = buttonNameGlobal + tSuffix;
if (event === "press") {
isPressed = true;
if (clickCount === 0) {
longFired = false;
timer.reset(tname); // Safeguard
timer.restart(tname, LONG_PRESS_MS);
}
}
else if (event === "release") {
isPressed = false;
if (!longFired) clickCount++;
}
}
/**
* Timer callback
*/
globalThis.OnTimerElapsed = (tname, isGlobal, time) => {
console.warn("PressHandler: Timer elapsed, name=", tname);
if (!buttonNameGlobal) return;
if (tname !== buttonNameGlobal + tSuffix) return;
handleTimerElapsed(tname, isGlobal, time);
};
function handleTimerElapsed(tname, isGlobal, time) {
// Remove these two lines when globalThis is available
//if (!buttonNameGlobal) return;
//if (tname !== buttonNameGlobal + tSuffix) return;
const count = clickCount;
clickCount = 0; // reset first
if (isPressed && longPressFn) {
longPressFn();
longFired = true;
}
else if (count === 1 && singlePressFn) {
singlePressFn();
}
else if (count >= 2 && doublePressFn) {
doublePressFn();
}
timer.reset(tname);
}
module.exports = {registerButton, ButtonHandler, handleTimerElapsed};
The timer elapsed event for bName_dt (=PressTest_dt) is never called in the main script, only in the exported module, even if i make it global, while i would expect to be fired and the button text return to âIdleâ.
It seems the problem to me.
Am I correct ?
Log Id for a single Press: 8837bc2b-d38a-483c-8f62-64b7227e921b
Yes. You canât have two functions with the same name in the same scope. When you define globalThis.OnTimerElapsed(), that function will replace the function in the main script.
Hello, itâs me again ![]()
I have a small annoyance when setting global variables, it seems their names are lowercased.
Example code in a background script :
// ------------------------
// Control Change mapping (same style as sysExList)
// ------------------------
const ccList = [
[15, 0, "g_trackNum"],
[15, 24, "g_sceneNum"]
];
// Fast lookup for CC
const ccMap = Object.fromEntries(
ccList.map(([ch, ctrl, name]) => [`${ch}_${ctrl}`, name])
);
// ------------------------
// CC handler
// ------------------------
function OnControlChangeReceived(channel, control, value) {
const key = `${channel}_${control}`;
const varName = ccMap[key];
if (!varName) {
//console.warn(`No variable mapped for CC ${channel},${control}`);
return;
}
gvar.set(varName, value);
console.warn(`BCC var set: ${varName} =`, value);
and in a scripted dial :
function OnGlobalVariableChanged(name, value) {
//console.warn ("Global var ", name, " changed, value= ", value);
switch(name) {
case "g_tname_sel": {refreshNames(); break;}
case "g_sname_sel": {refreshNames();; break;}
case "g_trackNum": {
console.warn (" g_trackNum changed, value= ", value);
ui.value(value, (l_disp == 2) ? true : false);
break;
}
case "g_tracknum": {
console.warn (" g_tracknum changed, value= ", value);
break;
}
//default: console.warn ("Default reached, name= ", name, " changed, value= ", value);
}
}
Log : 738f0760-3d50-406a-924e-9cab9744612c
Can it be changed so that capitalization is preserved ?
Thanks.
And an additional question.
We have ui.value to set the value, but to we have a method to read the value ?
Oh no, the dreaded superscripter! ![]()
The variable controller stores all variable names in lowercase.
Iâll take a look and see if I can find a solution that doesnât break compatibility with midiScript variables.
No. The plugin doesnât save the value anywhere; it just forwards it to the Stream Deck software. It would, of course, be possible to add caching of that particular value somewhere. Is your question about the ui.value property in particular, or ui in general?
It was in particular but I managed to have my own caching in this case (and others, given what I read in the documentation : Unlike midiScripts, JavaScript event handlers are always triggered and must decide for themselves whether to respond to an event.)
This version includes some breaking changes compared to the previous beta.
My plan is to release this version as soon as possible to (hopefully) bring some order to the port confusion caused by the MIDI Service on Windows. If you find any issues, please let me know.
There is something I donât understand when this code is executed :
function setLayout() {
console.warn("Setting layout: " + gvar.g_lp + "ts_fader_TR_SC.json, reset=", true);
layout.load(gvar.g_lp + "ts_fader_TR_SC.json", true);
layout.l_toptext_full_width.color = "#B8A46E"; // Light Yellow
layout.l_fadertext_left_side_upper_row.color = "Gray";
}
It seems that l_toptext_full_width.color and l_fadertext_left_side_upper_row.color are ignored despite being set.
Log : 145d95f2-4560-41fb-a9a0-3607f5269124
⊠and on the occasion, I am not sure that the old #default# keyword is working so I use layout.load(""); to clear the layout. Is it correct ?
.. the same goes for iconleft() with the #none# keyword, I just use iconleft(""). Correct ?
Yes, I see in your log file that the colors you set are ignored, but I canât reproduce this; the values are used when I copy your script. How is the text content set? I believe that might be whatâs different between your setup and mine.
Yes, #default# and #none# are not used; you are doing the right thing to use empty strings.
I use :
function trsc_GlobalVariableChanged(disp, name, value) {
//console.warn ("Global var ", name, " changed, value= ", value);
switch(name) {
// Names
case "g_tname_sel": {refreshNames(disp); break;}
case "g_sname_sel": {refreshNames(disp); break;}
and :
function refreshNames(disp) {
ui.title((disp==2) ? gvar.g_sname_sel : gvar.g_tname_sel);
ui.text((disp==2) ? gvar.g_tname_sel : gvar.g_sname_sel);
}
to set the content.
I still canât reproduce the issue. I can get it to flash white for a very brief moment when the layout loads, but I canât get it to send the code for white even though the color variable is set to something else, as shown in your log file.
If I receive the full script instead of random functions, I might notice something in the sequence of events. Also, what does the editor setting for the dial rotate action look like (title/value positions, value display and so on)?
Hello,
IIRC for the problem to reproduce you need to untick the custom layout option in the éditorial, and then execute the (provided) code that set the layout and the colors.
Iâm away for the week-end so if you still canât reproduce let me know and I will provide addition information tuesday morning.
Happy easter.
OK, I found it.
For some reason, I tested with the OnDialRotated() event instead of the OnDialReleased() event you have. It turned out that OnDialRotated() has an error that concealed a larger, structural issue in how the layout properties are managed(!)
So, now I understand what the problem is, and I just need to figure out what to do about it⊠![]()
Two questions:
This update includes fixes for the JavaScript layout handling.
Global settings spacing should be better on Mac.
The MIDI system selection page has been modified to better explain the available options, and the MIDI connections are made more robust when switching between options.
It is made ârelease-readyâ with a version info popup at first start.
Confirmed. Itâs fixed.
i have mixed feelings. Sometimes it helps, but sometimes i have weird results.
Take this example script :
[(config){TriggerOnLocalMidiEvents:No}{TriggerOnUnchangedVariables:No}]
[(init){@l_pset:1}{@l_disp:0}]
1st press
[(press)(@l_event:0){@l_event:1}{@lt_press:restart}]
[(release)(@l_event:1){@l_event:#@l_event+1#}]
[(press)(@l_event:1-8){@l_event:#@l_event+1#}]
[(release)(@l_event:1-8){@l_event:#@l_event+1#}]
Never happens
[(@lt_press:400)(@l_event:0){@lt_press:reset}{@l_event:0}]
Long Press : Do nothing
[(@l_disp:0)(@lt_press:400)(@l_event:1){@lt_press:reset}{@l_event:0}]
Single Clic
[(@l_disp:0)(@lt_press:400)(@l_event:2){@lt_press:reset}{@l_event:0}] Parameter view : Do nothing
[(@l_disp:1)(@lt_press:400)(@l_event:2){@lt_press:reset}{@l_event:0}{@l_disp:2}] Track View --> Scene Voiew
[(@l_disp:2)(@lt_press:400)(@l_event:2){@lt_press:reset}{@l_event:0}{@l_disp:1}] Scene View --> Track View
Double clic : Do nothing
[(@l_disp:0)(@lt_press:400)(@l_event:3-9){@l_event:0}{@lt_press:reset}]
Switch views
[(tap){@l_disp:#SWITCH(@l_disp,0,1,0)#}{@l_blockupd:0}]
Back to Parameter View
[(@g_pqb1:2-4){@l_disp:0}{@l_blockupd:0}]Switch in amy case
[(@g_pqb1:1){@l_disp:#IF(@l_blockupd=1,@l_disp,0)#}{@l_blockupd:0}] Switch only if device unchanged
New Device Received
[(@g_chnum0:-1){@l_blockupd:1}] Will always be set event if no device
Set State and design
[(init+)(@l_disp:0){display:V-pot}{layout:none}{minmax:0,127}] Design is set in editor
[(init+)(@l_disp:1){display:Fader}{iconleft:none}{layout:#CONCAT(@g_lp, "ts_fader_TR_SC.json")#}]
[(init+)(@l_disp:1-2){design:#CONCAT(@g_dp, SWITCH(@l_disp, 2, "SC_Selector.xml", "TR_Selector.xml"))#}
{minmax:1,#IF(@l_disp=2, 32, 24)#}
{layout:#CONCAT(@g_lp, "ts_fader_TR_SC.json")#}]
COLORS
[(init+)(@l_disp:0){@l_toptext_full_width.color:##B8A46E}] Light Yellow
[(init+)(@l_disp:0){@l_toptext_left_icon_present.color:##B8A46E}] Light Yellow
[(init+)(@l_disp:1){@l_toptext_full_width.color:##B8A46E}] Light Yellow
[(init+)(@l_disp:1){@l_fadertext_left_side_upper_row.color:Gray}]
[(init+)(@l_disp:2){@l_toptext_full_width.color:##B8A46E}] Light Yellow
[(init+)(@l_disp:2){@l_fadertext_left_side_upper_row.color:Gray}]
PARAMETER VIEW
Rebind parameter : cc 14, 1 / 2, 0 / 127
[(@g_pqb1:1){cc:14,#(@l_pset-1)*2+1#,0}]
[(@g_pqb1:2){cc:14,#(@l_pset-1)*2+1#,127}]
[(@g_pqb1:3){cc:14,#@l_pset*2#,0}]
[(@g_pqb1:4){cc:14,#@l_pset*2#,127}]
Set icon for quarter bank (1-4)
[(init+)(@l_disp:0)(@g_pqb1:*){@l_ipudt:1}{@l_ipudt:0}]
[(@l_disp:0)(@g_pupdt:1){@l_ipudt:1}{@l_ipudt:0}]
[(@l_ipudt:1){iconleft:#IF(@g_pqb1=@g_pqb0,"none",CONCAT(@g_pp,"PBank-",@g_nhb,"-",@g_pqb1,".png"))#}]
Pick correct title for parameter
[(init+)(@l_disp:0)(@g_pnames:*)(@g_pqb1:*)
{title:#MID(@g_pnames,1+(@g_pqb1-1)*64+(@l_pset-1)*16,16)#} /*{title:#@l_test#}*/]
Receive midi
[(init+)(@l_disp:0)(cc:1,64,0-127){value:#@e_ccvalue#}{text:#@e_ccvalue#}]
Send Midi
[(rotate)(@l_disp:0){cc:1,#64+@l_pset-1#,#@e_value#}]
TRACK/SCENE SELECTOR VIEW
Rotate
[(@l_disp:1)(rotate:1-24){@l_val:#@e_value#}{@lt_val:restart}]
[(@l_disp:2)(rotate:1-32){@l_val:#@e_value#}{@lt_val:restart}]
Send Midi after a delay
[(@lt_val:10)(@l_disp:1){cc:16,0,#@l_val#}{@lt_val:reset}]
[(@lt_val:100)(@l_disp:2){cc:16,24,#@l_val#}{@lt_val:reset}]
From DAW : Track, Scene # and names (Init+ necessary)
[(init+)(@l_disp:1)(cc:16,0,1-24){value:#@e_ccvalue#}]
[(init+)(@l_disp:1)(cc:16,24,1-32){value:p,#@e_ccvalue#}]
[(init+)(@l_disp:2)(cc:16,0,1-24){value:p,#@e_ccvalue#}]
[(init+)(@l_disp:2)(cc:16,24,1-32){value:#@e_ccvalue#}]
[(init+)(@l_disp:1)(@g_tname_sel:*)(@g_sname_sel:*){title:#@g_tname_sel#}{text:#@g_sname_sel#}{@l_title:A}]
[(init+)(@l_disp:2)(@g_tname_sel:*)(@g_sname_sel:*){title:#@g_sname_sel#}{text:#@g_tname_sel#}{@l_title:B}]
This is what i get :
// Auto-generated migration from midiScript -> JavaScript (Migrate2, Version 1.1)
// Generated: 2026-04-07 15:36:53 UTC
function OnControlChangeReceived(channel, control, value) {
if ((channel >= 1 && channel <= 1) && (control >= 64 && control <= 64) && (value >= 0 && value <= 127)) {
ui.value("#@e_ccvalue#");
ui.text("#@e_ccvalue#");
}
if ((channel >= 16 && channel <= 16) && (control >= 0 && control <= 0) && (value >= 1 && value <= 24)) {
ui.value("#@e_ccvalue#");
}
if ((channel >= 16 && channel <= 16) && (control >= 24 && control <= 24) && (value >= 1 && value <= 32)) {
ui.value("#@e_ccvalue#");
}
if ((channel >= 16 && channel <= 16) && (control >= 0 && control <= 0) && (value >= 1 && value <= 24)) {
ui.value("#@e_ccvalue#");
}
if ((channel >= 16 && channel <= 16) && (control >= 24 && control <= 24) && (value >= 1 && value <= 32)) {
ui.value("#@e_ccvalue#");
}
}
function OnDialRotated(ticks, value) {
midi.sendCC(1, "#64+@l_pset-1#", "#@e_value#");
l_val = "#@e_value#";
console.log("Timer restart missing interval definition: lt_val");
l_val = "#@e_value#";
console.log("Timer restart missing interval definition: lt_val");
}
function OnGlobalVariableChanged(name, value) {
if (name === "l_event") {
l_event = "";
console.log("Timer restart missing interval definition: lt_press");
}
if (name === "l_event") {
l_event = "#@l_event+1#";
}
if (name === "l_event") {
l_event = "#@l_event+1#";
}
if (name === "l_event") {
l_event = "#@l_event+1#";
}
if (name === "l_event") {
timer.reset("lt_press");
l_event = "";
}
if (name === "l_disp") {
timer.reset("lt_press");
l_event = "";
}
if (name === "l_event") {
timer.reset("lt_press");
l_event = "";
}
if (name === "l_disp") {
timer.reset("lt_press");
l_event = "";
}
if (name === "l_event") {
timer.reset("lt_press");
l_event = "";
}
if (name === "l_disp") {
timer.reset("lt_press");
l_event = "";
l_disp = "";
}
if (name === "l_event") {
timer.reset("lt_press");
l_event = "";
l_disp = "";
}
if (name === "l_disp") {
timer.reset("lt_press");
l_event = "";
l_disp = "";
}
if (name === "l_event") {
timer.reset("lt_press");
l_event = "";
l_disp = "";
}
if (name === "l_disp") {
l_event = "";
timer.reset("lt_press");
}
if (name === "l_event") {
l_event = "";
timer.reset("lt_press");
}
if (name === "g_pqb1") {
l_disp = "";
l_blockupd = "";
}
if (name === "g_pqb1") {
l_disp = "#IF(@l_blockupd=1,@l_disp,0)#";
l_blockupd = "";
}
if (name === "g_chnum0") {
l_blockupd = "";
}
if (name === "l_disp") {
ui.display("", false);
layout.load("none", false);
ui.minmax(0, 127);
}
if (name === "l_disp") {
ui.display("", false);
ui.iconleft("none");
layout.load("#CONCAT(@g_lp, \"ts_fader_TR_SC.json\")#", false);
}
if (name === "l_disp") {
ui.design("#CONCAT(@g_dp, SWITCH(@l_disp, 2, \"SC_Selector.xml\", \"TR_Selector.xml\"))#");
ui.minmax(1, "#IF(@l_disp=2, 32, 24)#");
layout.load("#CONCAT(@g_lp, \"ts_fader_TR_SC.json\")#", false);
}
if (name === "l_disp") {
l_toptext_full_width.color = "";
}
if (name === "l_disp") {
l_toptext_left_icon_present.color = "";
}
if (name === "l_disp") {
l_toptext_full_width.color = "";
}
if (name === "l_disp") {
l_fadertext_left_side_upper_row.color = "";
}
if (name === "l_disp") {
l_toptext_full_width.color = "";
}
if (name === "l_disp") {
l_fadertext_left_side_upper_row.color = "";
}
if (name === "g_pqb1") {
midi.sendCC(14, "#(@l_pset-1)*2+1#", 0);
}
if (name === "g_pqb1") {
midi.sendCC(14, "#(@l_pset-1)*2+1#", 127);
}
if (name === "g_pqb1") {
midi.sendCC(14, "#@l_pset*2#", 0);
}
if (name === "g_pqb1") {
midi.sendCC(14, "#@l_pset*2#", 127);
}
if (name === "l_disp") {
l_ipudt = "";
l_ipudt = "";
}
if (name === "g_pqb1") {
l_ipudt = "";
l_ipudt = "";
}
if (name === "l_disp") {
l_ipudt = "";
l_ipudt = "";
}
if (name === "g_pupdt") {
l_ipudt = "";
l_ipudt = "";
}
if (name === "l_ipudt") {
ui.iconleft("#IF(@g_pqb1=@g_pqb0,\"none\",CONCAT(@g_pp,\"PBank-\",@g_nhb,\"-\",@g_pqb1,\".png\"))#");
}
if (name === "l_disp") {
ui.title("#MID(@g_pnames,1+(@g_pqb1-1)*64+(@l_pset-1)*16,16)#");
}
if (name === "g_pnames") {
ui.title("#MID(@g_pnames,1+(@g_pqb1-1)*64+(@l_pset-1)*16,16)#");
}
if (name === "g_pqb1") {
ui.title("#MID(@g_pnames,1+(@g_pqb1-1)*64+(@l_pset-1)*16,16)#");
}
if (name === "l_disp") {
ui.value("#@e_ccvalue#");
ui.text("#@e_ccvalue#");
}
if (name === "l_disp") {
midi.sendCC(1, "#64+@l_pset-1#", "#@e_value#");
}
if (name === "l_disp") {
l_val = "#@e_value#";
console.log("Timer restart missing interval definition: lt_val");
}
if (name === "l_disp") {
l_val = "#@e_value#";
console.log("Timer restart missing interval definition: lt_val");
}
if (name === "l_disp") {
midi.sendCC(16, 0, "#@l_val#");
timer.reset("lt_val");
}
if (name === "l_disp") {
midi.sendCC(16, 24, "#@l_val#");
timer.reset("lt_val");
}
if (name === "l_disp") {
ui.value("#@e_ccvalue#");
}
if (name === "l_disp") {
ui.value("#@e_ccvalue#");
}
if (name === "l_disp") {
ui.value("#@e_ccvalue#");
}
if (name === "l_disp") {
ui.value("#@e_ccvalue#");
}
if (name === "l_disp") {
ui.title("#@g_tname_sel#");
ui.text("#@g_sname_sel#");
l_title = "";
}
if (name === "g_tname_sel") {
ui.title("#@g_tname_sel#");
ui.text("#@g_sname_sel#");
l_title = "";
}
if (name === "g_sname_sel") {
ui.title("#@g_tname_sel#");
ui.text("#@g_sname_sel#");
l_title = "";
}
if (name === "l_disp") {
ui.title("#@g_sname_sel#");
ui.text("#@g_tname_sel#");
l_title = "";
}
if (name === "g_tname_sel") {
ui.title("#@g_sname_sel#");
ui.text("#@g_tname_sel#");
l_title = "";
}
if (name === "g_sname_sel") {
ui.title("#@g_sname_sel#");
ui.text("#@g_tname_sel#");
l_title = "";
}
}
function OnInit() {
l_pset = "";
l_disp = "";
ui.display("", false);
layout.load("none", false);
ui.minmax(0, 127);
ui.display("", false);
ui.iconleft("none");
layout.load("#CONCAT(@g_lp, \"ts_fader_TR_SC.json\")#", false);
ui.design("#CONCAT(@g_dp, SWITCH(@l_disp, 2, \"SC_Selector.xml\", \"TR_Selector.xml\"))#");
ui.minmax(1, "#IF(@l_disp=2, 32, 24)#");
layout.load("#CONCAT(@g_lp, \"ts_fader_TR_SC.json\")#", false);
l_toptext_full_width.color = "";
l_toptext_left_icon_present.color = "";
l_toptext_full_width.color = "";
l_fadertext_left_side_upper_row.color = "";
l_toptext_full_width.color = "";
l_fadertext_left_side_upper_row.color = "";
l_ipudt = "";
l_ipudt = "";
ui.title("#MID(@g_pnames,1+(@g_pqb1-1)*64+(@l_pset-1)*16,16)#");
ui.value("#@e_ccvalue#");
ui.text("#@e_ccvalue#");
ui.value("#@e_ccvalue#");
ui.value("#@e_ccvalue#");
ui.value("#@e_ccvalue#");
ui.value("#@e_ccvalue#");
ui.title("#@g_tname_sel#");
ui.text("#@g_sname_sel#");
l_title = "";
ui.title("#@g_sname_sel#");
ui.text("#@g_tname_sel#");
l_title = "";
}
function OnKeyPressed(state) {
l_event = "";
console.log("Timer restart missing interval definition: lt_press");
l_event = "#@l_event+1#";
}
function OnKeyReleased(state) {
l_event = "#@l_event+1#";
l_event = "#@l_event+1#";
}
function OnScreenTouched(x, y) {
l_disp = "#SWITCH(@l_disp,0,1,0)#";
l_blockupd = "";
}
function OnTimerElapsed(name, isGlobal, time) {
timer.reset("lt_press");
l_event = "";
timer.reset("lt_press");
l_event = "";
timer.reset("lt_press");
l_event = "";
timer.reset("lt_press");
l_event = "";
l_disp = "";
timer.reset("lt_press");
l_event = "";
l_disp = "";
l_event = "";
timer.reset("lt_press");
midi.sendCC(16, 0, "#@l_val#");
timer.reset("lt_val");
midi.sendCC(16, 24, "#@l_val#");
timer.reset("lt_val");
}
there is a bunch of unnecessary repetitions that i donât understand.
and this is what ChatGPT produces after feeding him with a few pages from the documentation :
// =====================
// STATE
// =====================
let l_pset = 1;
let l_disp = 0;
let l_event = 0;
let l_blockupd = 0;
let l_val = 0;
let pressTimer = null;
let valTimer = null;
// =====================
// INIT
// =====================
function onInit() {
l_pset = 1;
l_disp = 0;
updateDesign();
updateColors();
}
// =====================
// PRESS / RELEASE
// =====================
function onPress() {
if (l_event === 0) {
l_event = 1;
startPressTimer();
} else if (l_event >= 1 && l_event <= 8) {
l_event++;
}
}
function onRelease() {
if (l_event >= 1 && l_event <= 8) {
l_event++;
}
}
// =====================
// CLICK HANDLING
// =====================
function startPressTimer() {
clearTimeout(pressTimer);
pressTimer = setTimeout(() => {
handleClickPattern();
}, 400);
}
function handleClickPattern() {
if (l_event === 1) {
// long press â do nothing
}
else if (l_event === 2) {
handleSingleClick();
}
else if (l_event >= 3) {
// double click â do nothing
}
l_event = 0;
}
function handleSingleClick() {
if (l_disp === 0) return;
if (l_disp === 1) {
l_disp = 2;
} else if (l_disp === 2) {
l_disp = 1;
}
updateDesign();
}
// =====================
// TAP (quick toggle)
// =====================
function onTap() {
l_disp = (l_disp === 0) ? 1 : 0;
l_blockupd = 0;
updateDesign();
}
// =====================
// ROTARY
// =====================
function onRotate(value) {
if (l_disp === 0) {
sendCC(1, 64 + l_pset - 1, value);
} else {
l_val = value;
startValTimer();
}
}
function startValTimer() {
clearTimeout(valTimer);
let delay = (l_disp === 1) ? 10 : 100;
valTimer = setTimeout(() => {
if (l_disp === 1) {
sendCC(16, 0, l_val);
} else if (l_disp === 2) {
sendCC(16, 24, l_val);
}
}, delay);
}
// =====================
// MIDI INPUT EVENTS
// =====================
function onCC(channel, cc, value) {
// Parameter view feedback
if (l_disp === 0 && channel === 1 && cc === 64) {
setValue(value);
setText(String(value));
}
// Track / Scene feedback
if (channel === 16) {
if (l_disp === 1) {
if (cc === 0) setValue(value);
if (cc === 24) setValue(value, true);
}
if (l_disp === 2) {
if (cc === 0) setValue(value, true);
if (cc === 24) setValue(value);
}
}
}
// =====================
// PARAMETER BANK (g_pqb1)
// =====================
function onParameterBank(val) {
// Rebind parameter
let base = (l_pset - 1) * 2;
if (val === 1) sendCC(14, base + 1, 0);
if (val === 2) sendCC(14, base + 1, 127);
if (val === 3) sendCC(14, base + 2, 0);
if (val === 4) sendCC(14, base + 2, 127);
// View switching logic
if (val >= 2 && val <= 4) {
l_disp = 0;
} else if (val === 1) {
if (l_blockupd !== 1) {
l_disp = 0;
}
}
l_blockupd = 0;
updateDesign();
}
// =====================
// DEVICE CHANGE
// =====================
function onNewDevice() {
l_blockupd = 1;
}
// =====================
// UI DESIGN
// =====================
function updateDesign() {
if (l_disp === 0) {
setDisplay("V-pot");
setLayout("none");
setMinMax(0, 127);
}
if (l_disp === 1 || l_disp === 2) {
setDisplay("Fader");
let design = (l_disp === 2)
? "SC_Selector.xml"
: "TR_Selector.xml";
setDesign(design);
setLayout("ts_fader_TR_SC.json");
setMinMax(1, (l_disp === 2) ? 32 : 24);
}
}
// =====================
// COLORS
// =====================
function updateColors() {
setColor("topText", "#B8A46E");
if (l_disp !== 0) {
setColor("faderText", "gray");
}
}
// =====================
// MIDI SEND
// =====================
function sendCC(channel, cc, value) {
midi.sendControlChange(channel, cc, value);
}
much more convincing, (and probably improvable with a few additional directives) but i donât know if it make sense of if itâs even possible for you to plug it to the editor.