
#include <string.h>
#include "lgi/common/Lgi.h"
#include "lgi/common/Midi.h"
#include "lgi/common/DbTable.h"
#include "mc4.h"
#include "resdefs.h"

#define LN(c)				((c) & 0xf)			// Extract the low nibble
#define HN(c)				(((c) >> 4) & 0xf)	// Extract the high nibble

#if !defined(WIN32) && !defined(MAC)
#define assert(v)
#else
#include <assert.h>
#endif

uint8_t McId[3] = { 0x00, 0x02, 0x75 };

LColour LedRingNormalCol(0, 0, 255);
LColour LedRingAmpModeColour(255, 0, 255);

///////////////////////////////////////////////////////////////////////////
int ParamGroupIndex(ParameterModes g)
{
	switch (g)
	{
		case ModeExtern1and2:
			return 0;
		case ModeExtern3and4:
			return 2;
		case ModeExtern5and6:
			return 4;
		case ModeExtern7and8:
			return 6;
		default:
			break;
	}
	return -1;
}

int MidiPacketSize(uint8_t *ptr, int len)
{
	if (!ptr || len < 1)
		return 0;

	if (ptr[0] == MIDI_SYSEX_START)
	{
		int i;
		for (i=1; i<len; i++)
		{
			if (ptr[i] == MIDI_SYSEX_END)
				return i + 1;
		}
	}
	else
	{
		switch (ptr[0] >> 4)
		{
			case 0x8:
			case 0x9:
			case 0xa:
			case 0xb:
			case 0xe:
				if (len >= 3)
					return 3;
				break;
			case 0xc:
			case 0xd:
				if (len >= 2)
					return 2;
				break;
			default:
				return 1;
				break;
		}
	}

	return 0;
}

static void MidiCmdNone(MidiCmd *c)
{
	c->Id = MIDI_CMD_NONE;
	c->Param = 0;
	c->Min = 0;
	c->Max = 0;
}

static void MidiCmdCc(MidiCmd *c, int cc)
{
	c->Id = MIDI_CMD_CC;
	c->Param = cc;
	c->Min = 0;
	c->Max = 127;
}

static void MidiCmdSysex(MidiCmd *c, int block, int param)
{
	c->Id = block;
	c->Param = param;
	c->Min = 0;
	c->Max = 255;
}

////////////////////////////////////////////////////////////////////////////////
GlobalCfg::GlobalCfg()
{
	MidiChannel = 0;
	TunerCc = 15;
	TempoCc = 14;
	ParamDamping = 25;
	ParamValid = 8;
	BankSize = 0;
	CurrentBtnPane = 0;

	for (int Mode=0; Mode<ModeMax; Mode++)
	{
		for (int Param=0; Param<MAX_PARAM; Param++)
		{
			for (int Cmd=0; Cmd<MAX_CMDS; Cmd++)
			{
				MidiCmdNone(Params[Mode][Param] + Cmd);
			}
		}
	}

	// Initialize the normal mode to send CC's according to the default Axefx extern CC's
	int Cc = AXEFX_EXTERN_BASE;
	MidiCmdCc(Params[ModeExtern1and2][0], Cc++);
	MidiCmdCc(Params[ModeExtern1and2][1], Cc++);
	MidiCmdCc(Params[ModeExtern3and4][0], Cc++);
	MidiCmdCc(Params[ModeExtern3and4][1], Cc++);
	MidiCmdCc(Params[ModeExtern5and6][0], Cc++);
	MidiCmdCc(Params[ModeExtern5and6][1], Cc++);
	MidiCmdCc(Params[ModeExtern7and8][0], Cc++);
	MidiCmdCc(Params[ModeExtern7and8][1], Cc++);

	// Initize Amp1 mode to vary Gain, Bass, Mid, Treble, Presence, Master on Amp1
	MidiCmdSysex(Params[ModeAmp1GainMaster][0], BlockAmp1, 1);
	MidiCmdSysex(Params[ModeAmp1GainMaster][1], BlockAmp1, 2);
	MidiCmdSysex(Params[ModeAmp1BassMids][0],   BlockAmp1, 3);
	MidiCmdSysex(Params[ModeAmp1BassMids][1],   BlockAmp1, 4);
	MidiCmdSysex(Params[ModeAmp1TreblePres][0], BlockAmp1, 20);
	MidiCmdSysex(Params[ModeAmp1TreblePres][1], BlockAmp1, 5);
	MidiCmdNone(Params[ModeAmp1Level][0]);
	MidiCmdNone(Params[ModeAmp1Level][1]);

	#if 0
	// Initize Amp2 mode to vary Gain, Bass, Mid, Treble, Presence, Master on Amp2
	MidiCmdSysex(Params[ModeAmp2][0], BlockAmp2, 1);
	MidiCmdSysex(Params[ModeAmp2][1], BlockAmp2, 2);
	MidiCmdSysex(Params[ModeAmp2][2], BlockAmp2, 3);
	MidiCmdSysex(Params[ModeAmp2][3], BlockAmp2, 4);
	MidiCmdSysex(Params[ModeAmp2][4], BlockAmp2, 20);
	MidiCmdSysex(Params[ModeAmp2][5], BlockAmp2, 5);
	MidiCmdNone(Params[ModeAmp2][6]);
	MidiCmdNone(Params[ModeAmp2][7]);
	#endif
	
	// Init params
	for (int i=0; i<CountOf(ParamValue); i++)
		ParamValue[i] = -1;
}

bool GlobalCfg::Serialize(const char *Path, bool Write)
{
	LFile f;
	auto Exists = LFileExists(Path);
	if
	(
		(!Write && !Exists)
		||
		!f.Open(Path, Write?O_WRITE:O_READ)
	)
	{
		LgiTrace("%s:%i - Failed to open '%s' for writing.\n", _FL, Path);
		return false;
	}

	LXmlTree t;
	LXmlTag r("Mc4Settings");

	if (!Write &&
		!t.Read(&r, &f))
	{
		LgiTrace("%s:%i - XML parse failed.\n", _FL, Path);
		return false;
	}

	LXmlTag *Values = r.GetChildTag("ParamValues", Write);
	if (Values)
	{
		for (unsigned i=0; i<CountOf(ParamValue); i++)
		{
			LString Nm;
			Nm.Printf("Value-%i", i);
			
			LXmlTag *c = Values->GetChildTag(Nm, Write);
			if (c)
			{
				if (Write)
					c->SetContent(ParamValue[i]);
				else
				{
					ParamValue[i] = c->GetContentAsInt();
					printf("Loaded %s as %i\n", Nm.Get(), ParamValue[i]);
				}
			}
		}
	}

	LXmlTag *Btns = r.GetChildTag("Buttons", Write);
	if (Btns)
	{
		const char *typeAttr = "type";
		for (int Pane = 0; Pane < MAX_BUTTON_PANES; Pane++)
		{
			LString s;
			s.Printf("Pane_%i", Pane);
			auto PaneTag = Btns->GetChildTag(s, Write);
			if (PaneTag)
			{
				for (int y=0; y<BUTTONS_Y; y++)
				{
					for (int x=0; x<BUTTONS_X; x++)
					{
						LString n;
						n.Printf("Button_%i_%i", x, y);
						auto BtnTag = PaneTag->GetChildTag(n, Write);
						if (BtnTag)
						{
							auto &Cfg = BtnConfig[Pane][x][y];

							if (Write)
								BtnTag->SetAttr(typeAttr, Cfg.b);
							else
							{
								auto t = BtnTag->GetAsInt(typeAttr);
								if (t >= BtnTypeNone && t < BtnTypeMax)
									Cfg.b = (HwButton)t;
								else
									Cfg.b = BtnTypeNone;
							}
						}
					}
				}
			}
		}

		if (!Write)
			ReindexBtns();
	}
	
	if (Write)
		return t.Write(&r, &f);

	return true;
}

////////////////////////////////////////////////////////////////////////////////
#define IaOpt_Colour			"Colour"
#define IaOpt_AutoSelectMode	"AutoSelectMode"
#define IaOpt_BlockAction		"BlockAction"
#define IaOpt_Group				"Group"
#define IaOpt_Command			"Command"
#define IaOpt_Type				"Type"
#define IaOpt_Param				"Param"
#define IaOpt_Min				"Min"
#define IaOpt_Max				"Max"

