The installation as a state machine
The classic state machine
This is a partial representation of the Echo & Narcissus installation as a classic state machine diagram:

All the main states are shown, but only some of the state transitions.
Each state is represented by a labelled circle, and each transition between states by a labelled arrow. The arrow label consists of the 'stimulation' which causes the transition, a horizontal line and the 'response' of the system (in addition to the state change) as a result of the transition.
The multi-threaded machine
The actual situation is rather more complicated than this, mainly because the installation player consists of several machines running in parallel in various states.
It is not the case that the system has moved from state 'Intro' to state 'Drama' when the control program has issued the four commmands shown, because the individual players are all responding to those commands with varying delays. It would be possible to define many more states to include the sub-state of each player, but this would get very complicated, and there is a simpler solution...
By putting the commands in the Vplayer-set's output buffer, and specifying gaps of sufficient length, the Vplayers in the set can be driven open-loop. The required gaps are easy to determine by experiment, and are insignificant in the context of the installation.
This is where the Vplayer-set's command output fifo comes into its own. The system is put into the special state 'Changing' at the start of the transition. This effectively inhibits all timed (but not button driven) transitions, while delaying the issuing of commands to allow the players to keep up without blocking Processing frames. This can be seen in the code fragments below. The state is then changed to 'Drama' when all the commands and their required time gaps have been executed.
Code in the 'installation' Processing tab
This code in draw() checks which installation is running, and calls the appropriate xxxx_process():
void draw()
   ...
   // Check for state changes caused by the passage of movie time
   String mstate = vps.mstate;
   int time = vps.mclck.time();
   ... 
   if (mstate.startsWith(MS_ECHO))
      { echo_process(mstate, time); }
   ...
Note the convention for naming states. Each installation is defined with a set of states, and xxxx_start_yyyy() and xxxx_process() functions. The xxxx_start_yyyy() functions are normally connected to buttons on the user interface. This is the process function for the 'Echo' installation:
void echo_process(String mstate, int msecs)
{
   if (mstate.equals(MS_ECHO_INTRO) && msecs > echo_intro_end)
   {
      vps.set_mstate(MS_CHANGING);
      vps.cmd_buffer.add("play_ith", 5000, "1");      
      vps.cmd_buffer.add("seek", "" + echo_drama_starts);   
      vps.cmd_buffer.add("set_mstate", MS_ECHO_DRAMA);      
   }
   if (mstate.equals(MS_ECHO_DRAMA) && msecs > echo_drama_vote_starts) 
   {
      vps.set_mstate(MS_CHANGING);
      vote_on();
      vps.set_mstate(MS_ECHO_VOTE);
   }
   if (mstate.equals(MS_ECHO_VOTE) && msecs > echo_drama_vote_ends)
   {
      vote_off();
      vote_request();
      vps.set_mstate(MS_ECHO_VOTE_OVER);
   }
   if (mstate.equals(MS_ECHO_VOTE_OVER) && msecs > echo_drama_vote_ends + 2000)    // Allow the vote result to be read
   {
      vps.set_mstate(MS_CHANGING);
      int endng = vote_result() + 1;
      println("Ending chosen: " + endng);
      if (endng == 2)
      {
         vps.cmd_buffer.add("seek", "" + echo_drama_ending2_starts);   
         vps.cmd_buffer.add("set_mstate", MS_ECHO_ENDING2);      
      } 
      else if (endng == 3)
      {
         vps.cmd_buffer.add("seek", "" + echo_drama_ending3_starts);   
         vps.cmd_buffer.add("set_mstate", MS_ECHO_ENDING3);      
      } 
      else
      {
         vps.cmd_buffer.add("seek", "" + echo_drama_ending1_starts);   
         vps.cmd_buffer.add("set_mstate", MS_ECHO_ENDING1);      
      } 
   }
   if (mstate.equals(MS_ECHO_ENDING1) && msecs > echo_drama_ending1_ends)
   {
      vps.set_mstate(MS_CHANGING);
      vps.cmd_buffer.add("play_ith", 5000, "2");      
      vps.cmd_buffer.add("seek", "10000");   
      vps.cmd_buffer.add("set_mstate", MS_ECHO_CREDITS);      
   }
   if (mstate.equals(MS_ECHO_ENDING2) && msecs > echo_drama_ending2_ends)
   {
      vps.set_mstate(MS_CHANGING);
      vps.cmd_buffer.add("play_ith", 5000, "2");      
      vps.cmd_buffer.add("seek", "" + echo_credits_starts);   
      vps.cmd_buffer.add("set_mstate", MS_ECHO_CREDITS);      
   }
   if (mstate.equals(MS_ECHO_ENDING3) && msecs > echo_drama_ending3_ends)
   {
      vps.set_mstate(MS_CHANGING);
      vps.cmd_buffer.add("play_ith", 5000, "2");      
      vps.cmd_buffer.add("seek", "" + echo_credits_starts);   
      vps.cmd_buffer.add("set_mstate", MS_ECHO_CREDITS);      
   }
   if (mstate.equals(MS_ECHO_CREDITS) && msecs > echo_credits_end)
   {
      vps.set_mstate(MS_CHANGING);
      vps.cmd_buffer.add("play_ith", 5000, "0");
      vps.cmd_buffer.add("seek", "" + echo_intro_starts);   
      vps.cmd_buffer.add("set_mstate", MS_ECHO_INTRO);  
      vote_off();
      vote_clear();    
   }
}
Notes
  • This statement vps.cmd_buffer.add("play_ith", 5000, "2") means put the command vps.play_ith("2") in the Vplayer_set's command fifo for execution as soon as possible (subject to commands already in the buffer and their gaps), and leave a gap of 5 seconds after it's been executed for the Vplayers to respond.
  • You might ask why the integer 2 in vps.cmd_buffer.add("play_ith", 5000, "2") has to be expressed as a string. This is to allow the Java function overloading mechanism to distinguish between a command with an integer parameter, and one with a gap other than the default.
  • Some state changes happen immediately. For example
    if (mstate.equals(MS_ECHO_VOTE) && msecs > echo_drama_vote_ends)
    {
       vote_off();
       vote_request();
       vps.set_mstate(MS_ECHO_VOTE_OVER);
    }
    
    means that as soon as the movie time exceeds echo_drama_vote_ends while in state MS_ECHO_VOTE, issue vote_off() and vote_request() and change state to MS_ECHO_VOTE_OVER.
  • If the state change involves several Vplayer_set commands, however, with their attendant delays and inertia, as here for example
    if (mstate.equals(MS_ECHO_ENDING1) && msecs > echo_drama_ending1_ends)
    {
       vps.set_mstate(MS_CHANGING);
       vps.cmd_buffer.add("play_ith", 5000, "2");      
       vps.cmd_buffer.add("seek", "10000");   
       vps.cmd_buffer.add("set_mstate", MS_ECHO_CREDITS);      
    }
    
    the player is put into the special 'Changing' state, in which all movie time generated state changes are inhibited, and the final destination state setting command vps.set_mstate(MS_ECHO_CREDITS) is put in the fifo for execution after all the other commands.