Home AMX User Forum NetLinx Studio

HOLD repeat times

Can anyone tell me how you set/adjust the rate at which a command is repeatedly sent from the HOLD action of a button event?

Comments

  • Spire_JeffSpire_Jeff Posts: 1,917
    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.

    Jeff
  • rfayer1rfayer1 Posts: 22
    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?
  • DHawthorneDHawthorne Posts: 4,584
    rfayer1 wrote: »
    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.
  • a_riot42a_riot42 Posts: 1,624
    rfayer1 wrote: »
    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()
      }
    }
    
    Paul
  • viningvining Posts: 4,368
    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.
  • jjamesjjames Posts: 2,908
    DHawthorne wrote: »
    Not directly. If you really need that, launch a repeating timeline on the hold, and kill it on the release.

    I prefer this way since HOLDs don't play nicely with arrays (can't fully rely on a GET_LAST in a HOLD.)
  • a_riot42a_riot42 Posts: 1,624
    vining wrote: »
    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
  • viningvining Posts: 4,368
    a_riot42 wrote:
    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:
    {
         PUSH:
    	  {
    	  STACK_VAR INTEGER nTEBtn ;
    	  STACK_VAR INTEGER nTETPIndx ;
    	  STACK_VAR INTEGER nTEBtnBIC ;
    	  
    	  nTEBtnBIC = BUTTON.INPUT.CHANNEL ;
    	  nTEBtn = GET_LAST(nETableBtnArry) ;
    	  nTETPIndx = GET_LAST(dvEventTableTPArry) ;
    	  fnTableEventDeBug("'PUSH: TP-',itoa(nTETPIndx),', tp/array, GET_LAST btn-',itoa(nTEBtn),', BIC btn-',itoa(nTEBtnBIC),'.   >-line-<',ITOA(__LINE__),'>'") ;
    	  if(!nTEHolding)
    	       {
    	       nTECurHoldBtn = nTEBtnBIC ;
    	       nTECurHoldTPIndx = nTETPIndx ;
    	       }
    	  }
         HOLD [10,REPEAT]:
    	  {
    	  nTEHolding = 1 ;
    	  fnTableEventDeBug("'HOLD: TP-',itoa(nTECurHoldTPIndx),', tp/array, HOLD btn-',itoa(nTECurHoldBtn),'.   >-line-<',ITOA(__LINE__),'>'") ;
    	  }
         RELEASE:
    	  {
    	  STACK_VAR INTEGER nTEBtn ;
    	  STACK_VAR INTEGER nTETPIndx ;
    	  STACK_VAR INTEGER nTEBtnBIC ;
    	  
    	  nTEBtnBIC = BUTTON.INPUT.CHANNEL ;
    	  nTEBtn = GET_LAST(nETableBtnArry) ;
    	  nTETPIndx = GET_LAST(dvEventTableTPArry) ;
    	  fnTableEventDeBug("'RELEASE: TP-',itoa(nTETPIndx),', tp/array, GET_LAST btn-',itoa(nTEBtn),', BIC btn-',itoa(nTEBtnBIC),'.   >-line-<',ITOA(__LINE__),'>'") ;
    	  if(nTEHolding && nTETPIndx == nTECurHoldTPIndx && nTEBtnBIC == nTECurHoldBtn)
    	       {
    	       nTEHolding = 0 ;
    	       fnTableEventDeBug("'RELEASE: TP-',itoa(nTETPIndx),', Released Hold!   >-line-<',ITOA(__LINE__),'>'") ;
    	       }
    	  }
         }
    
    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.
  • jjamesjjames Posts: 2,908
    vining wrote: »
    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.
  • HedbergHedberg Posts: 671
    vining wrote: »
    a_riot42 wrote:

    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)
  • viningvining Posts: 4,368
    Hedberg wrote:
    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.
  • jjamesjjames Posts: 2,908
    vining 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.
    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.
  • HedbergHedberg Posts: 671
    vining wrote: »
    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.
  • a_riot42a_riot42 Posts: 1,624
    jjames wrote: »
    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
  • Gold Star on your test paper, jj.
    jjames wrote: »
    ... 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.
  • jjamesjjames Posts: 2,908
    B_Clements wrote: »
    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. :D
  • viningvining Posts: 4,368
    B_Clements wrote:
    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.
  • jjamesjjames Posts: 2,908
    vining wrote: »
    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"
    			}
    		}
    	}
    }
    
  • viningvining Posts: 4,368
    jjames wrote:
    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.
  • viningvining Posts: 4,368
    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 ;
    	  }
         }
    
  • viningvining Posts: 4,368
    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 -- ;
    	       }
    	  }
         }
    
  • Example code using timelines

    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 Up
    	25, // 2 - Volume Down
    	26  // 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 1
    	1, // Master Bedroom 2
    	2, // Kitchen 1
    	2, // Kitchen 2
    	3  // 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 VolStruct Volume[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 zone
    		Volume[nVolumeZone].nVolChange = GET_LAST(dv_TP)
    			
    		// Set volume ramp flag indexed to the button array - 1 Up, 2 Down, 3 Mute
    		Volume[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 flag
    		Volume[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)
    
Sign In or Register to comment.