/*
PROGRAM NAME	CueDirector.DLL

USED BY			BBC Academy Studio C installation

PROGRAMMER:		Andy Woodhouse

Version: 1.0	Date 25th July 2017		Initial release

Overview
========
This module is the interface between the CTA SDI router, the Studio C SDI router the Studio C mixer
tally, and the Studio C Multiviewer UMD chain.

The serial bitstreams to the Multiviewer UMD displays are created by an instance of the BNCS TSL
driver. The BNCS UMD driver sends a 16 character string for each UMD address to the multiviewer.

The interface to the TSL driver is via an Infodriver. One of the nice features of the TSL driver
for the programmer is that the complex string of brightness, name and cue data can be written to
different slot sets in the infodriver, and the driver manages translation into the final output
string to the UMD.

The slot usage in the TSL driver is:

Slot No.      Mode  Function			Comments
===========   ====  ========            ========
   1 -  256   R/W   Text
 501 -  756   R/W   Cue, Brightness     Driver keeps these slots, the Cue slots,
                                        and the Brightness slots aligned
1001 - 1256   R/W   Cue                 0=Off  1=left cue on  2=right cue on  3=both cue on
1501 - 1756	  R/W   Brightness          0=Off  1=1/7th  2=1/2 (half)  3=full

This module uses a number of look-up tables to convert the raw data from the mixer tally system
and the routing data from the sdi routers into the required settings in the UMD driver slots. The
look up tables enable this module to track the use of the CCU allocations onto the vision mixer,
and output red-lights to the camera only if it has been allocated to the studio.

The tables include:
RTRtoMIX    A list of matrix destinations that feed input to the mixer. Although the first 24
            inputs are direct from the router, an extra	16 can be connected to spare matrix
            outputs via jackfield plugging. The destinations connections data needed by this
            task is stored in slots 181 to 196 of the Tally Infodriver.
Table_A		A list holding the SDI source number for each destination of the Studio C SDI router
Table_B		A list of CTA SDI destinations that feed Studio C router inputs. Index by Studio C router.
Table_C		A list holding the SDI source number for each destination of the CTA SDI router
Table_D		Source to CCU lookup table for CTA router
Table_E		Mixer Channel to CCU lookup. Used for Camera Cue
Table_M		Mixer Input Source List
Table_N		Mixer Tally State
Table_ST	A computed list of 128 entries, storing a logic 1 for those sources that are
			currently "on-air"
Table_KA	Holds the Key Association look-up table. This table supports the generation of UMD
            tally of the primary source, if an associated key signal is used as part of the
            on-air state. For example the character generator key can be used to put a matte
            colour into the image via the DSK. The main character generator monitor in the stack
            should still indicate that the device is "on-air"
Table_F		For each SDI source index, this table lists the UMD addresses that are showing the
            source name. Speeds Tally update processing.
CCU_Alloc	Holds the current allocation status for the CCU devices. Used to decide if a tally
            should be provided to a camera. Values stored are 0 (not allocated) and 1.

This module also manages the CCU primary tally GPI outputs, and monitors for changes in allocation
of the CCU units. Cameras that were "on-air" but are de-allocated from the studio must loose the
on-camera red light, but continue to tally in the stack.

For a full explaination of the processing concepts see document 

    "StC UMD and Camera tally BNCS V4dot5 docver_xdoty"

	where x and y are the version numbers for the Cue Director Automatic

This version of Cue Director includes a Key Association capability. This enables a character generator
fill signal to also tally the key signal if it is shown on the stack. Similarly using the key signal 
will also tally the fill signal. The Key association is controlled by paired entries in the tally infodriver
slots 901 to 1028 (Studio C Tally Association storeage). In essence, the tally is sent to the selected source 
plus the source identified in the Key Association Table. Two entries must be placed in this table for key
association to operate.

For example:
   Fill signal is router input 22
   Key  signal is router input 23.

In tally infodriver slot 922 (data for source 22) place the value 23; in tally infodriver slot 923
(data for source 23).

*/

#include <windows.h>
#include <stdio.h>
#include <bncs_string.h>
#include <bncs_config.h>
#include "CueDirector.h"

#define MAIN_PANEL	1
#define MAIN_PANEL_NAME	"BaseScreen.bncs_ui"
#define STARTUP_PANEL 2
#define STARTUP_PANEL_NAME "StartupScreen.bncs_ui"
#define SHOW_TABLES 3
#define SHOW_TABLES_NAME "ViewTables.bncs_ui"

// Some definitions for matrix names etc in config/instances.XML
#define STC_SDI_ROUTER		"StC SDI Router"
#define CTA_SDI_ROUTER		"CTA SDI Router"
#define TALLY_INFODRV		"CTA Tally Infodriver"
#define STC_UMD_DRIVER		"StC UMD Driver"
#define STC_MIXER_TALLY		"StC Mixer Tally"
#define CCU_PRIMARY_TALLY	"CCU 1 Tally"

#define TALLY_OFF "WN_Tally_Black"
#define TALLY_ON "WN_Tally_Red"

// Properties for the timers used in this module
#define TIMER_SETUP	1
#define	INITIAL_POLL_TIMEOUT 4000
#define UMD_ID_TIMER 2
#define UMD_ID_PERIOD 20
#define CTA_UMD_ID_TIMER 3
#define CTA_UMD_ID_PERIOD 20

// Define the values that represent studio status
#define STUDIO_OFF 0
#define STUDIO_REH 1
#define STUDIO_TX 2


///////////////////////////////////////////////////////////
// Macro to make our class visible to the outside world. //
///////////////////////////////////////////////////////////
EXPORT_BNCS_SCRIPT(CueDirector)

