Look in the NetLinx Keywords help file. Go to index and search for "Button Events". This will show you the format of HOLD portion. Just remember that holds are based on tenths of seconds, so 5 is really .5 seconds, or one half second. 10 is 1 second.
Thanks. This is the 'Time' parameter of the 'Hold' action. Is there a way to make it so that the time the code in the 'Hold' section is repeatedly sent is different from the time that the button must be held for the repeated command to be executed?
Thanks. This is the 'Time' parameter of the 'Hold' action. Is there a way to make it so that the time the code in the 'Hold' section is repeatedly sent is different from the time that the button must be held for the repeated command to be executed?
Not directly. If you really need that, launch a repeating timeline on the hold, and kill it on the release.
Thanks. This is the 'Time' parameter of the 'Hold' action. Is there a way to make it so that the time the code in the 'Hold' section is repeatedly sent is different from the time that the button must be held for the repeated command to be executed?
Yes. You can specify a 2 second hold time and then if it gets in the hold, turn on a variable that exists in a different hold event with a different repeat time. That should allow you to hold the button for 2 seconds and then repeat events at a different interval in the other hold event with the same channel number. Turn off the variable on the release.
button_event[dvTP,100]{
push:{}
hold[20]{
hold =1}
release:{
hold =0}}
button_event[dvTP,100]{
hold[5, repeat]{if(hold)
callFunctionEveryHalfSecond()}}
You could also just use the fastest repeat time that you would need and then use button.holdtime % x == 0 to get other hold times with in the same event handler. button.holdtime gives you the current holdtime in milliseconds.
button_event[dvTP,100]{
push:{}
hold[2,REPEAT]{//CODE HERE REPEATES EVERY 2/10th of a second//OR
SELECT
{
ACTIVE(button.holdtime %200==0)://do something every 2/10th of a second{//DO SOMETHING}
ACTIVE(button.holdtime %300==0)://do something every 3/10th of a second{//DO SOMETHING}
ACTIVE(button.holdtime %400==0)://do something every 4/10th of a second{//DO SOMETHING}//SO ON
ACTIVE(button.holdtime %1000==0)://do something every 1 second{//DO SOMETHING}
ACTIVE(button.holdtime %2000==0)://do something every 2 seconds{//DO SOMETHING}}}}
I think this code is correct. Actually you would either need to change the repeat time to 1 or make sure every select active is a multiple of the repeat time used.
I think this code is correct. Actually you would either need to change the repeat time to 1 or make sure every select active is a multiple of the repeat time used.
Will that work? Is holdtime guaranteed to be a perfect multiple of the repeat?
Paul
Will that work? Is holdtime guaranteed to be a perfect multiple of the repeat?
No, in the example I posted the repeat time was 2 (2/10th) so in this case the ACTIVE statement for 300ms could never be true:
ACTIVE(button.holdtime %300==0)://do something every 3/10th of a second{//DO SOMETHING}
Although the holdtime is in milliseconds the code would only repeat every 200 milliseconds so only multiples of 200 milliseconds could ever be evaluated as true.
jjames wrote:
I prefer this way since HOLDs don't play nicely with arrays (can't fully rely on a GET_LAST in a HOLD.)
True, I don't like anything that evalutes the button or TP in the HOLD event either for that reason and usually set the hold button and the TP that iniated it in the push event like:
If you're concerned about mutliple TPs you could create an array the size of the number of TPs and store the hold button for each TP in that.
II almost never use a HOLD event though, because of the limitation that we're talking about now. I'm curious though, would you put in multiple BUTTON_EVENTs to populate the array? GET_LAST isn't a very reliable option.
I'll do one of two things to replace a HOLD event, and of course works fine.
If I need to do something once (eq. to HOLD[20]), I'd do something like this:
BUTTON_EVENT[dv_TP,nPRESET_SAVE]{
PUSH:{
LOCAL_VAR INTEGER nPNL;
LOCAL_VAR INTEGER nIND;// Make sure no other panel can initiate this sequence
IF(!nCAM_PRESET_LOCK){
ON[nCAM_PRESET_LOCK]
WAIT 30'CAMERA PRESET'{// Okay, now we can save the data
ON[nCAM_PRESET_SAVE]// Allow saves & recalls to occur again
WAIT 20
OFF[nCAM_PRESET_SAVE]}}}
RELEASE:{// Cancel the wait
CANCEL_WAIT 'CAMERA PRESET'
OFF[nCAM_PRESET_LOCK]// We're not in the middle of saving
IF(!nCAM_PRESET_SAVE){// Go ahead and recall your preset}}}
Now the above code is good for multiple panels to save a certain type of preset that all the panels share. If you wanted each panel to have it's own set of presets, you'd simply create the array, and have multiple CANCEL_WAITs & WAITs within a SWITCH/CASE or SELECT/ACTIVE situation.
For a repeating hold (eq. to HOLD[2,REPEAT]), I use Brian Clement's ramping volume timeline code, which essentially starts a TIMELINE with the PUSH and kills it on the RELEASE (as Dave had mentioned earlier.)
sn't it funny how the HOLD event almost always stirs up some kind of in-depth conversation where everyone jumps in? It seems the HOLD event itself isn't as perfect or easy as the PUSH & RELEASE.
No, in the example I posted the repeat time was 2 (2/10th) so in this case the ACTIVE statement for 300ms could never be true:
ACTIVE(button.holdtime %300==0)://do something every 3/10th of a second{//DO SOMETHING}
Although the holdtime is in milliseconds the code would only repeat every 200 milliseconds so only multiples of 200 milliseconds could ever be evaluated as true.
I think the question is about the precision of the timing. When your hold time is .2 seconds, is it guaranteed that the timing will be precise to the ms? Perhaps that .2 second hold occurs at 201ms instead of 200 ms. Might be better to check for closeness rather than equality -- sort of like with a floating point number. (Never check to see if a floating point number is FALSE)
Might be better to check for closeness rather than equality -- sort of like with a floating point number.
So you remember the problems I have with floating point numbers. Actually you're correct I would probably opt to do a greater than less than kind of a thing.
(button.holdtime >199&& button.holdtime <399)
I don't really use holds that often and I don't think I have any holds using button.holdtime but it is an option and If one was to use it they would have to play with it and see if there are quirks.
jjames wrote:
I'm curious though, would you put in multiple BUTTON_EVENTs to populate the array? GET_LAST isn't a very reliable option.
What do you mean? I know of no reliability problems using GET_LAST during a triggered event. I don't really consider HOLD a triggered event since its really just the result of a push that hasn't been released. So if you use GET_LAST on the trigger events (PUSH, RELEASE) I see no problems.
What do you mean? I know of no reliability problems using GET_LAST during a triggered event. I don't really consider HOLD a triggered event since its really just the result of a push that hasn't been released. So if you use GET_LAST on the trigger events (PUSH, RELEASE) I see no problems.
Yeah, I guess you could do that. I see the HOLD event (non-repeating) as a WAIT. And if you have a GET_LAST within the WAIT, another panel could potentially come in and change its value.
I just think there's too much hassle with the HOLD event. I don't want to have to manage variables and in the PRESS and RELEASE in order for something in the HOLD to work properly; I'd much rather remove the HOLD and just do it all in the PRESS/RELEASE. Just a difference in personal style I guess if it works your way as well.
Not enough cats for all the different ways to skin 'em.
I don't really use holds that often and I don't think I have any holds using button.holdtime but it is an option and If one was to use it they would have to play with it and see if there are quirks.
I like holds for ramping volume on things like ClearOne mixers. Easy to understand, easy to implement, and it works well. Clearly, there are other methods.
I just think there's too much hassle with the HOLD event.
I haven't found holds to be a hassle at all, so I guess I don't know what you mean. get_last has always worked perfectly reliably for me. You just have to be aware that it is a global function so that any touch panel will change its value but it will always return the correct value. If you need to store that info for hold events, then that is something you can do easily enough.
Paul
... I see the HOLD event (non-repeating) as a WAIT. And if you have a GET_LAST within the WAIT, another panel could potentially come in and change its value.
This is one topic that cannot be argued without showing examples how to get unintended results from your running system. Of course two users would never be operating two different touch panels at the same time, right.
If there is only one touch panel in a system, HOLDs work great. If you use multiple panels with device arrays then there is always the potential for problems using the GET_LAST() function within a HOLD...not a recommended coding practice. This is a difficult concept to grasp until you get burnt.
This is a difficult concept to grasp until you get burnt.
Indeed, I've been burnt once fairly bad when I had to go back and change it all for a system of 18 panels, not fun and rather time consuming. I've never made that mistake again.
If you use multiple panels with device arrays then there is always the potential for problems using the GET_LAST() function within a HOLD...not a recommend coding practice.
Of all the posts in this thread there hasn't been anyone saying anything to the contrary to the above statement.
What's been said is that GET_LAST() should be used only in the PUSH or RELEASE and if you want or need to implement a HOLD the button value should be obtained from the PUSH and held in a safe place for that purpose. It's been suggested that a TIMELINE should be used but that has the same potential for erroneous results as does using the HOLD event since they both depend on the method you use to obtain the value for the button you wish to HOLD and or REPEAT. If done wrong either method's value can change by other events or user pushes. That's true even if you don't use a HOLD event handler and trigger your home brewed HOLD by a starting a timeline on a PUSH and killing upon receipt of a release.
Either method is as reliable as the other if done right but the HOLD is by far the easiest to implement.
It's been suggested that a TIMELINE should be used but that has the same potential for erroneous results as does using the HOLD event since they both depend on the method you use to obtain the value for the button you wish to HOLD and or REPEAT.
I've never run into a problem using a TIMELINE when created and killed in the PUSH & RELEASE events respectively. In the TIMELINE example you'd have one TIMELINE event per panel or zone or however you divvy it up and then just stack them.
Here's an example of getting burn with a HOLD event. This was part of a transport key section, to continuous send a FFWD or REW command to a DVD player or CD player. The problem came along when one panel was using the DVD player and another panel wanted to do the same thing with the CD player. (This is just the HOLD portion of the entire BUTTON_EVENT.)
BUTTON_EVENT[dv_TP,TRANSPORT_BTNS]{
HOLD[2,REPEAT]:{
LOCAL_VAR INTEGER nPANEL
LOCAL_VAR INTEGER nBTN
nBTN = BUTTON.INPUT.CHANNEL
nPANEL = GET_LAST(dv_TP)
IF(nBTN =4 OR nBTN =5){
SWITCH(nCUR_SRC[nPANEL]){// MANAGE IR DEVICES
CASE SRC_DVD:// DVD COMMANDS
SEND_STRING PANEL[nPANEL].DVD_PLAYER,"DVD_COMMANDS[nBTN],CR"
CASE SRC_CDC:// CD PLAYER
SEND_STRING dvCDC_HRT,"CD_COMMANDS[nBTN],CR"}}}}
The fix (back then with limited programming experience) was to create a BUTTON_EVENT for each panel for those two buttons. Having to do it 18 times wasn't fun.
BUTTON_EVENT[dv_TP[02],4]
BUTTON_EVENT[dv_TP[02],5]{
HOLD[2,REPEAT]:{
STACK_VAR INTEGER nBTN
nBTN = BUTTON.INPUT.CHANNEL
// ONLY FOR FAST FORWARD AND REWIND OF DVD & CD PLAYER
IF(nBTN =4 OR nBTN =5){
SWITCH(nCUR_SRC[2][nCUR_SEL[2]]){// MANAGE IR DEVICES
CASE SRC_DVD:// DVD COMMANDS
SEND_STRING PANEL[2].DVD_PLAYER,"DVD_COMMANDS[nBTN],CR"
CASE SRC_CDC:// CD PLAYER
SEND_STRING dvCDC_HRT,"CD_COMMANDS[nBTN],CR"}}}}
I've never run into a problem using a TIMELINE when created and killed in the PUSH & RELEASE events respectively. In the TIMELINE example you'd have one TIMELINE event per panel or zone or however you divvy it up and then just stack them.
That's good and probably means you're obtaining and storing your button number that the timeline events use correctly otherwise there would be the same potential of having that button number altered by other system events or user pushes. That should also mean that the same method you use to store your button number for the timeline could be used in a hold event handler with equal success.
As long as you're not obtaining your button number through a GET_LAST in a hold event there's really no difference. If you think about it you could use a GET_LAST in your timeline event and that would have the same potential for errors as a GET_LAST in a hold event handler.
So if you only use GET_LAST in the PUSH or RELEASE and then properly store the button number in a safe place either method will perform the same end result with equal reliability whether you use the HOLD or TIMELINE method. They're both good, the HOLD is just easier.
Here's a simple example that uses an array to store button values for use in the hold event handler. Typically I've only created a single variable for use with holds since I don't feel it's too likely that multiple users will be on different TPs at the same time trying to initiate a hold on the same device. Is it possible, yes. is it likely, no. This code will work should it happen.
In this example every TP could initiate a HOLD on this device at the same time but unless it's something like an autopatch switcher and every user is on a seperate zone you going to have other problems to worry about.
BUTTON_EVENT[dvTPArry,nBtnArry]{
PUSH:{
STACK_VAR INTEGER nGLBtn ;
STACK_VAR INTEGER nTPIndx ;
nGLBtn = GET_LAST(nBtnArry);
nTPIndx = GET_LAST(dvTPArry);if(nGLBtn == VOL_UP || nGLBtn == VOL_DN){//pre-qualify if you want so you only set buttons which need a hold.//This makes the hold event run quicker by not running the Switch for buttons other than vol up or down should they be held.
nTPHoldArry[nTPIndx]= nGLBtn ;}}
HOLD [2,REPEAT]:{
STACK_VAR INTEGER i ;for(i =1; i <= NUM_TPs ; i ++){if(nTPHoldArry[i]){
SWITCH(nTPHoldArry[i]){
CASE VOL_UP:{//do something cuz dvTPArry[i] is holding the vol up button.}
CASE VOL_DN:{//do something cuz dvTPArry[i] is holding the vol dn button.}}}}}
RELEASE:{
STACK_VAR INTEGER nGLBtn ;
STACK_VAR INTEGER nTPIndx ;
nGLBtn = GET_LAST(nBtnArry);
nTPIndx = GET_LAST(dvTPArry);//pre-qualify if you want so you only release buttons which which may have been used in a hold. //nothing gained here by pre-qualifying so just set to 0.
nTPHoldArry[nTPIndx]=0;}}
I didn't like the having to run the loop in the hold event even when the held button wasn't to be used as a HOLD button so I added a variable to flag and count current active holds. This code is a little more effiecient if you go the array method to store current hold buttons.
BUTTON_EVENT[dvTPArry,nBtnArry]{
PUSH:{
STACK_VAR INTEGER nGLBtn ;
STACK_VAR INTEGER nTPIndx ;
nGLBtn = GET_LAST(nBtnArry);
nTPIndx = GET_LAST(dvTPArry);if(nGLBtn == VOL_UP || nGLBtn == VOL_DN){//pre-qualify if you want so you only set buttons which need a hold.//This makes the hold event run quicker by not running the Switch for buttons other than vol up or down.
nTPHoldArry[nTPIndx]= nGLBtn ;
nHoldActive ++;//this makes the hold run faster by not doing anything when the held button isn't used for a hold.}}
HOLD [2,REPEAT]:{if(nHoldActive){
STACK_VAR INTEGER i ;
nHoldActive =0;//reset for recount, validate the current count.for(i =1; i <= NUM_TPs ; i ++){if(nTPHoldArry[i]){
nHoldActive ++;//recount current holds, ensures an accurate count.
SWITCH(nTPHoldArry[i]){
CASE VOL_UP:{//do something cuz dvTPArry[i] is holding the vol up button.}
CASE VOL_DN:{//do something cuz dvTPArry[i] is holding the vol dn button.}}}}}}
RELEASE:{
STACK_VAR INTEGER nGLBtn ;
STACK_VAR INTEGER nTPIndx ;
nGLBtn = GET_LAST(nBtnArry);
nTPIndx = GET_LAST(dvTPArry);//if you pre-qualify in the push you must also pre-qualify in the release since we're using nHoldActive to enable hold events. if(nGLBtn == VOL_UP || nGLBtn == VOL_DN){
nTPHoldArry[nTPIndx]=0;
nHoldActive --;}}}
Here is a code block that I put together that controls volume in multiple zones from multiple panels using TIMELINES instead of HOLD. It is completely scalable for as many panels and as many audio zones that you need.
The same concept can be applied to other repeating type Button Events.
Enjoy!
PROGRAM_NAME='Timeline Example, Rev 0-0 by BC'
DEFINE_DEVICE
// Touch panels - add as many as required
dvTP1 =10001:01:0// Master Bedroom 1
dvTP2 =10002:01:0// Master Bedroom 2
dvTP3 =10003:01:0// Kitchen 1
dvTP4 =10004:01:0// Kitchen 2
dvTP5 =10005:01:0// Family Room
DEFINE_CONSTANT
INTEGER nMinVolumeLevel =0// Minimum volume range
INTEGER nMaxVolumeLevel =40// Maximum volume range
INTEGER nTotalVolumeZones =3// Adjust to the number of volume zones in the system// Timeline values
LONG nVolumeRampDelay[]={500}// 1/2 second
LONG nVolumeRampRepeat[]={100}// 1/10 second// Volume Buttons
INTEGER nVolumeButtons[]={24,// 1 - Volume Up25,// 2 - Volume Down26// 3 - Volume Mute Toggle}// Volume zone map - maps volume zone to touch panel// Mapping allows multiple touch panels to control the same volume zone.
INTEGER nVolumeZoneMap[]={1,// Master Bedroom 11,// Master Bedroom 22,// Kitchen 12,// Kitchen 23// Family Room}
DEFINE_TYPE
STRUCTURE VolStruct{
INTEGER nVolRampState // Tracks volume ramping state
INTEGER nVolMute // Tracks volume mute state
INTEGER nVolLevel // Tracks volume level
INTEGER nVolChange // Tracks which panel is controlling volume zone}
DEFINE_VARIABLE
INTEGER nLoop
VOLATILE VolStructVolume[nTotalVolumeZones]// Array of all Touch Panels
DEV dv_TP[]={
dvTP1,// Master Bedroom 1
dvTP2,// Master Bedroom 2
dvTP3,// Kitchen 1
dvTP4,// Kitchen 2
dvTP5 // Family Room}
DEFINE_CALL 'ADJUST VOLUME'(INTEGER nZone, INTEGER nState){
SWITCH(nState){
CASE 1:// Ramp Up{
IF (Volume[nZone].nVolLevel < nMaxVolumeLevel){Volume[nZone].nVolLevel++// VOLUME LEVEL - VOLUME DEVICE CODE HERE}}
CASE 2:// Ramp Down{
IF (Volume[nZone].nVolLevel > nMinVolumeLevel){Volume[nZone].nVolLevel--// VOLUME LEVEL - VOLUME DEVICE CODE HERE}}
CASE 3:// Toggle Mute{
IF (Volume[nZone].nVolMute)// Mute{// VOLUME MUTE - VOLUME DEVICE CODE HERE}
ELSE // Unmute{// VOLUME UNMUTE - VOLUME DEVICE CODE HERE}}}}
DEFINE_CALL 'VOLUME FEEDBACK'(INTEGER nPanel){
STACK_VAR INTEGER nVolumeZone
// Determine which volume zone status based on which touch panel
nVolumeZone = nVolumeZoneMap[nPanel][dv_TP[nPanel],nVolumeButtons[1]]=(Volume[nVolumeZone].nVolRampState=1)// Volume Up Button[dv_TP[nPanel],nVolumeButtons[2]]=(Volume[nVolumeZone].nVolRampState=2)// Volume Down Button[dv_TP[nPanel],nVolumeButtons[3]]=(Volume[nVolumeZone].nVolMute)// Volume Mute Button// Assumes touch panel bargragh matches volume level range.
SEND_LEVEL dv_TP[nPanel],1,Volume[nVolumeZone].nVolLevel // Volume Bargraph}
DEFINE_EVENT // Button Events - Volume Control// This set of Timelines will delay the start of the volume ramping and then// will repeat volume ramping until a volume button is released.// Add as many timeslines as there are volume zones.
TIMELINE_EVENT[1]
TIMELINE_EVENT[2]
TIMELINE_EVENT[3]{// Restart same timelime with repeat time instead of delay time
TIMELINE_CREATE(TIMELINE.ID,nVolumeRampRepeat,1,TIMELINE_RELATIVE,TIMELINE_REPEAT)// Call volume control subroutine
CALL 'ADJUST VOLUME'(TIMELINE.ID,Volume[TIMELINE.ID].nVolRampState)}
BUTTON_EVENT[dv_TP,nVolumeButtons]// VOLUME CONTROL{
PUSH:{
STACK_VAR INTEGER nVolumeZone
// Determine which volume zone to control based on which touch panel is pressed
nVolumeZone = nVolumeZoneMap[GET_LAST(dv_TP)]// Make sure another panel is not adjusting volume in the same zone
IF (!Volume[nVolumeZone].nVolChange){// Set flag for tracking which panel is controlling the volume zoneVolume[nVolumeZone].nVolChange = GET_LAST(dv_TP)// Set volume ramp flag indexed to the button array - 1 Up, 2 Down, 3 MuteVolume[nVolumeZone].nVolRampState = GET_LAST(nVolumeButtons)// Not the mute button
IF (BUTTON.INPUT.CHANNEL <> nVolumeButtons[3]){
IF (Volume[nVolumeZone].nVolMute)// If volume is muted - unmute{Volume[nVolumeZone].nVolMute =0// Reset mute flag// Call volume control subroutine
CALL 'ADJUST VOLUME'(nVolumeZone,Volume[nVolumeZone].nVolRampState)}
ELSE // Not muted - Adjust volume one unit{// Call volume control subroutine
CALL 'ADJUST VOLUME'(nVolumeZone,Volume[nVolumeZone].nVolRampState)}// Start delay before continuing to ramp -// The delay allows a single button tap to increment/decrement one volume unit// before ramping continues.
TIMELINE_CREATE(nVolumeZone,nVolumeRampDelay,1,TIMELINE_RELATIVE,TIMELINE_ONCE)}// Mute button
ELSE
{Volume[nVolumeZone].nVolMute =!Volume[nVolumeZone].nVolMute // Toggle mute flag// Call volume control subroutine
CALL 'ADJUST VOLUME'(nVolumeZone,Volume[nVolumeZone].nVolRampState)}}}
RELEASE:{
STACK_VAR INTEGER nVolumeZone
// Determine which volume zone to control based on which touch panel is released
nVolumeZone = nVolumeZoneMap[GET_LAST(dv_TP)]// Make sure the releasing panel is the same panel that pushed
IF (Volume[nVolumeZone].nVolChange == GET_LAST(dv_TP)){Volume[nVolumeZone].nVolChange =0// Reset panel tracking flagVolume[nVolumeZone].nVolRampState =0// Reset volume ramp flag
IF (TIMELINE_ACTIVE(nVolumeZone))// Cancel repeating timeline for ramping volume
TIMELINE_KILL(nVolumeZone)}}}
DEFINE_PROGRAM
// Feedback for Volume Buttons and Bargraph on All Touch Panels
nLoop++
IF (nLoop>LENGTH_ARRAY(dv_TP))
nLoop =1
CALL 'VOLUME FEEDBACK'(nLoop)
Comments
Jeff
Not directly. If you really need that, launch a repeating timeline on the hold, and kill it on the release.
Yes. You can specify a 2 second hold time and then if it gets in the hold, turn on a variable that exists in a different hold event with a different repeat time. That should allow you to hold the button for 2 seconds and then repeat events at a different interval in the other hold event with the same channel number. Turn off the variable on the release.
Paul
I think this code is correct. Actually you would either need to change the repeat time to 1 or make sure every select active is a multiple of the repeat time used.
I prefer this way since HOLDs don't play nicely with arrays (can't fully rely on a GET_LAST in a HOLD.)
Will that work? Is holdtime guaranteed to be a perfect multiple of the repeat?
Paul
jjames wrote: True, I don't like anything that evalutes the button or TP in the HOLD event either for that reason and usually set the hold button and the TP that iniated it in the push event like: I then release it in the release event handler if everything matches.
If you're concerned about mutliple TPs you could create an array the size of the number of TPs and store the hold button for each TP in that.
I'll do one of two things to replace a HOLD event, and of course works fine.
If I need to do something once (eq. to HOLD[20]), I'd do something like this:
Now the above code is good for multiple panels to save a certain type of preset that all the panels share. If you wanted each panel to have it's own set of presets, you'd simply create the array, and have multiple CANCEL_WAITs & WAITs within a SWITCH/CASE or SELECT/ACTIVE situation.
For a repeating hold (eq. to HOLD[2,REPEAT]), I use Brian Clement's ramping volume timeline code, which essentially starts a TIMELINE with the PUSH and kills it on the RELEASE (as Dave had mentioned earlier.)
sn't it funny how the HOLD event almost always stirs up some kind of in-depth conversation where everyone jumps in? It seems the HOLD event itself isn't as perfect or easy as the PUSH & RELEASE.
I think the question is about the precision of the timing. When your hold time is .2 seconds, is it guaranteed that the timing will be precise to the ms? Perhaps that .2 second hold occurs at 201ms instead of 200 ms. Might be better to check for closeness rather than equality -- sort of like with a floating point number. (Never check to see if a floating point number is FALSE)
I don't really use holds that often and I don't think I have any holds using button.holdtime but it is an option and If one was to use it they would have to play with it and see if there are quirks.
jjames wrote: What do you mean? I know of no reliability problems using GET_LAST during a triggered event. I don't really consider HOLD a triggered event since its really just the result of a push that hasn't been released. So if you use GET_LAST on the trigger events (PUSH, RELEASE) I see no problems.
I just think there's too much hassle with the HOLD event. I don't want to have to manage variables and in the PRESS and RELEASE in order for something in the HOLD to work properly; I'd much rather remove the HOLD and just do it all in the PRESS/RELEASE. Just a difference in personal style I guess if it works your way as well.
Not enough cats for all the different ways to skin 'em.
I like holds for ramping volume on things like ClearOne mixers. Easy to understand, easy to implement, and it works well. Clearly, there are other methods.
I haven't found holds to be a hassle at all, so I guess I don't know what you mean. get_last has always worked perfectly reliably for me. You just have to be aware that it is a global function so that any touch panel will change its value but it will always return the correct value. If you need to store that info for hold events, then that is something you can do easily enough.
Paul
This is one topic that cannot be argued without showing examples how to get unintended results from your running system. Of course two users would never be operating two different touch panels at the same time, right.
If there is only one touch panel in a system, HOLDs work great. If you use multiple panels with device arrays then there is always the potential for problems using the GET_LAST() function within a HOLD...not a recommended coding practice. This is a difficult concept to grasp until you get burnt.
What's been said is that GET_LAST() should be used only in the PUSH or RELEASE and if you want or need to implement a HOLD the button value should be obtained from the PUSH and held in a safe place for that purpose. It's been suggested that a TIMELINE should be used but that has the same potential for erroneous results as does using the HOLD event since they both depend on the method you use to obtain the value for the button you wish to HOLD and or REPEAT. If done wrong either method's value can change by other events or user pushes. That's true even if you don't use a HOLD event handler and trigger your home brewed HOLD by a starting a timeline on a PUSH and killing upon receipt of a release.
Either method is as reliable as the other if done right but the HOLD is by far the easiest to implement.
Here's an example of getting burn with a HOLD event. This was part of a transport key section, to continuous send a FFWD or REW command to a DVD player or CD player. The problem came along when one panel was using the DVD player and another panel wanted to do the same thing with the CD player. (This is just the HOLD portion of the entire BUTTON_EVENT.)
The fix (back then with limited programming experience) was to create a BUTTON_EVENT for each panel for those two buttons. Having to do it 18 times wasn't fun.
As long as you're not obtaining your button number through a GET_LAST in a hold event there's really no difference. If you think about it you could use a GET_LAST in your timeline event and that would have the same potential for errors as a GET_LAST in a hold event handler.
So if you only use GET_LAST in the PUSH or RELEASE and then properly store the button number in a safe place either method will perform the same end result with equal reliability whether you use the HOLD or TIMELINE method. They're both good, the HOLD is just easier.
In this example every TP could initiate a HOLD on this device at the same time but unless it's something like an autopatch switcher and every user is on a seperate zone you going to have other problems to worry about.
Here is a code block that I put together that controls volume in multiple zones from multiple panels using TIMELINES instead of HOLD. It is completely scalable for as many panels and as many audio zones that you need.
The same concept can be applied to other repeating type Button Events.
Enjoy!