bool IA::Serialize(LXmlTag &t, bool Write)
{
	if (Write)
	{
		// IA -> tag
		if (!LedColour.IsTransparent())
		{
			LXmlTag *Col = t.GetChildTag(IaOpt_Colour, true);
			if (Col)
				Col->SetContent(LedColour.GetStr());
		}

		t.SetAttr(IaOpt_AutoSelectMode, AutoSelectMode);
		t.SetAttr(IaOpt_BlockAction, (int)BlockAction);
		if (Group >= 0)
			t.SetAttr(IaOpt_Group, Group);
		
		for (unsigned i=0; i<Cmds.Length(); i++)
		{
			MidiCmd &Cmd = Cmds[i];
			LXmlTag *c = new LXmlTag(IaOpt_Command);
			if (c)
			{
				c->SetAttr(IaOpt_Type, Cmd.Id);
				c->SetAttr(IaOpt_Param, Cmd.Param);
				c->SetAttr(IaOpt_Min, Cmd.Min);
				c->SetAttr(IaOpt_Max, Cmd.Max);
				t.InsertTag(c);
			}			
		}
	}
	else
	{
		// tag -> IA
		LXmlTag *Col = t.GetChildTag(IaOpt_Colour);
		LedColour.Empty();
		if (Col)
		{
			LCss::ColorDef c;
			const char *Defs = Col->GetContent();
			if (c.Parse(Defs) && c.Type == LCss::ColorRgb)
				LedColour.Set(c.Rgb32, 32);
		}

		auto Asm = t.GetAttr(IaOpt_AutoSelectMode);
		if (Asm)
			AutoSelectMode = (int)Atoi(Asm);

		auto Action = t.GetAttr(IaOpt_BlockAction);
		if (Action)
			BlockAction = (IaBlockAction)Atoi(Action);
		else
			BlockAction = BlockAction_OnOff; // default

		auto Grp = t.GetAttr(IaOpt_Group);
		if (Grp)
			Group = (int)Atoi(Grp);
		else
			Group = -1;
		
		Cmds.Length(0);
		for (int i=0; i<t.Children.Length(); i++)
		{
			LXmlTag *c = t.Children[i];
			if (c->IsTag(IaOpt_Command))
			{
				MidiCmd &Cmd = Cmds.New();
				Cmd.Id = c->GetAsInt(IaOpt_Type);
				Cmd.Param = c->GetAsInt(IaOpt_Param);
				Cmd.Min = c->GetAsInt(IaOpt_Min);
				Cmd.Max = c->GetAsInt(IaOpt_Max);
			}			
		}
	}
	
	return true;
}

LArray<IA*> PresetCfg::GetTypePress()
{
	LArray<IA*> a;
	for (auto &i: IAs)
		a.Add(&i);
	return a;
}

IA &PresetCfg::GetIa(IaRef ref)
{
	return IAs[ref.Index];
}

void PresetCfg::SetDirty()
{
	if (!Dirty)
	{
		Dirty = true;
		LgiTrace("%s:%i - Setting preset dirty...\n", _FL);

		if (Parent)
			Parent->PostEvent(M_SET_DIRTY);
		else
			LAssert(!"No parent ptr.");
	}
}

enum PresetIaFields
{
	IaNone,
	IaIndex,
	IaColour,
	IaType,
	IaParam,
	IaMin,
	IaMax,
};

bool PresetCfg::Serialize(const char *Path, bool Write)
{
	if (!Path)
	{
		LgiTrace("%s:%i - No path for preset file.\n", _FL);
		return false;
	}
		
	const char *Dot = strrchr(Path, '.');
	if (!Dot)
	{
		LgiTrace("%s:%i - No extension in preset file.\n", _FL);
		return false;
	}
	
	bool IsXml = !stricmp(Dot, ".xml");
	bool IsDb = !stricmp(Dot, ".db");
	if (!IsXml && !IsDb)
	{
		LgiTrace("%s:%i - Unknown preset file format: %s.\n", _FL, Dot);
		return false;
	}

	if (Write)
	{
		// Preset -> File
		if (IsDb)
		{
			LDbTable t;
			
			LProfile Prof("Write DB");
			
			t.AddField(IaIndex, GV_INT32);
			t.AddField(IaColour, GV_INT32);
			t.AddField(IaType, GV_INT32);
			t.AddField(IaParam, GV_INT32);
			t.AddField(IaMin, GV_INT32);
			t.AddField(IaMax, GV_INT32);

			Prof.Add("Rows");

			for (unsigned i=0; i<IAs.Length(); i++)
			{
				LDbRow *r = t.NewRow();
				if (!r)
				{
					LgiTrace("%s:%i - NewRow failed.\n", _FL);
					return false;
				}
				
				IA &ia = IAs[i];
				r->SetInt(IaIndex, i);
				
				if (ia.Cmds.Length() > 0)
				{
					MidiCmd &cmd = ia.Cmds[0];
					r->SetInt(IaType, cmd.Id);
					r->SetInt(IaParam, cmd.Param);
					r->SetInt(IaMin, cmd.Min);
					r->SetInt(IaMax, cmd.Max);
				}
			}

			Prof.Add("Write");
			
			if (!t.Serialize(Path, true))
			{
				LgiTrace("%s:%i - Failed to write DB: %s\n", _FL, Path);
				return false;
			}
		}
		else if (IsXml)
		{
			LFile f;
			if (!f.Open(Path, O_WRITE))
			{
				LgiTrace("%s:%i - Failed to open '%s' for writing.\n", _FL, Path);
				return false;
			}
			
			// Load values into XML tag
			LXmlTag r;
			LString sep = ",";
			r.SetTag("Preset");
			
			if (IAs.Length() > 0)
			{
				LXmlTag *InstAccess = r.GetChildTag("InstantAccess", true);
				if (InstAccess)
				{
					for (unsigned i=0; i<IAs.Length(); i++)
					{			
						IA &ia = IAs[i];
						LXmlTag *IaTag = new LXmlTag("IA");
						if (IaTag)
						{
							IaTag->SetAttr("Index", (int)i);
							ia.Serialize(*IaTag, Write);						
							InstAccess->InsertTag(IaTag);
						}
					}
				}
			}
			
			LXmlTree t;
			if (!t.Write(&r, &f))
			{
				LgiTrace("%s:%i - Failed to write XML to '%s'.\n", _FL, Path);
				return false;
			}
			
			f.Close();
		}
		else
		{
			LgiTrace("%s:%i - Unsupported write format: %s\n", _FL, Dot);
			return false;
		}
		
		// We are no longer dirty...
		Dirty = false;
	}
	else
	{
		IAs.Length(0);

		if (!LFileExists(Path))
			return false;

		printf("Reading '%s'\n", Path);

		if (IsXml)
		{
			LFile f;
			LXmlTree t;
			LXmlTag r;
			LString sep = ",";
	
			// File -> Preset
			if (!f.Open(Path, O_READ))
			{
				LgiTrace("%s:%i - Failed to open '%s' for reading.\n", _FL, Path);
				return false;
			}

			if (!t.Read(&r, &f))
			{
				LgiTrace("%s:%i - Failed to read XML from '%s'.\n", _FL, Path);
				return false;
			}
			
			// Read values from XML tag
			LXmlTag *InstAccess = r.GetChildTag("InstantAccess");
			if (InstAccess)
			{
				for (int i=0; i<InstAccess->Children.Length(); i++)
				{
					LXmlTag *c = InstAccess->Children[i];
					if (c->IsTag("IA"))
					{
						int Index = c->GetAsInt("Index");
						if (Index >= 0 && Index < IA_BTNS)
						{
							IA &ia = IAs[Index];
							ia.Serialize(*c, Write);
						}
					}
				}
			}
		}
		else if (IsDb)
		{
			LProfile Prof("Read DB");

			LDbTable t;
			if (!t.Serialize(Path, false))
			{
				LgiTrace("%s:%i - Failed to read DB from '%s'.\n", _FL, Path);
				return false;
			}

			Prof.Add("Rows");
						
			for (LDbRow *r = NULL; t.Iterate(r); )
			{
				int64 Index = r->GetInt(IaIndex);
				if (Index >= 0)
				{
					IA &ia = IAs[(size_t)Index];
					ia.LedColour.c32((uint32_t)r->GetInt(IaColour));
					
					MidiCmd &cmd = ia.Cmds[0];
					cmd.Id = (uint8_t)r->GetInt(IaType);
					cmd.Param = (uint8_t)r->GetInt(IaParam);
					cmd.Min = (uint8_t)r->GetInt(IaMin);
					cmd.Max = (uint8_t)r->GetInt(IaMax);
				}
			}
		}
		else
		{
			LgiTrace("%s:%i - Unsupported write format: %s\n", _FL, Dot);
			return false;
		}
	}
	
	return true;
}

///////////////////////////////////////////////////////////////////////////
class NullEventSink : public LEventSinkI
{
public:
	bool PostEvent(int Cmd, LMessage::Param a = 0, LMessage::Param b = 0)
	{
	    printf("%s:%i - NullEventSink::PostEvent...\n", _FL);
	    return false;
	}
};

Mc4Logic::Mc4Logic(Mc4Shared *shared)
{
	Shared = shared;
	Hw = NULL;
	Ui = new NullEventSink;
	TempoOnTs = 0;
	DelayTempoCycle = false;
	DelayToggle = false;	
	TunerIdx = -1;
	TempoIdx = -1;
	ZeroObj(EffectOn);	
	ZeroObj(LedRingValue);
}

