//
// The AVR Interface object and interaction methods
//
#include <stdio.h>
#include <memory.h>
#include <string.h>
#include <malloc.h>
#include <ctype.h>

#include "AVRInterface.h"
#include "AVRCommandDecoder.h"

#define LONGESTTEXT 150

typedef enum {
	eReady,
	ePowerOn,
	ePowerOff,
	eVolumeUp,
	eVolumeDown,
	eMute,
	eUnMute,
} AVRCommands_E;

typedef struct {
	AVRCommands_E MsgID;
	const char *pMsg;
	uint16_t MsgLen;
} Message_T;

typedef struct {
	uint8_t rCmd;
	uint8_t configOffset;	// offset into the Config DT0 - DT155 array, or 0xFF for no action
	uint8_t numToTransfer;	// number of bytes to transfer into the Config array, or 0 for no action
	bool showAll;			// true to call ShowAllStatusInfo after processing
	const char *TextFormatter;	// used primarily for development printf(TextFormatter, value)
	const char *(*fncValueToText)(uint8_t rDat);
} MessageTextHandler_T;



// This is based on the longest thing from the MessageHandlerText text field and the generated text
// "MultiCh Surround" "Surround"
const char *PCMessageFormat = "%18s %-20s";
const uint8_t LengthCheck = 40;

