 /*
PROGRAM NAME	UMD_and_Tally.DLL

USED BY			BBC Academy Studio A/CTA installation

PROGRAMMER:		Andy Woodhouse

Version		Date:
1.0			18th January 2015		Initial release
1.01		5th December 2018		Set mixer tally update to cameras to "immediate"

Overview
========
This module is the interface between the CTA SDI router, the Studio A mixer
tally, the Studio A UMD chain, and the CTA/Edit 6 UMD chain.

The serial bitstreams to the UMD displays are created by two instances of the
BNCS TSL driver. The two instances differ by the type of UMD display used. The
units in the Studio A galleries are 8-character devices. The BNCS UMD driver
sends a 16 character string, but the UMD only uses the first 8. The units used
in CTA and the Edit 6 bays are dual displays that operate in "virtualised" mode.
The information for the two 8-character displays is actually sent in a 16
character string. As far a BNCS control is concerned the two displays are
individual addresses in the driver. The driver manages the combining of the
strings, and managing the address sent to the UMD hardware.

The interface to the driver is via two Infodrivers, one for each UMD chain.
One of the nice features of the infodriver interface, 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 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 router into the required settings in the UMD driver slots. The
look up table 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	8 can be connected to spare matrix
				outputs via jackfield plugging. The destinations connections data needed by this 
				task is stored in slots 101 to 136 of the Tally Infodriver.
	Mixer_In	Mixer input number fed by matrix destination. Value of 0 means destination does not
				feed the mixer.
	Table_A		A list of the 36 current sources supplied to the vision mixer inputs.
	Table_B		A list of the 36 mixer tallies currently active
	Table_ST	A computed list of 128 entries, storing a logic 1 for those sources that are
				currently "on-air"
	Table_C		A list holding the SDI source number for each destination of the SDI router
	Table_D		Lists the UMD address used by the Studio A UMD chain, indexed by matrix destination
	Table_D_CTA	Lists the UMD address per matrix destination for the CTA UMD video devices.
	Table_Aud	Lists the UMD address per matrix destination for the CTA UMD audio chain.
	Table_E		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.
	Table_G		CCU number for this source. Value of 0 means the source is not a CCU.
	CCU_Alloc	Holds the current allocation status for the CCU devices. Used to decide if a tally
				should be provided to a camera.

This module must also manage the CCU primary tally GPI outputs, and monitor 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 "Studio A Gallery UMDs and Camera Tally Automatic Panel"

*/

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

// Define values for the user interface panels
#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 CTA_SDI_ROUTER "CTA SDI Router"
#define CTA_AUDIO_ROUTER "CTA Audio Router"
#define TALLY_INFODRV "CTA Tally Infodriver"
#define STA_UMD_DRIVER "StA TSL Driver"
#define CTA_UMD_DRIVER "CTA TSL Driver"
#define STA_MIXER_TALLY "StA Mixer Tally"
#define STA_UMD_NAMES "StA_UMD_Names_DriverID"
#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

// Make our class visible to the outside world
EXPORT_BNCS_SCRIPT( UMD_and_Tally )

