You can try my STROKE module for that: It has an experimental command for sending any hotkey to a specific module in the patch. Right now you can’t use the same hotkey for multiple “special commands” of STROKE but at least you can configure the resets in one place.
I like that! Less fiddly than multiple selects. It sounds like STROKE should also work (there’s always a @stoermelder ) but I’ll probably follow that design if no one does it first. 2HP since it needs an input jack, and I’ll plan to squeeze in three groups of those three controls (randomize, reset, and bypass–on reflection I do think triggered bypass would be useful).
The only thing that concerns me is that it wouldn’t work with modules that have expanders on both sides. (I don’t think there’s an easy general way to climb the “expander tree” to the root module–and besides which someone might want to modify the expander anyway). Not sure it’s worth changing the design dramatically for this very unusual case, but I could add a “select” button that linked to a remote module and had its own readout on the LED line (thinking a red light in the center).
Also, I’ll check STRIP, but what happens if there are modules on both sides? I don’t love hidden rules like “prefer right.” I guess “affect both” is the sensible behavior and provides at least one step towards batch functionality.
I vote yes! Please lets see your midi-scripting-thing.
I got what i was looking for with ModScript, but for now, all I wanted was for some knobs to control NRPN’s and CC’s for 8, 10 and 14 bit MIDI controls out of rack.
ModScript script
config.frameDivider = 32
config.bufferSize = 1
lastvalue = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
function NewPatchMaster(id)
mod = Module(id)
mod.params = {
["U1"] = {index = 0, min = 0.000000, max = 1.000000, default = 0.000000},
["U2"] = {index = 1, min = 0.000000, max = 1.000000, default = 0.000000},
["U3"] = {index = 2, min = 0.000000, max = 1.000000, default = 0.000000},
["U4"] = {index = 3, min = 0.000000, max = 1.000000, default = 0.000000},
["U5"] = {index = 4, min = 0.000000, max = 1.000000, default = 0.000000},
["U6"] = {index = 5, min = 0.000000, max = 1.000000, default = 0.000000},
["No name"] = {index = 6, min = 0.000000, max = 1.000000, default = 0.000000},
["No name"] = {index = 7, min = 0.000000, max = 1.000000, default = 0.000000},
}
mod.inputs = {
}
mod.outputs = {
}
mod.lights = {
{index = 0},
{index = 1},
{index = 2},
{index = 3},
{index = 4},
{index = 5},
{index = 6},
{index = 7},
{index = 8},
{index = 9},
{index = 10},
{index = 11},
{index = 12},
{index = 13},
{index = 14},
{index = 15},
{index = 16},
{index = 17},
{index = 18},
{index = 19},
{index = 20},
{index = 21},
{index = 22},
{index = 23},
{index = 24},
{index = 25},
{index = 26},
{index = 27},
{index = 28},
{index = 29},
{index = 30},
{index = 31},
{index = 32},
{index = 33},
{index = 34},
{index = 35},
{index = 36},
{index = 37},
{index = 38},
{index = 39},
{index = 40},
{index = 41},
{index = 42},
{index = 43},
{index = 44},
{index = 45},
{index = 46},
{index = 47},
{index = 48},
{index = 49},
{index = 50},
{index = 51},
{index = 52},
{index = 53},
{index = 54},
{index = 55},
{index = 56},
{index = 57},
{index = 58},
{index = 59},
{index = 60},
{index = 61},
{index = 62},
{index = 63},
{index = 64},
{index = 65},
{index = 66},
{index = 67},
{index = 68},
{index = 69},
{index = 70},
{index = 71},
}
return mod
end
myPM = NewPatchMaster(0x6775c924dc493)
myVPM = NewPatchMaster(0xf53a3a11ee620)
function process(block)
value = myPM:getParam("U1")
if value <= 0.01 then
value = 0
end
if (value ~= lastvalue[0]) then
lastvalue[0] = value
scaled = value * 100
upper = math.floor(scaled / 8)
lower = scaled % 8
sendMidiMessage(CC, 98, 0x48) --NRPN 48h
sendMidiMessage(CC, 63, lower) --Lower 3 bits of 8 bit value to CC#63
sendMidiMessage(CC, 6, upper) --Upper 5 bits of 8 bit value to CC#6
end
value = myPM:getParam("U2")
if value <= 0.01 then
value = 0
end
if (value ~= lastvalue[1]) then
lastvalue[1] = value
scaled = value * 100
upper = math.floor(scaled / 8)
lower = scaled % 8
sendMidiMessage(CC, 98, 0x49) --NRPN 49h
sendMidiMessage(CC, 63, lower) --Lower 3 bits of 8 bit value to CC#63
sendMidiMessage(CC, 6, upper) --Upper 5 bits of 8 bit value to CC#6
end
value = myPM:getParam("U3")
if value <= 0.01 then
value = 0
end
if (value ~= lastvalue[2]) then
lastvalue[2] = value
scaled = value * 100
upper = math.floor(scaled / 8)
lower = scaled % 8
sendMidiMessage(CC, 98, 0x4A) --NRPN 4Ah
sendMidiMessage(CC, 63, lower) --Lower 3 bits of 8 bit value to CC#63
sendMidiMessage(CC, 6, upper) --Upper 5 bits of 8 bit value to CC#6
end
value = myPM:getParam("U4")
if value <= 0.01 then
value = 0
end
if (value ~= lastvalue[3]) then
lastvalue[3] = value
scaled = value * 100
upper = math.floor(scaled / 8)
lower = scaled % 8
sendMidiMessage(CC, 98, 0x4B) --NRPN 4Bh
sendMidiMessage(CC, 63, lower) --Lower 3 bits of 8 bit value to CC#63
sendMidiMessage(CC, 6, upper) --Upper 5 bits of 8 bit value to CC#6
end
value = myPM:getParam("U5")
if value <= 0.01 then
value = 0
end
if (value ~= lastvalue[4]) then
lastvalue[4] = value
scaled = value * 100
upper = math.floor(scaled / 8)
lower = scaled % 8
sendMidiMessage(CC, 98, 0x4C) --NRPN 4Ch
sendMidiMessage(CC, 63, lower) --Lower 3 bits of 8 bit value to CC#63
sendMidiMessage(CC, 6, upper) --Upper 5 bits of 8 bit value to CC#6
end
value = myPM:getParam("U6")
if value <= 0.01 then
value = 0
end
if (value ~= lastvalue[5]) then
lastvalue[5] = value
scaled = value * 100
upper = math.floor(scaled / 8)
lower = scaled % 8
sendMidiMessage(CC, 98, 0x4D) --NRPN 4Dh
sendMidiMessage(CC, 63, lower) --Lower 3 bits of 8 bit value to CC#63
sendMidiMessage(CC, 6, upper) --Upper 5 bits of 8 bit value to CC#6
end
value = myVPM:getParam(0)
if value <= 0.01 then
value = 0
end
if (value ~= lastvalue[6]) then
lastvalue[6] = value
scaled = value * 200
upper = math.floor(scaled / 8)
lower = scaled % 8
sendMidiMessage(CC, 98, 0x40) --NRPN 40h
sendMidiMessage(CC, 63, lower) --Lower 3 bits of 8 bit value to CC#63
sendMidiMessage(CC, 6, upper) --Upper 5 bits of 8 bit value to CC#6
end
value = myVPM:getParam(1)
if value <= 0.01 then
value = 0
end
if (value ~= lastvalue[7]) then
lastvalue[7] = value
scaled = value * 200
upper = math.floor(scaled / 8)
lower = scaled % 8
sendMidiMessage(CC, 98, 0x41) --NRPN 41h
sendMidiMessage(CC, 63, lower) --Lower 3 bits of 8 bit value to CC#63
sendMidiMessage(CC, 6, upper) --Upper 5 bits of 8 bit value to CC#6
end
value = myVPM:getParam(2)
if value <= 0.01 then
value = 0
end
if (value ~= lastvalue[8]) then
lastvalue[8] = value
scaled = value * 200
upper = math.floor(scaled / 8)
lower = scaled % 8
sendMidiMessage(CC, 98, 0x42) --NRPN 42h
sendMidiMessage(CC, 63, lower) --Lower 3 bits of 8 bit value to CC#63
sendMidiMessage(CC, 6, upper) --Upper 5 bits of 8 bit value to CC#6
end
value = myVPM:getParam(3)
if value <= 0.01 then
value = 0
end
if (value ~= lastvalue[9]) then
lastvalue[9] = value
scaled = value * 200
upper = math.floor(scaled / 8)
lower = scaled % 8
sendMidiMessage(CC, 98, 0x43) --NRPN 43h
sendMidiMessage(CC, 63, lower) --Lower 3 bits of 8 bit value to CC#63
sendMidiMessage(CC, 6, upper) --Upper 5 bits of 8 bit value to CC#6
end
value = myVPM:getParam(4)
if value <= 0.01 then
value = 0
end
if (value ~= lastvalue[10]) then
lastvalue[10] = value
scaled = value * 200
upper = math.floor(scaled / 8)
lower = scaled % 8
sendMidiMessage(CC, 98, 0x44) --NRPN 40h
sendMidiMessage(CC, 63, lower) --Lower 3 bits of 8 bit value to CC#63
sendMidiMessage(CC, 6, upper) --Upper 5 bits of 8 bit value to CC#6
end
value = myVPM:getParam(5)
if value <= 0.01 then
value = 0
end
if (value ~= lastvalue[11]) then
lastvalue[11] = value
scaled = value * 200
upper = math.floor(scaled / 8)
lower = scaled % 8
sendMidiMessage(CC, 98, 0x45) --NRPN 40h
sendMidiMessage(CC, 63, lower) --Lower 3 bits of 8 bit value to CC#63
sendMidiMessage(CC, 6, upper) --Upper 5 bits of 8 bit value to CC#6
end
end
I’m working on optimizing my naive code, but there are many different kinds of controllers on the device.
grouping controls
for i = 0, 5 do
local value = myVPM:getParam(i)
if value <= 0.01 then
value = 0
end
if value ~= lastvalue[i + 12] then
lastvalue[i + 12] = value
scaled = value * 200
upper, lower = ??????????
sendMidiMessage(CC, 98, 0x40 + i)
sendMidiMessage(CC, 63, lower)
sendMidiMessage(CC, 6, upper)
end
end
This is interesting, because it is an real world example. Right now you can’t achive the same thing with my module, because is was designed as a “MIDI processor”, it won’t do anything without an incoming MIDI message. I will think about it.
Some details:
I was just trying STROKE out last night! Is there any chance you can allow multiple special commands for the same hotkey?
I had a thought - Can you add a gate input to each row as an alternate way of activating the action? It would be handy to have CV control over many of those special commands. Perhaps it makes more sense as new module, but I can see how it could work for this one.
This limitation did come from preparing the module for key-sequences instead of simple hotkeys - this got never implemented. But I will look if I can add some kind of „linking“ multiple command’s together.
The problem is that CV signals live in the audio threads and most of the commands are GUI related which makes things quite tricky. I will think about it but I won’t promise anything.
Hmm. I could not get STROKE to send a ⌘-I to initialize a shift register. It appears to come out as just an “I”.
detailed version: When I right-click on it to inspect Modules > Send hotkey to module (experimental) > Learn hotkey, The correct target module name (“ML Modules Shift Register”) appears, but below that it says “Hotkey: I”.
If it did work, I would still be wishing for a trigger input.
I’m sorry, I must have made this mistake with the key modifiers on Mac a dozen times… it is fixed now
No arm64 build? Please add that to your build pipeline…
I’m using Azure pipelines for the build and as far as I know this is not supported. If I’m wrong I appreciate any links on the topic - I don’t want to move to GitHub Workflows for the time being.
something like this?
Ah. That worked. Yay! I see also that you can re-use the same hotkey on different [parts?] of a STROKE module to accomplish different things at the same time. e.g. “Module: Send hotkey” on one, and “CV: Trigger” on the next one. Saves a few steps in Resetting and Initializing.
The only problem now is this:
If I let it update PackOne, it goes back to the version that doesn’t work. Is this the sort of thing that fixes itself when the Library is updated?
Meanwhile I’ll hang onto a copy of “Stoermelder-P1-2.0.24223eb-mac-x64-20231007.vcvplugin” in case I forget and “Update all”.