// rCmd: 2-ASCII Hex Bytes converted to uint8_t
// configOffset: offset into avrStatus.config.DTxx
// numToTransfer: number of bytes to transfer from message to config (0 = none)
// show all status 
// fncHelper: optional function to call upon receipt
//
static const MessageTextHandler_T MessageHandlerText[] = {
	//
	// Configuration Map for Response Messages
	// 
	//   +---------------------------- rCmd
	//   |    +----------------------- DT Block Offset
	//   |    |  +-------------------- Number of Bytes to transfer into the DT Block
	//   |    |  |  +----------------- Force a 'show all' status screen update
	//   |    |  |  |  
	//   |    |  |  |  +-------- sprintf format string for the following Helper
	//   |    |  |  |  |                    +--- Helper to convert numeric value to text
	//   |    |  |  |  |                    |
	{ 0x00,   7, 1, 1, "System Report: %s", BusyToText },
	{ 0x01,   0, 0, 0, "Warning Report: %s", WarnToText },
	{ 0x10,  31, 1, 1, "Playback Report: %s", PlaybackToText },
	{ 0x11,  32, 1, 1, "Fs Report: %s", FsToText },
	{ 0x12,  33, 1, 1, "Ex/Ex: %s", OffMatrixDiscreteText },	// EX/EX
	{ 0x13,  34, 1, 1, "Thr Bypass: %s", OffOnText },	// Thr / Bypass
	{ 0x14,  35, 1, 1, "RED dts: %s", ReleaseWaitText},	// RED dts
	{ 0x15,  38, 1, 1, "Tuner tuned: %s", NotTunedTunedText},	// Tuner tuned
	{ 0x16,  43, 1, 1, "DTS 96/24: %s", OffOnText},	// Dts 96/24
	{ 0x20,   8, 0, 1, "Zone Power: %s", ZonePower },
	{ 0x21,   9, 1, 1, "Input: %s", InputText},	// Input Source
	{ 0x22,  11, 1, 1, "Input Mode: %s", InputModeText},	// Input Mode
	{ 0x23,  12, 1, 1, "Mute: %s", OffOnText },
	{ 0x24,  13, 1, 1, "Zone 2 Input: %s", InputText},	// Zone 2 Input Source
	{ 0x25,  14, 1, 1, "Zone 2 Mute: %s", OffOnText },
	{ 0x26,  15, 2, 1, "Volume: %s", VolumeDB },
	{ 0x27,  17, 2, 1, "Zone 2 Vol: %s", VolumeDB},	// Zone 2 Vol
	{ 0x28,  19, 2, 1, "Program: %s", ProgramName },	// Program
	{ 0x29,  25, 1, 1, "Tuner Page: %s", PresetLabelText},	// Tuner Page
	{ 0x2A,  26, 1, 1, "Tuner Preset: %s", PresetNumberText},	// Tuner Preset Number
	{ 0x2B,  23, 1, 1, "OSD: %s", OSDFullShortOffText},	// OSD
	{ 0x2C,  24, 1, 1, "Sleep: %s", SleepTimerText},	// Sleep Timer
	{ 0x2D,  22, 1, 1, "EX/ES(Key): %s", OffMatrixDiscreteAutoText},	// EX/ES(Key)
	{ 0x2E,  29, 1, 1, "SP Relay A: ", OffOnText},	// Speaker Relay A
	{ 0x2F,  30, 1, 1, "SP Relay B: ", OffOnText},	// Speaker Relay B
	{ 0x30,   0, 0, 0, "Home Preset:%s", PresetLabelText },		// Preset A, B, ... F
	{ 0x31,   0, 0, 0, "Home Memory:%s", PresetLabelText },		// Preset A, B, ... F
	{ 0x32,   0, 0, 0, "Home Vol Preset: %s", PresetLabelText },		// Preset A, B, ... F
	{ 0x33,   0, 0, 0, "Home Vol Memory: %s", PresetLabelText },		// Preset A, B, ... F
	{ 0x34,  36, 1, 1, "Headphone: %s", OffOnText },
	{ 0x35,  37, 1, 1, "FM/AM: %s", FMAMText },
	{ 0x36,  39, 1, 1, "DC1 Trigger Out: %s", OffOnText },
	{ 0x37,   0, 0, 0, "Home Zone 2 Vol Preset: %s", PresetLabelText },
	{ 0x38,   0, 0, 0, "Home Zone 2 Vol: %s", PresetLabelText },
	{ 0x39,   0, 0, 0, "Dual Mono: %s", MainSubAllText },
	{ 0x3A,  42, 1, 1, "DC1 Trigger Control: %s", WhichZoneText },
	{ 0x3B,  44, 1, 1, "DC2 Trigger Control: %s", WhichZoneText },
	{ 0x3C,  45, 1, 1, "DC2 Trigger Out: %s", OffOnText },
	{ 0x3D, 104, 0, 0, "Main Level: %s", Norm10dbDownText },
	{ 0x3E,  46, 1, 1, "SP B Set: %s", Zone1Zone2Text },
	{ 0x3F,  47, 1, 1, "Zone 2 Speaker: %s", OffOnText },

	{ 0x40,  48, 2, 1, "Level Main R: %s", PM10dbText },
	{ 0x41,  50, 2, 1, "Level Main L: %s", PM10dbText },
	{ 0x42,  52, 2, 1, "Level Center: %s", PM10dbText },
	{ 0x43,  54, 2, 1, "Level Rear R: %s", PM10dbText },
	{ 0x44,  56, 2, 1, "Level Rear L: %s", PM10dbText },
	{ 0x45,  58, 2, 1, "Level Sur Back R: %s", PM10dbText },
	{ 0x46,  60, 2, 1, "Level Sur Back L: %s", PM10dbText },
	{ 0x47,  62, 2, 1, "Level Front R: %s", PM10dbText },
	{ 0x48,  64, 2, 1, "Level Front L: %s", PM10dbText },
	{ 0x49,  66, 2, 1, "Level Swfr 1: %s", PM10dbText },
	{ 0x4A,   0, 0, 0, "Level Swfr 2: %s", PM10dbText },

	{ 0x50,   0, 0, 0, "Main L/R Bal: %s", BalanceText },
	{ 0x51,  74, 2, 1, "LFE Level SP: %s", M20P0dbText },
	{ 0x52,  76, 2, 1, "LFE Level HP: %s", M20P0dbText },
	{ 0x53,  78, 2, 1, "Audio Delay: %s", ZeroTo160msText },
	{ 0x54,   0, 0, 0, "SP Delay Center: %s", ZeroTo5msText },
	{ 0x55,   0, 0, 0, "SP Delay Rear CT: %s", ZeroTo30msText },

	{ 0x60,  84, 1, 1, "Input Mode: %s", AutoLastText },
	{ 0x61,  85, 1, 1, "Dimmer: %s", M4To0Text },
	{ 0x62,  87, 2, 1, "OSD Shift: %s", M5toP5Text },
	{ 0x63,  89, 1, 1, "Gray Back: %s", OffAutoText },
	{ 0x64,  91, 1, 1, "Dynamic Range SP: %s", MaxStdMinText },
	{ 0x65,  92, 1, 1, "Dynamic Range HP: %s", MaxStdMinText },
	{ 0x66,  93, 0, 0, "Zone 2 Vol out: %s", VarFixText },
	{ 0x67,   0, 0, 0, "Zone 2 Mode: %s", Mode1Mode2Text },
	{ 0x68,  95, 1, 1, "Mem Guard: %s", OffOnText },
	{ 0x69,  90, 1, 1, "Video Conv: %s", OffOnText },
	{ 0x6A, 136, 1, 1, "Comp OSD: %s", OffOnText },
	{ 0x6B,   0, 0, 0, "Zone 3 Vol out: %s", VarFixText },
	{ 0x70,  96, 1, 1, "Center Sp Size: %s", LSNText },
	{ 0x71,  97, 1, 1, "Main Sp Size: %s", LSNText },
	{ 0x72,  98, 1, 1, "Rear LR Sp Size: %s", LSNText },
	{ 0x73,  99, 1, 1, "Sur Back Size: %s", LLSSNText },
	{ 0x74, 100, 1, 1, "Front Sp: %s", YesNoneText },
	{ 0x75, 101, 1, 1, "LFE Bass Out: %s", SwfrMainBothText },
	{ 0x76, 134, 1, 1, "SW1: %s", LrFrNoneText },

	{ 0x78, 102, 1, 1, "6 Ch Center: %s", CenterMainText },
	{ 0x79, 103, 1, 1, "6 Ch Swfr: %s", SwfrMainText },
	{ 0x7A, 133, 1, 1, "6 Ch Surround: %s", SurrMainText },
	{ 0x7B,   0, 0, 0, "Multi Ch Select: %s", SixEightText },
	{ 0x7E, 135, 1, 1, "SW Crossover", CrossOverText },

	{ 0x80, 105, 1, 1, "Test Mode: %s", OffDolbyDspText },
	{ 0x81,   0, 0, 0, "Analog Special: %s", OffOn2OnMultiText },
	{ 0x82,  27, 1, 1, "Night Mode: %s", OffOnText },

	{ 0x90,   0, 0, 0, "Multi Ch Level Main R: %s", M10P10dbText  },
	{ 0x91,   0, 0, 0, "Multi Ch Level Main L: %s", M10P10dbText  },
	{ 0x92,   0, 0, 0, "Multi Ch Level Center: %s", M10P10dbText  },
	{ 0x93,   0, 0, 0, "Multi Ch Level Rear R: %s", M10P10dbText  },
	{ 0x94,   0, 0, 0, "Multi Ch Level Rear L: %s", M10P10dbText  },
	{ 0x95,   0, 0, 0, "Multi Ch Level Sur B R: %s", M10P10dbText  },
	{ 0x96,   0, 0, 0, "Multi Ch Level Sur B L: %s", M10P10dbText  },
	{ 0x97,   0, 0, 0, "Multi Ch Level Front R: %s", M10P10dbText  },
	{ 0x98,   0, 0, 0, "Multi Ch Level Front L: %s", M10P10dbText  },
	{ 0x99,   0, 0, 0, "Multi Ch Level Swfr 1: %s", M20P0dbText  },
	{ 0x9A,   0, 0, 0, "Multi Ch Level Swfr 2: %s", M20P0dbText  },

	{ 0xA1,   0, 0, 0, "Zone 3 Mute: %s", OffOnText },
};