// ++++++++++++++++++++++++++++++++++++++++++++ //
// Constructor - equivalent to ApplCore STARTUP //
// ++++++++++++++++++++++++++++++++++++++++++++ //
UMD_and_Tally::UMD_and_Tally(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(MAIN_PANEL, MAIN_PANEL_NAME);
	panelInit(STARTUP_PANEL, STARTUP_PANEL_NAME);
	panelInit(SHOW_TABLES, SHOW_TABLES_NAME);

	// Display the panel that shows status data during module startup.
	panelShow(STARTUP_PANEL, STARTUP_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.
	// Matrix id's
	cta_sdi_id = 0;
	cta_audio_id = 0;
	tally_info_id = 0;
	sta_tsl_id = 0;
	cta_tsl_id = 0;
	sta_mixer_id = 0;
	sta_names_id = 0;

	// Working data
	revert_count = 0;			// Used during the initial revrtive processing.
	sta_umd_mode = 0;			// Studio A UMD operating mode
	sta_umd_brightness = 0;		// Studio A UMD brightness
	sta_umd_active_names = 0;	// Studio A active name set
	cta_umd_mode = 0;
	cta_umd_bright = 0;

	CurrentName = Mtx_Name;		// Point at a name set to initialise pointer. Use matrix names.

	for (int i = 0; i <= NUM_MIX_INPUT; i++) RTRtoMIX[i] = 0;
	for (int i = 0; i <= NUM_MIX_INPUT; i++) Table_A[i] = 0;
	for (int i = 0; i <= NUM_MIX_INPUT; i++) Table_B[i] = 0;
	for (int i = 0; i <= SDI_MTX_SIZE; i++) Mixer_In[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 <= SDI_MTX_SIZE; i++) Table_E[i] = 0;
	for (int i = 0; i <= SDI_MTX_SIZE; i++) Table_G[i] = 0;
	for (int i = 0; i <= SDI_MTX_SIZE; i++) Table_D_CTA[i] = 0;
	for (int i = 0; i <= AUDIO_MTX_SIZE; i++) Table_Aud[i] = 0;
	for (int i = 0; i <= AUDIO_MTX_SIZE; i++) AudioStatus[i] = 0;
	for (int i = 0; i <= NUM_CCUS; i++) CCU_Alloc[i] = 0;
	for (int i = 0; i <= SDI_MTX_SIZE; i++) Table_ST[i] = false;
	for (int i = 0; i <= SDI_MTX_SIZE; i++) sdi_poll[i] = false;
	for (int i = 0; i <= AUDIO_MTX_SIZE; i++) audio_poll[i] = false;
	for (int i = 0; i < TALLY_END; i++) tally_poll[i] = false;			// NOTE: TALLY_END definition includes the +1 to offset the 0 base of arrays
	for (int i = 0; i <= NUM_MIX_INPUT; i++) mixer_poll[i] = false;

	drivers_registered = false;
	init_polls = true;
	timeout_active = false;
	sdi_allpolled = false;
	audio_allpolled = false;
	mixer_allpolled = false;
	tally_allpolled = false;
	umd_id_active = false;
	cta_umd_id_active = false;

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

	// Read matrix id's from instances.xml
	getDev(CTA_SDI_ROUTER, &cta_sdi_id);		// SDI router
	getDev(CTA_AUDIO_ROUTER, &cta_audio_id);	// Audio router
	getDev(TALLY_INFODRV, &tally_info_id);		// "Tally" Infodriver
	getDev(STA_MIXER_TALLY, &sta_mixer_id);		// Studio A Sony mixer Tally driver
	getDev(STA_UMD_DRIVER, &sta_tsl_id);		// Studio A TSL UMD driver
	getDev(CTA_UMD_DRIVER, &cta_tsl_id);		// CTA TSL UMD driver
	getDev(CCU_PRIMARY_TALLY, &tally_gpi_id, &primary_offset);		// Get GPI driver and contact offsets
	getDev(STA_UMD_NAMES, &sta_names_id);		// UMD Names store

	// Get the list of CCU matrix sources from wn_academy.xml
	myVals = bncs_config("wn_academy.StAJoystick.CTA_CCU");
	ccuList = myVals.attr("value");

	// Report driver ID's
	textPut("add", "SDI router id = " + bncs_string(cta_sdi_id), STARTUP_PANEL, "Status");
	textPut("add", "Audio router id = " + bncs_string(cta_audio_id), STARTUP_PANEL, "Status");
	textPut("add", "Tally Infodriver id = " + bncs_string(tally_info_id), STARTUP_PANEL, "Status");
	textPut("add", "Studio A Sony Mixer tally driver id = " + bncs_string(sta_mixer_id), STARTUP_PANEL, "Status");
	textPut("add", "Studio A TSL UMD driver id = " + bncs_string(sta_tsl_id), STARTUP_PANEL, "Status");
	textPut("add", "CTA TSL UMD driver id = " + bncs_string(cta_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");
	textPut("add", "UMD names driver id = " + bncs_string(sta_names_id), STARTUP_PANEL, "Status");

	if ((cta_sdi_id != 0) && (cta_audio_id != 0) && (tally_info_id != 0) && (sta_mixer_id != 0) && (sta_tsl_id != 0) && (cta_tsl_id != 0) && (sta_names_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 names from the various drivers into memory for faster access
	textPut("add", "Read name sets into memory.", STARTUP_PANEL, "Status");
	for (int i = 1; i <= SDI_MTX_SIZE; i++) routerName(cta_sdi_id, 2, i, Mtx_Name[i]);		// Matrix short names in database 2
	for (int i = 1; i <= SDI_MTX_SIZE; i++) routerName(sta_names_id, 1, i, UserNames_1[i]);	// User names set 1
	for (int i = 1; i <= SDI_MTX_SIZE; i++) routerName(sta_names_id, 2, i, UserNames_2[i]);	// User names set 2
	for (int i = 1; i <= SDI_MTX_SIZE; i++) routerName(sta_names_id, 3, i, UserNames_3[i]);	// User names set 3
	for (int i = 1; i <= SDI_MTX_SIZE; i++) routerName(sta_names_id, 4, i, UserNames_4[i]);	// User names set 4
	for (int i = 1; i <= SDI_MTX_SIZE; i++) routerName(sta_names_id, 5, i, UserNames_5[i]);	// User names set 5
	for (int i = 1; i <= SDI_MTX_SIZE; i++) routerName(sta_names_id, 6, i, UserNames_6[i]);	// User names set 6
	for (int i = 1; i <= SDI_MTX_SIZE; i++) routerName(sta_names_id, 7, i, UserNames_7[i]);	// User names set 7
	for (int i = 1; i <= SDI_MTX_SIZE; i++) routerName(sta_names_id, 8, i, UserNames_8[i]);	// User names set 8

	// "Zap" the slots in the Studio A and CTA UMD drivers to no-name, no_tally, one-seventh brightness
	for (int i = 1; i <= 128; i++)
	{
		infoWrite(sta_tsl_id, "", i);				// Write empty name.
		infoWrite(sta_tsl_id, "0,1", i + 500);		// Write no tally, one seventh brightness.
		infoWrite(cta_tsl_id, "", i);				// Write empty name.
		infoWrite(cta_tsl_id, "0,1", i + 500);		// Write no tally, one seventh brightness.
	}

	// Register for revertives from various drivers
	textPut("add", "Register for revertives.", STARTUP_PANEL, "Status");
	routerRegister(cta_sdi_id, 1, SDI_MTX_SIZE);		// All destinations of CTA SDI router
	routerRegister(cta_audio_id, 1, AUDIO_MTX_SIZE);	// Need the audio destinations for CTA UMD chain
	infoRegister(tally_info_id, 1, STA_KEY_ASSOC + SDI_MTX_SIZE - 1);
	infoRegister(sta_mixer_id, 1, NUM_MIX_INPUT);
	routerRegister(sta_names_id, 1, SDI_MTX_SIZE);		// Needs to be registered so that we get database callbacks
	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 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(cta_sdi_id, 1, SDI_MTX_SIZE);		// Read the SDI router state.
}


// +++++++++++++++++++++++++++++++++++++++++++++ //
// Destructor - equivalent to ApplCore CLOSEDOWN //
// +++++++++++++++++++++++++++++++++++++++++++++ //
UMD_and_Tally::~UMD_and_Tally()
{
	if (drivers_registered)
	{
		routerUnregister(cta_sdi_id);
		routerUnregister(cta_audio_id);
		infoUnregister(tally_info_id);
		infoUnregister(sta_mixer_id);
		routerUnregister(sta_names_id);
	}
}


// ++++++++++++++++++++++++++++++++++++++++++++++ //
// All button pushes and notifications come here. //
// ++++++++++++++++++++++++++++++++++++++++++++++ //
void UMD_and_Tally::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 RTRtoMIX table
			textPut("clear", SHOW_TABLES, "Displaybox");
			textPut("text", "Table RTRtoMix - Matrix Destination to Mixer Input", 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() == "2")
		{
			// Show Mixer_In table
			textPut("clear", SHOW_TABLES, "Displaybox");
			textPut("text", "Table Mixer_In - Mixer Input by matrix destination", SHOW_TABLES, "TableName");
			for (int i = 1; i <= SDI_MTX_SIZE; i++)
				textPut("add", bncs_string("%1 = %2").arg(i, '0', 3, 10).arg(Mixer_In[i]), SHOW_TABLES, "Displaybox");
		}

		if (b->id() == "3")
		{
			// Show Table_A
			textPut("clear", SHOW_TABLES, "Displaybox");
			textPut("text", "Table_A - Matrix Input Source SDI", 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_A[i]), SHOW_TABLES, "Displaybox");
		}

		if (b->id() == "4")
		{
			// Show Table_B
			textPut("clear", SHOW_TABLES, "Displaybox");
			textPut("text", "Table_B - Mixer Tally Status", 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_B[i]), SHOW_TABLES, "Displaybox");
		}

		if (b->id() == "5")
		{
			// Show Table_ST
			textPut("clear", SHOW_TABLES, "Displaybox");
			textPut("text", "Table_ST - Tally Status per SDI 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_ST[i]), SHOW_TABLES, "Displaybox");
		}

		if (b->id() == "6")
		{
			// Show Table_C
			textPut("clear", SHOW_TABLES, "Displaybox");
			textPut("text", "Table_C - SDI Router current source id", 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() == "7")
		{
			// Show Table_D
			textPut("clear", SHOW_TABLES, "Displaybox");
			textPut("text", "Table_D - 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_D[i]), SHOW_TABLES, "Displaybox");
		}

		if (b->id() == "8")
		{
			// Show Table_D_CTA
			textPut("clear", SHOW_TABLES, "Displaybox");
			textPut("text", "Table_D_CTA - CTA chain video 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_D_CTA[i]), SHOW_TABLES, "Displaybox");
		}

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

		if (b->id() == "10")
		{
			// Show Table_E
			textPut("clear", SHOW_TABLES, "Displaybox");
			textPut("text", "Table_E - 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_E[i]), SHOW_TABLES, "Displaybox");
		}

		if (b->id() == "11")
		{
			// Show Table_F
			textPut("clear", SHOW_TABLES, "Displaybox");
			textPut("text", "Table_F - UMD address displaying a 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() == "12")
		{
			// Show Table_G
			textPut("clear", SHOW_TABLES, "Displaybox");
			textPut("text", "Table_G - CCU number by SDI 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_G[i]), SHOW_TABLES, "Displaybox");
		}

		if (b->id() == "13")
		{
			// Show table CCU_Alloc
			textPut("clear", SHOW_TABLES, "Displaybox");
			textPut("text", "CCU_Alloc - CCU assignment to Studio A", SHOW_TABLES, "TableName");
			for (int i = 1; i <= NUM_CCUS; i++)
				textPut("add", bncs_string("%1 = %2").arg(i, '0', 3, 10).arg(CCU_Alloc[i]), SHOW_TABLES, "Displaybox");
		}

		if (b->id() == "14")
		{
			// Show table AudioStatus
			textPut("clear", SHOW_TABLES, "Displaybox");
			textPut("text", "AudioStatus - Audio router current source", SHOW_TABLES, "TableName");
			for (int i = 1; i <= AUDIO_MTX_SIZE; i++)
				textPut("add", bncs_string("%1 = %2").arg(i, '0', 3, 10).arg(AudioStatus[i]), SHOW_TABLES, "Displaybox");
		}

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

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

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

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

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

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

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

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

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

	}	// End of if (b->panel() == SHOW_TABLES)
}	// End of UMD_and_Tally::buttonCallback( buttonNotify *b )


// +++++++++++++++++++++++++ //
// All revertives come here. //
// +++++++++++++++++++++++++ //
int UMD_and_Tally::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 UMD_and_Tally::databaseCallback( revertiveNotify * r )
{
	// There are three databases that are significant for this module:
	// 1) CTA SDI Router, database 2 which holds the short names shown by the mimics, the CTA/Edit 6 UMDs and Studio A UMDs (if user selects them)
	// 2) CTA Audio Router, database 2 which has the short names used by the mimics and the CTA/Edit 6 displays
	// 3) User Names databases 1 to 8 which may be used by the Studio A UMDs

	// ***************************************** //
	// Start with a check for the CTA SDI router //
	// ***************************************** //
	if ((r->device() == cta_sdi_id) && (r->database() == 2))
	{
		// Need to capture the update into local copy of the data. 
		Mtx_Name[r->index()] = r->sInfo();

		// Is this source on display in one of the Studio A UMDs?
		if (Table_F[r->index()] != "")
		{
			umd_addr = Table_F[r->index()];		// Get comma delimited list
			for (int j = 0; j < umd_addr.count(); j++)
			{
				infoWrite(sta_tsl_id, r->sInfo(), umd_addr[j]);				// Update UMD
			}
		}

		// Is this source on display in one of the CTA UMDs?
		for (int i = 1; i <= SDI_MTX_SIZE; i++)		// Scan the matrix state table
		{
			// Look for a UMD on that destination
			if (Table_D_CTA[i] != 0)
			{
				// Does the destination carry the source whose name has changed
				if (Table_C[i] == r->index())
				{
					infoWrite(cta_tsl_id, r->sInfo(), Table_D_CTA[i]);		// Update the UMD	
					textPut("text", r->sInfo(), MAIN_PANEL, bncs_string("CTA_") + bncs_string(Table_D_CTA[i])); // Update mimic
				}
			}
		}
	}	// End of if ((r->device == cta_sdi_id) && (r->database() == 2))


	// ************************** //
	// Check for CTA Audio Router //
	// ************************** //
	if ((r->device() == cta_audio_id) && (r->database() == 2))
	{
		// CSI will keep a copy in the Dev_xxx.ini file we use for lookup. So only need to know
		// if the updated source is one that is on one of the CTA Audio UMD devices.
		// Scan through the possible destinations, and work out if any destination with a UMD
		// is using the updated source.
		for (int i = 1; i <= AUDIO_MTX_SIZE; i++)
		{
			if (Table_Aud[i] != 0)
			{
				// Is the source for this destination the changed source?
				if (AudioStatus[i] == r->index())
				{
					// Send new name to UMD and Mimic
					infoWrite(cta_tsl_id, r->sInfo(), Table_Aud[i]);
					textPut("text", r->sInfo(), MAIN_PANEL, bncs_string("CTA_") + bncs_string(Table_Aud[i]));
				}
			}
		}
	}	// End of ((r->device() == cta_audio_id) && (r->database() == 2))


	// ******************************************************** //
	// Check for update to a user name database in range 1 to 8 //
	// ******************************************************** //
	if ((r->device() == sta_names_id) && (r->database() >= 1) && (r->database() <= 8))
	{
		// Need to record the new name in the local memory copy of the databases.
		
		switch (r->database())	// Get a pointer to the database store which is to be updated.
		{
			case 1: dbpointer = UserNames_1; break;
			case 2: dbpointer = UserNames_2; break;
			case 3: dbpointer = UserNames_3; break;
			case 4: dbpointer = UserNames_4; break;
			case 5: dbpointer = UserNames_5; break;
			case 6: dbpointer = UserNames_6; break;
			case 7: dbpointer = UserNames_7; break;
			case 8: dbpointer = UserNames_8; break;
		}

		dbpointer[r->index()] = r->sInfo();	// Save the new name

		// Is the updated database the one that is in use on the UMD?
		if (r->database() == sta_umd_active_names)
		{
			// See if the source name is displayed on a UMD?
			if (Table_F[r->index()] != "")
			{
				umd_addr = Table_F[r->index()];	// Get the comma delimited list of UDM addresses
				for (int j = 0; j < umd_addr.count(); j++)
				{
					infoWrite(sta_tsl_id, r->sInfo(), umd_addr[j]);	// Update the UMD
				}
			}
		}
	}	// End of if ((r->device() == sta_names_id) && (r->database >= 1) && (r->database <= 8))

}	// End of database update callback function.


// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //
// All parent notifications come here i.e. when this script is just one    //
// component of another dialog then our host might want to tell us things. //
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //
bncs_string UMD_and_Tally::parentCallback( parentNotify *p )
{
	if(p->command() == "return")
	{
		if(p->value() == "all")
		{	// Persisting values for bncs_vis_ed
			bncs_stringlist sl;
			
			sl << bncs_string( "myParam=%1" ).arg( m_myParam );
			
			return sl.toString( '\n' );
		}

		else if(p->value() == "myParam")
		{	// Specific value being asked for by a textGet
			return( bncs_string( "%1=%2" ).arg( p->value() ).arg( m_myParam ) );
		}

	}
	else if(p->command() == "instance" && p->value() != m_instance)
	{	// Our instance is being set/changed
		m_instance = p->value();
		//Do something instance-change related here
	}

	else if(p->command() == "myParam")
	{	// Persisted value or 'Command' being set here
		m_myParam = p->value();
	}

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

		sl << "notify=*";		
		
		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 << "myParam=[value]";
		
		return sl.toString('\n');
	}

	return "";
}


// +++++++++++++++++++++++ //
// Timer events come here. //
// +++++++++++++++++++++++ //
void UMD_and_Tally::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 (2 seconds). If the poll has timed out, it is reissued, and the timeout period restarted.

		if (!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 2 second period for the first retuen of revertive data
			timeout_active = true;
			routerPoll(cta_sdi_id, 1, SDI_MTX_SIZE);		// Read the SDI matrix state
			break;
		}

		if (!audio_allpolled)
		{
			textPut("add", "+++ Re-issue initial poll of Audio router.", STARTUP_PANEL, "Status");
			timerStart(TIMER_SETUP, INITIAL_POLL_TIMEOUT);	// Allow a 2 second period for the first retuen of revertive data
			timeout_active = true;
			routerPoll(cta_audio_id, 1, AUDIO_MTX_SIZE);	// Poll the Audio matrix status.
			break;
		}

		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();	// Poll the Audio matrix status.
			break;
		}

		if (!mixer_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;
			infoPoll(sta_mixer_id, 1, NUM_MIX_INPUT);		// Poll the Audio matrix status.
			break;
		}
		
		break;	// End of timer number TIMER_SETUP

	case UMD_ID_TIMER:
		// UMD display address mode active uses this timer to show and increment the addresses as known to BNCS (1 to 128)
		id_count++;		// Increment the counter
		infoWrite(sta_tsl_id, bncs_string("MyID=%1").arg(id_count, '0', 3, 10), id_count);		// Send to UMD chain
		// The mimics only have a limed number of addresses. We should only send to these valid addresses which match the entries in Table_D
		// We use bool table umd_sta_used to check if the UMD is a mimic display
		if (umd_sta_used[id_count] == true)
			textPut("text", bncs_string("MyID=%1").arg(id_count, '0', 3, 10), MAIN_PANEL, bncs_string("UMD_") + bncs_string(id_count));
		
		if (id_count > 128) id_count = 0;
		break;


	case CTA_UMD_ID_TIMER:
		// CTA UMD display address mode active uses this timer to show and increment the addresses as known to BNCS (1 to 128)
		id_count_cta++;		// Increment the counter
		infoWrite(cta_tsl_id, bncs_string("MyID=%1").arg(id_count_cta, '0', 3, 10), id_count_cta);		// Send to CTA UMD chain
		// The mimics only have a limited number of addresses. We should only send to these valid addresses which we have listed in 
		//  bool table umd_cta_used.
		if (umd_cta_used[id_count_cta])
			textPut("text", bncs_string("MyID=%1").arg(id_count_cta, '0', 3, 10), MAIN_PANEL, bncs_string("CTA_") + bncs_string(id_count_cta));
		
		if (id_count_cta > 128) id_count_cta = 0;
		break;

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


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


// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //
// Revertive processsing differes 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 UMD_and_Tally::initialRevertives(revertiveNotify *r)
{
	// This method is called to process the revertives during the initial phase of task startup

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

		// Have we yet had all the revertives from the SDI router?
		revert_count = 0;
		for (int i = 1; i <= SDI_MTX_SIZE; i++) if (sdi_poll[i]) revert_count++;
		if (revert_count == SDI_MTX_SIZE)
		{
			// All revertives received. Stop timeout timer. Poll next data source
			sdi_allpolled = true;
			textPut("add", "Initial SDI router poll completed.", STARTUP_PANEL, "Status");
			if (timeout_active)
			{
				timerStop(TIMER_SETUP);
				timeout_active = false;
			}

			textPut("add", "Initial poll of audio router issued.", STARTUP_PANEL, "Status");
			timeout_active = true;
			timerStart(TIMER_SETUP, INITIAL_POLL_TIMEOUT);	// Allow a 2 second period for the first return of revertive data
			routerPoll(cta_audio_id, 1, AUDIO_MTX_SIZE);	// Poll all the audio destinations
		}	// End of if (revert_count == SDI_MTX_SIZE)...
	}	// End of if (r->device() == cta_sdi_id)...


	// *********************** //
	// Audio Router Revertives //
	// *********************** //
	if (r->device() == cta_audio_id)
	{
		// Record the initial revertives from the audio router
		AudioStatus[r->index()] = r->info();
		audio_poll[r->index()] = true;

		// Have we yet had all the revertives from the Audio router?
		revert_count = 0;
		for (int i = 1; i <= SDI_MTX_SIZE; i++) if (audio_poll[i]) revert_count++;
		if (revert_count == AUDIO_MTX_SIZE)
		{
			audio_allpolled = true;
			textPut("add", "Initial audio router poll completed.", STARTUP_PANEL, "Status");
			if (timeout_active)
			{
				timerStop(TIMER_SETUP);
				timeout_active = false;
			}

			textPut("add", "Initial poll of tally infodriver issued.", STARTUP_PANEL, "Status");
			timeout_active = true;
			timerStart(TIMER_SETUP, INITIAL_POLL_TIMEOUT);		// Allow a 2 second period for the first return of revertive data
			pollall_tally_infodriver();							// Issue a poll for all the tally infodriver slots that are relevant
		}	// End of if (revert_count == AUDIO_MTX_SIZE)...

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


	// *************************** //
	// Tally Infodriver Revertives //
	// *************************** //
	if (r->device() == tally_info_id)
	{
		// Record the initial tally revertives in relevant stores. These are in 8 address groups
		if (r->index() == 1)
		{
			// Studio Off/Rehearse/Transmit Status
			StudioA_TX_Status = r->sInfo().toInt();		// Record status
			tally_poll[r->index()] = true;
		}

		
		if ((r->index() >= 11) && (r->index() <= 20))
		{
			// Table CCU_Alloc[] was initially filled with zero's in the constructor code.
			CCU_Alloc[r->index() - 10] = (r->sInfo() == "1") ? 1 : 0;
			tally_poll[r->index()] = true;
		}

		if ((r->index() >= MTX_TO_MIX_1) && (r->index() <= MTX_TO_MIX_LAST))
		{
			// RTRtoMTX information
			RTRtoMIX[r->index() - MTX_TO_MIX_1 + 1] = r->sInfo().toInt();	// Apply offset to incoming index to put into range 1 to 36
			Mixer_In[r->sInfo().toInt()] = r->index() - MTX_TO_MIX_1 + 1;	// Table Mixer_In is mixer input number fed by destination.
			tally_poll[r->index()] = true;
		}

		if ((r->index() >= STA_NAME_MODE) && (r->index() <= STA_NAME_SET))
		{
			// Studio A UMD information information
			switch (r->index())
			{
			case STA_NAME_MODE:		// Expected values are 0 or 1
				sta_umd_mode = r->sInfo().toInt();
				tally_poll[r->index()] = true;
				break;

			case STA_UMD_BRIGHT:	// Expected values in range 0 to 3
				sta_umd_brightness = r->sInfo().toInt();
				tally_poll[r->index()] = true;
				break;

			case STA_NAME_SET:		// Expected values are in range 0 to 8
				sta_umd_active_names = r->sInfo().toInt();
				tally_poll[r->index()] = true;
				break;
			}
		}	// End of if ((r->index() >= STA_NAME_MODE) && (r->index() <= STA_NAME_SET))

		if (r->index() == CTA_NAME_MODE)
		{
			// CTA UMD information information
			cta_umd_mode = r->sInfo().toInt();
			tally_poll[r->index()] = true;
		}

		if (r->index() == CTA_UMD_BRIGHT)
		{
			// CTA UMD information
			cta_umd_bright = r->sInfo().toInt();
			tally_poll[r->index()] = true;
		}

		if ((r->index() >= STA_UMD_ADDR) && (r->index() <= (STA_UMD_ADDR + SDI_MTX_SIZE - 1)))
		{
			// Studio A UMD address per matrix destination = Table_D
			Table_D[r->index() - STA_UMD_ADDR + 1] = r->sInfo().toInt();
			tally_poll[r->index()] = true;
		}

		if ((r->index() >= CTA_UMD_ADDR) && (r->index() <= (CTA_UMD_ADDR + SDI_MTX_SIZE - 1)))
		{
			// CTA UMD address per matrix destination
			Table_D_CTA[r->index() - CTA_UMD_ADDR + 1] = r->sInfo().toInt();
			tally_poll[r->index()] = true;
		}

		if ((r->index() >= CTA_UMD_ADDR_AUD) && (r->index() <= (CTA_UMD_ADDR_AUD + AUDIO_MTX_SIZE - 1)))
		{
			// CTA Audio UMD address per matrix destination
			Table_Aud[r->index() - CTA_UMD_ADDR_AUD + 1] = r->sInfo().toInt();
			tally_poll[r->index()] = true;
		}

		if ((r->index() >= STA_KEY_ASSOC) && (r->index() <= (STA_KEY_ASSOC + SDI_MTX_SIZE - 1)))
		{
			// Master Source Lookup table = Table_E
			Table_E[r->index() - STA_KEY_ASSOC + 1] = r->sInfo().toInt();
			tally_poll[r->index()] = true;
		}


		// Have we yet had all the revertives from the tally infodriver?
		revert_count = 0;
		for (int i = 1; i <= TALLY_END; i++) if (tally_poll[i]) revert_count++;

		if (revert_count == (1 + NUM_MIX_INPUT + 5 + (SDI_MTX_SIZE * 3) + AUDIO_MTX_SIZE + NUM_CCUS))
		{
			tally_allpolled = true;
			textPut("add", "Initial tally infodriver poll completed.", STARTUP_PANEL, "Status");
			if (timeout_active)
			{
				timerStop(TIMER_SETUP);
				timeout_active = false;
			}

			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 2 second period for the first return of revertive data
			infoPoll(sta_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() == sta_mixer_id)
	{
		// Record the revertives from the Sony mixer tally state
		Table_B[r->index()] = r->sInfo().toInt();
		mixer_poll[r->index()] = true;

		// Have we yet received revertives from all mixer tally slots?
		revert_count = 0;
		for (int i = 1; i <= NUM_MIX_INPUT; i++) if (mixer_poll[i]) revert_count++;
		if (revert_count == NUM_MIX_INPUT)
		{
			mixer_allpolled = true;
			textPut("add", "Initial mixer tally poll completed.", STARTUP_PANEL, "Status");
			if (timeout_active)
			{
				timerStop(TIMER_SETUP);
				timeout_active = false;
			}
		}	// End if (revert_count == NUM_MIX_INPUT)...

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

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

		// Initial polls complete. Startup the normal tally processing
		buildTable_A();				// Build the list of mixer input sources
		buildTable_ST();			// Build the source tally table.
		buildTable_G();				// Build the CCU allocation look up table
		buildTable_F();				// Build the list of UMDs showing each source
		init_polls = false;
		setUMDNameSet();			// Set the UMD nameset pointer to the value read from the tally infodriver
		updateAll();
		if (sta_umd_mode == 1)	infoWrite(tally_info_id, "0", STA_NAME_MODE);	// Make sure we have the "normal" umd mode active for Studio
		if (cta_umd_mode == 1)	infoWrite(tally_info_id, "0", CTA_NAME_MODE);	// Make sure we have the "normal" umd mode active for CTA
	}

	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 thesdi router etc have been read and outpute values established.                                                   //
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //
void UMD_and_Tally::runningRevertives(revertiveNotify *r)
{

	// ******************* //
	// Mixer Tally Changes //
	// ******************* //
	if (r->device() == sta_mixer_id)
	{
		// Process tally changes reported by the vision mixer
		// Step 1 - Update the local copy of the mixer tally status = TableB[]
		Table_B[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:
		// Table_A[r->index()] is the source number whose tally state has changed. Table_F[] is a list indexed by source number
		// holding comma delimited strings of the UMD addresses that have that source on an associated monitor. So we use Table_F[]
		// to extract the list of UMDs that need a tally state update.
		//
		umd_addr = bncs_stringlist(Table_F[Table_A[r->index()]]);	// umd_addr now contains comma delimited list of UMD addr or null string

		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(sta_tsl_id, Table_ST[Table_A[r->index()]] ? bncs_string("3") : bncs_string("0"), 1000 + umd_addr[j].toInt(), true);			// UMD Cue light
				textPut("statesheet", (Table_ST[Table_A[r->index()]] ? TALLY_ON : TALLY_OFF), MAIN_PANEL, "TX_" + bncs_string(umd_addr[j].toInt()));	// Update the mimic tally
			}
		}

		// Process the Camera Red Lights. 
		// Table_A[r->index()] tells us the SDI router source id changed tally mixer input. Table_B[r->index()] tells us the new tally
		// state (as an integer). TableG[source_id] tells us if the source is a CCU. Table CCU_Alloc[CCU_ID] tells us if the camera is
		// assigned to Studio A. Update the CCU tally, via a GPI output, only if the CCU is allocated to Studio A.

		if (Table_G[Table_A[r->index()]] != 0)
		{
			// The input whose tally changed is a CCU.
			if (CCU_Alloc[Table_G[Table_A[r->index()]]])
			{
				// Update CCU primary tally GPI state. Note that the base switch is for CCU_1 so must subtract 1 to get CCU_1 to
				// drive correct contact. Updated to force immediate send, AMW 05/12/18
				gpiSwitch(tally_gpi_id, (Table_B[r->index()] == 1 ? 1 : 0), (Table_G[Table_A[r->index()]] + (primary_offset - 1)), true);
			}
		}

		return;		// Mixer tally change processing complete
	}


	// ***************************** //
	// SDI Router Crosspoint Changes //
	// ***************************** //
	if (r->device() == cta_sdi_id)
	{
		// Process changes in SDI router crosspoint tables
		// Need to work out if the destination that has changed is a stack monitor or a mixer input. All other crosspoint changes can be ignored
		// Table Mixer_In lists the mixer input channel fed by each destination of the matrix. Thus by reading Mixer_In[changed_destination] we can
		// discover what the destination feeds. A returned value of zero mans that the changed destination is not a mixer input.

		if (Mixer_In[r->index()] == 0)
		{
			// Not a mixer input, so it may be a stack monitor or it may be a destination that is not of signifiucance to this panel
			// A stack monitor has a UMD address associated with the destination. We must cover two possible actions: Studio A UMD chain 
			// and CTA UMD chain.
			if (Table_D[r->index()] != 0)
			{
				// Enter this code block when a Studio A monitor destination changes. Update the name on the UMD and the UMD tally state.
				// UMD number is returned by Table_D[r->index(). The new source is returned by r->info(), so the new name is returned by
				// CurrentName[r->info()]. This name should also be copied to the panel mimic umd. The tally state is available in
				// Table_ST[r->info()] and this should be placed in the infodriver slot and the local mimic.
				infoWrite(sta_tsl_id, CurrentName[r->info()], Table_D[r->index()]);						// Update UMD via infodriver
				infoWrite(sta_tsl_id, Table_ST[r->info()] ? "3" : "0", 1000 + Table_D[r->index()]);		// Update UMD tally state;
				textPut("text", Mtx_Name[r->info()], MAIN_PANEL, bncs_string("UMD_") + bncs_string(Table_D[r->index()]));	// Update mimic name
				textPut("statesheet", Table_ST[r->info()] ? TALLY_ON : TALLY_OFF, MAIN_PANEL, bncs_string("TX_") + bncs_string(Table_D[r->index()]));
			}

			// See if the changed destination has a CTA chain UMD address 
			if (Table_D_CTA[r->index()] != 0)
			{
				// Enter this code block because the updated destination is one associated with a dual-umd display. This code updates the video element
				// The UMD number is returned by Table_D_CTA[r->index()]. The new source is returned by r->info(), so the new name is returned by
				// Mtx_Name[r->info()]. This name should also be copied to the panel mimic UMD. No tally data is used here.
				infoWrite(cta_tsl_id, Mtx_Name[r->info()], Table_D_CTA[r->index()]);
				textPut("text", Mtx_Name[r->info()], MAIN_PANEL, bncs_string("CTA_") + bncs_string(Table_D_CTA[r->index()]));
			}

			// Now update Table C and Table_F
			Table_C[r->index()] = r->info();	// Update Table_C
			buildTable_F();
			return;
		}
		else
		{
			// We have changed a mixer input source so process all possible consequences.
			// Possibility 1 is that the "old" source was a CCU allocated to this studio, with an active tally light. If those conditions
			// are true, we need to clear the CCU primary tally.
			// We already know from the "if" test that jumped us to this "else" clause that the updated destination is a mixer input.
			// The value returned by Mixer_In[r->index()] gives us the mixer input number. We can use this number as an index into Table_A,
			// which tells us the "old" source. We use this number as an index into Table_G[]. The returned number is non-zero if the old
			// source was a CCU. Using this as an index into CCU_Alloc tells us if the CCU was allocated to this studio. If so we must ensure
			// that the primary tally is set to "off".
			// One advantage of the structure of the lookup table with table_offset 0 (eg CCU_Alloc[0]) is zero, and we can just do the index
			// process as a single hit, testing the final value for zero (not CCU) or non-zero.

			if (CCU_Alloc[Table_G[Table_A[Mixer_In[r->index()]]]] != 0)
			{
				// "old" source was a CCU allocated to this studio. Clear the primary tally if it is set. Table_B[Mixer_In[r->index()]] tells
				// us if the tally on that mixer channel is currently active. CCU ID is Table_G[Table_A[Mixer_In[r->index()]]]
				if (Table_B[Mixer_In[r->index()]] == 1)
				{
					gpiSwitch(tally_gpi_id, false, Table_G[Table_A[Mixer_In[r->index()]]] + primary_offset - 1);
				}
			}	// End of if (CCU_Alloc[Table_G[Table_A[Mixer_In[r->index()]]]] != 0)

			// 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[Table_C[r->index()]] != "")
			{
				// Here 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[Table_C[r->index()]]);
				for (int i = 0; i < umd_addr.count(); i++)
				{

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

			// Now we need to update Table_A, the mixer source lookup, with the new source id, and update Table_C, the router source list.
			Table_A[Mixer_In[r->index()]] = r->info();
			Table_C[r->index()] = r->info();

			// Update the UMD chain. First rebuild the source tally table.
			buildTable_ST();
			
			if (Table_F[r->info()] != "")
			{
				// UMD address string is non-blank. So update UMD and panel mimics with tally state.
				umd_addr = bncs_stringlist(Table_F[Table_C[r->index()]]);
				for (int i = 0; i < umd_addr.count(); i++)
				{
					infoWrite(sta_tsl_id, bncs_string(Table_ST[r->info()]*3), (1000 + umd_addr[i].toInt()), true);									// CCU primary tally
					textPut("statesheet", Table_ST[r->info()] ? TALLY_ON : TALLY_OFF, MAIN_PANEL, bncs_string("TX_%1").arg(umd_addr[i].toInt()));	// Panel mimic
				}
			}

			// Now check if the new source is a CCU, as it may need to have it's primary tally switched on.
			// Use the new source as an index into Table_G, which returns a non-zero value if the new source is a CCU. We also need to know if
			// the newly selected CCU is assigned to this studio. If so, a CCU primary tally must be set.
			// Again we can cascade all of the lookup process into a single C++ instruction..
			if (CCU_Alloc[Table_G[r->info()]] != 0)
			{
				// Check if the mixer tally is active for the new source, and set CCU primary tally if it is
				if (Table_B[Mixer_In[r->index()]] == 1)
				{
					gpiSwitch(tally_gpi_id, true, Table_G[Table_A[Mixer_In[r->index()]]] + primary_offset - 1);
				}
			}
		}	// End of if (Mixer_In[r->index()] == 0) ELSE clause

		return;
	}	// End of if (r->device() == cta_sdi_id)...


	// ******************************* //
	// Audio Router Crosspoint Changes //
	// ******************************* //
	if (r->device() == cta_audio_id)
	{
		// Process changes from CTA audio router. These will affect CTA UMD Chain.
		AudioStatus[r->index()] = r->info();	// Update the state of table AudioStatus[dest]

		// Is there a UMD for that destination?
		if (Table_Aud[r->index()] != 0)
		{
			// UMD exists - so update displays
			routerName(cta_audio_id, 2, AudioStatus[r->index()], audio_src_name);		// Get the source short name
			infoWrite(cta_tsl_id, audio_src_name, Table_Aud[r->index()]);		// Update UMD chain
			textPut("text", audio_src_name, MAIN_PANEL, bncs_string("CTA_") + bncs_string(Table_Aud[r->index()]));	// Update mimic
		}
	}	// End of if (r->device() == cta_audio_id) Audio processing


	// ************************ //
	// Tally Infodriver changes //
	// ************************ //
	if (r->device() == tally_info_id)
	{
		// Process revertives from Tally Infodriver. The data sources from the tally infodriver include:
		// Slots		Data type
		//   1			Studio A TX status (interlock on setting UMD ID chain.
		//  11 -  20	CCU allocation to a studio
		// 101 - 132	Matrix destination feed mixer input channel N. Channel N = slot_no - 100
		// 200			Studio A Name Mode. 0 = Normal tally, 1 = UMD address ID (MyId=007 etc)
		// 201			Studio A UMD Brightness
		// 202			Studio A UMD Active name set. 0 = Matrix short names, 1 to 8 is user name set.
		// 205			CTA UMD Name mode. 0 = Normal tally, 1 = UMD address ID (MyId=007 etc)
		// 206			CTA UMD Brightness
		// 207			Not yet used. Could become CTA name set.
		// 301 - 428	Studio A Matrix destination to UMD address look up table
		// 501 - 628	CTA Matrix destination to UMD address look up table
		// 701 - 828	Master source look up table (key source also talies...)


		// *********************** //
		// Studio TX status update //
		// *********************** //
		if (r->index() == 1)
		{
			if (StudioA_TX_Status != r->sInfo().toInt())
			{
				// Status has updated.
				StudioA_TX_Status = r->sInfo().toInt();	// Keep a record of new state
				if (StudioA_TX_Status == STUDIO_TX)
				{
					// If the UMD chain is in mode 1 (show address), revert to mode 0 (show names
					if (sta_umd_mode == 1)
					{
						timerStop(UMD_ID_TIMER);	// Stop the counter chain
						sta_umd_mode = 0;			// Set new status value
						updateUMDnames();			// Update the name displays
						// Force the infodriver slot to match the mode
						infoWrite(tally_info_id, "0", STA_NAME_MODE);
					}	// End of if (sta_umd_mode....
				}
			}

			return;
		}	// End of if (r->index() == 1).....


		// ********************* //
		// 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 A, signalled by a new
			// status value of 1 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 C as the primary operational and mixing point, also routes the camera
			// outputs to Studio A where a second operator can try mixing the pictures. The camera operators need to see
			// primary tally from their home mixer in Studio C. The operator in studio A 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)

			if ((CCU_Alloc[r->index() - CCU_ALLOC_1 + 1] == 0) && (r->sInfo() != "1"))
			{
				// Change in value not relevant to Studio A. CCU_Alloc[r->index() - CCU_ALLOC_1 + 1] returns 0 if modified CCU
				// is not allocated to Studio A. Only interested if new value is "1".

				return;		// Processing complete
			}

			// Check for CCU being allocated to Studio A
			if ((CCU_Alloc[r->index() - CCU_ALLOC_1 + 1] == 0) && (r->sInfo() != "1"))
			{
				CCU_Alloc[r->index() - CCU_ALLOC_1 + 1] = 1;	// Record the allocation.
				// Now check if the CCU is routed to the mixer. If so, is the mixer tally active for the new CCU.
				// bncs_stringlist variable ccuList was loaded during intial startup of this program with the comma delimted list of CCU
				// matrix source numbers. We can therefore extract the source number for modified CCU from that list. That source number is
				// then used to index into Table_ST which tells us the tally status for that source.

				if (Table_ST[ccuList[r->index() - CCU_ALLOC_1].toInt()] == true)		// Note that we need 0 based offsets here, and Table_ST is boolean
					gpiSwitch(tally_gpi_id, true, primary_offset + (r->index() - CCU_ALLOC_1));		// Set the primary tally light
				else
					gpiSwitch(tally_gpi_id, false, primary_offset + (r->index() - CCU_ALLOC_1));	// Clear the primary tally light

				return;
			}

			// Check for CCU being deallocated from Studio A
			if ((CCU_Alloc[r->index() - CCU_ALLOC_1 + 1] == 1) && (r->sInfo() != "0"))
			{
				CCU_Alloc[r->index() - CCU_ALLOC_1 + 1] = 0;									// Record the new status
				gpiSwitch(tally_gpi_id, false, primary_offset + (r->index() - CCU_ALLOC_1));	// Clear the primary tally light
				return;
			}

			return;
		}


		// ************************************************************ //
		// Mixer Input: Destination feeding an input has been modified. //
		// ************************************************************ //
		if ((r->index() >= MTX_TO_MIX_1) && (r->index() <= MTX_TO_MIX_LAST))
		{
			// Enter this code segment when the user informs the system that the sdi matrix destinations that deliver signals
			// to one of the mixer inputs has changed. This routing data would be fairly static, so we do not need to be too 
			// time contious in this code block. We can therefore do a "full" tally state update after we have recorded the new
			// state.

			// Update table RTRtoMIX and table Mixer_In using the revertive data. Start by removing the old entry in Mixer_In.
			// RTRtoMIX[(r->index() - MTX_TO_MIX_1 + 1)] returns old matrix destination
			Mixer_In[RTRtoMIX[(r->index() - MTX_TO_MIX_1 + 1)]] = 0;
			RTRtoMIX[(r->index() - MTX_TO_MIX_1 + 1)] = r->sInfo().toInt();			// Update RTRtoMIX with new destination id.
			Mixer_In[r->sInfo().toInt()] = r->index() - MTX_TO_MIX_1 + 1;			// Update table Mixer_In to give destination to mixer map
			Table_A[(r->index() - MTX_TO_MIX_1 + 1)] = Table_C[r->sInfo().toInt()];	// Update Mixer input=source_id list
			buildTable_ST();		// Update source tally table

			// Assume that physical jacks have been inserted as required for sources 25 to 36
			// Now scan through the UMD lists in Table_F, updating the tally state as per data in Table_ST
			for (int i = 1; i <= SDI_MTX_SIZE; i++)
			{
				if (Table_F[i] != "")
				{
					umd_addr = bncs_stringlist(Table_F[i]);
					for (int j = 0; j < umd_addr.count(); j++)
					{
						infoWrite(sta_tsl_id, Table_ST[i] ? "3" : "0", 1000 + umd_addr[j].toInt());	// Update stack tally
						textPut("statesheet", Table_ST[i] ? TALLY_ON : TALLY_OFF, MAIN_PANEL, bncs_string("TX_") + bncs_string(umd_addr[j].toInt()));
					}
				}
			}

			// Now scan through the mixer input source numbers looking for a CCU that is allocated to studio A.
			// Update the primary tally state.
			for (int input_no = 1; input_no <= NUM_MIX_INPUT; input_no++)
			{
				// Table_A has a list of sources feeding the mixer indexed by mixer input number.
				// Use the returned value as an index into Table_G that returns the CCU number for each matrix source. Thus a non-zero
				// CCU number indicates that there is a CCU routed to that mixer input. Use the non-zero CCU number as an index into
				// CCU_Alloc to define if the CCU needs primary tally control from studio A. Set tally value to match tally value from Table_ST.
				// In practice all tables have a index 0 entry which has the value 0, meaning we can just do the indexing
				if (CCU_Alloc[Table_G[Table_A[input_no]]] == 1)
				{
					// Need to send a tally.
					gpiSwitch(tally_gpi_id, Table_ST[Table_A[input_no]], (primary_offset + Table_G[Table_A[input_no]] - 1));
				}
			}

			return;
		}


		// ********************************************* //
		// The Studio A name/id display mode has changed //
		// ********************************************* //
		if (r->index() == STA_NAME_MODE)
		{
			// If the mode is zero, standard display names are used. If the mode is set to 1, the name is of the form "MyID=012"
			// In mode 1 the data is refreshed using a timer, so that when chaging addresses on the UMD units, the maximum delay before the display
			// shows an ID related to the address. Use of UMD address mode is interlocked with the studio TX status recorded in the tally infodriver
			// slot 2.
			if (r->sInfo().toInt() == 0)
			{
				// Request is to enter "Normal" address mode.
				sta_umd_mode = 0;	// Set the local variable to the "standard" mode value
				if (umd_id_active)	timerStop(UMD_ID_TIMER);	// Stop counter system if it is active
				timerStop(UMD_ID_TIMER);						// Stop the counter chain
				umd_id_active = false;
				updateUMDnames();								// Restore normal name displays 
			}

			if ((r->sInfo().toInt() == 1) && (StudioA_TX_Status != STUDIO_TX))
			{
				// Enter the special address mode
				sta_umd_mode = 1;
				id_count = 0;
				// Build UMD address used on mimic look up table needed by timer response routine
				for (int i = 0; i <= SDI_MTX_SIZE; i++)	umd_sta_used[i] = false;								// Clear the array
				for (int i = 1; i <= SDI_MTX_SIZE; i++)	
				{
					if (Table_D[i] != 0)
						umd_sta_used[Table_D[i]] = true;	// Set flags for addresses that are in use
				}
				// Start the time loop
				umd_id_active = true;
				timerStart(UMD_ID_TIMER, UMD_ID_PERIOD);	// Timer service routine does the work!
			}

			return;
		}


		// ****************************** //
		//  Studio A UMD brighness change //
		// ****************************** //
		if (r->index() == STA_UMD_BRIGHT)
		{
			// Allowed values are in the range 0 to 3.
			if ((r->sInfo().toInt() >= 0) && (r->sInfo().toInt() <= 3))
			{
				sta_umd_brightness = r->sInfo().toInt();	// Save the new brightness
				updateStAbrightnessMimic();					// Update mimic report
				for (int i = 1; i <= SDI_MTX_SIZE; i++)
				{
					infoWrite(sta_tsl_id, sta_umd_brightness, 1500 + i);	// Send updates to the driver
				}
			}
			return;
		}


		// ***************************** //
		//  Studio A UMD name set change //
		// ***************************** //
		if (r->index() == STA_NAME_SET)
		{
			// Allowed values are the range 0 to 8. 0 means use the matrix short names, 1 to 8 are the User editable sets
			if ((r->sInfo().toInt() >= 0) && (r->sInfo().toInt() <= 8)) sta_umd_active_names = r->sInfo().toInt();	// Save the valid value
			setUMDNameSet();
			updateUMDnames();
			return;
		}


		// ************************ //
		// CTA UMD name mode change //
		// ************************ //
		if (r->index() == CTA_NAME_MODE)
		{
			// If the mode is zero, standard display names are used. If the mode is set to 1, the name is of the form "MyID=012"
			// In mode 1 the data is refreshed using a timer, so that when changing addresses on the UMD units, there is a mimimum delay before the
			// display shows an ID related to the address. Use of UMD address mode is interlocked with the studio TX status recorded in the tally
			// infodriver slot 1.
			if (r->sInfo().toInt() == 0)
			{
				// Request is to enter "Normal" address mode.
				cta_umd_mode = 0;	// Set the local variable to the "standard" mode value
				if (cta_umd_id_active)	timerStop(CTA_UMD_ID_TIMER);	// Stop counter system if it is active
				timerStop(CTA_UMD_ID_TIMER);							// Stop the counter chain
				updateCTAUMDnames();									// Restore normal name displays 
			}

			if ((r->sInfo().toInt() == 1) && (StudioA_TX_Status != STUDIO_TX))
			{
				// Enter the special address mode
				cta_umd_mode = 1;
				id_count_cta = 0;
				// Build UMD address used on mimic look up table needed by timer response routine
				for (int i = 0; i <= SDI_MTX_SIZE; i++)	umd_cta_used[i] = false;	// Clear the array
				for (int i = 1; i <= SDI_MTX_SIZE; i++)
				{
					if (Table_D_CTA[i] != 0)
						umd_cta_used[Table_D_CTA[i]] = true;						// Set flags for addresses that are in use in video elements
				}
				for (int i = 1; i <= AUDIO_MTX_SIZE; i++)
				{
					if (Table_Aud[i] != 0)
						umd_cta_used[Table_Aud[i]] = true;							// Set flags for addresses that are in use in video elements
				}

				cta_umd_id_active = true;
				timerStart(CTA_UMD_ID_TIMER, CTA_UMD_ID_PERIOD);	// Timer service routine does the work!
			}

			return;
		}


		// ************************* //
		// CTA UMD brightness change //
		// ************************* //
		if (r->index() == CTA_UMD_BRIGHT)
		{
			// Allowed values are in the range 0 to 3.
			if ((r->sInfo().toInt() >= 0) && (r->sInfo().toInt() <= 3))
			{
				cta_umd_bright = r->sInfo().toInt();					// Save the new brightness
				updateNameSetMimic();									// Update mimic report
				for (int i = 1; i <= SDI_MTX_SIZE; i++)
				{
					infoWrite(cta_tsl_id, cta_umd_bright, 1500 + i);	// Send updates to the driver
				}
			}
			updateCTAbrightnessMimic();									// Update panel mimic data

			return;
		}


		// ************************************** //
		// Studio A UMD system UMD address change //
		// ************************************** //
		if ((r->index() >= STA_UMD_ADDR) && (r->index() < (STA_UMD_ADDR + SDI_MTX_SIZE)))
		{
			// Studio A Matrix destination to UMD address look up table has changed an entry.
			// Need to rebuild Table_D (umd allocation per destination address) and Table_F (UMD addess per source)
			// Then do an all UMD data and tally update.
			Table_D[1 + r->index() - STA_UMD_ADDR] = r->sInfo().toInt();	// Update Table_D
			buildTable_F();													// Update list of UMDS on source x
			updateAll();
			return;
		}


		// ****************************************** //
		// CTA/Edit 6 UMD system video address change //
		// ****************************************** //
		if ((r->index() >= CTA_UMD_ADDR) && (r->index() < (CTA_UMD_ADDR + SDI_MTX_SIZE)))
		{
			// CTA/Edit 6 Matrix destination to UMD video address look up table has a changed entry.
			Table_D_CTA[1 + r->index() - CTA_UMD_ADDR] = r->sInfo().toInt();	// Update Table_D_CTA
			updateCTAUMDnames();												// Update all CTA UMDs
			return;
		}


		// ****************************************** //
		// CTA/Edit 6 UMD system audio address change //
		// ****************************************** //
		if ((r->index() >= CTA_UMD_ADDR_AUD) && (r->index() < (CTA_UMD_ADDR_AUD + SDI_MTX_SIZE)))
		{
			// CTA/Edit 6 Matrix destination to UMD audio address look up table has changed entry
			Table_Aud[1 + r->index() - CTA_UMD_ADDR_AUD] = r->sInfo().toInt();	// Update Table_Aud.
			updateCTAUMDnames();												// Update UMD units.
			return;
		}


		// ***************************** //
		// Key Association value changed //
		// ***************************** //
		if ((r->index() >= STA_KEY_ASSOC) && (r->index() < (STA_KEY_ASSOC + SDI_MTX_SIZE)))
		{
			// Studio A source to master tally table has updated an entry. We need to keep local copy (Table_E) up to date,
			// then rebuild the source tally table (Table_ST), and update the Studio_A UMD tally displays.
			Table_E[1 + r->index() - STA_KEY_ASSOC] = r->sInfo().toInt();	// Update the local copy
			buildTable_ST();												// Rebuild Table_ST
			for (int i = 1; i < NUM_MIX_INPUT; i++)							// Scan all sources connected to the mixer		
			{
				if (Table_F[i] != "")										// Is there a UMD showing this source?
				{
					// There is at least 1 UMD to consider.
					umd_addr = bncs_stringlist(Table_F[i]);	// Get the list of UMD addresses
					for (int j = 0; j < umd_addr.count(); j++)
					{
						infoWrite(sta_tsl_id, Table_ST[i] ? "3" : "0", 1000+ umd_addr[j]);	// Update the Cue slot
						textPut("statesheet", Table_ST[i] ? TALLY_ON : TALLY_OFF, MAIN_PANEL, bncs_string("TX_") + bncs_string(umd_addr[j])); // Update mimic
					}
				}
			}
			return;
		}

		return;		// Catch all return for Tally Infodriver Updates
	}

	return;
}	// End of runningRevertives(revertiveNotify *r)


// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //
// function pollall_tally_infodriver() is called to issue the   //
// poll requestsfor all relevant slots in the tally infodriver. //
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //
void UMD_and_Tally::pollall_tally_infodriver(void)
{
	// The relevant revertives are in several blocks.
	infoPoll(tally_info_id, CCU_ALLOC_1, CCU_ALLOC_1 + NUM_CCUS - 1);					// CCU allocation to studio
	infoPoll(tally_info_id, MTX_TO_MIX_1, MTX_TO_MIX_LAST);								// Matrix destinations feeding mixer. Fills table RTRtoMTX
	infoPoll(tally_info_id, STA_NAME_MODE, STA_NAME_SET);								// Studio A UMD properties
	infoPoll(tally_info_id, CTA_NAME_MODE, CTA_UMD_BRIGHT);								// CTA UMD properties
	infoPoll(tally_info_id, STA_UMD_ADDR, STA_UMD_ADDR + SDI_MTX_SIZE - 1);				// UMD address per matrix destination
	infoPoll(tally_info_id, CTA_UMD_ADDR, CTA_UMD_ADDR + SDI_MTX_SIZE - 1);				// CTA UMD address per matrix destination
	infoPoll(tally_info_id, CTA_UMD_ADDR_AUD, CTA_UMD_ADDR_AUD + AUDIO_MTX_SIZE - 1);	// CTA UMD address per audio matrix destination
	infoPoll(tally_info_id, STA_KEY_ASSOC, STA_KEY_ASSOC + SDI_MTX_SIZE - 1);			// Master Source lookup table
}


// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //
// function buildTable_A() is called to create the list of sources that are applied to the Mixer Inputs.        //
// The table built is called Table_A, and it uses tow elements of source information - the content of table     //
// RTRtoMIX which says which matrix destination feeds which mixer input, and Table_C which contains the current //
// state of the SDI router.                                                                                     //
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //
void UMD_and_Tally::buildTable_A(void)
{
	// We need to scan RTRtoMIX. Each non-zero value is the matrix destination that feeds that mixer input.
	// Use this return value to index into the matrix status held in Table_C, and store the result in Table_A.
	// Table RTRtoMIX and Table_A have identical sizes. Note that Table_C can be accessed at offset 0 where the 
	// value of 0 is stored.

	for (int idx = 1; idx <= NUM_MIX_INPUT; idx++)
	{
		Table_A[idx] = Table_C[RTRtoMIX[idx]];
	}
}


// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //
// function Table_ST (ST = Source Tally) is a list of all possible source ID's that are routed to the mixer, and //
// that have an active tally on the mixer. It is created by scanning Table_B, the mixer tally, and checking if a //
// non-zero value has a source_id which is non-zero. If that condition is met, a flag of "true" is set at the    //
// offset source_id in table ST.                                                                                 //
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //
void UMD_and_Tally::buildTable_ST(void)
{
	// Initially clear the list in Table_ST.
	for (int i = 1; i < SDI_MTX_SIZE; i++) Table_ST[i] = false;

	// Now scan Table_B for active tally. If present, look at source_id that is feeding that mixer input, and set
	// entry in Table_ST true.
	for (int idx = 1; idx <= NUM_MIX_INPUT; idx++)
	{
		if (Table_B[idx] > 0)					// Is the tally active?
		{
			// YES - so get the matrix source id that feeds this mixer input (stored in Table_A)
			// src_id1 = Table_A[idx];
			// If src_id1 is a key source (eg from a character generator or a stills store) then we should tally the
			// associated video source as well as the key. This data is stored in Table_E. So use src_id1 as index into
			// Table_E. Note that we do not need to trap for index 0 as the tables include an index entry of 0. The value
			// is set to 0 by the initialisation code.
			// src_id2 = Table_E[src_id1]; 
			// Table_ST[src_id2];
			// But we can put this in a single line of nested indexing...
			Table_ST[Table_A[idx]] = true;				// Set flag for actual source
			Table_ST[Table_E[Table_A[idx]]] = true;		// And for any associated master source
		}
	}	// End of for (int idx=0 ....
}


// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //
// Table_G[] is a list in source number order that shows if the source is a CCU, and if so, which CCU number. //
//                                                                                                            //
// function buildTable_G() builds that look up table from a string list that was read in from an entry in     //
// file wn_academy.xml. The string list is a comma delimited list of source numbers.                          //
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //
void UMD_and_Tally::buildTable_G(void)
{
	// Clear the table to zeros
	for (int i = 0; i <= SDI_MTX_SIZE; i++)	Table_G[i] = 0;

	// Fill in the CCU data
	for (int i = 1; i <= ccuList.count(); i++)
	{
		Table_G[ccuList[i - 1].toInt()] = i;
	}
}


// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //
// The array called Table_F is a list of strings in SDI source order. Each element of the array holds a comma 
// delimited string. The string value is either a null string or a comma delimited list of UMD's used on destinations
// displaying the source id.
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
void UMD_and_Tally::buildTable_F(void)
{
	// Clear the table to null strings
	for (int i = 1; i <= SDI_MTX_SIZE; i++) Table_F[i] = "";

	// Scan all SDI matrix destinations
	for (int idx = 1; idx <= SDI_MTX_SIZE; idx++)
	{
		// We only have to process destinations that have a UMD associated with them. Table D lists the UMD address
		// used for each destination. The entry is 0 for destinations that do not have a UMD.
		if (Table_D[idx] != 0)
		{
			// There is a UMD used on this destination. The UMD address is Table_D[idx].
			// This numeric address needs to be stored in Table_F, at the index given by the source.
			// The index for the storage is the source index returned by reading Table_C[idx].
			Table_F[Table_C[idx]] = Table_F[Table_C[idx]] + bncs_string(Table_D[idx]) + ",";
		}
	}
}	//	End of function buildTable_F


// +++++++++++++++++++++++++++++++++++++++ //
// Update all UMD and local mimic displays //
// +++++++++++++++++++++++++++++++++++++++ //
void UMD_and_Tally::updateAll(void)
{
	// Scan through all sources to update names
	for (int i = 1; i <= SDI_MTX_SIZE; i++)
	{
		// Initial check for Studio_A UMD chain
		if (Table_F[i] != "")
		{
			// Only do work on sources that are feeding destinations with a UMD
			umd_addr = bncs_stringlist(Table_F[i]);
			for (int j = 0; j < umd_addr.count(); j++)
			{
				// Notes:
				// umd_addr[j].toInt() is the address of a UMD
				// Table_ST[i] is the computed tally state of the source
				textPut("text", Mtx_Name[i], MAIN_PANEL, "UMD_" + bncs_string(umd_addr[j].toInt()));								// Update the mimic name
				textPut("statesheet", (Table_ST[i] ? TALLY_ON : TALLY_OFF), MAIN_PANEL, "TX_" + bncs_string(umd_addr[j].toInt()));	// Update the mimic tally
				infoWrite(sta_tsl_id, CurrentName[i], umd_addr[j].toInt());		// Update the TSL driver Infodriver slot for the name
				infoWrite(sta_tsl_id, Table_ST[i] ? bncs_string("3") : bncs_string("0"), 1000 + umd_addr[j].toInt());	// Cue light
				infoWrite(sta_tsl_id, bncs_string(sta_umd_brightness), 1500 + umd_addr[j].toInt(),true);				// Brightness
			}
		}

		// Then check for CTA UMD video chain
		if (Table_D_CTA[i] != 0)
		{
			// There is a UMD on destination "i". This code updates the video element of the UMD.
			// The UMD number is returned by Table_D_CTA[r->index()]. The new source is returned by r->info(), so the new name is returned by
			// Mtx_Name[r->info()]. This name should also be copied to the panel mimic UMD. No tally data is used here.
			int index;
			int UMD_id;
			bncs_string thename;
			index = i;
			UMD_id = Table_D_CTA[i];
			thename = Mtx_Name[i];

  			// infoWrite(cta_tsl_id, Mtx_Name[i], Table_D_CTA[i]);
			// bncs_string(Mtx_Name[Table_C[vid]])
			infoWrite(cta_tsl_id, bncs_string(Mtx_Name[Table_C[i]]), Table_D_CTA[i]);
			textPut("text", bncs_string(Mtx_Name[Table_C[i]]), MAIN_PANEL, bncs_string("CTA_") + bncs_string(Table_D_CTA[i]));
		}
	}

	// Process the CTA audio chain.
	for (int i = 1; i <= AUDIO_MTX_SIZE; i++)
	{
		bncs_string audio_source_name;

		if (Table_Aud[i] != 0)		// Check for non-zero UMD address
		{
			routerName(cta_audio_id, 2, AudioStatus[i], audio_source_name);		// Get the source short name
			infoWrite(cta_tsl_id, audio_source_name, Table_Aud[i] );
			textPut("text", audio_source_name, MAIN_PANEL, bncs_string("CTA_") + bncs_string(Table_Aud[i]));
		}
	}


	// Next thing to do is update CCU tallies as appropriate
	for (int i = 1; i <= NUM_MIX_INPUT; i++)
	{	
		// Scan all mixer inputs looking for a camera. This uses Table_A[] to get the source number applied to each mixer input.
		// Use that source number to index into Table_G which identifies the CCUs. 
		// If the value is non-zero, then the CCU tally may need to be updated if that CCU is allocated to the studio.
		// We can use the non-zero CCU id to index into table CCU_Alloc. If that value equals 1 the CCU is assigned to Studio A.
		if (Table_G[Table_A[i]] != 0)
		{
			if (CCU_Alloc[Table_G[Table_A[i]]])
				gpiSwitch(tally_gpi_id, Table_B[i] ? true : false, Table_G[Table_A[i]] + (primary_offset - 1));
		}
	}	// End of for (int i = 1; i <= NUM_MIX_INPUT; i++)...

	updateStAbrightnessMimic();	// Update mimic display of Studio A brightness
	updateNameSetMimic();		// Update mimic display of Studio A active name set
	updateCTAbrightnessMimic();	// Update mimic display of CTA/Edit 6 brightness
}	// End of updateAll()


// +++++++++++++++++++++++++++++++++++++++++ //
// Update the Studio A UMD Brightness Mimic. //
// +++++++++++++++++++++++++++++++++++++++++ //
void UMD_and_Tally::updateStAbrightnessMimic(void)
{
	switch (sta_umd_brightness)
	{
		case 0:	textPut("text", "Off", MAIN_PANEL, "UMD_Bright"); break;
		case 1:	textPut("text", "One Seventh", MAIN_PANEL, "UMD_Bright"); break;
		case 2:	textPut("text", "Half", MAIN_PANEL, "UMD_Bright"); break;
		case 3:	textPut("text", "Full", MAIN_PANEL, "UMD_Bright"); break;
	}
}


// ++++++++++++++++++++++++++++++++++++++++++++++++ //
// Update the panel display showing current nameset //
// ++++++++++++++++++++++++++++++++++++++++++++++++ //
void UMD_and_Tally::updateNameSetMimic(void)
{
	if (sta_umd_active_names == 0)
		textPut("text", "Matrix Short Names", MAIN_PANEL, "Nameset");
	
	if ((sta_umd_active_names >= 1) && (sta_umd_active_names <= 8))
		textPut("text", bncs_string("User Name Set ") + bncs_string(sta_umd_active_names), MAIN_PANEL, "Nameset");
}


// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //
// function updateUMDnames is called when it is nexessary to flush the  //
// name displays on the UMDs and mimics with the current names (UMD)    //
// and the driver short names (mimic displays).                         //
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //
void UMD_and_Tally::updateUMDnames(void)
{
	for (int src = 1; src <= SDI_MTX_SIZE; src++)
	{
		if (Table_F[src] != "")
		{
			// UMD is showing this source
			umd_addr = bncs_stringlist(Table_F[src]);
			for (int i = 0; i < umd_addr.count(); i++)
			{
				// Update UMD and mimimic displays
				infoWrite(sta_tsl_id, CurrentName[src], umd_addr[i]);
				textPut("text", Mtx_Name[src], MAIN_PANEL, bncs_string("UMD_") + bncs_string(umd_addr[i]));
			}
		}	// End of if (Table_F[src] != "")
	}
}


// ++++++++++++++++++++++++++++++ //
// Set the nameset pointer to the // 
// current selected nameset.      //
// ++++++++++++++++++++++++++++++ //
void UMD_and_Tally::setUMDNameSet(void)
{
	switch (sta_umd_active_names)
	{
		case 0:	CurrentName = Mtx_Name; break;
		case 1:	CurrentName = UserNames_1; break;
		case 2:	CurrentName = UserNames_2; break;
		case 3:	CurrentName = UserNames_3; break;
		case 4:	CurrentName = UserNames_4; break;
		case 5:	CurrentName = UserNames_5; break;
		case 6:	CurrentName = UserNames_6; break;
		case 7:	CurrentName = UserNames_7; break;
		case 8:	CurrentName = UserNames_8; break;
	}
	updateNameSetMimic();
}


// ++++++++++++++++++++++++++++++++++++ //
// Show the mimic text that corresponds //
// to the CTA UMD brightness setting.   //
// ++++++++++++++++++++++++++++++++++++ //
void UMD_and_Tally::updateCTAbrightnessMimic(void)
{
	// Update the CTA/Edit 6 UMD Brightness Mimic
	switch (cta_umd_bright)
	{
	case 0:	textPut("text", "Off", MAIN_PANEL, "CTA_UMD_Bright"); break;
	case 1:	textPut("text", "One Seventh", MAIN_PANEL, "CTA_UMD_Bright"); break;
	case 2:	textPut("text", "Half", MAIN_PANEL, "CTA_UMD_Bright"); break;
	case 3:	textPut("text", "Full", MAIN_PANEL, "CTA_UMD_Bright"); break;
	}
}


// +++++++++++++++++++++++++++++++++++++++++ //
// function updateCTAUMDnames() is called to //
// perform a full update of the names on the //
// CTA UMD chain.                            //
// +++++++++++++++++++++++++++++++++++++++++ //
void UMD_and_Tally::updateCTAUMDnames(void)
{
	// Scans all possible SDI and audio router destination indicies, using Table_D_CTA and Table_Aud to check
	// if there is a UMD address recorded for the index. If so, the local panal mimic and the UMD chain names 
	// are updated to show the short matrix name of the source.

	// Scan the video matrix first
	for (int vid = 1; vid <= SDI_MTX_SIZE; vid++)
	{
		if (Table_D_CTA[vid] != 0)
		{
			// Found a destination with a UMD attached
			infoWrite(cta_tsl_id, bncs_string(Mtx_Name[Table_C[vid]]), Table_D_CTA[vid]);								// Update the UMD
			textPut("text", bncs_string(Mtx_Name[Table_C[vid]]), MAIN_PANEL, bncs_string("CTA_") + bncs_string(Table_D_CTA[vid]));	// Update the mimic
		}
	}

	// Now scan the audio matrix
	for (int aud = 1; aud <= AUDIO_MTX_SIZE; aud++)
	{
		if (Table_Aud[aud] != 0)
		{
			routerName(cta_audio_id, 2, AudioStatus[aud], audio_src_name);							// Read audio source name from database
			infoWrite(cta_tsl_id, audio_src_name, Table_Aud[aud]);									// Update the UMD
			textPut("text", audio_src_name, MAIN_PANEL, bncs_string("CTA_") + bncs_string(Table_Aud[aud]));	// Update the mimic
		}
	}
}