///////////////////////////////////////////////////
// Constructor - equivalent to ApplCore STARTUP. //
///////////////////////////////////////////////////
CueDirector::CueDirector(bncs_client_callback * parent, const char * path) : bncs_script_helper(parent, path)
{
	// Initialise the user interface panels so that they can get display data pushed to them even if the
	// panel is not on show.
	panelInit(STARTUP_PANEL, STARTUP_PANEL_NAME);
	panelInit(SHOW_TABLES, SHOW_TABLES_NAME);
	panelInit(MAIN_PANEL, MAIN_PANEL_NAME);

	textPut("clear", STARTUP_PANEL, "Status");		// Clear the listbox that holds the status messages.
	textPut("add", "Initialise variables to safe values.", STARTUP_PANEL, "Status");

	// Initialise variables to ensure "safe" initial conditions.
	stc_sdi_id = 0;			// Holds the BNCS driver ID for Studio C SDI router
	cta_sdi_id = 0;			// Holds the BNCS driver id for CTA SDI router
	tally_info_id = 0;		// Holds the BNCS driver id for the "Tally" infodriver
	stc_mixer_id = 0;		// Holds the BNCS driver id for the Studio C mixer tallies
	stc_tsl_id = 0;			// Holds the BNCS driver id for the Studio C TSL driver
	tally_gpi_id = 0;		// GPI driver for camera tally
	primary_offset = 0;		// Contact number of first primary tally GPI out relay

	// Working data
	revert_count = 0;			// Used during the initial revertive processing.

	for (int i = 0; i <= NUM_MIX_INPUT; i++) RTRtoMIX[i] = 0;
	for (int i = 0; i <= SDI_MTX_SIZE; i++) Table_A[i] = 0;
	for (int i = 0; i <= SDI_MTX_SIZE; i++) Table_B[i] = 0;
	for (int i = 0; i <= SDI_MTX_SIZE; i++) Table_C[i] = 0;
	for (int i = 0; i <= SDI_MTX_SIZE; i++) Table_D[i] = 0;
	for (int i = 0; i <= NUM_MIX_INPUT; i++) Table_E[i] = 0;
	for (int i = 0; i <= SDI_MTX_SIZE; i++) Table_KA[i] = 0;
	for (int i = 0; i <= NUM_MIX_INPUT; i++) Table_M[i] = 0;
	for (int i = 0; i <= NUM_MIX_INPUT; i++) Table_N[i] = 0;
	for (int i = 0; i <= SDI_MTX_SIZE; i++) Table_ST[i] = false;
	for (int i = 0; i <= NUM_CCUS; i++) CCU_Alloc[i] = 0;

	// Set the bit stores to empty.
	clearAllCheckBit(stc_sdi_poll);			// Clear revertive check array for StC SDI
	clearAllCheckBit(cta_sdi_poll);			// Clear revertive check array for CTA SDI
	clearAllCheckBit(tally_poll);			// Clear revertive check array for Tally Infodriver
	clearAllCheckBit(mixer_poll);			// Clear revertive check array for mixer tallies

	drivers_registered = false;
	init_polls = true;
	timeout_active = false;
	stc_sdi_allpolled = false;
	cta_sdi_allpolled = false;
	mixer_allpolled = false;
	tally_allpolled = false;

	textPut("add", "Initialise variables completed.", STARTUP_PANEL, "Status");

	// Read matrix id's from instances.xml
	getDev(STC_SDI_ROUTER, &stc_sdi_id);		// Studio C SDI router driver ID
	getDev(CTA_SDI_ROUTER, &cta_sdi_id);		// CTA SDI router driver ID
	getDev(TALLY_INFODRV, &tally_info_id);		// "Tally" Infodriver driver ID
	getDev(STC_MIXER_TALLY, &stc_mixer_id);		// Studio C Mixer tally driver ID
	getDev(STC_UMD_DRIVER, &stc_tsl_id);		// Studio C TSL UMD driver ID
	getDev(CCU_PRIMARY_TALLY, &tally_gpi_id, &primary_offset);		// Get CCU Red lights GPI driver and contact offsets

	// Report driver ID's to status listbox
	textPut("add", "Studio C SDI router id = " + bncs_string(stc_sdi_id), STARTUP_PANEL, "Status");
	textPut("add", "CTA SDI router id = " + bncs_string(cta_sdi_id), STARTUP_PANEL, "Status");
	textPut("add", "Tally Infodriver id = " + bncs_string(tally_info_id), STARTUP_PANEL, "Status");
	textPut("add", "Studio C Kahuna Mixer tally driver id = " + bncs_string(stc_mixer_id), STARTUP_PANEL, "Status");
	textPut("add", "Studio C TSL UMD tally driver id = " + bncs_string(stc_tsl_id), STARTUP_PANEL, "Status");
	textPut("add", "CCU Primary Tally GPI driver id = " + bncs_string(tally_gpi_id), STARTUP_PANEL, "Status");
	textPut("add", "CCU GPI driver offset to first CCU Switch = " + bncs_string(primary_offset), STARTUP_PANEL, "Status");

	if ((stc_sdi_id != 0) && (cta_sdi_id != 0) && (tally_info_id != 0) && (stc_mixer_id != 0))
		textPut("add", "All driver ID values are non-zero - continuing.", STARTUP_PANEL, "Status");
	else
	{
		textPut("add", "INVALID DRIVER ID found. Investigate, Fix and Restart this task", STARTUP_PANEL, "Status");
		return;		// Exit with Status box on show
	}

	// Read the string with a list of CCU matrix sources from object_settings.xml
	// This list is used to build Table_D THE SOURCE TO ccu
	ccuList = getObjectSetting("CTA_CCU_SRCID", "cculist");

	// Build Table_D - the source to CCU lookup. It was initially filled with 0, so we need to extract the numbers 
	// from cculist and index into table_D using the value of each entry as the index, and the (loop count + 1)
	// as the CCU number
	for (int idx = 0; idx < ccuList.count(); idx++)
	{
		Table_D[ccuList[idx]] = idx + 1;	// Offset from base 0 to base 1
	}

	// Read the string which has the list of CTA SDI destinations that feed Studio C mixer inputs
	stc_iml = getObjectSetting("CTA_TO_STC", "iml_inputs");
	stcFromCta = getObjectSetting("CTA_TO_STC", "ctadest");
	for (int idx = 0; idx < stc_iml.count(); idx++)
	{
		Table_B[stc_iml[idx]] = stcFromCta[idx];
	}

	// Create the first 24 entries in Table RTRtoMIX (first 24 destinations of the router).
	for (int idx = 0; idx <= 24; idx++) RTRtoMIX[idx] = idx;

	// "Zap" the slots in the Studio C UMD driver to no_tally, full brightness
	// Note that the multiviewer software ignores the brightness in all software releases to date.

	for (int i = 1; i <= 128; i++)
	{
		infoWrite(stc_tsl_id, "0,3", i + 500);		// Write no tally, full brightness.
	}

	// Register for revertives from various drivers
	textPut("add", "Register for revertives.", STARTUP_PANEL, "Status");
	routerRegister(stc_sdi_id, 1, SDI_MTX_SIZE);		// Local Studio C router
	routerRegister(cta_sdi_id, 1, SDI_MTX_SIZE);		// All destinations of CTA SDI router
	infoRegister(tally_info_id, 1, STC_KEY_ASSOC + SDI_MTX_SIZE - 1);	
	infoRegister(stc_mixer_id, 1, NUM_MIX_INPUT);
	drivers_registered = true;

	// Now gather data using the revertives from the various drivers
	// Initialise the timer that is used for timeouts, and start the information inputs
	textPut("add", "Initial poll of Studio C SDI router issued.", STARTUP_PANEL, "Status");
	timerStart(TIMER_SETUP, INITIAL_POLL_TIMEOUT);	// Allow a 2 second period for the first return of revertive data
	timeout_active = true;
	routerPoll(stc_sdi_id, 1, SDI_MTX_SIZE);		// Read the SDI router state.

	// Show the startup panel that displays the status reports during automatic start up.
	panelShow(STARTUP_PANEL, STARTUP_PANEL_NAME);
}


////////////////////////////////////////////////////
// Destructor - equivalent to ApplCore CLOSEDOWN. //
////////////////////////////////////////////////////
CueDirector::~CueDirector()
{
	if (drivers_registered)
	{
		if (stc_sdi_id) routerUnregister(stc_sdi_id);
		if (cta_sdi_id) routerUnregister(cta_sdi_id);
		if (tally_info_id) infoUnregister(tally_info_id);
		if (stc_mixer_id) infoUnregister(stc_mixer_id);
	}
}