const char *StateMachineTextLabel[] = {
	"Powering Up",
	"Awaiting Ready Response",
	"Initializing",
	"Retry Initializing",
	"Ready",
	"Awaiting Response",
	"Failed to contact",
	"Unknown State",
};


// static instance definition
AVRInterface *AVRInterface::s_instance = nullptr;

// Trampoline: assigned into AVRDriver::NotifyHost so the driver calls into
// AVRInterface::ProcessReportResponse(instance, ...). This avoids changing
// the existing NotifyCallBack signature which has no context pointer.
bool AVRInterface::NotifyTrampoline(AVRMessageType_T type, const void *message, uint32_t attrib) {
	if (AVRInterface::s_instance) {
		return AVRInterface::s_instance->ProcessReportResponse(type, message, attrib);
	}
	return false;
}

AVRInterface::AVRInterface(
	NotifyCallBack notifyCB,	///<! used to notify the host of internal events
	SendCallBack sendCB			///<! used to send to the RS-232 interface
) : AVRDriver(nullptr, sendCB) {
	// store application callback
	AppNotify = notifyCB;

	// mark global instance and install trampoline so AVRDriver calls instance method
	s_instance = this;
	// AVRDriver::NotifyHost is protected; accessible here via inheritance
	NotifyHost = &AVRInterface::NotifyTrampoline;

	RS232Send = sendCB;
	IsSanityCheckOK();
	MessageHandlerSanityCheck();
	//Initialize();

	//avrDrvr = new AVRDriver(ProcessReportResponse, HostSendPassthru);
}


AVRInterface::~AVRInterface() {
	// clear static instance if this was set
	if (s_instance == this) s_instance = nullptr;
}

bool AVRInterface::IsSanityCheckOK() {
	return true;
}


// MessageReport
// 
// Generate a text record to provide buffer to the console, translating non-printable
// characters into hex codes enclosed in square brackets.
//
// Consider line-wrapping to align the next line
//
void AVRInterface::MessageReport(const char *prefix, const void *buf, size_t len) {
	int i = 0;
	const char *p = (const char *)buf;
	uint32_t now_ms = GetTick();
	char txtBuf[LONGESTTEXT] = "";
	char smallBuf[10] = "";
	char *pTxtAppend = txtBuf;

	if (len == 0) len = strlen(p);
	pTxtAppend += sprintf_s(txtBuf, LONGESTTEXT, "%9.3f: [%3d]%s", (float)(now_ms) / 1000.0f, (int)strlen(p), prefix);
	while (*p && ((unsigned)(p - (const char *)buf) < len)) {
		if (isprint(*p)) {
			*pTxtAppend++ = *p;
			*pTxtAppend = 0;	// Keep it null-terminated
			i++;
		} else if (*p == '\r') {
			if (AppNotify) {	// Guard it
				(*AppNotify)(mtInfo, txtBuf, (uint32_t) strlen(txtBuf));
			}
			txtBuf[0] = 0;		// clear it
		} else if (*p == '\n') {
			// skip it
		} else {
			sprintf_s(smallBuf, 10, "[%02X]", (unsigned char)*p);
			strcat_s(txtBuf, LONGESTTEXT, smallBuf);
			pTxtAppend = txtBuf + strlen(txtBuf);
		}
		if ((i & 3) == 0) {
			if (strlen(txtBuf) > 100) {
				if (AppNotify) {	// Guard it
					(*AppNotify)(mtInfo, txtBuf, (uint32_t)strlen(txtBuf));
				}
				sprintf_s(txtBuf, LONGESTTEXT, "                      ");	// new line
				pTxtAppend = txtBuf + strlen(txtBuf);
			} else {
				strcat_s(txtBuf, LONGESTTEXT, " ");
				pTxtAppend++;
			}
			i = 0;
		}
		p++;
	}
	if (strlen(txtBuf)) {
		if (AppNotify) {	// Guard it
			(*AppNotify)(mtInfo, txtBuf, (uint32_t)strlen(txtBuf));
		}
	}
}


//bool AVRInterface::HostSendPassthru(const uint8_t * message, uint32_t len) {
//	return (*HostSendCB)(message, len);
//}

const char *AVRInterface::GetStateLabel(uint32_t st) {
	if (st >= stMaxStates) {
		st = stMaxStates;
	}
	return StateMachineTextLabel[st];
}