Mc4Logic::~Mc4Logic()
{
	auto Sh = Shared->Lock(_FL);
	if (Hw && Sh)
	{
		for (int i=0; i<Sh->Global.GetButtons(); i++)
			Hw->SetLed(i, LedOff);
	}
}

void Mc4Logic::SetInterfaces(Hardware *hw, LEventSinkI *ui)
{
    DeleteObj(Ui);
	Hw = hw;
	Ui = ui;
}

void Mc4Logic::SetCtrlColour(const LColour &c, bool DoNum, bool DoName)
{
	Ui->PostEvent(	M_SET_CTRL_COLOUR,
					(LMessage::Param)c.c32(),
					(LMessage::Param)( (DoNum?DO_NUM:0)|(DoName?DO_NAME:0) ));
}

void Mc4Logic::SetPreset(int i, uint8_t SendMidi)
{
	if (i >= MAX_PATCH)
		i = MAX_PATCH - 1;

	auto Sh = Shared->Lock(_FL);
	if (!Sh || !Hw)
		return;
		
	if (i != Sh->PresetIndex)
	{
		SetCtrlColour(ColourDisabled, true, true);
		SetPresetIndex(i);
		
		if (SendMidi)
		{
			// Set the preset
			unsigned char midi[] =
			{
				// The bank change
				(uint8_t) (0xb0 | Sh->Global.MidiChannel),
				0x0,
				(uint8_t) ((Sh->PresetIndex >> 7) & 3),

				// The preset change
				(uint8_t) (0xc0 | Sh->Global.MidiChannel),
				(uint8_t) (Sh->PresetIndex & 0x7f),
			};
			Hw->SendMidi(midi, sizeof(midi));

			// Start the post preset commands
			{
				auto Sh = Shared->Lock(_FL);
				if (Sh)
				{
					EffectOn.Length(0);
					
					int IaIdx = 0;
					int Btns = BUTTONS_X * BUTTONS_Y;
					for (int n=0; n<Btns; n++)
					{
						auto Cfg = Sh->Global.GetBtn(n);
						if (Cfg.b == BtnTypeIA)
						{
							EffectOn[IaIdx] = false;
							Hw->SetLed(n, LedOff);
							IaIdx++;
						}
					}
				}
			}

			PostPreset = 1;
			OnPostPreset();
		}
	}
}

//////////////////////////////////////////////////////////////////////////////////////////////
// Midi Input																				//
//////////////////////////////////////////////////////////////////////////////////////////////
bool Mc4Logic::IsTempo(uint8_t *p, size_t len)
{
	if (len == 7 &&
		p[5] == MIDI_TEMPO_BEAT &&
		Hw &&
		TempoIdx >= 0)
	{
		Hw->SetLed(TempoIdx, LColour::Red);
		TempoOnTs = LCurrentTime();
		Hw->SetTimer(TEMPO_LIGHT_MS);
		return true;
	}

	return false;
}

void Mc4Logic::IsAxefxParam(uint8_t *p, size_t len)
{
	if (p[5] == MIDI_PARAMETER)
	{
		ParamType Blk = AxefxDecode(p+6, false);

		switch (Blk)
		{
			// Route the param responses to the user interface
			case BlockAmp1:
			{
				ParamType Id = AxefxDecode(p+8, false);
				ParamType Value = AxefxDecode(p+10, true);
				
				// We have to tell the UI and the LED rings about this
				
				// 1) The UI
				Ui->PostEvent(	M_AMP_PARAM,
								(LMessage::Param)(ssize_t)Id,
								(LMessage::Param)(ssize_t)Value);
				

				ParameterModes ParamGroup = ModeMax;
				{
					auto Sh = Shared->Lock(_FL);
					if (Sh) ParamGroup = Sh->ParamGroup;
				}

				int RingIdx = -1;
				int RingVal = -1;
				switch (ParamGroup)
				{
					case ModeAmp1GainMaster:
						if (Id == AmpBlock_Gain) RingIdx = 0;
						else if (Id == AmpBlock_Master) RingIdx = 1;
						RingVal = Value >> 9;
						break;
					case ModeAmp1BassMids:
						if (Id == AmpBlock_Bass) RingIdx = 0;
						else if (Id == AmpBlock_Mid) RingIdx = 1;
						RingVal = Value >> 9;
						break;
					case ModeAmp1TreblePres:
						if (Id == AmpBlock_Treble) RingIdx = 0;
						else if (Id == AmpBlock_Bright) RingIdx = 1;
						RingVal = Value >> 9;
						break;
					case ModeAmp1Level:
						if (Id == AmpBlock_Level) RingIdx = 0;
						RingVal = Value >> 9;
						break;
				}

				// 2) The LED rings
				if (RingIdx >= 0 && RingVal >= 0)
				{
				    LedRingValue[ParamGroup][RingIdx] = RingVal;
				    Hw->PostEvent(  M_LED_RING_VALUE,
				                    (LMessage::Param)RingIdx,
				                    (LMessage::Param)LedRingValue[ParamGroup][RingIdx]);
				    /*
				    printf("Hw->M_LED_RING_VALUE %i, %i, %i\n",
				        ParamIndex,
				        LedRingValue[InAmpMode][ParamIndex],
				        Value);
				    */
				}
			}
			case BlockDelay1:
			{
				if (!DelayTempoCycle)
					break;

				DelayTempoCycle = false;
				ParamType Id = AxefxDecode(p+8, false);
				if (Id == DelayBlock_Tempo)
				{
					ParamType Value = AxefxDecode(p+10, true);
					switch (Value)
					{
						case Tempo_4trip:
							SetAxefxParam(BlockDelay1, DelayBlock_Tempo, Tempo_4);
							Ui->PostEvent(M_STATUS_MSG, (LMessage::Param)new LString("Delay: 1/4"));
							break;
						case Tempo_4:
							SetAxefxParam(BlockDelay1, DelayBlock_Tempo, Tempo_4dot);
							Ui->PostEvent(M_STATUS_MSG, (LMessage::Param)new LString("Delay: Dotted 1/4"));
							break;
						case Tempo_4dot:
							SetAxefxParam(BlockDelay1, DelayBlock_Tempo, Tempo_4trip);
							Ui->PostEvent(M_STATUS_MSG, (LMessage::Param)new LString("Delay: 1/4 Triplet"));
							break;
						///////////////////////////////////////////////////////////////////////////////////
						///////////////////////////////////////////////////////////////////////////////////
						///////////////////////////////////////////////////////////////////////////////////
						case Tempo_8trip:
							SetAxefxParam(BlockDelay1, DelayBlock_Tempo, Tempo_8);
							Ui->PostEvent(M_STATUS_MSG, (LMessage::Param)new LString("Delay: 1/8"));
							break;
						case Tempo_8:
							SetAxefxParam(BlockDelay1, DelayBlock_Tempo, Tempo_8dot);
							Ui->PostEvent(M_STATUS_MSG, (LMessage::Param)new LString("Delay: Dotted 1/8"));
							break;
						case Tempo_8dot:
							SetAxefxParam(BlockDelay1, DelayBlock_Tempo, Tempo_8trip);
							Ui->PostEvent(M_STATUS_MSG, (LMessage::Param)new LString("Delay: 1/8 Triplet"));
							break;
					}
				}
				break;
			}
		}
		
		if (DelayToggle)
		{
			DelayToggle = false;

			ParamType Id = AxefxDecode(p+8, false);
			if (Id == DelayBlock_Bypass)
			{
				ParamType Value = AxefxDecode(p+10, true);
				bool Bypassed = (Value & 1) == 0;
				auto Delay1 = GetBlockById(BlockDelay1);
				if (Delay1)
				{
					SendCc(Delay1->DefaultCc, Bypassed ? 0xff : 0);
				}
			}
		}
	}
}

void Mc4Logic::IsAccessBlockXy(uint8_t *Ptr, size_t Len)
{
	if (Ptr[5] == MIDI_ACCESS_BLOCK_XY)
	{
		ParamType Blk = AxefxDecode(Ptr+6, false);
		auto Idx = ToggleBlockXY.IndexOf(Blk);
		if (Idx >= 0)
		{
			ToggleBlockXY.DeleteAt(Idx);
			SetAxefxBlockXY(Blk, Ptr[8] ? BlockXy_SetX : BlockXy_SetY);

			auto Block = GetBlockById(Blk);
			LAutoPtr<LString> m(new LString);
			m->Printf("Set '%s' state to '%s'", Block ? Block->Name : "#err", Ptr[8] ? "X" : "Y");
			Ui->PostEvent(M_STATUS_MSG, (LMessage::Param) m.Release());
		}
	}
}