////////////////////////////////////////////////////
// All button pushes and notifications come here. //
////////////////////////////////////////////////////
void CueDirector::buttonCallback(buttonNotify *b)
{
	if (b->panel() == MAIN_PANEL)
	{
		if (b->id() == "ShowTables")
		{
			// Show the table viewer panel
			panelRemove(MAIN_PANEL);
			panelShow(SHOW_TABLES, SHOW_TABLES_NAME);
		}

		if (b->id() == "ShowStart")
		{
			// Show the status data issued during panel startup
			panelRemove(MAIN_PANEL);
			panelShow(STARTUP_PANEL, STARTUP_PANEL_NAME);
		}
	}

	if (b->panel() == STARTUP_PANEL)
	{
		if (b->id() == "ShowStatus")
		{
			// Show the table viewer panel
			panelRemove(STARTUP_PANEL);
			panelShow(MAIN_PANEL, MAIN_PANEL_NAME);
		}

		if (b->id() == "ShowTables")
		{
			// Show the status data issued during panel startup
			panelRemove(STARTUP_PANEL);
			panelShow(SHOW_TABLES, SHOW_TABLES_NAME);
		}
	}

	if (b->panel() == SHOW_TABLES)
	{
		if (b->id() == "ShowStatus")
		{
			// Show the table viewer panel
			panelRemove(SHOW_TABLES);
			panelShow(MAIN_PANEL, MAIN_PANEL_NAME);
		}

		if (b->id() == "ShowStart")
		{
			// Show the status data issued during panel startup
			panelRemove(SHOW_TABLES);
			panelShow(STARTUP_PANEL, STARTUP_PANEL_NAME);
		}

		// Now check for the table display request buttons
		if (b->id() == "1")
		{
			// Show Look Up Table A - StC SDI Router Map
			textPut("clear", SHOW_TABLES, "Displaybox");
			textPut("text", "Table A - Studio C SDI Router Map", SHOW_TABLES, "TableName");
			for (int i = 1; i <= SDI_MTX_SIZE; i++)
				textPut("add", bncs_string("%1 = %2").arg(i, '0', 3, 10).arg(Table_A[i]), SHOW_TABLES, "Displaybox");
		}

		if (b->id() == "2")
		{
			// Show Look Up Table B - CTA to Studio C 
			textPut("clear", SHOW_TABLES, "Displaybox");
			textPut("text", "Table B - CTA to Studio C Mapping", SHOW_TABLES, "TableName");
			for (int i = 1; i <= SDI_MTX_SIZE; i++)
				textPut("add", bncs_string("%1 = %2").arg(i, '0', 3, 10).arg(Table_B[i]), SHOW_TABLES, "Displaybox");
		}

		if (b->id() == "3")
		{
			// Show Look Up Table C - CTA SDI Router Map
			textPut("clear", SHOW_TABLES, "Displaybox");
			textPut("text", "Table_C - CTA SDI Router Map", SHOW_TABLES, "TableName");
			for (int i = 1; i <= SDI_MTX_SIZE; i++)
				textPut("add", bncs_string("%1 = %2").arg(i, '0', 3, 10).arg(Table_C[i]), SHOW_TABLES, "Displaybox");
		}

		if (b->id() == "4")
		{
			// Show Look Up Table D
			textPut("clear", SHOW_TABLES, "Displaybox");
			textPut("text", "Table_D - CTA Source to CCU Map", SHOW_TABLES, "TableName");
			for (int i = 1; i <= SDI_MTX_SIZE; i++)
				textPut("add", bncs_string("%1 = %2").arg(i, '0', 3, 10).arg(Table_D[i]), SHOW_TABLES, "Displaybox");
		}

		if (b->id() == "5")
		{
			// Show Look Up Table E - Mixer Channel to CCU ID
			textPut("clear", SHOW_TABLES, "Displaybox");
			textPut("text", "Table_E - Mixer Channel to CCU ID", SHOW_TABLES, "TableName");
			for (int i = 1; i <= NUM_MIX_INPUT; i++)
				textPut("add", bncs_string("%1 = %2").arg(i, '0', 3, 10).arg(Table_E[i]), SHOW_TABLES, "Displaybox");
		}

		if (b->id() == "6")
		{
			// Show Look Up Table M - Mixer Input Source Map
			textPut("clear", SHOW_TABLES, "Displaybox");
			textPut("text", "Table_C - SDI Router current source id", SHOW_TABLES, "TableName");
			for (int i = 1; i <= NUM_MIX_INPUT; i++)
				textPut("add", bncs_string("%1 = %2").arg(i, '0', 3, 10).arg(Table_M[i]), SHOW_TABLES, "Displaybox");
		}

		if (b->id() == "7")
		{
			// Show Table_N
			textPut("clear", SHOW_TABLES, "Displaybox");
			textPut("text", "Table_N - Studio A chain UMD address", SHOW_TABLES, "TableName");
			for (int i = 1; i <= SDI_MTX_SIZE; i++)
				textPut("add", bncs_string("%1 = %2").arg(i, '0', 3, 10).arg(Table_N[i]), SHOW_TABLES, "Displaybox");
		}

		if (b->id() == "8")
		{
			// Show Table_ST
			textPut("clear", SHOW_TABLES, "Displaybox");
			textPut("text", "Table_ST - ", SHOW_TABLES, "TableName");
			for (int i = 1; i <= SDI_MTX_SIZE; i++)
				textPut("add", bncs_string("%1 = %2").arg(i, '0', 3, 10).arg(Table_ST[i]), SHOW_TABLES, "Displaybox");
		}

		if (b->id() == "9")
		{
			// Show Table_F
			textPut("clear", SHOW_TABLES, "Displaybox");
			textPut("text", "Table_F - UMD addresses displaying source", SHOW_TABLES, "TableName");
			for (int i = 1; i <= SDI_MTX_SIZE; i++)
				textPut("add", bncs_string("%1 = %2").arg(i, '0', 3, 10).arg(Table_F[i]), SHOW_TABLES, "Displaybox");
		}

		if (b->id() == "10")
		{
			// Show RTRtoMIX table
			textPut("clear", SHOW_TABLES, "Displaybox");	// Clear the list box area
			textPut("text", "RTRtoMIX - Router destination no feeding mixer inputs", SHOW_TABLES, "TableName");
			for (int i = 1; i <= NUM_MIX_INPUT; i++)
				textPut("add", bncs_string("%1 = %2").arg(i, '0', 3, 10).arg(RTRtoMIX[i]), SHOW_TABLES, "Displaybox");
		}

		if (b->id() == "11")
		{
			// Show Table_KA - Key Association
			textPut("clear", SHOW_TABLES, "Displaybox");
			textPut("text", "Table_KA - Studio C Key Association", SHOW_TABLES, "TableName");
			
			for (int i = 1; i <= SDI_MTX_SIZE; i++)
			textPut("add", bncs_string("%1 = %2").arg(i, '0', 3, 10).arg(Table_KA[i]), SHOW_TABLES, "Displaybox");
		}

		if (b->id() == "12")
		{
			// Show Table_???
			textPut("clear", SHOW_TABLES, "Displaybox");
			textPut("text", "Not yet defined", SHOW_TABLES, "TableName");
			/*
			for (int i = 1; i <= SDI_MTX_SIZE; i++)
			textPut("add", bncs_string("%1 = %2").arg(i, '0', 3, 10).arg(Table_E[i]), SHOW_TABLES, "Displaybox");
			*/
		}

	}	// End of if (b->panel() == SHOW_TABLES)
}


///////////////////////////////
// All revertives come here. //
///////////////////////////////
int CueDirector::revertiveCallback(revertiveNotify * r)
{

	// There are two states for the revertives - "initial" and "running". There are a lot of things to read in the 
	// startup phase, and therefore a check is made on the revertives returned in the initial burst to make sure
	// none are missing. The status for the revertives are flaged by the state of boolean variable init_polls.
	if (init_polls)
		initialRevertives(r);
	else
		runningRevertives(r);

	return 0;
}


//////////////////////////////////////////////
// All database name changes come back here //
//////////////////////////////////////////////
void CueDirector::databaseCallback(revertiveNotify * r)
{
}


///////////////////////////////////////////////////////////////////////////////////
// All parent notifications come here. When this script is just one component    //
// of another dialog, our host might want to tell us things or ask us questions. //
//                                                                               //
// NOTE: Not used by this Automatic - Minimum support only coded below.          //
///////////////////////////////////////////////////////////////////////////////////
bncs_string CueDirector::parentCallback(parentNotify *p)
{
	if (p->command() == "return")
	{
		if (p->value() == "all")
		{	
			// Persisting values for bncs_vis_ed
			bncs_stringlist sl;
			sl << bncs_string("");
			return sl.toString('\n');
		}
	}

	// ***** CONNECTIONS EVENTS HELPER LIST *****
	else if (p->command() == "_events")
	{
		// Helper-list of everything in this component generated by hostNotify's
		bncs_stringlist sl;
		sl << "";			
		return sl.toString('\n');
	}

	// ***** CONNECTIONS COMMANDS HELPER LIST *****
	else if (p->command() == "_commands")
	{	
		// Helper-list of any commands/parameters you might want to set at run-time
		bncs_stringlist sl;
		sl << "";
		return sl.toString('\n');
	}

	return "";
}


