Forum Replies Created
-
AuthorPosts
-
Does this panel work? LUA version, but see below for a no lua solution. ?
Attachments:
You must be logged in to view attached files.Here is lua code for this. (Example LFO1 Speed 18:50)
See ctrlr.org/forums/topic/novation-bass-station-2-panel/
In a CC MULTI field:
CC,ByteValue,MSB7bitValue,18,-1 CC,ByteValue,LSB7bitValue,50,-1
In the Expression field:
(modulatorValue+128)*64
add 128 for values -127 ~ +127
Logic suggested by Possemo:
Hi An0n3mau5 and possemo,
I found the answer thanks to sniffing out the midi from this online editor. studiocode.dev/bs2-editor/
Also this topic has been raised before:
ctrlr.org/forums/topic/bass-station-ii-parameter-question/…and here’s how to do it in lua … the ‘why‘ I don’t know – phew – not so straight forward!
CC 0-127 is sent for values -127 ~ +127 in stages 0=0,1=0,2=1,3=1 etc and then a separate CC is sent 0 for the first number and toggles to 64 for the next number , so +127, which is 255, for LFO1 Speed would be B0 32 00 // B0 12 7F. See attached panel.
† This code doesn’t work because the CC messages are sent in the wrong order
lfo_sweep = function(--[[ CtrlrModulator --]] mod --[[ number --]], value --[[ number --]], source) local channelOut = panel:getProperty("panelMidiOutputChannelDevice") local statusByte = 0xb0 + (channelOut - 1) -- use this code for dynamic channel changes local switcher = {0, 64} local number = {18, 50} value = value + 128 local even1, even2, odd1, odd2 = 0, 0, 0, 0 if value % 2 == 0 then even1 = CtrlrMidiMessage({statusByte, number[2], switcher[2]}) panel:sendMidiMessageNow(even1) even2 = CtrlrMidiMessage({statusByte, number[1], value / 2}) panel:sendMidiMessageNow(even2) else odd1 = CtrlrMidiMessage({statusByte, number[2], switcher[1]}) panel:sendMidiMessageNow(odd1) odd2 = CtrlrMidiMessage({statusByte, number[1], math.floor(value / 2)}) panel:sendMidiMessageNow(odd2) end end -- function
Attachments:
You must be logged in to view attached files.[20:15:54:000366]: Ch:[ 8] No:[ 17] Val:[ 0] RAW:[b7 11 00]
[20:15:54:000366]: Ch:[ 8] No:[ 49] Val:[ 64] RAW:[b7 31 40]
[20:15:54:000430]: Ch:[ 8] No:[ 17] Val:[ 1] RAW:[b7 11 01]
[20:15:54:000430]: Ch:[ 8] No:[ 49] Val:[ 0] RAW:[b7 31 00]
[20:15:54:000475]: Ch:[ 8] No:[ 17] Val:[ 1] RAW:[b7 11 01]
[20:15:54:000475]: Ch:[ 8] No:[ 49] Val:[ 64] RAW:[b7 31 40]
[20:15:54:000510]: Ch:[ 8] No:[ 17] Val:[ 2] RAW:[b7 11 02]
[20:15:54:000510]: Ch:[ 8] No:[ 49] Val:[ 0] RAW:[b7 31 00]
[20:15:54:000535]: Ch:[ 8] No:[ 17] Val:[ 2] RAW:[b7 11 02]
[20:15:54:000535]: Ch:[ 8] No:[ 49] Val:[ 64] RAW:[b7 31 40]
[20:15:54:000565]: Ch:[ 8] No:[ 17] Val:[ 3] RAW:[b7 11 03]
[20:15:54:000565]: Ch:[ 8] No:[ 49] Val:[ 0] RAW:[b7 31 00]
[20:15:54:000620]: Ch:[ 8] No:[ 17] Val:[ 3] RAW:[b7 11 03]So as you turn the knob LFO2 depth on the Machine is it spitting out alternate cc numbers 31/11 31/11 and values like above. It’s switching between values 0/64 (0x40) for 31 between value changes and sending cc number 11 twice with the same value? Must be something I don’t understand. What do you get when you go above 127 (ie 0)?
?Thanks An0n3mau5,
Congratulations on having a 4 month old. My daughter has a 4 month old too!
Well here is the panel again using those split CCNUMBERS, this time 17:49. Must be unusual if Possemo has never seen it.
Samoht’s post is good, but you would still have to change the CCNUMBER, which needs lua.
You are using 5.3.201 on Windows 10? I just downloaded that panel and was able to open it.
Here’s another one using 17:49 – you should be able to use this as a template for all the other split CCNUMBERs.
Regards,
John
Panel was not correct so deleted!
See below for correct code.- This reply was modified 3 years, 8 months ago by dnaldoog. Reason: deleted panel because it is not working
Hi An0n3mau5,
I think you might have to do that in Lua. I looked at the manual. Is this what you are after?
See attached panel. I think the CC number changes when the value exceeds a 7 bit number. Not sure how to do that without resorting to lua.
lfo_sweep = function(--[[ CtrlrModulator --]] mod --[[ number --]], value --[[ number --]], source) local channelOut = panel:getProperty("panelMidiOutputChannelDevice") local statusByte = 0xb0 + (channelOut - 1) -- use this code for dynamic channel changes local ccNumber = 51 if value < 0 then -- values range -127 ~ +127 value = value + 127 ccNumber = 18 end -- DEBUGGING panel:getLabel("OUT"):setText(string.format("%.2x %.2x %.2x", statusByte, ccNumber, value)) -- SEND MESSAGE local myMessage = CtrlrMidiMessage({statusByte, ccNumber, value}) panel:sendMidiMessageNow(myMessage) end
Regards,
Attachments:
You must be logged in to view attached files.Hi Tedjuh,
I got to thinking that Possemo’s suggestion of on the fly mouse creation was excellent.
You can avoid those cumbersome loops and probably super complex code.
Here is a panel that creates a `fillRect’ on the grid where the mouse was clicked. Instead of generating vertical and horizontal lines, you could have a mock png image in a different layer as backdrop.
You would then have to move the image according to your scrolling modulator or alternatively redraw the graphics lines in the component and offset the mouseclick y coordinate. It’s probably not too difficult finding the new coordinates that way although I haven’t got that far yet.
Here is the code. ※ Variables
mpX
andmpY
are mouseUp coordinates. (see below)paintMe2 = function(--[[ CtrlrComponent --]] comp --[[ CtrlrComponent --]], g) g:setColour(Colour(0xff000000)) local height = comp:getHeight() local width = comp:getWidth() local x, y = 0, 0 local wDiv = width / 32 local hDiv = height / 16 --************************************ --draw surrounding area g:drawVerticalLine(width - 1, 0, height) g:drawVerticalLine(0, 0, height) g:drawHorizontalLine(0, 0, width) g:drawHorizontalLine(height - 1, 0, width) -- end draw surrounding area --************************************ local verticalCoords = {0} local horizontalCoords = {0} for i = 1, 32 do g:drawVerticalLine(wDiv * i, 0, height) table.insert(verticalCoords, (wDiv * i)) end -- loop 32 horizontal for i = 1, 16 do g:drawHorizontalLine(hDiv * i, 0, width) table.insert(horizontalCoords, (hDiv * i)) end -- loop 32 horizontal for j = 1, #verticalCoords do if verticalCoords[j] > mpX then x = verticalCoords[j - 1] break end end for j = 1, #horizontalCoords do if horizontalCoords[j] > mpY then y = horizontalCoords[j - 1] break end end local rec = Rectangle(x, y, wDiv, hDiv) g:setColour(Colour(0xff0000ff)) g:fillRect(rec) end -- function
Other functions:
mouseUp = function(--[[ CtrlrComponent --]] comp, --[[ MouseEvent --]] event) repaint_component() end mouseMove = function(--[[ CtrlrComponent --]] comp, --[[ MouseEvent --]] event) mpX = event.x mpY = event.y end repaint_component = function(--[[ CtrlrModulator --]] mod, --[[ number --]] value, --[[ number --]] source) panel:getComponent("c"):repaint() end
This code must be lean, because when doing debug prints of the tables to console(), it doesn’t slow down the program much!
- This reply was modified 3 years, 8 months ago by dnaldoog. Reason: fixed missing [i]
Attachments:
You must be logged in to view attached files.Hi Tedjuh!
_G["block" ..i]:setBounds()
would work as long as you have already declared an object `_G[“block” ..i]:Rectangle(). so I guess they could be included in the same loop:paintc = function(--[[ CtrlrComponent --]] comp --[[ CtrlrComponent --]], g) local x, y, w, h = 20, 30, comp:getWidth(), comp:getHeight() backdrop = 0xff99ccff g:fillAll(Colour(backdrop)) g:setColour(Colour(0xff000000)) for i = 1, 512 do _G["block" .. i] = Rectangle() _G["block"..i]:setBounds(x,y,w,h) end g:setColour(Colour(0xff000000)) -- block502:setBounds(x + 20, y + 20, (w / 2) + 20, (h / 2) + 20) g:setColour(Colour(0xffffff00)) g:fillRect(block502) -- block500:setBounds(x, y, w / 2, h / 2) g:setColour(Colour(0xff000000)) g:drawRect(block500, 2.5) -- block501:setBounds(x + 10, y + 10, (w / 2) + 10, (h / 2) + 10) g:setColour(Colour(0xff000000)) g:drawRect(block501, 2.5) end
You could then dynamically change the x,y positioning within the loop
Regards,
John
This code seems to work!
paintc = function(--[[ CtrlrComponent --]] comp --[[ CtrlrComponent --]], g) local x, y, w, h = 20, 30, comp:getWidth(), comp:getHeight() backdrop = 0xff99ccff g:fillAll(Colour(backdrop)) g:setColour(Colour(0xff000000)) for i = 1, 512 do _G["block" .. i] = Rectangle() end g:setColour(Colour(0xff000000)) block502:setBounds(x + 20, y + 20, (w / 2) + 20, (h / 2) + 20) g:setColour(Colour(0xffffff00)) g:fillRect(block502) block500:setBounds(x, y, w / 2, h / 2) g:setColour(Colour(0xff000000)) g:drawRect(block500, 2.5) block501:setBounds(x + 10, y + 10, (w / 2) + 10, (h / 2) + 10) g:setColour(Colour(0xff000000)) g:drawRect(block501, 2.5) end
Attachments:
You must be logged in to view attached files.He Tedjuh,
Does
for i=1,512 do _G["block"..i]=Rectangle() end
work?
Note: When posting iterators you can’t use [i] because it is interpreted as BBCode for italic formatting, therefore the unclosed [i]text[/i] is deleted, so you need to substitute something like [j]
August 12, 2020 at 3:07 pm in reply to: Radio button in a modulator group needs a default setting #119393Hi Damien,
Great to hear from you! Those SPX990s seem to going up in price. I have a Yamaha REV500 (that I hope to write a panel for someday). Hope the DSP arrives soon!
I think I finally got this whole radio button problem sorted out. Below is a summary of my findings so far, for people trying to find a solution in future.
- GOAL: Create a set of uiImageButton as radio buttons (ie you click on one button and the others in the set switch off)
- PROBLEM: If you attach a callback function Called when the modulator value changes to each button you will end up with an infinite loop (and crash Ctrlr). This is because when you set all non clicked buttons to 0,true (false doesn’t update the image) the function is fired again and an infinite loop is created.
Note:: The clicked on button will always be set to ‘1’.
- SOLUTION: The third parameter in the callback function ‘source’ returns 4 when a user clicks on a button, otherwise if lua or the program changes the button, you get ‘5’,or’6′ returned.
In the callback function we can block the code from running when not clicked on by checking for:
if source == 4 then end
Put each button name in a table in the function:
local t={"button1","button2","button3"}
At the beginning of the function, determine the name of the button you clicked on:
local sName=L(mod:getName())
Loop through each button and run code for the clicked on button and switch other buttons to OFF!
if source == 4 then -- this code is only run by the button you clicked on for _,v in ipairs (t) do if v == sName then -- do something, send MIDI etc else panel:getModulatorByName(v):setValue(0, true) -- this sets the other buttons to off -- they in turn will fire this function but send 5 or 6 as 'source' so the infinite loop is avoided end
- EXTRA:
In an init function which runs when the program is loaded Called when the program has finished loading, you can set the radio buttons to a default (on panel load) setting:panel:getModulatorByName("button1"):setValue(1,true) panel:getModulatorByName("button2"):setValue(0,true) panel:getModulatorByName("button3"):setValue(0,true)
- But wait, there’s more!
It is better to initialise all modulators by assigning them to lua variables. This will optimise and speed up the panel:
in an init function, which runs when the program is loaded Called when the program has finished loading
button1=panel:getModulatorByName("button1") -- global lua variable button2=panel:getModulatorByName("button2") -- can be now used to refer button3=panel:getModulatorByName("button3") -- to the object anywhere in your program button1:setValue(1,true) button2:setValue(0,true) button3:setValue(0,true)
In the callback function you can now access the global lua variable directly using _G[].
if source == 4 then -- this code is only run by the button you clicked on for _,v in ipairs (t) do if v == sName then -- do something, send MIDI etc else _G[v]:setValue(0, true) -- this sets the other buttons to off -- they in turn will fire this function but send 5 or 6 as 'source' so the infinite loop is avoided end
Attachments:
You must be logged in to view attached files.See this post for a fuller explanation:
ctrlr.org/forums/topic/radio-button-in-a-modulator-group-needs-a-default-setting/page/2/#post-119393
August 11, 2020 at 11:10 am in reply to: Radio button in a modulator group needs a default setting #119377Just an observation that the following code I wrote(see this post) is not logical:
local UnitModValue = panel:getModulatorByName(n):getModulatorValue() local t={ViButComA=0,ViButComB=1,ViButComC=2,ViButComD=3} console("Unit Selected Value : "..tostring(UnitModValue)) for k,v in pairs(t)do if k==n and UnitModValue == 0 then console("Unit Selected : "..tostring(k)) panel:getModulatorByName(k):setValue(1, true)
local UnitModValue = panel:getModulatorByName(n):getModulatorValue()This could just be value, but we don’t need it anyway as value passed in to the function parameter is always 1:if k==n and UnitModValue == 0 then
this condition can never be reached (See #1)!- Should be:
for k,v in pairs(t) do if k==n then -- n = name of modulator clicked on --do something else panel:getModulatorByName(k):setValue(0, true) --set other radio buttons to 0 or off end
- This reply was modified 3 years, 9 months ago by dnaldoog.
Attachments:
You must be logged in to view attached files.Hi BAUS,
It’s not trivial in my opinion and you need lua.
Here are two panels that emulate radio button functionality. (see attached)
One uses the onMouseDown listener and the other onValueChange:
Note that both panels have an init() function that sets the default radio button on panel load.
Regards,
- This reply was modified 3 years, 9 months ago by dnaldoog. Reason: added extra comment
- This reply was modified 3 years, 9 months ago by dnaldoog. Reason: removed buggy onValueChange panel
- This reply was modified 3 years, 9 months ago by dnaldoog. Reason: re uploaded onValueChange panel
Attachments:
You must be logged in to view attached files.BTW, Is that good for complete dumps, individual messages, or both?
If you turn a knob on the unit and it sends out a sysex message, then you should be able to capture that message again by checking for that incoming sysex message size as in the bulk dump example above, so you can have a series of if/else statements to filter out incoming messages of different packet sizes and then change the single modulator in Ctrlr. Unfortunately none of the older synths I have worked on with Ctrlr do that, as Possemo says is usually the case.
- This reply was modified 3 years, 10 months ago by dnaldoog.
Hi Martin,
Glad you found that example useful.
Anyway, I wrote about this recently for someone using a Roland device, so here is a slightly altered version/strategy for incoming sysex:
I would find out from the manual which byte is to be assigned to which EQ parameter in the dump message and make a table of your Ctrlr modulators uiSliders variable names etc in that exact order:
myList={
“my20HzSlider”,
“my125KhzSlider”,
“my20KhzSlider”, …
}You would then in the Ctrlr program create a function that reacts to incoming MIDI LuaPanelMidiReceived eg myMidiReceived=function()
When you perform a patch dump from the machine it will be a certain size. You monitor for incoming messages of that size triggering the function only when the packet size is that size; I would pass the MIDI memory block as a parameter to a function myUpdateInterface=function(). This function will update all the uiSliders.
myMidiReceived = function(--[[ CtrlrMidiMessage --]] midi) local s = midi:getSize() --------------------------------------------------------- if s == 129 then -- change to the size of your dump updateInterface(midi) -- pass the whole MIDI message to the function end end
in the updateInterface(midi) function you would loop through the table assigning MIDI message bytes values to each control.
Usually there will be a sysex header offset of about 9 bytes before you get to the data.
Loop though each byte with the table.
For example
updateInterface=function(midi) local offset =9 for i,v in ipairs (myList) do panel:getModulatorByname(v):setValue(midi:getData():getByte(i+offset,true) end end
i+offset might be 10, so you’ll have to play around with that to get the correct byte position.
Good luck!
NOTE:
Not too sure about this, but if you find yourself in a loop where the updated modulator is firing off a sysex message again after being updated, try;
updateInterface=function(midi) panel:setPropertyInt("panelMidiPauseOut",1) -- PAUSE SENDING MIDI local offset =9 for i,v in ipairs (myList) do panel:getModulatorByname(v):setValue(midi:getData():getByte(i+offset,true) end panel:setPropertyInt("panelMidiPauseOut",0) end end
or
updateInterface=function(midi) local offset =9 for i,v in ipairs (myList) do panel:getModulatorByname(v):setValue(midi:getData():getByte(i+offset,false) --SET TO FALSE end end
JG.
Hi Martin,
You need uiFixedSlider.
In the field slider contents add text of 60 steps in 0.5 increments.
There are two ways to invert the sysex value.
[1] In slider contents the first entry is -15 (this would normally send 0x00 in the sysex). Change it to -15=60 // -14.5 = 59 — +15=0 etc
[2] in slider contents the first entry is -15 (second -14.5 … +15 etc). In the field Expression to evaluate when calculating the midi message value from the modulator value type 60-modulatorValue.
see attached panel
Regards,
Attachments:
You must be logged in to view attached files.See post #119140 for reply.
For some reason it wouldn’t let me post the text here?????????
Hi megasis,I would find out from the manual which control each byte is to be assigned to in the dump message and make a table of your Ctrlr modulators uiSliders variable names etc in that exact order:
myList={ "VibratoRate", "VibratoDepth", "Vibreto Delay", ... }
You would then in the Ctrlr program create a function that reacts to incoming MIDI LuaPanelMidiReceived eg myMidiReceived()
When you perform a patch dump from the machine it will be a certain size. You monitor for incoming messages of that size and then run code on it triggering the function only when the packet size is the appropriate one; I would pass the MIDI memory block as a parameter to that function updateInterface() and optionally the size of the message to avoid recalculating it.
myMidiReceived = function(--[[ CtrlrMidiMessage --]] midi) local s = midi:getSize() --------------------------------------------------------- if s == 129 then -- update controls 129 = size of dump updateInterface(midi,s) end end
in the updateInterface() function you would loop through the table assigning MIDI message bytes values to each control.
Usually there will be an offset of about 9 bytes before you get to the data.
The wave forms, of which there are 255, might be represented by two of the bytes in the message MSB and LSB, so you’d have to parse that and then maybe update a uiLabel with the name referenced from a table of waveform names.
Loop though each byte with the table.
For example
local offset =9 for i,v in ipairs (myList) do panel:getModulatorByname(v):setValue(midi:getData():getByte(i+offset,true) end
i+offset might be 10, so you’ll have to play around with that to get the correct byte position.
Something like that and it’s just off the top of my head.
There are many clues for this in my Roland JD-990 panel which incorporates the SR-JV80-04 card, but wading through someone else’s code is almost more difficult than writing it yourself!
Good luck!
-
AuthorPosts