Beta version 4.3

Sorry, I have made a mistake on the temporary documentation page. The correct event names the plugin uses are:

OnDeviceConnected(name)
OnDeviceDisconnected(name)

I see that the disconnect event passes the device id, not the device name. I should fix that, but it will be a breaking change since the same information is used from midiScript.

I also have an issue with this

function rotateEnd(ticks) {
    layout.l_bar_main_pan.bar_bg_c = COLOR_ACTIVE;
    timer.restart(CL_TIMER1, 2000);

    if (ticks > 0) {
        // Rotate right — extend end
        console.warn ("CL Selected: Rotate, ticks=" + ticks);
        midi.sendSysex([0x0A, 0x5D, ticks, 0x7F, 0xF7]);
        midi.sendSysex([240, 93, ticks , 127, 247]);
    } else {
        // Rotate left — shrink end
        if (l_block == 0) {
            midi.sendSysex([0x0A, 0x5D, ((-ticks) & 0x7F), 0x00]);
        } else {
            // Blocked — show visual clamp at start pos
            const clampVal = (gvar.g_clstart > 1048) ? 1 : gvar.g_clstart + 1;
            ui.value(clampVal);
        }
    }
}

The warning appears in the console but is seems no Sysex is sent.
Log: 7682ec16-556e-41a1-9c57-6c934319b35f

It maybe a good idea to work with Id and name … I don’t know.
What if we have to streamdecks of the same type connected. What will happen ?

Every device has a unique ID used only internally. What you (should) receive in the events is the device name, i.e., the name you have assigned to the device in the Stream Deck settings.

If you assign the same name to two devices, you cannot distinguish between them in the event handler. But the ID wouldn’t help either, since it isn’t displayed anywhere, so you don’t know which device it represents.

Hello,
I wanted to recall this one, with more accurate data, after a few checks and corrections :

function rotateEnd(ticks) {
    layout.l_bar_main_pan.bar_bg_c = COLOR_ACTIVE;
    timer.restart(CL_TIMER1, 2000);
    console.warn ("CL Selected: Rotate, ticks=" + ticks);
    if (ticks > 0) {
        // Rotate right — extend end
        if (ticks < 0 || ticks > 127) {console.warn("CL Selected: ticks must be 0–127 for SysEx"); return;}
        const msg = [0xF0, 0x0A, 0x5D, ticks, 0x7F, 0xF7];
        console.warn ("CL Selected: Sending Sysex=" + msg);
        midi.sendSysEx(msg);
        
    } else {
        // Rotate left — shrink end
        if (l_block == 0) {
            midi.sendSysex([0xF0, 0x0A, 0x5D, (-ticks), 0x7F, 0xF7]);
            // Blocked — show visual clamp at start pos
            const clampVal = (gvar.g_clstart > 1048) ? 1 : gvar.g_clstart + 1;
            ui.value(clampVal);
        }
    }
}

I have tested with both the ā€œmodernā€ and the new Midi services here is the result :

2026-04-16 06:15:16.4636      0,02ms  WARN   165  ScriptEngine_JS  console.warn                  [36c20fb1bee3f5c952829316b53c7fbd CL Selected: Sending Sysex=240,10,93,1,127,247]
2026-04-16 06:15:16.4636      0,04ms  WARN   165  MidiController   Send_SysEx                    [36c20fb1bee3f5c952829316b53c7fbd message could not be sent: Data: f0 0a 5d 01 7f f7 ]

Log ID: b4d77560-5bb4-4267-bcc6-5b9fc47ad884

Thanks for your help.

OK, found it. Thanks.

Hello, it seems this bug (editor unresponsiveness) is still occurring after copy/paste of scripted dials.
Log ID: a57238ad-085f-4bcc-b21e-d3319669e920

What does the OnInit() function look like in that script?

I have this code :

/// <reference path="C:/Users/LPA/AppData/Roaming/Elgato/StreamDeck/Plugins/se.trevligaspel.midi.sdPlugin/ScriptJint/streamdeck-midi.d.ts" />
const mParam = require("./DP_Dial.js");
const mChain = require("./CH_Selected_Dial.js");
const mVar   = require("./VAR_Selected_Dial.js");
const modules = [mParam, mChain, mVar]; // 0=Param, 1=Chain, 2=Variation
const timerMap = {};