void Mc4Logic::IsTuner(uint8_t *p, size_t len)
{
	if (len == 10 && p[5] == 0x0d)
	{
		const char *Names[] = {"A", "A#", "B", "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "Err", "Err", "Err", "Err" };
		const char *Note = Names[p[6]];

		// Show note name
		LString *Msg = new LString;
		if (Msg)
			Msg->Printf("%s %i", Note, p[7]+1);
		Ui->PostEvent(M_TUNER_STATUS, (LMessage::Param)(ssize_t)p[8], (LMessage::Param)Msg);
	}
}

void Mc4Logic::IsPresetNumber(uint8_t *p, size_t len)
{
	if (len == 10 && p[5] == MIDI_GET_PRESET_NUMBER)
	{
		int Index = ((p[6] & 0x3) << 7) | (p[7] & 0x7f);
		SetPresetIndex(Index);
		
		auto Sh = Shared->Lock(_FL);
		if (Sh && !Sh->PresetName)
			RequestPatchName(Sh->PresetIndex);
	}
}

void Mc4Logic::IsPresetName(uint8_t *Ptr, size_t Len)
{
	int i = 5, p;
	if (Ptr[i++] != MIDI_GET_PRESET_NAME)
		return;

	// decode characters into preset name buffer...
	char CurPatchName[256];
	for (p=0; i<Len-1 && p<sizeof(CurPatchName)-1; p++)
	{
		CurPatchName[p] = Ptr[i++];
		if (!CurPatchName[p])
			break;
	}
	while (p > 0 && CurPatchName[p-1] == ' ')
		p--;
	CurPatchName[p] = 0;

	auto Sh = Shared->Lock(_FL);
	if (Sh)
	{
		Sh->PresetName = CurPatchName;

		// Tell the user interface
		Ui->PostEvent(M_SET_PRESET_NAME);
	}
}

void Mc4Logic::IsFirmwareVersion(uint8_t *Ptr, size_t Len)
{
	int i = 5;

	if (Ptr[i++] != MIDI_GET_FIRMWARE_VERSION)
	{
        printf("%s:%i - Error\n", _FL);
		return;
	}

	auto Sh = Shared->Lock(_FL);
	if (Sh)
	{	
		Sh->FirmwareVer[0] = Ptr[i];
		Sh->FirmwareVer[1] = Ptr[i+1];
	}
	Ui->PostEvent(M_FIRMWARE_VER);
}

void Mc4Logic::OnPostPreset()
{
	// Go through to the next stage of post preset actions
	while (PostPreset > 0)
	{
		if (PostPreset == 1)
		{
			// Request the bypass states...
			RequestEffectsAndBypass();
			break;
		}
		else
		{
			PostPreset = 0;
			break;
		}

		PostPreset++;
	}
}

void Mc4Logic::IsStateChanged(uint8_t *Ptr, size_t Len)
{
	// Do post preset
	SetCtrlColour(LColour::White, true, false);
	PostPreset = 1;
	OnPostPreset();
}

void Mc4Logic::IsEffectAndBypass(uint8_t *Ptr, size_t Len)
{
	uint8_t *b;
	unsigned e, c;
	
	if (Ptr[5] != MIDI_GET_PRESET_EFFECT_BLOCKS_AND_CC_AND_BYPASS_STATE)
	{
		assert(!"Invalid midi packet.");
		return;
	}
	
	auto Sh = Shared->Lock(_FL);
	
	PresetEffects = 0;
	
	// Loop over the block list...
	for (b = Ptr + 6; (b - Ptr) + 5 < (ssize_t)Len - 2; b += 5)
	{
		bool Enabled	= (b[0] & 0x01) != 0;
		// bool YState		= (b[0] & 0x02) == 0;
		// int IA_CC		= ((b[1] >> 1) & 0x1f) | ((b[2] & 0x3) << 5);
		int EffectId	= ((b[3] >> 3) & 0x0f) | ((b[4] & 0xf) << 4);
		auto Effect	= GetBlockById(EffectId);

		if (DelayToggle)
		{
			if (EffectId == BlockDelay1)
			{
				SendCc(Effect->DefaultCc, Enabled ? 0 : 0xff);
				Ui->PostEvent(M_STATUS_MSG, (LMessage::Param) new LString(Enabled?"Delay: Off":"Delay: On"));
			}
		}
		else
		{		
			// Store the effect ID in our list of effects
			if (PresetEffects < sizeof(PresetEffect) - 1)
				PresetEffect[PresetEffects++] = EffectId;
			
			// Check the IA buttons and see if we need to update an LEDs
			auto Press = Sh->Preset.GetTypePress();
			for (e=0; e<Press.Length(); e++)
			{
				auto ia = Press[e];
				LArray<MidiCmd> &Cmds = ia->Cmds;
				for (c=0; c<Cmds.Length(); c++)
				{
					MidiCmd *cmd = &Cmds[c];
					if (cmd->Id == MIDI_CMD_BLOCK &&
						cmd->Param < AxefxBlockCount())
					{
						auto blk = AxefxGetBlocks() + cmd->Param;
						if (blk->BlockId == EffectId)
						{
							if (EffectOn[e] != Enabled)
							{
								LAssert(EffectOn.IdxCheck(e));
								LAssert(IaIdxs.IdxCheck(e));
								
								EffectOn[e] = Enabled;								
								if (Hw)
									Hw->SetLed(IaIdxs[e], Enabled ? ia->GetColour() : LedOff);
                        		else
                        			LgiTrace("%s:%i - No HW\n", _FL);
							}
						}
					}
				}
			}
		}
	}

	DelayToggle = false;
}

//////////////////////////////////////////////////////////////////////////////////////////////
// Midi Output																				//
//////////////////////////////////////////////////////////////////////////////////////////////
void Mc4Logic::RequestPatchNumber()
{
	unsigned char midi[] = {	MIDI_SYSEX_START,
								AxefxId[0], AxefxId[1], AxefxId[2], AxefxModel,
								MIDI_GET_PRESET_NUMBER,
								#if AXEFX_VER == 2
								0, // checksum
								#endif
								MIDI_SYSEX_END };

	#if AXEFX_VER == 2
	midi[sizeof(midi)-2] = AxefxChecksum(midi, sizeof(midi)-2);
	#endif
	if (Hw)
    	Hw->SendMidi(midi, sizeof(midi));
	else
	    LgiTrace("%s:%i - No HW\n", _FL);
}

void Mc4Logic::RequestPatchName(int i)
{
	unsigned char midi[] = {	MIDI_SYSEX_START,
								AxefxId[0], AxefxId[1], AxefxId[2], AxefxModel,
								MIDI_GET_PRESET_NAME,
								#if AXEFX_VER == 2
								0, // checksum
								#endif
								MIDI_SYSEX_END };

	#if AXEFX_VER == 2
	midi[sizeof(midi)-2] = AxefxChecksum(midi, sizeof(midi)-2);
	#endif
	if (Hw)
	    Hw->SendMidi(midi, sizeof(midi));
	else
	    LgiTrace("%s:%i - No HW\n", _FL);
}

void Mc4Logic::SetSendSysex(uint8_t SendSysex)
{
	unsigned char midi[] = {	MIDI_SYSEX_START,
								AxefxId[0], AxefxId[1], AxefxId[2], AxefxModel,
								(uint8_t) (SendSysex ? MIDI_GET_FIRMWARE_VERSION : MIDI_STOP_SYSEX_OVER_USB),
								#if AXEFX_VER == 2
								0, // checksum
								#endif								
								MIDI_SYSEX_END };

	#if AXEFX_VER == 2
	midi[sizeof(midi)-2] = AxefxChecksum(midi, sizeof(midi)-2);
	#endif
	if (Hw)
    	Hw->SendMidi(midi, sizeof(midi));
	else
	    LgiTrace("%s:%i - No HW\n", _FL);
}

void Mc4Logic::RequestEffectsAndBypass()
{
	unsigned char midi[] = {	MIDI_SYSEX_START,
								AxefxId[0], AxefxId[1], AxefxId[2], AxefxModel,
								MIDI_GET_PRESET_EFFECT_BLOCKS_AND_CC_AND_BYPASS_STATE,
								#if AXEFX_VER == 2
								0, // checksum
								#endif
								MIDI_SYSEX_END };

	#if AXEFX_VER == 2
	midi[sizeof(midi)-2] = AxefxChecksum(midi, sizeof(midi)-2);
	#endif
	if (Hw)
	    Hw->SendMidi(midi, sizeof(midi));
	else
	    LgiTrace("%s:%i - No HW\n", _FL);
}

void Mc4Logic::SendCc(uint8_t Cc, uint8_t Val)
{
	if (Cc < 128)
	{
		unsigned char midi[3];

		if (Val > 127)
			Val = 127;

		int MidiCh = -1;
		{
			auto Sh = Shared->Lock(_FL);
			if (Sh) MidiCh = Sh->Global.MidiChannel;
		}
		if (MidiCh >= 0)
		{
			midi[0] = 0xb0 | (MidiCh & 0xf);
			midi[1] = Cc;
			midi[2] = Val;
			
			if (Hw)
			    Hw->SendMidi(midi, sizeof(midi));
			else
			    LgiTrace("%s:%i - Error: No hardware object.\n", _FL);
		}
	}
	else
	{
		LString *s = new LString;
		if (s)
		{
			s->Printf("InvalidCc: %i", Cc);
			Ui->PostEvent(M_STATUS_MSG, (LMessage::Param)s);
		}
	}
}

void Mc4Logic::ToggleTuner()
{
	bool Cur = false;
	{
		auto Sh = Shared->Lock(_FL);
		if (Sh)
			Cur = Sh->TunerOn;
	}
	SetTuner(!Cur);
}

/*
void Mc4Logic::SetAmpMode(bool On)
{
	if (InAmpMode == On)
		return;

	InAmpMode = On;
		
	bool Connected = false;
	{
		auto Sh = Shared->Lock(_FL);
		if (!Sh)
			return;
		Connected = Sh->IsConnected;
	}
	
	if (Connected && InAmpMode)
	{
		// Initate requests for the parameters
		GetAxefxParam(BlockAmp1, AmpBlock_Gain);
		GetAxefxParam(BlockAmp1, AmpBlock_Master);
		GetAxefxParam(BlockAmp1, AmpBlock_Level);
		GetAxefxParam(BlockAmp1, AmpBlock_Bass);
		GetAxefxParam(BlockAmp1, AmpBlock_Mid);
		GetAxefxParam(BlockAmp1, AmpBlock_Treble);
	}

	// Set the LED rings to purple
	LColour Cols[2] = { LedRingNormalCol, LedRingAmpModeColour };
	for (unsigned i=0; i<MAX_ENCODER_PARAM; i++)
	{
	    Hw->PostEvent(M_LED_RING_VALUE, (LMessage::Param)i, (LMessage::Param)LedRingValue[InAmpMode][i]);
	    Hw->PostEvent(M_LED_RING_COLOUR, (LMessage::Param)i, (LMessage::Param)Cols[InAmpMode].c32());
	}
}
*/

void Mc4Logic::SetTuner(bool On)
{
	int TunerCc = -1;
	{
		auto Sh = Shared->Lock(_FL);
		if (Sh)
		{
			Sh->TunerOn = On;
			TunerCc = Sh->Global.TunerCc;
		}
		else
		{
			printf("%s:%i - Failed to lock.\n", _FL);
			return;
		}
	}
	if (TunerCc >= 0)
	{
		SendCc(TunerCc, (uint8_t) (On ? 127 : 0));
		Ui->PostEvent(M_TUNER_STATE);
	}
	else printf("%s:%i - TunerCc not set.\n", _FL);
}

void Mc4Logic::GetAxefxParam(ParamType BlockId, ParamType ParamId)
{
	uint8_t midi[] = {	MIDI_SYSEX_START,
						AxefxId[0], AxefxId[1], AxefxId[2], AxefxModel,
						MIDI_PARAMETER,
						#if AXEFX_VER == 1
						LN(BlockId), HN(BlockId),
						LN(ParamId), HN(ParamId),
						LN(Value), HN(Value),
						#elif AXEFX_VER == 2
						(uint8_t) (BlockId & 0x7f), (uint8_t) ((BlockId >> 7) & 0x7f),
						(uint8_t) (ParamId & 0x7f), (uint8_t) ((ParamId >> 7) & 0x7f),
						0, 0, 0,
						#endif
						AxefxParamGet,
						#if AXEFX_VER == 2
						0, // checksum
						#endif 
						MIDI_SYSEX_END };

	#if AXEFX_VER == 2
	midi[sizeof(midi)-2] = AxefxChecksum(midi, sizeof(midi)-2);
	#endif
	if (Hw)
	    Hw->SendMidi(midi, sizeof(midi));
	else
	    LgiTrace("%s:%i - No HW\n", _FL);
}

void Mc4Logic::SetAxefxBlockXY(ParamType BlockId, AxefxBlockXY Mode)
{
	uint8_t midi[] = {	MIDI_SYSEX_START,
						AxefxId[0], AxefxId[1], AxefxId[2], AxefxModel,
						MIDI_ACCESS_BLOCK_XY,
						#if AXEFX_VER == 1
							LN(BlockId), HN(BlockId),
						#elif AXEFX_VER == 2
							(uint8_t) (BlockId & 0x7f), (uint8_t) ((BlockId >> 7) & 0x7f),
						#endif
						(uint8_t)(Mode == BlockXy_SetY ? 1 : 0),
						(uint8_t)(Mode == BlockXy_Get || Mode == BlockXy_Toggle ? AxefxParamGet : AxefxParamSet),
						#if AXEFX_VER == 2
						0, // checksum
						#endif 
						MIDI_SYSEX_END };

	#if AXEFX_VER == 2
	midi[sizeof(midi)-2] = AxefxChecksum(midi, sizeof(midi)-2);
	#endif
	
	if (Mode == BlockXy_Toggle)
		ToggleBlockXY.Add(BlockId); // Processed on response in IsAccessBlockXy
	
	if (Hw)
	    Hw->SendMidi(midi, sizeof(midi));
	else
	    LgiTrace("%s:%i - No HW\n", _FL);
}

void Mc4Logic::SetAxefxParam(ParamType BlockId, ParamType ParamId, uint16_t Value)
{
	uint8_t midi[] = {	MIDI_SYSEX_START,
						AxefxId[0], AxefxId[1], AxefxId[2], AxefxModel,
						MIDI_PARAMETER,
						#if AXEFX_VER == 1
						LN(BlockId), HN(BlockId),
						LN(ParamId), HN(ParamId),
						LN(Value), HN(Value),
						#elif AXEFX_VER == 2
						(uint8_t) (BlockId & 0x7f), (uint8_t) ((BlockId >> 7) & 0x7f),
						(uint8_t) (ParamId & 0x7f), (uint8_t) ((ParamId >> 7) & 0x7f),
						(uint8_t) (Value & 0x7f), (uint8_t) ((Value >> 7) & 0x7f), (uint8_t) ((Value >> 14) & 0x3),
						#endif
						AxefxParamSet,
						#if AXEFX_VER == 2
						0, // checksum
						#endif 
						MIDI_SYSEX_END };

	#if AXEFX_VER == 2
	midi[sizeof(midi)-2] = AxefxChecksum(midi, sizeof(midi)-2);
	#endif
	if (Hw)
	    Hw->SendMidi(midi, sizeof(midi));
	else
	    LgiTrace("%s:%i - No HW\n", _FL);
}

void Mc4Logic::SetScene(int Scene)
{
	unsigned char midi[] = {	MIDI_SYSEX_START,
								AxefxId[0], AxefxId[1], AxefxId[2], AxefxModel,
								MIDI_SET_SCENE_NUMBER,
								(uint8_t) Scene,
								#if AXEFX_VER == 2
								0, // checksum
								#endif								
								MIDI_SYSEX_END };

	#if AXEFX_VER == 2
	midi[sizeof(midi)-2] = AxefxChecksum(midi, sizeof(midi)-2);
	#endif
    if (Hw)
    	Hw->SendMidi(midi, sizeof(midi));
	else
	    LgiTrace("%s:%i - No HW\n", _FL);
}

//////////////////////////////////////////////////////////////////////////////////////////////
// Hardware Events																			//
//////////////////////////////////////////////////////////////////////////////////////////////
void Mc4Logic::SetThreadId(OsThreadId ThreadId)
{
	LAssert(0);
}

void Mc4Logic::SetDelayDivision(DelayBlockTempo d)
{
	SetAxefxParam(BlockDelay1, DelayBlock_Tempo, d);

	LString *s = new LString;
	s->Printf("Delay: %s", ToString(d));
	Ui->PostEvent(M_STATUS_MSG, (LMessage::Param)s);
}

LMessage::Result Mc4Logic::OnEvent(LMessage *Msg)
{
	switch (Msg->Msg())
	{
		case M_SET_DELAY_DIV:
		{
			SetDelayDivision((DelayBlockTempo)Msg->A());
			break;
		}
		case M_ON_BUTTON_CONFIG:
		{
			TunerIdx = -1;
			TempoIdx = -1;
			IaIdxs.Length(0);

			auto Sh = Shared->Lock(_FL);
			if (!Sh)
				break;
			
			// Update all the IA leds to show the right thing...
			// Also keep track of where the Tuner and Tempo LEDs are...
			int IaIdx = 0;
			for (int n=0; n<Sh->Global.GetButtons(); n++)
			{
				auto Cfg = Sh->Global.GetBtn(n);
				
				switch (Cfg.b)
				{
					case BtnTypeIA:
						IaIdxs.Add(n);
						if (EffectOn[IaIdx])
							Hw->SetLed(n, Sh->Preset.GetIa(IaRef(IaIdx)).GetColour());
						IaIdx++;
						break;
					case BtnTypeTuner:
						TunerIdx = n;
						break;
					case BtnTypeTempo:
						TempoIdx = n;
						break;
				}
			}			
			break;
		}
		case M_SET_TUNER:
		{
			SetTuner(Msg->A() != 0);
			break;
		}
		case M_SET_PRESET_NUMBER:
		{
			SetPreset((int)Msg->A(), true);
			break;
		}
		case M_PARAM_MODE_CHANGE:
		{
			// Get the new param group...
			ParameterModes ParamGroup = ModeMax;
			{
				auto Sh = Shared->Lock(_FL);
				if (Sh) ParamGroup = Sh->ParamGroup;
			}

			// Update the value from the Axefx if possible...
			switch (ParamGroup)
			{
				case ModeExtern1and2:
				case ModeExtern3and4:
				case ModeExtern5and6:
				case ModeExtern7and8:
				{
					for (int i=0; i<MAX_KNOB_PARAM; i++)
						Hw->PostEvent(M_LED_RING_VALUE, i, LedRingValue[ParamGroup][i]);
					break;
				}
				case ModeAmp1GainMaster:
				{
					GetAxefxParam(BlockAmp1, AmpBlock_Gain);
					GetAxefxParam(BlockAmp1, AmpBlock_Master);
					break;
				}
				case ModeAmp1BassMids:
				{
					GetAxefxParam(BlockAmp1, AmpBlock_Bass);
					GetAxefxParam(BlockAmp1, AmpBlock_Mid);
					break;
				}
				case ModeAmp1TreblePres:
				{
					GetAxefxParam(BlockAmp1, AmpBlock_Treble);
					GetAxefxParam(BlockAmp1, AmpBlock_Bright);
					break;
				}
				case ModeAmp1Level:
				{
					GetAxefxParam(BlockAmp1, AmpBlock_Level);
				    Hw->PostEvent(  M_LED_RING_VALUE,
				                    (LMessage::Param)1,
				                    (LMessage::Param)0);
					break;
				}
			}
			break;
		}
	}
	return 0;
}

void Mc4Logic::OnConnection(bool Connected)
{
	{
		auto Sh = Shared->Lock(_FL);
		if (!Sh)
			return;

		Sh->IsConnected = Connected;
		if (Connected)
		{
			SetSendSysex(1);
			RequestPatchNumber();
			
			for (int grp = 0; grp < ModeMax; grp++)
			{
				for (int param=0; param<MAX_KNOB_PARAM; param++)
				{
					printf("%s:%i OnParam %i\n", _FL, KNOB_BASE+param);
					OnParam(KNOB_BASE+param, LedRingValue[grp][param]);
				}
			}
		}
		else
		{
			Sh->PresetName.Empty();
			Sh->PresetIndex = -1;
		}
	}

	Ui->PostEvent(M_UPDATE_PLAY_SCREEN);
}

void Mc4Logic::OnButton(HwButton b, int i, bool Down, bool Held)
{
	#if 0
	printf("%s:%i - Mc4Logic::OnButton(%i,%i,%i,%i)\n", _FL, b, i, Down, Held);
	#endif

	if (Held)
	{
    	switch (b)
    	{
			case BtnTypeModeSwitch:
			{
				Ui->PostEvent(M_SWITCH_MODE, 1);
				break;
			}
		}
	}
    else if (Down)
    {
    	switch (b)
    	{
    		case BtnTypePreset:
    		{
				auto Sh = Shared->Lock(_FL);
    			SetPreset(Sh->BankStart + i, 1);
    			break;
    		}
    		case BtnTypeBankUp:
    		{
				auto Sh = Shared->Lock(_FL);
				int n = Sh->BankStart + Sh->Global.BankSize;
    			if (n < MAX_PATCH - 1)
    			{
    				Sh->BankStart = n;
					Ui->PostEvent(M_DRAW_BANK_RANGE);
    			}
    			break;
    		}
    		case BtnTypeBankDown:
    		{
				auto Sh = Shared->Lock(_FL);
				int n = MAX(0, Sh->BankStart - Sh->Global.BankSize);
    			if (n >= 0)
    			{
    				Sh->BankStart = n;
					Ui->PostEvent(M_DRAW_BANK_RANGE);
    			}
    			break;
    		}
    		case BtnTypeTuner:
    		{
    			ToggleTuner();
    			break;
    		}
    		case BtnTypeTempo:
    		{
				auto Sh = Shared->Lock(_FL);
    			SendCc(Sh->Global.TempoCc, 1);
    			break;
    		}
    		case BtnTypeBank:
    		{
    			{
					auto Sh = Shared->Lock(_FL);
					if (!Sh) break;					
	   				Sh->BankStart = i * Sh->Global.BankSize;
	   			}
   				Ui->PostEvent(M_LEAVE_BANK_SELECT);
				Ui->PostEvent(M_DRAW_BANK_RANGE);
    			break;
    		}
			case BtnTypeModeSwitch:
			{
				Ui->PostEvent(M_SWITCH_MODE);
				break;
			}
    		case BtnTypeIA:
    		{
    			// This is a NOP, the up event is used.
    			break;
    		}
			case BtnTypeDelayDivision:
			{
				if (i >= 0 && i < CountOf(Mc4DelayTempos))
				{
					SetDelayDivision(Mc4DelayTempos[i]);
					Ui->PostEvent(M_LEAVE_BANK_SELECT);
				}
				else LAssert(!"Invalid index.");				
				break;
			}
			default:
			{
				printf("%s:%i - Unhandled button type: %s\n", _FL, ToString(b));
				break;
			}
    	}
    }
    else // Up
    {
    	switch (b)
    	{
    		case BtnTypeIA:
    			ToggleEffect(i, true);
    			break;
    		default:
    			break;
    	}
    }
}

void Mc4Logic::OnParam(int Index, int NewValue)
{
    if (!Hw)
    {
    	printf("%s:%i - No HW ptr.\n", _FL);
    	return;
    }
    if (!Ui)
    {
    	printf("%s:%i - No UI ptr.\n", _FL);
    	return;
    }
    if (Index < 0 || Index >= EXTERN_PARAMS)
    {
    	printf("%s:%i - Index %i out of range [0,%i].\n", _FL, Index, EXTERN_PARAMS);
    	return;
    }
    
	ParameterModes ParamGroup = ModeMax;
	{
		auto Sh = Shared->Lock(_FL);
		if (Sh)
			ParamGroup = Sh->ParamGroup;
	}
	if (ParamGroup >= ModeMax)
	{
    	printf("%s:%i - Invalid param group.\n", _FL);
		return;
	}
	
	int ParamIndex = ParamGroupIndex(ParamGroup);

	// For knob based parameters:
	int Knob = Index - ParamIndex;
	
	// printf("%s:%i OnParam Grp=%i Idx=%i ParamIdx=%i Knob=%i\n", _FL, ParamGroup, Index, ParamIndex, Knob);
	
	if (Knob >= 0 && Knob < MAX_KNOB_PARAM)
	{
		// This code needs to update:
		// 
		// 1) The screen (owned by Mc4Ui)
		//
		// 2) The Axefx (via the hardware thread sending MIDI)
		//
		// 3) The LED ring value (owned by the hardware)
	    LedRingValue[ParamGroup][Knob] = NewValue;

		int BlockId = -1;
		int BlockVal = -1;
		int Cc = -1;
		
		switch (ParamGroup)
		{
			case ModeAmp1GainMaster:
			{
				BlockId = Knob == 0 ? AmpBlock_Gain : AmpBlock_Master;
				BlockVal = NewValue << 9; // Convert to 16 bit
				break;
			}
			case ModeAmp1BassMids:
			{
				BlockId = Knob == 0 ? AmpBlock_Bass : AmpBlock_Mid;
				BlockVal = NewValue << 9; // Convert to 16 bit
				break;
			}
			case ModeAmp1TreblePres:
			{
				BlockId = Knob == 0 ? AmpBlock_Treble : AmpBlock_Bright;
				BlockVal = NewValue << 9; // Convert to 16 bit
				break;
			}
			case ModeAmp1Level:
			{
				if (Knob == 0)
					BlockId = AmpBlock_Level;
				BlockVal = NewValue << 9; // Convert to 16 bit
				break;
			}
			case ModeExtern1and2:
			case ModeExtern3and4:
			case ModeExtern5and6:
			case ModeExtern7and8:
			{
				LAssert(ParamIndex >= 0 && ParamIndex < EXTERN_PARAMS);
				Cc = AXEFX_EXTERN_BASE + Index;
				break;
			}
		}

		if (BlockId >= 0 && BlockVal >= 0)
		{
			// 1) Axefx
			SetAxefxParam(BlockAmp1, BlockId, BlockVal);
			
			// 2) Screen
			Ui->PostEvent(M_AMP_PARAM, (LMessage::Param)BlockId, (LMessage::Param)BlockVal);
			
			// 3) LED Ring
			Hw->PostEvent(M_LED_RING_VALUE, (LMessage::Param)Knob, (LMessage::Param)NewValue);
		}
		else if (Cc >= 0 && NewValue >= 0)
		{
			// 1) Axefx
			SendCc(Cc, NewValue);
        
			// 2) Screen
			Ui->PostEvent(M_ON_PARAM, (LMessage::Param)Index, (LMessage::Param)NewValue);
		
			// 3) LED Ring
			Hw->PostEvent(M_LED_RING_VALUE, (LMessage::Param)Knob, (LMessage::Param)NewValue);
		}
	}
	else // Not knob based, Expression pedals..
	{
		int Cc = AXEFX_EXTERN_BASE + Index;

		// 1) Axefx
		SendCc(Cc, NewValue);
    
		// 2) Screen
		Ui->PostEvent(M_ON_PARAM, (LMessage::Param)Index, (LMessage::Param)NewValue);
		
		if (ParamGroup == ModeExtern1and2)
		{
			// 3) LED Ring
			Hw->PostEvent(M_LED_RING_VALUE, (LMessage::Param)Index, (LMessage::Param)NewValue);
		}
	}		
}

void Mc4Logic::OnMidiIn(uint8_t *Ptr, size_t Len)
{
    if (!Ptr || Len == 0)
    {
        printf("%s:%i - Invalid MIDI.\n", _FL);
        return;
    }    

    if (!Hw)
    {
        printf("%s:%i - Error: No hardware interface yet.\n", _FL);
        return;
    }
    
    #if 0
    LString m = "MidiIn={";
    for (int k=0; k<Len; k++)
    {
		LString s;
		s.Printf("%02.2x%s", Ptr[k], k < Len - 1 ? ", " : "}\n");
		m += s;
    }
    LgiTrace("%s", m.Get());
    #endif

	if (*Ptr == MIDI_SYSEX_START)
	{
		// Sysex message
		if (Len < 6)
			return;

		if (Ptr[1] == AxefxId[0] &&
			Ptr[2] == AxefxId[1] &&
			Ptr[3] == AxefxId[2])
		{
			// Axefx sysex commands
			switch (Ptr[5])
			{
				// Ported
				case MIDI_TEMPO_BEAT:
					IsTempo(Ptr, Len);
					break;
				case MIDI_GET_PRESET_NUMBER:
					IsStateChanged(Ptr, Len);
					IsPresetNumber(Ptr, Len);
					break;
				case MIDI_GET_PRESET_NAME:
					IsPresetName(Ptr, Len);
					break;
				case MIDI_GET_FIRMWARE_VERSION:
					IsFirmwareVersion(Ptr, Len);
					break;
				case MIDI_STATE_CHANGED_EVENT:
					IsStateChanged(Ptr, Len);
					break;
				case MIDI_GET_PRESET_EFFECT_BLOCKS_AND_CC_AND_BYPASS_STATE:
					IsEffectAndBypass(Ptr, Len);
					break;
				case MIDI_TUNER_INFO:
					IsTuner(Ptr, Len);
					break;
				case MIDI_PARAMETER:
					IsAxefxParam(Ptr, Len);
					break;
				case MIDI_ACCESS_BLOCK_XY:
					IsAccessBlockXy(Ptr, Len);
					break;

				// Unported... FIXME
				case MIDI_PATCH_DUMP:
					// Mc1_OnPatch(Ptr, Len);
					break;
					
				default:
					return;
			}
		}
		else if (Ptr[1] == McId[0] &&
				 Ptr[2] == McId[1] &&
				 Ptr[3] == McId[2])
		{
			// Memecode controller sysex commands
			switch (Ptr[4])
			{
				case MC_MIDI_GET_INIT:
				{
					uint16_t GlobalSize = sizeof(GlobalCfg);
					uint16_t PresetSize = sizeof(PresetCfg);
					uint8_t cmd[] =
					{
						MIDI_SYSEX_START,
						McId[0],
						McId[1],
						McId[2],
						MC_MIDI_GET_INIT,
						(uint8_t) (GlobalSize & 0xf), (uint8_t) ((GlobalSize >> 4) & 0xf),
						(uint8_t) ((GlobalSize >> 8) & 0xf), (uint8_t) ((GlobalSize >> 12) & 0xf),
						(uint8_t) (PresetSize & 0xf), (uint8_t) ((PresetSize >> 4) & 0xf),
						(uint8_t) ((PresetSize >> 8) & 0xf), (uint8_t) ((PresetSize >> 12) & 0xf),
						MIDI_SYSEX_END
					};
					if (Hw)
					    Hw->SendMidi(cmd, sizeof(cmd));
                	else
                	    LgiTrace("%s:%i - No HW\n", _FL);
					break;
				}
				case MC_MIDI_GET_GLOBAL:
				{
					uint8_t cmd[] =
					{
						MIDI_SYSEX_START,
						McId[0],
						McId[1],
						McId[2],
						MC_MIDI_GET_GLOBAL
					};

					auto Sh = Shared->Lock(_FL);
					if (Sh)
					{
						uint8_t *p = (uint8_t*)&Sh->Global;
						int i;

						if (Hw)
						    Hw->SendMidi(cmd, sizeof(cmd));
                    	else
                    	{
                    	    LgiTrace("%s:%i - No HW\n", _FL);
                    	    break;
                    	}
						
						for (i=0; i<sizeof(Sh->Global); i++)
						{
							cmd[0] = LN(*p);
							cmd[1] = HN(*p);
							p++;
							
							Hw->SendMidi(cmd, 2);
						}

						cmd[0] = MIDI_SYSEX_END;					
						Hw->SendMidi(cmd, 1);
					}
					break;
				}
				case MC_MIDI_GET_PRESET:
				{
					uint8_t cmd[] =
					{
						MIDI_SYSEX_START,
						McId[0],
						McId[1],
						McId[2],
						MC_MIDI_GET_PRESET,
						0,
						0
					};
					PresetCfg Tmp;
					uint8_t *p = (uint8_t*)&Tmp;
					int i, Index;

					// Copy the index requested over to the cmd
					cmd[5] = Ptr[5];
					cmd[6] = Ptr[6];

					// Load the preset from storage
					Index = Ptr[5] | (Ptr[6] << 4);
					LAssert(!"Fix me.");
					// Hw->Eeprom(ADDR_PRESET_SETTING(Index), (uint8_t*)&Tmp, sizeof(Tmp), false);
					
					// Serialize the preset to the midi out port					
					if (Hw)
					    Hw->SendMidi(cmd, sizeof(cmd));
					else
					{
                	    LgiTrace("%s:%i - No HW\n", _FL);
                	    break;
                	}					
					
					for (i=0; i<sizeof(Tmp); i++)
					{
						cmd[0] = LN(*p);
						cmd[1] = HN(*p);
						p++;
						
						Hw->SendMidi(cmd, 2);
					}

					cmd[0] = MIDI_SYSEX_END;					
					Hw->SendMidi(cmd, 1);
					break;
				}
				case MC_MIDI_SET_GLOBAL:
				{
					auto Sh = Shared->Lock(_FL);
					if (!Sh)
						break;
					
					uint8_t *o = (uint8_t*)&Sh->Global;
					uint8_t *i = Ptr + 5;
					uint8_t *e = o + sizeof(Sh->Global);
					
					if (Len == 6 + (sizeof(Sh->Global) * 2))
					{
						// Decode to out structure
						while (o < e)
						{
							*o++ = i[0] | (i[1] << 4);
							i += 2;
						}

						// Write to storage
						// Device_Eeprom(0, (uint8_t*)&Global, sizeof(Global), true);
						LAssert(!"Fix me.");
						Ui->PostEvent(M_STATUS_MSG, (LMessage::Param) new LString("Updated Global"));
					}
					else Ui->PostEvent(M_STATUS_MSG, (LMessage::Param) new LString("Err: GlobSizeErr"));
					break;
				}
				case MC_MIDI_SET_PRESET:
				{
					PresetCfg Tmp;
					uint8_t *o = (uint8_t*)&Tmp;
					uint8_t *i = Ptr + 5;
					uint8_t *e = o + sizeof(Tmp);
					
					if (Len == 8 + (sizeof(Tmp) * 2))
					{
						// Decode the index
						int Index = i[0] | (i[1] << 4);
						i += 2;
						if (Index >= 0 && Index < 128)
						{
							// Decode to out structure
							while (o < e)
							{
								*o++ = i[0] | (i[1] << 4);
								i += 2;
							}

							// Write to storage
							// Eeprom(ADDR_PRESET_SETTING(Index), (uint8_t*)&Tmp, sizeof(Tmp), true);
							LAssert(!"Fixme");

							auto Sh = Shared->Lock(_FL);
							if (Sh && Sh->PresetIndex == Index)
							{
								// They changed our current preset, so store that in working memory
								auto Sh = Shared->Lock(_FL);
								if (Sh)
								{
									Sh->Preset = Tmp;
									Ui->PostEvent(M_STATUS_MSG, (LMessage::Param) new LString("UpdatedCurPreset"));
								}
							}
							else
							{
								LString *s = new LString;
								if (s)
								{
									s->Printf("UpdatedPre: %i", Index + 1);
									Ui->PostEvent(M_STATUS_MSG, (LMessage::Param) s);
								}
							}
						}
					}
					else
					{
						LString *s = new LString;
						if (s)
						{
							s->Printf("Err: PreSz %i", Len);
							Ui->PostEvent(M_STATUS_MSG, (LMessage::Param) s);
						}
					}
					break;
				}
				default:
					return;
			}
		}
		else
		{
			Ui->PostEvent(M_STATUS_MSG, (LMessage::Param) new LString("UnknownSysexDev"));
		}
	}
	else if (Len == 2 && (Ptr[0] & 0xf0) == 0xc0)
	{
		// Preset change
		// Mc1_SetPreset(Ptr[1], 0);
	}
}

void Mc4Logic::OnTimer()
{
	// TEMPO_LIGHT_MS
	if (Hw && TempoIdx >= 0)
	{
		Hw->SetLed(TempoIdx, LedOff);
		Hw->SetTimer();
	}
	else
		LgiTrace("%s:%i - No HW.\n", _FL);
}

int MapParamMode(int i)
{
	switch (i)
	{
		case IDC_EXTERN_12:
			return IDC_EXTERN_1_2;
		case IDC_EXTERN_34:
			return IDC_EXTERN_3_4;
		case IDC_EXTERN_56:
			return IDC_EXTERN_5_6;
		case IDC_EXTERN_78:
			return IDC_EXTERN_7_8;
	}

	return 0;
}

bool Mc4Logic::GetEffectOn(int idx)
{
	if (idx >= 0 && idx < EffectOn.Length())
		return EffectOn[idx];

	return false;
}

void Mc4Logic::ToggleEffect(uint8_t Idx, uint8_t GrpCheck)
{
	auto Sh = Shared->Lock(_FL);
	if (!Sh)
		return;

	auto Press = Sh->Preset.GetTypePress();
	if (Idx >= Press.Length())
	{
		Ui->PostEvent(M_STATUS_MSG, (LMessage::Param) new LString("Invalid effect index."));
		return;
	}

	const AxefxBlock *b = NULL;
	int SceneSelect = -1;
	LString Msg;

	// Check there is more than zero commands for this preset
	auto Effect = Press[Idx];
	LAssert(Effect != NULL);
	if (Effect->Cmds.Length() == 0)
	{
		Ui->PostEvent(M_STATUS_MSG, (LMessage::Param) new LString("No IA configured."));
	}
	else if (Effect->BlockAction == BlockAction_SelectXY)
	{
		// Look through the commands for blocks to AB mode switch...
		for (unsigned i=0; i<Effect->Cmds.Length(); i++)
		{
			MidiCmd &Cmd = Effect->Cmds[i];
			if (Cmd.Id == MIDI_CMD_BLOCK)
			{
				// Switch block A:B setting:
				b = AxefxGetBlocks() + Cmd.Param;
				SetAxefxBlockXY(b->BlockId, BlockXy_Toggle);
				break;
			}
		}
	}
	else
	{
		if (GrpCheck)
		{
			// Check group bits
			auto Press = Sh->Preset.GetTypePress();
			for (unsigned i=0; i<Press.Length(); i++)
			{
				auto Cur = Press[i];
				if (Cur->Group == Effect->Group)
				{
					if (i != Idx && EffectOn[i])
						// Switch off grouped effect
						ToggleEffect(i, false);
				}   
			}
		}

		// Toggle effect
		LAssert(EffectOn.IdxCheck(Idx));
		LAssert(IaIdxs.IdxCheck(Idx));

		EffectOn[Idx] = !EffectOn[Idx];
        if (Hw)
		    Hw->SetLed(IaIdxs[Idx], EffectOn[Idx] ? Effect->GetColour() : LedOff);
       	else
       	    LgiTrace("%s:%i - No HW.\n", _FL);
		
		// Send commands associated with effect
		LArray<MidiCmd> &Cmds = Effect->Cmds;
		for (unsigned i=0; i<Cmds.Length(); i++)
		{
			MidiCmd &Cmd = Cmds[i];
			switch (Cmd.Id)
			{
				case MIDI_CMD_NONE:
				{
					// Do nothing
					break;
				}
				case MIDI_CMD_SCENE:
				{
					// Scene selection
					if (SceneSelect < 0) // If we haven't selected a scene yet...
					{
						// Change the scene
						SetScene(SceneSelect = EffectOn[Idx] ? Cmd.Param - 1 : 0);
            			Msg.Printf("Scene: %i", SceneSelect + 1);
					}
					break;
				}
				case MIDI_CMD_CC:
				{
					// Send MIDI CC
					SendCc(Cmd.Param, (uint8_t) (EffectOn[Idx] ? 127 : 0));
					break;
				}
				case MIDI_CMD_BLOCK:
				{
					// Turn on/off Axefx block
					b = AxefxGetBlocks() + Cmd.Param;
					Msg.Printf("%s %s", b->Name, EffectOn[Idx] ? "On" : "Off");
					SendCc(b->DefaultCc, (uint8_t) (EffectOn[Idx] ? 127 : 0));
					break;
				}
				default: // Axefx sysex cmd
				{
					b = GetBlockById(Cmd.Id);
					SetAxefxParam(Cmd.Id, Cmd.Param, (uint8_t) (EffectOn[Idx] ? Cmd.Max : Cmd.Min));
					break;
				}
			}
		}
		
		if (SceneSelect > 0 && GrpCheck)
		{
			// Check if the other effects are scene selects and switch off the LED
			int IaIdx = 0;
			for (int n=0; n<Sh->Global.GetButtons(); n++)
			{
				auto Cfg = Sh->Global.GetBtn(n);
				if (Cfg.b == BtnTypeIA)
				{
					if (IaIdx != Idx) // Only look at effects that our not the current one
					{
						uint8_t HasScene = false;
						
						auto Ia = Sh->Preset.GetIa(IaRef(Idx));
						LArray<MidiCmd> &Cmds = Ia.Cmds;
						for (unsigned c=0; c<Cmds.Length(); c++) // For all commands
						{
							if (Cmds[c].Id == MIDI_CMD_SCENE)
							{
								HasScene = true;
								break;
							}
						}
						if (HasScene && EffectOn[IaIdx])
						{
							// Switch off other scene effect
							EffectOn[IaIdx] = false;
							
							if (Hw)
							    Hw->SetLed(n, EffectOn[IaIdx] ? Sh->Preset.GetIa(IaRef(IaIdx)).GetColour() : LedOff);
	                       	else
	                       	    LgiTrace("%s:%i - No HW.\n", _FL);
						}
					}
					
					IaIdx++;
				}
			}
		}
		
		// Show name of the effect
		Ui->PostEvent(M_STATUS_MSG, (LMessage::Param) new LString(Msg.Get() ? Msg.Get() : sNone));
	}			

	// Select the right param group if configured...
	if (Effect->AutoSelectMode)
	{
		if (EffectOn[Idx])
		{
			int i = MapParamMode(Effect->AutoSelectMode);
			if (i)
				Ui->PostEvent(M_PARAM_MODE_CHANGE, i);
		}
		else
		{
			// Revert to the first auto-param in remaining engaged effects...
			bool Found = false;
			for (unsigned i=0; i<Press.Length(); i++)
			{
				auto IA = Press[i];
				if (EffectOn[i] && IA->AutoSelectMode)
				{
					Ui->PostEvent(M_PARAM_MODE_CHANGE, MapParamMode(IA->AutoSelectMode));
					Found = true;
					break;
				}
			}
			if (!Found)
				Ui->PostEvent(M_PARAM_MODE_CHANGE, -1);
		}
	}
}


void Mc4Logic::SetPresetIndex(int Idx)
{
	auto Sh = Shared->Lock(_FL);
	if (!Sh)
		return;

	if (Sh->PresetIndex != Idx && Hw)
	{
		Sh->PresetName.Empty();
		Sh->PresetIndex = Idx;

		Ui->PostEvent(M_SET_PRESET_NUMBER, (LMessage::Param)Sh->PresetIndex);

		int PresetBtns = 0;
		int PresetOffset = Sh->PresetIndex - Sh->BankStart;
		for (int i=0; i<Sh->Global.GetButtons(); i++)
		{
			auto Cfg = Sh->Global.GetBtn(i);
			if (Cfg.b == BtnTypePreset)
			{
				Hw->SetLed(i, PresetBtns == PresetOffset ? LedPresetColour : LedOff);
				PresetBtns++;
			}
		}
	}
}