#if 1
// ProcessReportResponse
//
// @param[in] szBuffer is the received message
// @param[in] len is the null terminated string length of the message
//
// Typical Message:
//	'\x02'	'type'	'guard'	'rcmd0'	'rcmd1'	'rdat0'	'rdat1'	'\x03'
//	[0]		[1]		[2]		[3]		[4]		[5]		[6]		[7]
//
bool AVRInterface::ProcessReportResponse(AVRMessageType_T mtype, const void *szBuffer, uint32_t param) {
	const uint8_t *p = (const uint8_t *)szBuffer;
	char buf[LONGESTTEXT];
	(void)param;	// not used
	bool showAllFlag = false;

	switch (mtype) {
	case mtModelInfo:
		(*AppNotify)(mtype, szBuffer, param);
		break;
	case mtState:
		(*AppNotify)(mtype, szBuffer, param);
		break;
	case mtInfo:
		(*AppNotify)(mtype, szBuffer, param);
		break;
	case mtStreamStart:
		(*AppNotify)(mtype, szBuffer, param);
		break;
	case mtStream:
		(*AppNotify)(mtype, szBuffer, param);
		break;
	case mtRcvdMsg:
		if (p) {
			// These don't have a checksum ...
			// Example: [02] 4026 66[03]
			//		4 - controlled by encoder
			//		0 - guard status 0 = no guard
			//		26 - master vol
			//		66 - volume setting (-54.6db)
			uint8_t type = (uint8_t)Hex2Dec(p + 1, 1);
			uint8_t guard = (uint8_t)Hex2Dec(p + 2, 1);
			uint8_t rcmd = (uint8_t)Hex2Dec(p + 3, 2);
			uint8_t rdat = (uint8_t)Hex2Dec(p + 5, 2);
			bool found = false;

			for (int i = 0; i < sizeof(MessageHandlerText) / sizeof(MessageHandlerText[0]); i++) {
				if (MessageHandlerText[i].rCmd == rcmd) {
					found = true;
					//if (MessageHandlerText[i].numToTransfer == 1) {
					//	//memcpy(&avrStatus.config.DT0 + MessageHandlerText[i].configOffset, p + 6, 1);
					//}
					//if (MessageHandlerText[i].numToTransfer == 2) {
					//	//memcpy(&avrStatus.config.DT0 + MessageHandlerText[i].configOffset, p + 5, 2);
					//}
					if (MessageHandlerText[i].TextFormatter && MessageHandlerText[i].fncValueToText) {
						if (AppNotify) {	// Guard it
							sprintf_s(buf, LONGESTTEXT, MessageHandlerText[i].TextFormatter, MessageHandlerText[i].fncValueToText(rdat));
							(*AppNotify)(mtInfo, buf, (uint32_t)strlen(buf));
						}
					}
					if (MessageHandlerText[i].showAll) {
						showAllFlag = true; //  ShowAllStatusInfo();
					}
					break;	// no need to scan more
				}
			}
			if (!found && AppNotify) {
				sprintf_s(buf, LONGESTTEXT, "***** type: %X, guard: %X, cmd: %02X, data: %02X", type, guard, rcmd, rdat);
				(*AppNotify)(mtInfo, buf, (uint32_t)strlen(buf));
			}
		} else {
			(*AppNotify)(mtype, szBuffer, param);
		}
	case mtXmtdMsg:
		(*AppNotify)(mtype, szBuffer, param);
		break;
	case mtStatusMsg:
		(*AppNotify)(mtype, szBuffer, param);
		break;
	case mtOperationResp:
		(*AppNotify)(mtype, szBuffer, param);
		break;
	case mtParamUpdated:
		(*AppNotify)(mtype, szBuffer, param);
		break;
	case mtExtendedResp:
		(*AppNotify)(mtype, szBuffer, param);
		break;
	case mtErrNotFound:
		// forward formatted error to application callback
		if (AppNotify) {
			sprintf_s(buf, LONGESTTEXT, "AVRCommand(%d,%d,%d) is invalid.", (param >> 16) & 0xFF, (param >> 8) & 0xFF, param & 0xFF);
			(*AppNotify)(mtype, buf, (uint32_t)strlen(buf));
		}
		break;
	case mtErrRxLength:
		if (AppNotify) {
			sprintf_s(buf, LONGESTTEXT, "Receive Message Length (%d) is invalid.", param);
			(*AppNotify)(mtype, buf, (uint32_t)strlen(buf));
		}
		break;
	case mtErrMalloc:
		if (AppNotify) {
			sprintf_s(buf, LONGESTTEXT, "Malloc (%d) Failed.", param);
			(*AppNotify)(mtype, buf, (uint32_t)strlen(buf));
		}
		break;
	case mtFailed:
		if (AppNotify) {
			(*AppNotify)(mtype, szBuffer, (uint32_t)strlen((const char *)szBuffer));
		}
	};
	return showAllFlag;
}
#endif

void AVRInterface::MessageHandlerSanityCheck() {
	uint8_t usedCommands[256] = { 0 };
	uint8_t blockOffset[256] = { 0 };

	bool fail = false;
	// Ensure that the MessageHandlerText table is correct
	for (int i = 0; i < sizeof(MessageHandlerText) / sizeof(MessageHandlerText[0]); i++) {
		usedCommands[MessageHandlerText[i].rCmd]++;
		if (usedCommands[MessageHandlerText[i].rCmd] > 1) {
			printf("***** MessageHandler entry %d has duplicate rCmd of 0x%02X\n", i, MessageHandlerText[i].rCmd);
			fail = true;
		}
		blockOffset[MessageHandlerText[i].configOffset]++;
		if (MessageHandlerText[i].configOffset != 0 && blockOffset[MessageHandlerText[i].configOffset] > 1) {
			printf("***** MessageHandler entry %d has duplicate configOffset of %d\n", i, MessageHandlerText[i].configOffset);
			fail = true;
		}

		if (MessageHandlerText[i].numToTransfer > 2) {
			printf("***** MessageHandler entry %d has invalid numToTransfer of %d\n", i, MessageHandlerText[i].numToTransfer);
		}
		if (MessageHandlerText[i].configOffset + MessageHandlerText[i].numToTransfer > sizeof(AVR_Configuration_T)) {
			printf("***** MessageHandler entry %d has invalid configOffset of %d\n", i, MessageHandlerText[i].configOffset);
		}
	}
	if (fail) {
		printf("***** MessageHandler table sanity check failed. Exiting.\n");
	}
	return;
}