////////////////////////////
// Timer events come here //
////////////////////////////
void CueDirector::timerCallback(int id)
{
	switch(id)
	{
		case TIMER_SETUP:
			// This timer is used during the initial poll to check that the poll has completed in an appropriate
			// time period (4 seconds). If the poll has timed out, it is re-issued, and the timeout period restarted.
			if (!stc_sdi_allpolled)		// Has the sdi router replied to all the poll range?
			{
				textPut("add", "+++ Re-issue initial poll of SDI router.", STARTUP_PANEL, "Status");
				timerStart(TIMER_SETUP, INITIAL_POLL_TIMEOUT);	// Allow a 4 second period for the first return of revertive data
				timeout_active = true;
				routerPoll(stc_sdi_id, 1, SDI_MTX_SIZE);		// Read the Studio C SDI matrix state
				break;					// Do not test other tally sources until studio C router is known
			}

			if (!cta_sdi_allpolled)		// Has the sdi router replied to all the poll range?
			{
				textPut("add", "+++ Re-issue initial poll of SDI router.", STARTUP_PANEL, "Status");
				timerStart(TIMER_SETUP, INITIAL_POLL_TIMEOUT);	// Allow a 4 second period for the first return of revertive data
				timeout_active = true;
				routerPoll(cta_sdi_id, 1, SDI_MTX_SIZE);		// Read the CTA SDI matrix state
				break;					// Do not test other tally sources until CTA router state is known
			}

			if (!tally_allpolled)
			{
				textPut("add", "+++ Re-issue initial poll of tally infodriver.", STARTUP_PANEL, "Status");
				timerStart(TIMER_SETUP, INITIAL_POLL_TIMEOUT);	// Allow a 2 second period for the first retuen of revertive data
				timeout_active = true;
				pollall_tally_infodriver();			// Issue Polls for the various tally infodriver blocks.
				break;
			}

			if (!mixer_allpolled)
			{
				textPut("add", "+++ Re-issue initial poll of Mixer Tally state.", STARTUP_PANEL, "Status");
				timerStart(TIMER_SETUP, INITIAL_POLL_TIMEOUT);	// Allow a 2 second period for the first retuen of revertive data
				timeout_active = true;
				infoPoll(stc_mixer_id, 1, NUM_MIX_INPUT);		// Poll the Mixer Tally status.
				break;
			}

			if (stc_sdi_allpolled && cta_sdi_allpolled && tally_allpolled && mixer_allpolled)
			{
				timeout_active = false;
				timerStop(TIMER_SETUP);
				textPut("add", "All revertives received. Stopping timeout timer.", STARTUP_PANEL, "Status");
			}

			break;

		default:	// Unhandled timer event
			timerStop(id);
			break;
	}
}


///////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////// Callbacks above - Methods below ///////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////

// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// Bit Stores
// ==========
// Before starting the process loop this cue director needs the current status of various elements 
// of equipment such as SDI routers and mixer cues. Once these are known the cue director software
// can build the look up tables it requires. 
//
// A poll request is issued as part of the startup routines. As each revertive is delivered, the
// data is stored in a simple array, and a 'tick' is placed against the destination number that 
// reported the revertive. The Bit Store is the unit that holds the ticks. The bit store comprises
// an array of unsigned integers that form an array of bits. A 4 element bit sore is shown below.
//
//             store[3]         store[2]         store[1]         store[0]     
//         ________________ ________________ ________________ ________________ 
//        |________________|________________|________________|________________|
//         32            01 32            01 32            01 32            01
// Bit id 128...............................................................1
//
// There are 3 support functions:
// 1)    clearAllCheckBit(UINT32 *store)
//           Sets all bits in the array store to 0
//
// 2)    setCheckBit(int bit_ID, UINT32 *store)
//           Sets bit bit_ID of the array store to 1. bit_id must be between 1 and 256
//
// 3)    countCheckBit(UINT32 *store)
//           Counts the number of 1 bits and returns that value. The caller can then check if 
//           this matches the number for "all known".
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

/////////////////////////////////////////////////////////////////////////////////////////////////
// function CueDirector::clearAllCheckBit(UINT32 *store) clears all bits in the store to logic 0.
//
void CueDirector::clearAllCheckBit(UINT32 *store)
{
	// We can use a fast clear function that sets all of the INT32 elements to 0
	for (int i = 0; i < BIT_STORE_SIZE; i++)
	{
		store[i] = 0;
	}
}

/////////////////////////////////////////////////////////////////////////////////////////////////
// function CueDirector::setCheckBit(int bit_ID, UINT32 *store) Sets bit "bit_ID" to a logic 1.
// Some protection against invalid bit numbers is implemented.
void CueDirector::setCheckBit(int bit_ID, UINT32 *store)
{
	int wordID;				// Which 32 bit integer holds bit "bit_id"?
	int element;			// Which bit within wordID has to be set
	UINT32 bitMask = 1;		// The mask we use to set the bit

	if ((bit_ID <= 0) || (bit_ID > BIT_SIZE))
	{
		debug(bncs_string("CueDirector:  setCheckBit reports invalid bit index of %1. Ignoring\n").arg(bit_ID));
		return;
	}

	wordID = (bit_ID - 1) / 32;	// Compute which unsigned integer has bit of interest - range 0 to (BIT_STORE_SIZE - 1)
	element = (bit_ID - 1) % 32;			// Compute which bit of the unsigned integer has to be set - range 0 to 31

	// Shift the initial mask left by relevant number of bits. A value of zero has correct mask
	if (element) bitMask = bitMask << element;

	// Set the bit by using an OR operation

	store[wordID] |= bitMask;
}


/////////////////////////////////////////////////////////////////////////////////////////////////
// function CueDirector::countCheckBit(UINT32 *store) returns the number of bits set to a 1 in 
// the bit store; 
int  CueDirector::countCheckBit(UINT32 *store)				// Returns the number of '1" bits in unsigned integer array "store"
{
	int sum = 0;
	UINT32 mask;
	UINT32 bitSet;

	for (int idx = 0; idx < BIT_STORE_SIZE; idx++)
	{
		mask = 1;						// Initialise mask to rightmost bit
		bitSet = store[idx];			// Fetch the 32 bit integer
		for (int i = 0; i < 32; i++)
		{
			if (bitSet & mask) sum++;	// Add 1 if the bit is set
			mask <<= 1;					// Shift the mask bit left 1 bit position
		}
	}

	return sum;
}



// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //
// Revertive processsing differs between initial capture of the values from  //
// the routers and the infodrivers and subsequent updates to those values.   //
// initialRevertives() is the routine that does the processing until we have //
// received values from all data sources.                                    //
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //
void CueDirector::initialRevertives(revertiveNotify *r)
{
	// This method is called to process the revertives during the initial phase of task startup.

	// ****************************** //
	// Studio C SDI Router Revertives //
	// ****************************** //
	if (r->device() == stc_sdi_id)
	{
		// Record the initial revertives for the Studio C router into Table A
		Table_A[r->index()] = r->info();			// Save the crosspoint
		setCheckBit(r->index(), stc_sdi_poll);		// Note which destination reported it's state

		if (countCheckBit(stc_sdi_poll) == SDI_MTX_SIZE)
		{
			stc_sdi_allpolled = true;
			textPut("add", "Initial Studio C SDI router poll completed.", STARTUP_PANEL, "Status");
			textPut("add", "Initial poll of CTA SDI router issued.", STARTUP_PANEL, "Status");
			timeout_active = true;
			timerStart(TIMER_SETUP, INITIAL_POLL_TIMEOUT);	// Allow a 4 second period for the first return of revertive data
			routerPoll(cta_sdi_id, 1, SDI_MTX_SIZE);		// StC SDI polled, now poll CTA SDI
		}
	}
	 

	// ************************* //
	// CTA SDI Router Revertives //
	// ************************* //
	if (r->device() == cta_sdi_id)
	{
		// Record the initial revertives from the CTA SDI router
		Table_C[r->index()] = r->info();		// Record the data returned
		setCheckBit(r->index(), cta_sdi_poll);	// Note that we have received a response for this matrix destination

		// Have we yet had all the revertives from the CTA SDI router?
		if (countCheckBit(cta_sdi_poll) == SDI_MTX_SIZE)
		{
			// All CTA SDI revertives received. Stop timeout timer. Poll next data source
			cta_sdi_allpolled = true;
			textPut("add", "Initial CTA SDI router poll completed.", STARTUP_PANEL, "Status");
			textPut("add", "Initial poll of Tally Infodriver issued.", STARTUP_PANEL, "Status");
			timeout_active = true;
			timerStart(TIMER_SETUP, INITIAL_POLL_TIMEOUT);	// Allow a 4 second period for the first return of revertive data
			pollall_tally_infodriver();						// StC SDI polled, now poll the slots of interest in Tally Infodriver

		}	// End of if (revert_count == SDI_MTX_SIZE)...
	}	// End of if (r->device() == cta_sdi_id)...



	// *************************** //
	// Tally Infodriver Revertives //
	// *************************** //
	if (r->device() == tally_info_id)
	{
		// Record the initial tally revertives in relevant stores. These are in 3 address groups
		// Studio Off/Reh/Tx Status
		if (r->index() == 3)
		{
			// Studio Off/Rehearse/Transmit Status
			StudioC_TX_Status = r->sInfo().toInt();		// Record status
			setCheckBit(r->index(), tally_poll);		// Note revertive received
		}

		// CCU Allocation	
		if ((r->index() >= CCU_ALLOC_1) && (r->index() <= CCU_ALLOC_10))
		{
			// Table CCU_Alloc[] was initially filled with zero's in the constructor code.
			CCU_Alloc[r->index() - CCU_ALLOC_1 + 1] = (r->sInfo() == "3") ? 1 : 0;	// "3" is index for studio C
			setCheckBit(r->index(), tally_poll);		// Note revertive received
		}

		// Studio C Patch Mixer Inputs
		if ((r->index() >= MTX_TO_MIX_25) && (r->index() <= MTX_TO_MIX_40))
		{
			// Mixer Inputs that are driven by spare matrix outputs. Matrix destination stored in tally infodriver
			// We use indexes 181 to 196 to record revertive reception of Patch data.
			// The revertive values are stored in indicies 25 to 40 of Table RTRtoMIX
			RTRtoMIX[(r->index() - MTX_TO_MIX_25) + 25] = r->sInfo().toInt();	// Save Data
			setCheckBit(r->index(), tally_poll);									// Note revertive received
		}

		// Studio C Key Association Data
		if ((r->index() >= STC_KEY_ASSOC) && (r->index() <= (STC_KEY_ASSOC + SDI_MTX_SIZE - 1)))
		{
			// Key Association Look Up table is Table_KA. Uses slots 901 to 1028 in Tally Infodriver
			// So need to remap to tally received offsets 31 to 158
			Table_KA[r->index() - STC_KEY_ASSOC + 1] = r->sInfo().toInt();	// Save Data
			setCheckBit(r->index() - STC_KEY_ASSOC + 31, tally_poll);		// Note revertive received
		}

		// Have we yet had all the revertives from the tally infodriver?
		// Expect Rx/Tx state, 10 CCU, 16 mixer input patches plus 128 Key Association
		if (countCheckBit(tally_poll) == (1 + NUM_CCUS + NUM_MIX_PATCHED + SDI_MTX_SIZE))
		{
			tally_allpolled = true;
			textPut("add", "Initial Tally infodriver poll completed.", STARTUP_PANEL, "Status");
			textPut("add", "Initial poll of mixer On-Air tally states issued.", STARTUP_PANEL, "Status");
			timeout_active = true;
			timerStart(TIMER_SETUP, INITIAL_POLL_TIMEOUT);		// Allow a 4 second period for the first return of revertive data
			infoPoll(stc_mixer_id, 1, NUM_MIX_INPUT);			// Poll the infodriver that is the mixer tally interface
		}	// End of if (revert_count == (NUM_MIX_INPUT+5+(SDI_MTX_SIZE*3)))
	}	// End of if (r->device() == tally_info_id)


	// ********************** //
	// Mixer Tally Revertives //
	// ********************** //
	if (r->device() == stc_mixer_id)
	{
		// Record the revertives from the Studio C mixer tally state
		Table_N[r->index()] = r->sInfo().toInt();
		setCheckBit(r->index(), mixer_poll);

		// Have we yet received revertives from all mixer tally slots?
		if (countCheckBit(mixer_poll) == NUM_MIX_INPUT)
		{
			mixer_allpolled = true;
			textPut("add", "Initial Mixer tally poll completed.", STARTUP_PANEL, "Status");
		}	// End if (revert_count == NUM_MIX_INPUT)...

	}	// End of if (r->device() == sta_mixer_id)

	// Have all the initial polls been completed? 
	if (stc_sdi_allpolled && cta_sdi_allpolled && mixer_allpolled && tally_allpolled)
	{
		// Swap the display panels to the default mimic
		panelRemove(STARTUP_PANEL);
		panelShow(MAIN_PANEL, MAIN_PANEL_NAME);

		buildTable_M();			// Create the initial mixer input source list
		buildTable_E();			// Create the mixer channel to CCU map
		buildTable_ST();		// Create the table that has the 'OR'ed tally. ST stands for Source Tally.
		buildTable_F();			// Build the list of UMDs showing each source
		updateAll();			// Set cue lights and tallies for all possible sources
		init_polls = false;		// Change revertive processing mode
	}

	return;
}	// End of function initialRevertives()



// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //
// function runningRevertives(revertiveNotify *r) processes update revertives from the various data sources such as mixer tally //
// changes, SDI router crosspoint changes, tally infodriver changes. The internal tables are updated in response to the changed //
// data, and new values set for the mimics and UMD chains. This function is used to prosess revertives only after the initial   //
// states of the sdi router etc have been read and output values established by initialRevertives(revertiveNotify *r).                                                   //
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //
void CueDirector::runningRevertives(revertiveNotify *r)
{
	// Start processing by checking for the most probable event - Mixer Tally Change

	if (r->device() == stc_mixer_id)
	{
		int srcID;				// Store source ID for mixer channel that has changed tally
		int KAsrcID;			// Store source ID for any Key Association channel
		bool assocTally;		// The logical OR of the mixer tally for srcID and KAsrcID when key association is active

		// Mixer Tally Change
		// ==================
		// Step 1 - Update the local copy of the mixer tally status in Table_N
		Table_N[r->index()] = r->sInfo().toInt();
		
		// Step 2 - Update (rebuild) the Source Tally table
		buildTable_ST();

		// Step 3 - Use the changed mixer channel index (ie r->index()) to set new cue status in UMD infodriver and panel mimic.
		// Process outline:
		// We know which mixer channel changed tally state (r->index()). Table_M[] holds the source number for each mixer input.
		// Thus the source number that changed is Table_M[r->index()]. Table_F[] has a comma delimited string of UMD addresses
		// indexed by source ID. So Table_F[Table_M[r->index()]] is a list of umd addresses for the changed tally state. Loop
		// through the list of addresses updating the local mimic and the TSL driver cue slot.
		//
		//  REWRITE THE ABOVE DESCRIPTION

		// Test for a Key Associated source.
		srcID = Table_M[r->index()];		// Find primary source ID using table M
		KAsrcID = Table_KA[srcID];			// Find the entry in the source association table.
		if (srcID != KAsrcID)
		{
			// Key association is active. We need to form a logical "OR" of tally for the primary source and 
			// tally for the associated source
			assocTally = Table_ST[srcID] || Table_ST[KAsrcID];

			// Now send the OR'd tally to the UMD list for primary and associated sources.
			// Start with the primary source list.
			umd_addr = bncs_stringlist(Table_F[srcID]);		// Get comma delimited string
			if (umd_addr.count() != 0)						// Only need to process if UMD list is not empty
			{
				for (int j = 0; j < umd_addr.count(); j++)
				{
					infoWrite(stc_tsl_id, assocTally ? bncs_string("3") : bncs_string("0"), 1000 + umd_addr[j].toInt());					// UMD Cue light
					textPut("statesheet", assocTally ? TALLY_ON : TALLY_OFF, MAIN_PANEL, "UMD_" + bncs_string(umd_addr[j].toInt(), '0', 2U, 10));	// Update the mimic tally
				}
			}

			// Repeat UMD process for associated source
			umd_addr = bncs_stringlist(Table_F[KAsrcID]);		// Get comma delimited string
			if (umd_addr.count() != 0)						// Only need to process if UMD list is not empty
			{
				for (int j = 0; j < umd_addr.count(); j++)
				{
					infoWrite(stc_tsl_id, assocTally ? bncs_string("3") : bncs_string("0"), 1000 + umd_addr[j].toInt());					// UMD Cue light
					textPut("statesheet", assocTally ? TALLY_ON : TALLY_OFF, MAIN_PANEL, "UMD_" + bncs_string(umd_addr[j].toInt(), '0', 2U, 10));	// Update the mimic tally
				}
			}
		}
		else
		{
			// No key association. Process only for the primary source
			umd_addr = bncs_stringlist(Table_F[srcID]);			// Get the comma delimited string. Convert to bncs_stringlist.
			if (umd_addr.count() != 0)	// Only need to do processing if UMD list is not empty
			{
				for (int j = 0; j < umd_addr.count(); j++)
				{
					infoWrite(stc_tsl_id, Table_ST[srcID] ? bncs_string("3") : bncs_string("0"), 1000 + umd_addr[j].toInt());					// UMD Cue light
					textPut("statesheet", (Table_ST[srcID] ? TALLY_ON : TALLY_OFF), MAIN_PANEL, "UMD_" + bncs_string(umd_addr[j].toInt(), '0', 2U, 10));	// Update the mimic tally
				}
			}
		}

		// Process the Camera Red Lights.
		cameraTally(r->index());		

		return;
	}


	// ***************************** //
	// SDI Router Crosspoint Changes //
	// ***************************** //

	// Studio C Local SDI Router
	if (r->device() == stc_sdi_id)
	{
		int oldSource;
		int newSource;
		int myDest;
		bool tallyState;

		// Studio C SDI router has 24 outputs that directly connect to the mixer inputs. Some spare outputs 
		// may also be connected to inputs 25 to 40 via a jackfield behind the mixer. For tally to function
		// for these routes, the routing is held in the Tally Infodriver and hence reported to this module.
		// The router also sends 48 signals to the multiviewer. So if a multiviewer route changes the tally
		// state for that destination must be re-computed and output.
		myDest = r->index();			// We use this value quite often - so a local copy saves multiple function calls
		newSource = r->info();			// Newly selected source
		oldSource = Table_A[myDest];	// Note the previously selected source on changed destination

		Table_A[myDest] = newSource;		// Update the local table copy of the Studio C sdi router crosspoint map

		if (newSource == oldSource) return;	// Source has not changed - no processing required.

		// 1) Look for a multiviewer destination. This is one where r->index() is between MV01 and MV48
		if ((myDest >= MV01) && (myDest <= MV48))
		{
			// Table_F[] holds a comma delimited list of UMD addresses that are showing each source number. A
			// change to a multiviewer destination therefore requires a re-build of that address table.
			buildTable_F();

			// Use Table_ST[] to fetch the current tally state for the source, and send that to the changed
			// multiviewer UMD address (address = r->index() - MV01 + 1). Need to update tally state of both
			// the multiviewer and the local mimic.
			tallyState = Table_ST[newSource];
			infoWrite(stc_tsl_id, tallyState ? bncs_string("3") : bncs_string("0"), 1000 + (myDest - MV01 + 1));	// UMD Cue light
			textPut("statesheet", tallyState ? TALLY_ON : TALLY_OFF, MAIN_PANEL, "UMD_" + bncs_string((myDest - MV01 + 1), '0', 2U, 10));	// Update the mimic tally
		}

		// 2) Look for any mixer input destination that has changed. Note that it is possible to route the same
		// source id to multiple mixer input channels. Table RTRtoMIX stores the matrix destination ID for each 
		// mixer input. Scanning this table means we can test if the changed destination is one of those feeding
		// a mixer input. 
		for (int idx = 1; idx <= NUM_MIX_INPUT; idx++)
		{
			if (RTRtoMIX[idx] == myDest)
			{
				// Process for possible consequences. If the old source was a CCU, and the mixer channel is
				// on-air, the tally to the camera must be switched off unless the same CCU is active on another
				// mixer input channel that has active tally. Table_E has the CCU numbers stored indexed by 
				// mixer input channel.
				if (Table_E[idx] != 0)
				{
					int myCCUId = Table_E[idx];	// Note the CCU ID on this input

					// Have a CCU routed to changed destination. Clear down tally on that CCU 
					// if the mixer channel is on-air.
					if (Table_N[idx] != 0)
					{
						// The mixer tally is "on". Question: Is the CCU routed to another channel,
						// and is that channel also on-air? If so the current tally to the camera is 
						// correct. Otherwise the tally should be set to off.
						bool myNewTally = false;

						for (int i = 1; i <= NUM_MIX_INPUT; i++)
						{
							if ((i != idx) && (Table_E[i] == myCCUId) && (Table_N[i] != 0)) myNewTally = 1;
						}
						gpiSwitch(tally_gpi_id, myNewTally, primary_offset + Table_E[idx] - 1, true);
					}
				}

				// Need to sort out any active UMD tally states for the old source. Table_F lists which 
				// UMD addresses are showing the old source.
				if (Table_F[oldSource] != "")
				{
					// Executing this code because at least one UMD is displaying old source. We are disconnecting
					// the source, so active tallies must be switched off.
					umd_addr = bncs_stringlist(Table_F[oldSource]);
					for (int i = 0; i < umd_addr.count(); i++)
					{

						infoWrite(stc_tsl_id, "0", (1000 + umd_addr[i].toInt()), true);
						textPut("statesheet", TALLY_OFF, MAIN_PANEL, bncs_string("UMD_%1").arg(umd_addr[i].toInt(),'0',2U));	// Panel mimic
					}
				}

				// Now we need to update Table_M[], the mixer source lookup, with the new source id.
				Table_M[idx] = newSource;

				// Prepare to update the UMD chain. Build table ST
				buildTable_ST();

				// Check if the new source has a UMD monitoring it. If so update the tally status
				if (Table_F[newSource] != "")
				{ 
					umd_addr = bncs_stringlist(Table_F[newSource]);		// Get the comma delimited list of UMD addresses.
					for (int i = 0; i < umd_addr.count(); i++)
					{
						infoWrite(stc_tsl_id, Table_ST[newSource] ? "3" : "0", (1000 + umd_addr[i].toInt()), true);								// UMD tally
						textPut("statesheet", Table_ST[newSource] ? TALLY_ON : TALLY_OFF, MAIN_PANEL, bncs_string("UMD_%1").arg(umd_addr[i].toInt(), '0', 2U));	// Panel mimic
					}
				}

				// Finally check if the new source is an allocated CCU, and set its tally as appropriate
				buildTable_E();								// Re-build the mixer input to CCU number lookup
				if (Table_E[idx] != 0) cameraTally(idx);	// Allows for multiple routes of CCU

			}	// End of if (RTRtoMIX[idx] == myDest)
		}	// End of for (int idx = 1; idx <= NUM_MIX_INPUT; idx++)

		return;
	}	
	// End of processing for Studio C SDI router crosspoint changed 


	// CTA SDI Router
	if (r->device() == cta_sdi_id)
	{
		// CTA SDI Router sends various signals to the Studio C sdi router inputs. There are some OS (Outside Source) feeds,
		// some general video signals (eg feeds to wall boxes if StA uses StC floor) and the cameras. The Studio C control
		// suite does NOT provide traceback names to original source. The Studio C UMD name will be "CTA OS1", or a logical name
		// such as "Dog Show OB" set up by the Studio C technical co-ordinator. Thus cue information is not affected by route
		// changes on OS and wall box lines.
		//
		// When a CCU signal is routed on one of the CTA to StC lines it may need to receive primary (red) tally switching control
		// from this cue director. The processing for CTA route changes can ignore any route change where the old and new 
		// sources are not CCUs. 
		
		int dest;			// Holds the desintation ID for the changed route
		int oldSource;		// Holds the previous source ID for the route that has changed
		int newSource;		// Holds the newly selected source ID

		dest = r->index();
		oldSource = Table_C[dest];		
		newSource = r->info();		
		Table_C[dest] = newSource;		// Update table that holds the CTA router map.

		// Check the old source - was this a CCU? Table_D lists CCU by CTA source id.
		if (Table_D[oldSource] != 0)
		{
			// It is a CCU. But was the feed just for monitoring or is that CCU allocated to Studio C?
			// Table CCU_Alloc[] is list of CCU's allocated to Studio C
			if (CCU_Alloc[Table_D[oldSource]] != 0)
			{
				// CCU is allocated to Studio C, so this cue director is in charge of the CCU primary tally.
				// We can safely switch the tally to off. When the new route is made, and the tables rebuilt 
				// an update of all CCU tallies is done, so if there were two routes carrying the camera to the
				// mixer, we are still able to get true tally.
				gpiSwitch(tally_gpi_id, false, primary_offset + Table_D[oldSource] - 1, true);
			}
		}

		// Now re-build the relevant tables for the new source routed
		buildTable_E();				// Mixer channel to CCU look up table

		// Now scan for CCU on the mixer, and set any CCU primary tally to correct state
		for (int i = 1; i <= NUM_MIX_INPUT; i++)
		{
			if (Table_E[i] != 0) cameraTally(i);
		}

		return;
	}
	// End of processing for CTA SDI router crosspoint changed


	// ************************ //
	// Tally Infodriver changes //
	// ************************ //
	if (r->device() == tally_info_id)
	{
		// Tally Infodriver records the allocation of the CCU to a studio, the matrix output ID's used for mixer 
		// inputs 25 to 40, and the Key Association Table.

		// --------------------- //
		// CCU Allocation Change //
		// --------------------- //
		if ((r->index() >= CCU_ALLOC_1) && (r->index() < (CCU_ALLOC_1 + NUM_CCUS)))
		{
			// Because the CCUs are shared between studios, a CCU must be allocated to a studio to define which mixer
			// controls the CCU primary tally. Therefore a status update may come through here that requires no action
			// beyond recording the new value in our internal table. Only allocation to Studio C, signalled by a new
			// status value of 3 in the revertive, or deallocation signalled by a status value of 0 in the revertive
			// require processing.
			//
			// Allocation and deallocation only require the CCU primary tally value being cleared/set. All stack tally
			// indications remain valid as the pictures from a CCU can still be used when it is not allocated. For example, a
			// vision mixer training event using studio A as the primary operational and mixing point, also routes the camera
			// outputs to Studio C where a second operator can try mixing the pictures. The camera operators need to see
			// primary tally from their home mixer in Studio A. The operator in studio C needs their stack to show them what
			// sources contribute to their local mixer output.
			//
			// CCU_Alloc[1] id for CCU 1. The index computation for the array CCU_Alloc is (r->index() - CCU_ALLOC_1 + 1)

			// Update to the new value
			CCU_Alloc[r->index() - CCU_ALLOC_1 + 1] = (r->sInfo() == "3") ? 1 : 0;	// "3" is index for studio C

			// If the new value is "unallocated", ensure CCU primary tally is off
			if (CCU_Alloc[r->index() - CCU_ALLOC_1 + 1] == 0)
			{
				gpiSwitch(tally_gpi_id, false, primary_offset + r->index() - CCU_ALLOC_1, true);
			}
			
			// Rebuild the mixer channel to CCU look up table
			buildTable_E();		

			// Do a forced update for the tally state of all cameras
			for (int i = 1; i <= NUM_MIX_INPUT; i++)
			{
				if (Table_E[i] != 0) cameraTally(i);
			}
			return;
		}	// End of CCU Allocation Change

		// ************************************************************** //
		// Destination feeding a mixer input (25.. 40) has been modified. //
		// ************************************************************** //
		if ((r->index() >= MTX_TO_MIX_25) && (r->index() <= MTX_TO_MIX_40))
		{
			// Update RTRtoMIX table that holds the list if matrix destination that feed mixer inputs
			RTRtoMIX[r->index() - MTX_TO_MIX_25 + 25] = r->sInfo().toInt();

			// Rebuild Table_M[] which holds the list of sources that feed the mixer input
			buildTable_M();

			// Rebuild the source tally index table
			buildTable_ST();

			// Force a full tally update
			updateAll();
			return;
		}


		// ***************************************** //
		// Static Key Association Table has changed. //
		// ***************************************** //
		if ((r->index() >= STC_KEY_ASSOC) && (r->index() <= STC_KEY_ASSOC + SDI_MTX_SIZE + 1))
		{
			// Need to store the updated association value in Table_KA[], then rebuild the source tally table (Table_ST),
			// and run a forced UMD update.
			Table_KA[r->index() - STC_KEY_ASSOC + 1] = r->sInfo().toInt();
			buildTable_ST();

			// Do the UMD tally update
			for (int id = 1; id <= SDI_MTX_SIZE; id++)
			{
				if (Table_F[id] != "")
				{
					// Only do work on sources that are feeding destinations with a UMD
					umd_addr = bncs_stringlist(Table_F[id]);		// Move the comma delimited list to a stringlist
					for (int j = 0; j < umd_addr.count(); j++)		// bncs_stringlist computes number of tokens
					{
						// Notes: umd_addr[j].toInt() is the address of a UMD, Table_ST[i] is the computed tally state of the source
						textPut("statesheet", (Table_ST[id] ? TALLY_ON : TALLY_OFF), MAIN_PANEL, bncs_string("UMD_%1").arg(umd_addr[j].toInt(), '0', 2U));	// Update the mimic tally
						infoWrite(stc_tsl_id, Table_ST[id] ? "3" : "0", 1000 + umd_addr[j].toInt());														// Cue light on multiviewer
					}
				}
			}	// End of for (int id = 1; id <= SDI_MTX_SIZE; id++)
			return;
		}	

		return;		// Not a tally slot of interest to this module
	}	// End of if (r->device() == tally_info_id)

}	// End of void CueDirector::runningRevertives(revertiveNotify *r)


// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //
// function pollall_tally_infodriver() is called to issue the   //
// poll requests for all relevant slots in the tally infodriver //
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //
void CueDirector::pollall_tally_infodriver(void)
{
	infoPoll(tally_info_id, 3, 3);										// Poll for studio on-air status
	infoPoll(tally_info_id, CCU_ALLOC_1, CCU_ALLOC_1 + NUM_CCUS - 1);	// Poll CCU allocation to studios
	infoPoll(tally_info_id, MTX_TO_MIX_25, MTX_TO_MIX_40);				// Poll for mixer inputs that are patched
	infoPoll(tally_info_id, STC_KEY_ASSOC, STC_KEY_ASSOC + SDI_MTX_SIZE);	// Poll for key association data
}


///////////////////////////////////////////////////////////////////////////////////////////////////
// The Camera Cue system needs to know which channels of the mixer have a assigned CCU connected
// to them. This enables a very fast react to mixer tally change for the camera primary tally.
// 
// funtion buildTable_E(void) is called at startup and every time that a mixer input route
// changes - either the direct SDI or the extra 15 inputs from matrix spare outputs.
void CueDirector::buildTable_E(void)
{
	int ccu_id=0;

	// Start by clearing the table store.
	for (int i = 0; i <= NUM_MIX_INPUT; i++) Table_E[i] = 0;

	// The processing required to create the table takes longer to document than it does to code.
	//
	// The process runs for each mixer input. The description below uses some intermediate variables
	// (we call these 'a', 'b', 'c', 'd', 'e' in this description) to describe the steps in the 
	// process. In the description idx is the mixer input channel number.
	//
	// Step 1:      Find the video source ID feeding the mixer input being processed
	//              Table_M already exists and contains the video source per mixer input.
	// 'a' = Table_M[idx];  
	//
	// Step 2:      Find out if the source number could be a CCU. If so it comes from the CTA matrix. 
	//              Table_B has a list of input signals on Studio C router that come from the CTA sdi
	//              router. We can use variable 'a' from step 1 as an index into Table_B. If the result
	//              is non-zero the source is from CTA, and 'b' is the destination index of CTA router.
	// 'b' = Table_B['a'];
	// 
	// Step 3:      Table_C keeps an up to date list of the routing on the CTA router. So we can use 
	//              this table to convert 'b' into the input source id which is our 'c'.
	// 'c' = Table_C['b'];
	//
	// Step 4:      Table_D holds a list, indexed by CTA input source id. If the source is not a CCU,
	//              Table_D returns zero. If it is a CCU it returns the CCU number (1..10).
	// 'd' = Table_D['c'];
	//
	// Step 5:      We only wish to note CCU numbers if that CCU is allocated to Studio C. The 
	//              allocation of CCU to the studio is held in table CCU_Alloc. The stored value 
	//              is 0 for not allocated to Studio C, and 1 if the CCU is allocated to Studio C.
	// 'e' = CCU_Alloc['d'];
	// 
	// Step 6:		Save the masked result in Table_E[idx]. 
	// Table_E[idx] = 'd' * 'e';	// This process is why CCU_Alloc returns 0 or 1
	//
	// Because all of the look up  tables have a start index of 0, and that slot contains 0 the 
	// steps 1 to 4 above can be folded into one operation.
	// 'd' = Table_D[Table_C[Table_B[Table_M[idx]]]];
	
	for (int id = 1; id <= NUM_MIX_INPUT; id++)
	{
		ccu_id = Table_D[Table_C[Table_B[Table_M[id]]]];	// ccu_id is either 0 or a CCU number in range 1 to 10
		Table_E[id] = ccu_id * CCU_Alloc[ccu_id];			// Store the masked value
	}
}