var l_pset = 3; // Parameter index within half bank
const GPQB_PREFIX = "g_pqb"; // Global variable name PREFIX for current half bank
const g_pqbvar = GPQB_PREFIX + l_pset; // Global variable name for current half bank
const CTRL_NAME_PREFIX = "DP_SKIP"; // Timer name prefix for skipReset timer
// Persist track view when we originated the change
var skipReset = false; 
const tnSkipReset = CTRL_NAME_PREFIX + l_pset // Timer name prefix for skipReset timer
const SKIPRESET_MS = 1000;

// Pass l_pset for init (for mParam only) and build timers map dynamically
modules.forEach((mod, i) => {
    mod.InitOnce((i == 0) ? l_pset : undefined).forEach(name => // only DP_Dial has parameter
        {timerMap[name] = i;});}); 

var l_disp = 0; // 0 = Parameter View, 1=Track View, 2=Scene View


// Init
function OnInit() {l_disp = 0;skipReset = false;switchView()}

// Manage Views
function OnScreenTouched(x, y) {
    l_disp = (l_disp==0) ? 1 : 0;
    //io.saveFile(null, gvar.g_dp + "ldisp.txt", "string text" + l_disp + "\n");
    switchView();
}
function OnDialReleased() {
    if (l_disp == 0) return;
    l_disp = 3 - l_disp;
    switchView();
}
function switchView() {modules[l_disp].SetView()};

and in the first called Setview :

function SetView() {
    // Show Device when activated
    gvar.g_showClip = false;
    setParamDesign();   
    setTitle();
    // Rebind to get the latest value
    rebindParameters();
}

//
// Internal functions
// ==================
//
function rebindParameters() {
    console.warn("DP_Dial: Rebinding with l_pqb=" + l_pqb);     
    switch(l_pqb) {
        case 1: {midi.sendCC(14, cc1, 0);break;}
        case 2: {midi.sendCC(14, cc1, 127);break;}
        case 3: {midi.sendCC(14, cc2, 0); break;}
        case 4: {midi.sendCC(14, cc2, 127); break;}
    }
}

function setParamDesign() {
    layout.load("");
    // Design is set in editor for V-Pot
    ui.display("V-pot");
    ui.minmax(1, 127);
    ui.iconright("");
    // Colors (Not permitted with standard layouts)
    //layout.l_toptext_full_width.color =  "#B8A46E"; // Light Yellow
    //layout.l_toptext_left_icon_present.color = "#B8A46E"; // Light Yellow
}

function setTitle() {
    //ui.iconleft(gvar.get(g_pqbvar) == gvar.g_pqb0 ? "" : gvar.g_pp + "PBank-" + gvar.g_nhb + "-" + gvar.get(g_pqbvar)  + ".png");
    ui.iconleft(l_pqb == gvar.g_pqb0 ? "" : gvar.g_pp + "PBank-" + gvar.g_nhb + "-" + l_pqb  + ".png")
    //console.warn("DP_Dial: Setting title with l_pset=" + l_pset + ", g_pqbvar=" + g_pqbvar);
    ui.title(gvar.g_pname[l_pset-1 + (l_pqb  - 1)*4]);

    //{title:#MID(@g_pnames,1+(@g_pqb2-1)*64+(@l_pset-1)*16,16)#}]
}

I forget to uncomment the colors setting, but i guess it’s fixed now.

I can’t find how to show a title in button>generic midi>V-Pot/Scrub Wheel:

It seems that the option Title is not available like we have in button>generic midi>fader.

Is this by design or a bug?

A bug by design, maybe. :rofl:

The title option is explicitly coded to be available only for faders. If you have a fader selected with the title options enabled and then switch to something else, you will see ā€œTitle (Not Found)ā€ displayed.

It’s not only the dropdown option that is ā€œmissingā€; there’s no code to handle the title field for anything other than faders.

I don’t remember why it’s designed this way, and I have way too many other things to take care of to get this version released, so this issue will get low priority.

I have been able to reproduce the issue. It’s a timing issue between the HTML frontend and the C# backend that occurs in a very specific case where the dial is selected before you paste a copy into it.

I’m not sure how to fix it, but at least I now understand what the problem is. :roll_eyes:

I have decided not to fix it.