// PCMessage
//
//	Various responses need to be formatted and shown on screen
//	The last param will allow resetting the column counter
//	and pre and post-pending \n
// 
// @returns how far we moved in the buffer
//
uint8_t AVRInterface::PCMessage(const char *msg, uint8_t len, uint8_t **src, const char *(fncHelper)(uint8_t val), bool highlight) {
	char buf[LONGESTTEXT] = "";
	const char *p = buf;

	if (fncHelper) {
		// Get the binary value if we need it
		uint8_t val = (uint8_t)Hex2Dec(*src, len);
		p = (*fncHelper)(val);
		*src += len;
	} else {
		// Create a string value when we need that
		strncpy_s(buf, LONGESTTEXT, (char *)*src, len);	// s/w
		*src += len;
		buf[len] = '\0';
	}

	char outBuf[LONGESTTEXT] = "";
	sprintf_s(outBuf, LONGESTTEXT, PCMessageFormat,
		msg,
		p
	);
	if (AppNotify) {	// Guard it
		(*AppNotify)(mtStream, outBuf, highlight);
	}
	return len;
}


void AVRInterface::ExportInformation(AVRSubsystem_T subsystem,
	AVRFunction_T function,
	AVRArg_T arg) {
	char buf[LONGESTTEXT] = "";
	typedef struct {
		uint16_t value;
		const char *helpText;
	} ValuePurpose_T;
	const ValuePurpose_T subsysList[] = {
		{ sysCommand, "System Command" },
		{ subMain, "Main Subsystem" },
		{ subRadio, "Radio Control" },
		{ subAudio, "Audio Control" },
		{ subZone1, "Zone 1 Control" },
		{ subZone2, "Zone 2 Control" },
		{ subZone3, "Zone 3 Control" },
	};
	const ValuePurpose_T funcList[] = {
		{ fncReady, "Ready Check" },
		{ fncReportEnable, "Report Enable" },
		{ fncReportDelay, "Report Delay" },
		{ fncRequest, "Report Request" },
		{ fncSetValue, "Set Function Value" },

		{ fncPower, "Power" },
		{ fncVolume, "Volume" },
		{ fncMute, "Mute" },
		{ fncVolumeMemory, "Volume Memory" },
		{ fncVolumeRecall, "Volume Memory Recall" },
		{ fncSpeakerAOnOff, "Speaker A" },
		{ fncSpeakerBOnOff, "Speaker B" },
		{ fncSpeakerBZone, "Speaker B Zone Assignment" },
		{ fncZone2SpeakerOnOff, "Zone 2 Speaker" },
		{ fncNightModeOnOff, "Night Mode Sound Level" },
		{ fncEffect, "Effect <something> " },
		{ fncDSPSoundScape, "DSP Sound Scape" },
		{ fncInput, "Input Selection" },
		{ fncRadioBand, "Radio Band Selection" },
		{ fncRadioTune, "Radio Tune" },
		{ fncRadioPresetPage, "Radio Preset Page" },
		{ fncRadioPresetNumber, "Radio Preset Number" },
		{ fncRadioPresetMemory, "Radio Preset Memory" },
		{ fncRadioPresetRecall, "Radio Preset Recall" },
		{ fncSleep, "Sleep Timer" },
		{ fncOSD, "On Screen Display Control" },
		{ fnc6ChInput, "6 Channel Input mode" },
		{ fncEx_EsOnOff, "EX / ES Mode" },
		{ fncInputMode, "Input Mode" },
		{ fncDualMono, "Dual Mono" },
		{ fncDC1TrigControl, "DC1 Trigger Control" },
		{ fncDC2TrigControl, "DC2 Trigger Control" },
		{ fncDC1OnOff, "DC1" },
		{ fncDC2OnOff, "DC2" },
	};
	const ValuePurpose_T valueList[] = {
		{ eOn,			"On" },
		{ eOff,			"Off" },
		{ eStandby,		"Standby" },
		{ eUp,			"Up" },
		{ eDown,		"Down" },
		{ eOSDOff,		"Off" },
		{ eOSDShort,	"Short" },
		{ eOSDFull,		"Full" },
		{ eSleepOff,	"Off" },
		{ eSleep120,	"120" },
		{ eSleep90,		"90" },
		{ eSleep60,		"60" },
		{ eSleep30,		"30" },
		{ eOnMatrix,	"EX-ES Mode Matrix On" },
		{ eESESOff,		"EX-ES Mode Off" },
		{ eAuto,		"EX-ES Mode Auto" },
		{ eDiscrete,	"EX-ES Mode Discrete" },
		{ eEffectOn,	"Effect On" },
		{ eStereo,		"Effect Stereo" },
		{ eFM,			"FM Band" },
		{ eAM,			"AM Band" },
		{ eA,			 "Group A" },
		{ eB,			 "Group B" },
		{ eC,			 "Group C" },
		{ eD,			 "Group D" },
		{ eE,			 "Group E" },
		{ eF,			 "Group F" },
		{ e1,			 "Preset 1" },
		{ e2,			 "Preset 2" },
		{ e3,			 "Preset 3" },
		{ e4,			 "Preset 4" },
		{ e5,			 "Preset 5" },
		{ e6,			 "Preset 6" },
		{ e7,			 "Preset 7" },
		{ e8,			 "Preset 8" },
		{ ePhono,		 "Phono" },
		{ eCD,			 "CD" },
		{ eTuner,		 "Tuner" },
		{ eCDR,			 "CDR" },
		{ eMD_Tape,		 "MD_Tape" },
		{ eDVD,			 "DVD" },
		{ eDTV,			 "DTV" },
		{ eCable,		 "Cable" },
		{ eSat,			 "Sat" },
		{ eVCR1,		 "VCR1" },
		{ eVCR2_DVR,	 "VCR2_DVR" },
		{ eVCR3,		 "VCR3" },
		{ eV_Aux,		 "V Aux" },

		{ Hall_A, 			"Hall A" 			},	// 00
		{ Hall_B, 			"Hall B" 			},	// 01
		{ Hall_C, 			"Hall C" 			},	// 02
		{ Hall_USA, 		"Hall C2" 			},	// 04
		{ Hall_E, 			"Hall E" 			},	// 05
		{ Live_Concert, 	"Live Concert" 		},	// 06
		{ Tokyo, 			"Tokyo" 			},	// 08
		{ Freiburg, 		"Freiburg" 			},	// 09
		{ Royaumont, 		"Royaumont" 		},	// 0A
		{ Village_Gate, 	"Village Gate" 		},	// 0C
		{ Village_Vanguard, "Village Vanguard" 	},	// 0D
		{ The_Bottom_Line, 	"The Bottom Line" 	},	// 0E
		{ The_Roxy_Theater, "The Roxy Theater" 	},	// 10
		{ Warehouse_Loft, 	"Warehouse Loft" 	},	// 11
		{ Arena, 			"Arena" 			},	// 12
		{ Disco, 			"Disco" 			},	// 14
		{ Party, 			"Party" 			},	// 15
		{ Game,  			"Game" 				},	// 16
		{ Stereo_6_8Ch, 	"Stereo 6/8Ch" 		},	// 17
		{ Pop_Rock, 		"Pop/Rock" 			},	// 18
		{ DJ, 				"DJ" 				},	// 19
		{ Opera, 			"Opera" 			},	// 1C
		{ Pavillion, 		"Pavillion" 		},	// 1D
		{ Mono_Movie, 		"Mono/Movie" 		},	// 20
		{ Variety_Sports, 	"Variety/Sports" 	},	// 21
		{ Spectacre, 		"Spectacre" 		},	// 24
		{ Sci_Fi, 			"Sci-Fi" 			},	// 25
		{ Adventure, 		"Adventure" 		},	// 28
		{ General, 			"General" 			},	// 29
		{ Normal, 			"Normal" 			},	// 2C
		{ Enhanced, 		"Enhanced" 			},	// 2D
		{ PLII_Movie, 		"PLII Movie" 		},	// 30
		{ PLII_Music, 		"PLII Music" 		},	// 31
		{ Neo_6_Movie, 		"Neo 6 Movie" 		},	// 32
		{ Neo_6_Music, 		"Neo 6 Music" 		},	// 33
		{ Direct_2Ch, 		"Direct 2Ch" 		},	// 34
		{ Stereo_2Ch, 		"Stereo 2Ch" 		},	// 35
		{ THX_Ultra_PL, 	"THX A Ultra PL"	},	// 36
		{ THX_Music, 		"THX B Music" 		},	// 37
		{ THX_Ultra_PL2, 	"THX Ultra PL2" 	},
		{ THX_Ultra_NEO6, 	"THX Ultra NEO6"	},

		{ eInpAuto, "Input Auto" },
		{ eDD_RF, "Input DD/RF" },
		{ eDTS, "Input DTS" },
		{ eDigital, "Input Digital" },
		{ eAnalog, "Input Analog" },
		{ eAAC, "Input AAC" },

		{ eZone1, "Zone 1" },
		{ eZone2, "Zone 2" },
		{ eZone3, "Zone 3" },
		{ eZoneOR, "Zone OR" },

		{ eDualMain, "Dual Main" },
		{ eDualSub, "Dual Sub" },
		{ eDualAll, "Dual All" },

		{ e0ms, "0 ms" },
		{ e50ms, "50 ms" },
		{ e100ms, "100 ms" },
		{ e150ms, "150 ms" },
		{ e200ms, "200 ms" },
		{ e250ms, "250 ms" },
		{ e300ms, "300 ms" },
		{ e350ms, "350 ms" },
		{ e400ms, "400 ms" },
		{ eTuningFreq, "Tuning Frequency" },
		{ eMainVolDB, "Main Volume" },
		{ eZone2VolDB, "Zone 2 Volume" },
		{ eInputName, "Zone 1 Input Name" },
		{ eZone2InputName, "Zone 2 Input Name" },
		{ eZoneXVolDB, "Zone X Volume" },
		{ eZoneXInputName, "Zone X Input Name" },

		{ eMasterVol, "Master Volume" },
		{ eZone2Vol, "Zone 2 Volume" },
		{ eMainLRBal, "Left-Right Balance" },
		{ eMainLevel, "Main Level" },
		{ eZone3Vol, "Zone 3 Volume" },

		{ eMainLevelR, "Main Level R" },
		{ eMainLevelL, "Main Level L" },
		{ eCenterLevel, "Center Level" },
		{ eRearR, "Rear Level R" },
		{ eRearL, "Rear Level L" },
		{ eFrontR, "Front Level R" },
		{ eFrontL, "Front Level L" },
		{ eSurBackR, "Surround Back R" },
		{ eSurBackL, "Surround Back L" },
		{ eSwfr1, "Subwoofer 1" },
		{ eSwfr2, "Subwoofer 2" },


		{ e0ms, "0 ms" },
	};
	(*NotifyHost)(mtInfo, "", 0);
	(*NotifyHost)(mtInfo, "AVR Command Line Control uses 3 numeric parameters:", 0);
	(*NotifyHost)(mtInfo, "     1) Subsystem Identifier for the subsystem control", 0);
	(*NotifyHost)(mtInfo, "     2) Function Identifier valid for the subsystem", 0);
	(*NotifyHost)(mtInfo, "     3) Value Identifier valid for the subsystem and function", 0);
	(*NotifyHost)(mtInfo, "", 0);
	(*NotifyHost)(mtInfo, "     NOTE: Most commands are unavailable when the system is off.", 0);
	(*NotifyHost)(mtInfo, "     ", 0);
	(*NotifyHost)(mtInfo, "", 0);
	sprintf_s(buf, LONGESTTEXT, "%8d: %-21s | %3d: %-28s | %3d: %-22s | %-13s",
		1, "Subsystem Identifier", 2, "Function Identifier", 3, "Value Identifier", "Stream"
	);
	(*NotifyHost)(mtInfo, buf, (uint32_t)strlen(buf));
	sprintf_s(buf, LONGESTTEXT, "      %2s: %-21s | %3s: %-28s | %3s: %22s | %13s",
		"##", "#####################", "##", "############################", "##", "######################", "#############"
	);
	(*NotifyHost)(mtInfo, buf, (uint32_t)strlen(buf));

	AVRSubsystem_T refSub = subsystemCount;
	AVRFunction_T refFnc = fncFunctionCount;
	const char *pSubsystemText = NULL, *pFunctionText = NULL, *pValueText = NULL;
	uint16_t count = GetMessageTableSize();
	for (uint16_t i = 0; i < count; i++) {
		MessageTable_T *p = GetMessageTableRecord(i);
		// Subsystem scan or filter
		//
		if (subsystem != subsystemCount && subsystem != p->subsystem) {
			continue;
		}
		if (function != fncFunctionCount && function != p->function) {
			continue;
		}
		if (arg != eARGCount && arg != p->arg) {
			continue;
		}

		if (refSub != p->subsystem) {
			refSub = p->subsystem;
			for (int j = 0; j < sizeof(subsysList) / sizeof(ValuePurpose_T); j++) {
				if (subsysList[j].value == refSub) {
					pSubsystemText = subsysList[j].helpText;
					break;
				} else {
					pSubsystemText = "";
				}
			}
		} else {
			pSubsystemText = "";
		}
		//
		// function choices
		//
		if (refFnc != p->function) {
			refFnc = p->function;
			for (int j = 0; j < sizeof(funcList) / sizeof(ValuePurpose_T); j++) {
				if (funcList[j].value == refFnc) {
					pFunctionText = funcList[j].helpText;
					break;
				} else {
					pFunctionText = "";
				}
			}
		} else {
			pFunctionText = "";
		}
		//
		// argument choices
		//
		for (int j = 0; j < sizeof(valueList) / sizeof(ValuePurpose_T); j++) {
			if (valueList[j].value == p->arg) {
				pValueText = valueList[j].helpText;
				break;
			} else {
				pValueText = "";
			}
		}
		sprintf_s(buf, LONGESTTEXT, "%8d: %-21s | %3d: %-28s | %3d: %-22s | %s",
			refSub, pSubsystemText,
			refFnc, pFunctionText,
			p->arg, pValueText,
			MessageToText(p->Message, p->MessageLen)
		);
		(*NotifyHost)(mtInfo, buf, (uint32_t)strlen(buf));
	}	
};