///////////////////////////////////////////////////////////////////////////////////////////////////
// Table_F is used to speed processing of UMD tallies when a mixer on-air tally changes. For each
// source on the SDI router it holds a comma delimited list of the UMD addresses that are displaying
// that source. When a mixer tally changes, this module detects the change by the revertive from
// the mixer tally driver. This identifies the mixer channel that has changed. Table_ST is rebuilt 
// to hold the new cue state. The new state needs to be sent to all UMDs showing the source that
// feeds the mixer input(s) that have changed state.
// 
void CueDirector::buildTable_F(void)
{
	int src;
	bncs_string umdList;

	// Table F is an array of bncs_string entries with commas to delimit the entries. The entries
	// are indexed by studio C sdi router source id.
	// 
	// The table is built in several stages. The first process is to clear the array.

	for (int i = 0; i <= SDI_MTX_SIZE; i++) Table_F[i] = "";	// Clear the table

	// Run a scan of the multiviewer inputs to the stack. These all come from the SDI router, 
	// destinations 27 to 74 (symbolic constants MV01 and MV48). For each feed to the multiviewer
	// use Table_A to find the source SDI number. Convert the MV feed number number to a string,
	// appending it and a comma to any existing string in Table_F[sdi_source_id].
	for (int idx = MV01; idx <= MV48; idx++)
	{
		src = Table_A[idx];			// Extract the source ID
		umdList = Table_F[src];		// Fetch the existing string for this source id
		umdList += bncs_string("%1,").arg((idx-MV01) + 1);
		Table_F[src] = umdList;
	}
}


///////////////////////////////////////////////////////////////////////////////////////////////////
// Table_M is built at startup, and updated very time one of the mixer destinations has a source 
// route change. The table is indexed by mixer input number (1..40) and holds the Studio C SDI
// source number feeding that mixer input.
void CueDirector::buildTable_M(void)
{
	for (int i = 0; i < NUM_MIX_INPUT; i++) Table_M[i] = 0;	// Clear the table store.

	// Use table RTRtoSDI to find the SDI destination for the input channel. For a non-zero value
	// use the value as an index into Table_A that has the current studio C source map for the
	// local sdi matrix. Actually we can miss out the test for zero as the tables have an index 0
	// entry that is value 0. So a non assigned destination produces a source index of 0.
	for (int idx = 1; idx < NUM_MIX_INPUT; idx++)
	{
		Table_M[idx] = Table_A[RTRtoMIX[idx]];
	}
}


////////////////////////////////////////////////////////////////////////////////////////////////////
// Table_ST is built both during initial processing and when mixer tally or route changes occur.
// The table is the size of the SDI router for the area. After the processing completes there is a
// 1 in any source number that has an active tally reported by the mixer.
//
void CueDirector::buildTable_ST(void)
{
	for (int i = 0; i <= SDI_MTX_SIZE; i++) Table_ST[i] = false;	// Clear existing table

	// Run the process loop for each mixer input
	for (int idx = 1; idx <= NUM_MIX_INPUT; idx++)
	{
		if (Table_N[idx])	// We skip processing for channels without active tally
		{
			// Table_M[idx] is the source number feeding mixer input idx.
			// We are inside this "if test" process code because the tally for this source is true, so set the
			// tally in Table_ST[video_source_id] to a 1. This folds into the single line of code below.
			Table_ST[Table_M[idx]] = true;		// Set the tally active flag
		}
	}
}


////////////////////////////////////////////////////////////////////////////////////////////////////
// function updateAll() is called to perform a forced refresh of the UMD cue state and the CCU tally
// state.
void CueDirector::updateAll(void)
{
	// Do the UMD tally update
	for (int id = 1; id <= SDI_MTX_SIZE; id++)
	{
		if (Table_F[id] != "")
		{
			// Only do work on sources that are feeding destinations with a UMD
			umd_addr = bncs_stringlist(Table_F[id]);		// Move the comma delimited list to a stringlist
			for (int j = 0; j < umd_addr.count(); j++)		// bncs_stringlist computes number of tokens
			{
				// Notes: umd_addr[j].toInt() is the address of a UMD, Table_ST[i] is the computed tally state of the source
				textPut("statesheet", (Table_ST[id] ? TALLY_ON : TALLY_OFF), MAIN_PANEL, bncs_string("UMD_%1").arg(umd_addr[j].toInt(),'0',2U));	// Update the mimic tally
				infoWrite(stc_tsl_id, Table_ST[id] ? bncs_string("3") : bncs_string("0"), 1000 + umd_addr[j].toInt());				// Cue light on multiviewer
			}
		}
	}	// End of for (int id = 1; id <= SDI_MTX_SIZE; id++)

	// Update CCU tallies allowing for same CCU to be multipally allocated
	for (int id = 1; id <= NUM_MIX_INPUT; id++)
	{
		// Scan all mixer inputs looking for a camera. Table_E contains the CCU numbers indexed by mixer input
		// The stored value is zero for non-camera or unallocated camera, and a number between 1 and 10 for
		// an allocated camera.
		if (Table_E[id != 0]) cameraTally(id);
	}	// End of for (int i = 1; i <= NUM_MIX_INPUT; i++)...
}	// End of void CueDirector::updateAll(void)


////////////////////////////////////////////////////////////////////////////////////////////////////
// function cameraTally(int mixCh) is called to switch on/off the camera primary tally. It checks
// that a CCU is alocated to the channel number passed as a parameter. If a CCU is found, the 
// function checks for further instance of the same CCU alloctaed to other channels. If a second
// instance is found, a logical OR of the tally states is formed, and the result is sent to the GPI
// interface that controls the CCU light.
//
void CueDirector::cameraTally(int mixCh)
{
	// Process the Camera Red Lights.
	// Table_E[] is a list indexed by mixer inputID. It holds the CCU_ID if a camera is feeding that mixer input. 
	// Use the number in Table_E to compute the GPI contact used for tally and set/clear the relay.
	if (Table_E[mixCh] != 0)
	{
		// This mixer input is a CCU. Look for same CCU on other inputs and form logical OR of the tally
		// values when same CCU is present multiple times.
		int myCCUID = Table_E[mixCh];		// Extract CCU ID of interest
		int myTally = Table_N[mixCh];		// Get tally for this mixer channel that has changed.

		for (int idx = 1; idx <= NUM_MIX_INPUT; idx++)
		{
			// Scan mixer inputs for same CCU. Don't reprocess channel that triggered the scan
			if ((Table_E[idx] == myCCUID) && (idx != mixCh))
			{
				myTally |= Table_N[idx];
			}
		}
		gpiSwitch(tally_gpi_id, myTally ? true : false, primary_offset + Table_E[mixCh] - 1, true);
	}
}	// End of void CueDirector::cameraTally(int mixCh)

////////////////////////////////////////////////////////////////////////////////////////////////////