It is a catch-22 timing situation that is very tricky to address, and it is a very specific situation that is easy to avoid. To trigger the issue, all of the following are required:

  1. Create a scripted dial.
  2. Add an OnInit() or (init) command.
  3. Add at least one action in the OnInit/init command that makes a change to an editor parameter, e.g., minmax.
  4. Copy that source dial.
  5. Select the target dial (important!)
  6. Paste the copied source dial to the selected target dial.

If any of the above is not fulfilled, there is no issue. It is very easy to avoid the issue either by NOT selecting the target dial before pasting the source there, or by quickly unselecting and reselecting the target dial in the editor after the paste.

Ok, understood. Thank you for investigating.

Now i’m having a hard time understanding the display logic.
This what I set and what I get :

In summary :
1, 2 and 3 should be identical, they are not
5, 6 and 7 should be identical, they are not

Some of them a bold and some not, for a reason I don’t understand.
I have also noticed that Changing the font size and weight in the layout files has almost no effect.

Log ID : 504fcd6e-e3cf-43bd-9e2f-4011f4fd7ba5

I’ve also seen that they are some ā€œstrangeā€ calculations in the log regarding font sizes and available spaces. What is the logic ?

… and is the a way to have those fonts and text sizes identical ?
Thanks for your hard work.

Font size and weight in the layout file are mostly ignored. The plugin adjusts the font size so the text fits in the available space. Font weight is somewhat random (obviously). Once upon a time, I found that in some color combinations (text/background), the text needed to be bold to stand out better. So the text weight is calculated based on the colors, in a way that probably seemed logical at the time, but I can no longer explain it.

These weight calculations were made during the button-only era and were carried over to the dials, obviously not consistently, and probably not even necessary. :roll_eyes:

The logic behind font sizing is to ask the graphical subsystem how much space the text will require. A loop starts with the largest available font size (available in the editor text popup) and reduces the font size step by step until the text fits within the rect defined for the text. The calculation isn’t exact; there are edge cases where characters may be partially outside the rect to avoid further reducing the text.

With scripted dials, yes. Font size and weight set in a script always take precedence, and the automatic calculations are ignored. For other dials, unfortunately, not.

Thank you for your time

i seems not to be true, when setting the fonts like with :

layout.l_toptext_full_width.font.size = 10;
layout.l_toptext_full_width.font.weight = 200;

Log Id: 504fcd6e-e3cf-43bd-9e2f-4011f4fd7ba5

So I thought,
maybe the calculation could be ignored with a dedicated keyword in the advanced preferences
Or
I could use my own fied names with a corresponding entry in the layouts.

What is the best medicine ?

OK, some new features and bug fixes.

  • The issue with font size and weight not being applied when set from JavaScript is fixed.
  • There is now a ā€œRun initā€ button in script actions.
  • The number of global ports is now configurable. Please report any issues you see during the transition from a fixed number of ports to a configurable number of ports.
  • There are now script actions for setting MIDI ports.
    midiScript: {inputport:GPx} {outputport:GPx}
    JavaScript: midi.inputport(ā€œGPxā€); midi.outputport(ā€œGPxā€);
    x = port number, 1-30 (or whatever max port you have set). If the port number is set to a ā€œNot usedā€ global port, it will be listed as ā€œNot presentā€ in the port list, since unused global ports are not present there.
  • …and as normal, probably a bunch of other things I have forgotten about…:roll_eyes:

Version 4.2.0.96

I tested three of the new port actions without problems… and, of course, there was a bug in the fourth one.

The intention was to allow all ports, not just global ports, but I convert the port name to uppercase too early, so it doesn’t work with normal ports.

The ā€œRun Initā€œ button works like a charm. This is a button to save time when testing changes in the actions of the (init) event, but the confirmation dialog slows down the workflow. I would propose to keep the dialog but with a ā€œDon’t show this confirmation againā€œ checkbox, or just remove it.

I have tested the new option of changing the qty of ports and it works perfectly. This is great. I have tested if the new added global ports (GP20-GP21-GP22) can send and receive midi messages and they are working as expected.

ISSUE 1
The actions for changing ports are giving errors when you want to use ports higher than 10:

ISSUE 2
The {inputoport:GPx} action works as expected if you use GP1-GP10. There were no problems in the first tests.
But the {outputport:GPx} action is not working even you use GP1-GP10. It always shows the global port as not present in the editor. It is not only a cosmetic issue. It is not sending any midi message:

After running init:

After press:

The dropdown menus shows this: