Happy Monday. I’m recently retired and so have way too much time to spend on this So @oldgearguy I think I see the challenge. Setting the displayed range, say from -15 to +15, is not the same thing as restricting the range of midi values from 0 to 30. I’m able to do the former using @NewIgnis method, but that does not simultaneously change the midi value range. Or am I missing something?
Ok, this is going to take a bit of typing and explaining and I’ll try to put in as much as I can, but it might be a bit sporadic.
First - I looked over the parameter list/values in the Integra parameter guide document. I noticed that a lot of parameters across the effects had the same range. So, any Gain variable goes from -15dB to +15dB. Levels are a generic 0-127. Same with attack/decay parameters. The Balance parameter is 100%W:0%D - 0%W:100%D and so on.
Since there’s a lot of commonality and only 32 parameters, one way I’d think about it is going through all the algorithms and determine the maximum of each type of parameter range I would need for each effect. So, something like:
effect 1: 4 gains, 2 Qs, 2 freq ranges (200-8000 Hz), 2 freq lists (200,400 / 2000,4000,8000 Hz), 1 level and so on for each effect.
You may discover a lot of commonality across everything. Then I would create a page or two of controls that cover the various variable types and use the control:setSlot(slot,page)
to move them to the appropriate spot. This way you can have controls with dedicated ranges ready to go and for most you can have a dedicated formatter() that displays the correct units, etc.
If you need more generic controls, you can define a bunch of controls and then assign their names and ranges and formatter() functions as needed. This becomes more challenging in some ways.
Say you want to set the first control to Low Gain. The displayed range is -15 to +15 and (maybe - don’t know where to find it defined) the MIDI values will be either 0-30 or -15 to +15. You’ll need to look into each type of parameter to see how they implemented it.
You can set the range with a value:setRange(min,max,default,applyToMessage)
call. This can be done as soon as you parse the sysex dump and discover what the MFX number is for that area. I might implement this using an array of control IDs for each effect type.
So,
mfx = {}
for i=1,67 do
mfx[i] = {}
end
-- then inside each, I'd define something like:
mfx[1]={[10]={0,30,0}, [11]={200,8000,200}, [12]={0,4,0}}
-- where the number in brackets is the control ID and the array is the min,max,default for the range and you'd have to know that control 12 was the Mid1 Q control.
-- or
mfx[1]={{10,0,30,0}, {11,200,8000,200}, {12,0,4,0}}
-- where the control ID is embedded with the rest and you'd do something like (you can condense some of these separate steps once you understand, but expanded here for clarity):
for i=1,#mfx[1] do
tbl = mfx[1][i]
ctl = controls.get(tbl[1])
valObj = ctl:getValue("value")
valObj:setRange(tbl[2],tbl[3],tbl[4],true)
end
This approach will result in you defining a matrix of 67 entries (one row for each MFX type) and anywhere between 4 and 32 ranges per effect row.
So that is a way to deal with the ranges. The formatter() functions can be handled either by dedicating a formatter() per control and parameter range type and then inside that coding up various exceptions or overloading. As long as you set the range appropriately and the values are linear (so a delay can be 0-1300 ms or 0-2600 ms with a 1 to 1 correspondence between MIDI value and parameter value) there’s nothing extra that needs to be done.
If a formatter() needs to handle something like LF:200,400 and HF: 2000,4000,8000, you’ll need to change the range (0-1 vs 0-3) and the display. So you might do something like:
function fmtFreqLIst(valueObject,value)
-- somewhere during parsing the MFX number is set to a global
-- you can use the global directly or assign it to a local variable for ease of typing or possible local manipulation
local cf = current_global_mfx_value
local ctlId = valueObject:getControl():getId()
local multiplier = 200
-- if the formatter is dependent on the effect type you might use this
-- if (cf == 1) or (cf == 3) then
-- if the formatter is dependent on which control is being used, this might work
if (ctlId == 4) or (ctlId == 11) or (ctlId == 17) then
multiplier = 2000
end
return(string.format("%d Hz", (value+1) * multiplier))
end
Fantastic! I think this is actually pretty much how I have it setup. But I had the set range function midi message option as false instead of true. I will change that and see what happens. The formatter information will be very very useful too. I had not thought that far ahead.
FYI, I got -15 to 15 from the Integra parameter guide which lists the parameter functions for each of the 67 effects. It was to indicate -15dB to +15dB for the gain. I used the parameter guide to build the table for the parameter names.
Something else to consider is expanding the array/table example a little more.
mfx = {}
for i=1,67 do
mfx[i] = {}
end
mfx[1]={{10,0,30,0,"fmtGain"}, {11,200,8000,200,"fmtFreq"}, {12,0,4,0,"fmtFreqList"}}
This allows you to dynamically call the formatter() function from inside something else.
So you can have the standard:
-- assumption that value is MIDI between 0 and 30
function fmtGain(valueObject,value)
return(string.format("%+d dB", (value - 15)))
end
and then if you need to invoke that formatter from some other function, you can do:
local fmtr = tbl[5]
local valObj = controls.get(tbl[1]):getValue("value")
return(_G[fmtr](valObj,value))
-- or if the formatter() doesn't need the valueObject param you can simply code
return(fmtGain(nil,value))
-- or
return(_G([fmtr](nil,value)))
Thanks. Here is how I have things setup so far (just using the first 3 effects to illustrate). It is doing what I expect. I need to change the displayed 0 to 30 to -15 to15+, etc. But that is where the formatter function comes in. Next step.
function setFx(valueObject,value) ---- hides the non used controls of effects, sets parameter names and sets midi value ranges
-- names of parameters for the effects
local fxNames = {
{0},{"low freq","low gain","mid1 freq", "mid1 gain", "mid1 Q","mid2 freq","mid2 gain", "mid2 Q", "high freq", "high gain", "level"},
{"band 1","band 2","band 3","band 4","band 5","band 6","band 7","band 8","q","level"},
{"boost freq","boost gain","boost width","low gain","high gain","level"}
}
-- fx parameter min, default, and max midi values for each effect
local fxMin={{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}}
local fxDef={{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} }
local fxMax={{0},{1,30,16,30,4,16,30,4,3,30,127},{30,30,30,30,30,30,30,30,4,127},{8,12,2,30,30,127}}
-- index for the off and 67 effects
local fxRef={} for i = 1,68 do fxRef[i] = i end
--local message = valueObject:getMessage ()
-- index offset for the 27 ctrl ids because they are not all sequential! Only need 27 even though 32 available
local j = {122,122,122,122,122,122,123,123,123,123,123,123,123,123,130,130,132,132,132,132,132,132,132,132,133,133,155}
local name=0
local min = 0
local max = 0
local control ={}
local ctrlValue ={}
local index = value+1
for i = 1,27 do
name = fxNames[fxRef[index]][i]
min = fxMin[fxRef[index]][i]
max = fxMax[fxRef[index]][i]
def = fxDef[fxRef[index]][i]
control = controls.get (i+j[i])
ctrlValue = control:getValue ("")
if name == 0 or name == nil then
control:setVisible (false)
else
control:setName (name)
ctrlValue:setRange(min,max,def,true)
control:setVisible (true)
end
end
end
great news that it’s working for you. Smart to do an initial subset/small test.
Often, I’ll get something to work a certain way, then after it’s implemented, go back and see where I can condense/streamline/revamp areas to minimize the amount of redundancy (hopefully without making it too complex to follow a month later - lol).
When you start laying out all the arrays/tables for all the possible mfx, you may end up seeing places where you can add one more indirection or layer and eliminate a lot of duplication.
However, the bottom line is always to get it working solidly first, then revisit, so it’s looking good for you so far. I do admit that having to look at Roland and Yamaha sysex implementations usually gives me headaches after a while, so I’m glad you’re persevering on this since a number of the Roland synths/effects use a similar approach.
FYI, I have posted a couple of initial presets for the Integra for testing, here and here. I have all the MFX parameters displaying with visibility and labeling according to the effect selected. They display the correct MIDI values, but they do not display the units and are not scaled to those units. For example, a gain parameter whose range is -15 to +15dB is currently displayed as 0-30, an on/off switch is displayed as 0-1, etc. I have not figured out how to dynamically set those formats, or how to change a fader to a toggle button. My attempts so far have not worked - to be honest I still don’t quite understand the logic of how to do it.
I’ll take a look maybe today, definitely tomorrow on the formatter() functions.
Changing the nature of a control is not currently possible.
I take the lame way out - I use a control with a range of 0,1 for on/off if I need to replace it.
The alternate is to have 2 controls - on/off button and a regular range control and use the hide/show and setSlot stuff to move and hide them as necessary.
OK, had a thought and tried an initial simple implementation. You can decide it gets too unwieldy (or not).
First, above the first function, I declared a global to help me track things:
-- globals
curMfx = 0
-- fx management functions ----------------------------------------------------------------------------------------
function setFx(valueObject,value) ---- hides the non used controls of effects, sets parameter names and sets midi value ranges
-- names of parameters for the effects
next, inside setFx() I added a line to save the current mfx:
{125,1,200,21,127,100,125,1,200,21,127}
}
-- save to global
curMfx = value
-- index for the off and the 67 effects
local fxRef={} for i = 1,68 do fxRef[i] = i end
then I started to add some formatters:
function fmt2(valueObject,value)
if (curMfx == 1) or (curMfx == 2) then
return((value - 15) .. "dB")
elseif (curMfx == 3) then
return(math.floor(value) .. " dB")
else
return(value)
end
end
you can generally see how this will go. Define 27 generic formatters and inside each, send back the correct formatting based on the selected mfx.
In the graphical UI, you will need to add the formatter name to each control like this:
So control 1 will call fmt1, control 2 calls fmt2, etc. Inside each formatter() function, make sure you have an "else return(value) after all the if … elseif … statements so that it doesn’t error out and always returns something to display. That way, you can work on it at your leisure and keep adding to them.
Implemented for the first effect and your strategy works perfectly!! Thank you so much. Its going to be a long tedious process because there are a lot of different formats. And some formats are not linear transformations of value. But it is worth it because as it stands the MFX page is not very informative without also looking the parameter guide or at the Integra’s LCD screen.
So once you get into it, we can discuss ways to re-use chunks of code.
Also, if you sometimes reorder the controls on screen, you might get more controls using the same formatting.
That’s where the dance comes in trying to display an order that can make sense while minimizing the amount of tedium.
The beauty of programming is that there’s 100 different ways to solve the problem.
You wouldn’t believe the disorder in the way Roland implemented the mapping from the parameter list in the guide and on the integra’s screen to the order of the parameter bytes. For about 1/4 of the effects it doesn’t match!
I have noticed some patterns, so I will be looking for ways to make the programing more efficient.
2 down, 65 to go
Roland and Yamaha sysex = frustration and headaches.
However, when it works, it works well.
The next one you do will be much faster and cleaner. And the one after that and the one after that …
Lol
When you want, let me know and we can talk some optimization and compaction.
Ha ha. We will see. I have completed 9 of 67 and need to take a break. What is the best way to share my progress?
Your choice - assuming you are making updates to the same presets link(s) you posted earlier, then those are accessible. If you cloned it, then you can share a private link via direct message/email using the chat icon or one of the options in that menu to the right of the chat icon.
You guys interested in my Work In Progress for a Roland XV5080?
Absolutely! The Integra took several features from the XV5080 such as the PCM engine and SRX expansions. Once I finish the SuperNatural presets I will be moving on to the PCM synth component. I always learning something from your programming.
I like how you have things laid out in the UI @NewIgnis. I will definitely use some of those ideas.
Here’s the XV5080 preset Work-In-Progress, it is certainly not finished and I’m not busy on it right now.
https://app.electra.one/preset/pk0s8g53AxbvqI4TWpVf
Here’s what you need to know, when diving into it:
-
Virtual parameters below 9992 will be considered related directly to a parameter of a patch in memory buffer.
-
Virtual parameters 11001-11004 wil be used for tone select: which tone or tones is it you want to control?
-
Virtual parameters 11006-11029 wil be used for system common parameters, offset by exactly 11000
-
Virtual parameters 11031-11033 wil be used for Wave selection, send as 8231 for tone one (offset by 2800)
-
virtual parameters 11101-11109 are TMT tone parameters, offset by exactly 7000 to those of tone 1. They will indirectly control the TMT parameters of the selected tones.
-
Virtual parameters 11192-11456 are all other patch tone parameters, offset by exactly 3000 to those of tone 1. They will indirectly control the parameters of the selected tones.
-
Virtual parameters above 12000 are for other duties.
-
Make sure you give parameter 11031 “Wave Group” the values of resp sysEx bytes (assuming 0xF0 is byte 0) 10, 13 and 14 of the sysEx message that sends the waveform. These bytes will differ for the internal waveforms, the samples, and each of the JV and SRX cards. The value of byte 10 must be multiplied by 1000, the value of byte 13 multiplied by 100, and all needs to be summed with value of byte 14.
-
toneParameter is the table that will store all tone dependent parameters, and to which I will parse SysEx later on. The table is read to set all parameter when you select a tone to control.
-
all other patch Parameters reside in Virtual parameters found on the E1 pages.
-
setChorus() and setReverb() control their parameters, I haven’t done the MXF yet
-
bankSelect() has a table bankGroup which will be unique to each user’s installed SR-JV and SRX-cards.
-
onChange() is the function that sends the appropiate SysEx to the XV5080
Almost on each page you get to see 'Tone Switch" and “Tone Select”:
The “Tone Switch” allows you to select which tone you should here.
The “Tone Select” allows you the select the tone you want to change. You may select more than one at the time, in which case all selected tones will change accordingly.
Part of it is thus working, but be aware it is far from finished: There is no parsing yet, and I haven’t done aynthing yet on the MFX side of things. My starting point usually is that I first design the user interface, so I do not forget anything.
This preset already does
- tone FX send levels
- hides parameters when not in use
- all tone parameters
- program change is complete
@oldgearguy is there a clever way to visualize and control the 9 stage Envelopes better?