const char *AVRInterface::MessageToText(const char *p, size_t len) {
	static char privateBuffer[LONGESTTEXT] = "";
	char *pOut = privateBuffer;
	for (int i = 0; i < len; i++) {
		if (isprint(*p)) {
			*pOut++ = *p++;
		} else {
			pOut += sprintf_s(pOut, LONGESTTEXT - strlen(privateBuffer), "[%02X]", *p++);
		}
		*pOut = '\0'; // keep it null terminated
	}
	return privateBuffer;
}


//
// Single Linear according to the on-screen display of Volume
//		db = 0.5 x - 99.5
// therefore:
//		db + 99.5 = 0.5 x
//		0.5 x == db + 99.5
//		x = 2 * (db + 99.5)
//
uint8_t AVRInterface::VolumeDBtoAPIValue(float db) {
	if (db >= -80.0f && db <= +16.5f) {
		return (uint8_t)(2 * (db + 99.5));
	} else {
		return (0);	// mute
	}
}

/// ReportAllStatus
///
/// This emits (via a callback) all the status, in DT0 to DT137 order.
/// You cannot rearrange the sequence to make grouping more logical.
/// 
/// @param[in] offsetChanged indicates what index into the big buffer changed
/// 
void AVRInterface::ReportAllStatus(uint8_t offsetChanged) {
	const AVRDriver::AVR_Status_T * avStat = GetAVRStatusData();
	if (avStat->headerValid && AppNotify) {
		char buf[LONGESTTEXT];
		sprintf_s(buf, LONGESTTEXT, "Model ID: %c%c%c%c%c, ver: %c\n", avStat->header.type[0], avStat->header.type[1],
			avStat->header.type[2], avStat->header.type[3], avStat->header.type[4],
			avStat->header.version);
		(*AppNotify)(mtModelInfo, buf, (uint32_t)strlen(buf));

		uint8_t *p = (uint8_t *)&avStat->config;
		(*AppNotify)(mtStreamStart, "---- AVR Status Report ----\n", 0);
		int offset = 0;
		offset += PCMessage("Baud Rate", 1, &p, NULL, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
		offset += PCMessage("Rx Buffer", 2, &p, NULL, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
		offset += PCMessage("Cmd Timeout", 3, &p, NULL, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
		offset += PCMessage("Handshake", 1, &p, NULL, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
		offset += PCMessage("Busy", 1, &p, BusyToText, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
		offset += PCMessage("Power", 1, &p, ZonePower, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
		if (avStat->configValid) {
			offset += PCMessage("Zone 1 Input", 1, &p, InputText, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("6 ch", 1, &p, OffOnText, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("Inp mode", 1, &p, InputModeText, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("Mute", 1, &p, OffOnText, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("Zone 2 Input", 1, &p, InputText, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("Zone 2 Mute", 1, &p, OffOnText, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("Volume", 2, &p, VolumeDB, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("Zone 2 Volume", 2, &p, VolumeDB, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("Prog", 2, &p, ProgramName, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("Effect", 1, &p, OffOnText, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("6.1/es status", 1, &p, OffMatrixDiscreteAutoText, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("OSD", 1, &p, OSDFullShortOffText, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("Sleep", 1, &p, SleepTimerText, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("Tuner Pg", 1, &p, PresetLabelText, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("Tuner #", 1, &p, PresetNumberText, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("Night", 1, &p, OffOnText, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("?????", 1, &p, NULL, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("Spkr A", 1, &p, OffOnText, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("Spkr B", 1, &p, OffOnText, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("Playback", 1, &p, PlaybackToText, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("Fs", 1, &p, FsToText, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("Ex/Es", 1, &p, OffMatrixDiscreteText, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("Thr Bypass", 1, &p, OffOnText, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("Red DTS", 1, &p, ReleaseWaitText, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("Headphone", 1, &p, OffOnText, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("Tuner Band", 1, &p, FMAMText, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("Tuner Tuned", 1, &p, NotTunedTunedText, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("DC1 Control Out", 1, &p, OffOnText, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("?????", 2, &p, NULL, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("DC1 Trig Ctrl", 1, &p, NULL, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("DTS 96/24", 1, &p, OffOnText, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("DC2 Trig Ctrl", 1, &p, WhichZoneText, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("DC2 Trig Out", 1, &p, OffOnText, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("Spkr B Set", 1, &p, Zone1Zone2Text, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("Zone 2 SP out", 1, &p, OffOnText, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("Main R", 2, &p, PM10dbText, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("Main L", 2, &p, PM10dbText, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("Center", 2, &p, PM10dbText, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("Rear R", 2, &p, PM10dbText, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("Rear L", 2, &p, PM10dbText, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("Sur Bk R", 2, &p, PM10dbText, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("Sur Bk L", 2, &p, PM10dbText, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("Front R", 2, &p, PM10dbText, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("Front L", 2, &p, PM10dbText, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("Subwfr 1", 2, &p, PM10dbText, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("?????", 6, &p, NULL, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("LFE SP", 2, &p, M20P0dbText, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("LFE HP", 2, &p, M20P0dbText, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("Audio Delay", 2, &p, ZeroTo160msText, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("?????", 4, &p, NULL, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("Inp Mode Set", 1, &p, AutoLastText, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("Dimmer", 1, &p, M4To0Text, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("OSD Msg", 1, &p, NULL, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("OSD Shift", 2, &p, M5toP5Text, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("Gray Back", 1, &p, OffAutoText, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("Video Conv", 1, &p, OffOnText, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("D Range SP", 1, &p, MaxStdMinText, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("D Range HP", 1, &p, MaxStdMinText, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("Zone 2 Vol out", 1, &p, VarFixText, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("?????", 1, &p, NULL, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);		// "Zone 2 Mode: %s", Mode1Mode2Text ???
			offset += PCMessage("Memory Guard", 1, &p, OffOnText, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("SP set center", 1, &p, NULL, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("SP set main", 1, &p, NULL, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("SP set rear L/R", 1, &p, NULL, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("SP set rear ct", 1, &p, NULL, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("SP set front", 1, &p, NULL, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("SP set LFE/Bass", 1, &p, SwfrMainBothText, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("6 ch center", 1, &p, NULL, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("6 ch sub", 1, &p, NULL, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("Main level", 1, &p, NULL, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("Test Mode", 1, &p, OffDolbyDspText, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("?????", 1, &p, NULL, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("Lvl 6 Ch main L",	2, &p, M10P10dbText, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("Lvl 6 Ch main R",	2, &p, M10P10dbText, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("Lvl 6 Ch center",	2, &p, M10P10dbText, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("Lvl 6 Ch sl",		2, &p, M10P10dbText, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("Lvl 6 Ch sr",		2, &p, M10P10dbText, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("Lvl 6 Ch sbl",		2, &p, M10P10dbText, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("Lvl 6 Ch sbr",		2, &p, M10P10dbText, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("Lvl 6 Ch F L",	2, &p, M10P10dbText, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("Lvl 6 Ch F R",	2, &p, M10P10dbText, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("Lvl 6 Ch swfr",		2, &p, M20P0dbText , (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("Zone 3 Input", 1, &p, PlaybackToText, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("Zone 3 Mute", 1, &p, OffOnText, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("Zone 3 Volume", 2, &p, VolumeDB, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("?????", 1, &p, NULL, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("MultiCh Select", 1, &p, SixEightText, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("MultiCh Surround", 1, &p, SurrMainText, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("SP Set SW1", 1, &p, LrFrNoneText, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("SP Set Crossover", 1, &p, CrossOverText, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("Component OSD", 1, &p, OffOnText, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
			offset += PCMessage("PB/SB Select", 1, &p, PRSBText, (offsetChanged == 0xFF || offsetChanged == offset) ? true : false);
		}
	}
}
