plinstrument:
{
calc:"
// (based on ROBIN Spiider)

#include	colorconv
#include	colortable
#include	ledref
#include	strobe

// controls
cMod1;	// rgbw/pt
cMod2;	// -/fx
cMod3;	// -/rgb/rgbw

cR; cG; cB; cW; cCtc; cCol; cSht; cDim; cPan; cTlt; cFnc; cRst; cZom; cRot;
cEfc; cEfs; cEff; cEfR; cEfG; cEfB; cEfW; cEfD; cBDm; cEfT; cEfp; cFSt; cBSt; cBSl;
cPix;
cSpd; cSFx; cDFx; cBR; cBG; cBB; cBW;

// parameters
pRPn;
pRTl;
pSwp;
pMod;

// constants
kPosY = 0.000;
kDia = 0.250;
kBaseWid = 0.320;
kBaseHi  = 0.094;
kBaseDep = 0.28;
kArmDep  = 0.104;
kArmWid  = 0.370;
kCenterHi = 0.36;
kZBack  =  0.114;
kZFront = -0.092;
kHeadWid = 0.280;
kHeadHi  = 0.280;

kCnt = 2;
kSource = LEDRefConv(19 * 40, kDia, kLEDRefWatt_RGBW, kLEDRefDia_RGBW, kLEDRefSrc_RGBW);
kWhite = kLEDRefWhite_RGBW;

kPixs = 19;


// protocol parameters
CENTER[1] = kCenterHi;
PAN;
TILT;
LIGHT;
SHAPE;
CTLS;

PRM=1;
CTL;


any#	// (val, v0, v1, ...) val is any of v_
(
	v = $0;
	@(++i < $#)
	(
		(v == $i)?
		(
			b = 1;	// true
			i = $#;	// exit
		);
	);
	b
);


// pan, tilt

kPan = 540;
kTilt = 250;
kPanSpeedMin = kPan / 200;		// [deg/s] (of XL575)
kPanSpeedMax = kPan / 2.1;		// [deg/s]
kTiltSpeedMin = kTilt / 110;	// [deg/s] (of XL575)
kTiltSpeedMax = kTilt / 1.1;	// [deg/s]

gPan;
gTilt;

PanVal#
(
	(pRPn? -1 : 1) * ((pSwp? cTlt : cPan) / 256 - 0.5)
);

TiltVal#
(
	(pRTl? -1 : 1) * ((pSwp? cPan : cTlt) / 256 - 0.5)
);

PanTiltTime#
(
	pd = kPan  * PanVal() - gPan;
	td = kTilt * TiltVal() - gTilt;

	(pd != 0 | td != 0)?	// change
	(
		(pMod == 1 | pMod == 6)?
		(
			ps = ParamSqr(kPanSpeedMin,  kPanSpeedMax,  255, 0, cSpd);
			ts = ParamSqr(kTiltSpeedMin, kTiltSpeedMax, 255, 0, cSpd);
		)
		:
		(
			ps = kPanSpeedMax;
			ts = kTiltSpeedMax;
		);
		gPan  += LimitChange($0, pd, ps);
		gTilt += LimitChange($0, td, ts);
		1
	)
	:
	0
);


// shutter (common)

kStrobeMin =1;			// [Hz]
kStrobeMax = 25;		// [Hz]
kStrobeDur = 1 / 30;	// [s] (?)

ShutterTimeO#	// (time, obj, ctl) shutter
(
	so = $1;
	sc = $2;

	(pMod == 1 | pMod == 6)?
	(
		(sc < 4)?	// open(?)
		(
			sd = 1;
		)
		:(sc < 201)?	// strobe
		(
			ss = ParamSqr(kStrobeMin, kStrobeMax, 4, 200, sc);
			StrobeProgress(so, $0 * ss, kStrobeDur * ss);
			sd = so.ef;
		)
		:	// random
		(
			(sc < 216)?		// lo
			(
				ss = 0.25;
			)
			:(sc < 235)?	// med
			(
				ss = 0.25;
			)
			:				// hi
			(
				ss = 0.75;
			);
			ss = StrobeRandomSpeed(so, kStrobeMin, kStrobeMax, 0.5, ss);
			StrobeProgress(so, $0 * ss, kStrobeDur * ss);
			sd = so.ef;
		);
	)
	:
	(
		(sc < 4)?	// close
		(
		)
		:(sc < 104)?	// strobe
		(
			ss = ParamSqr(kStrobeMin, kStrobeMax, 4, 103, sc);
			StrobeProgress(so, $0 * ss, kStrobeDur * ss);
			sd = so.ef;
		)
		:(sc < 108)?	// open(?)
		(
			sd = 1;
		)
		:(sc < 208)?	// pulse (50% duty?)
		(
			ss = ParamSqr(kStrobeMin, kStrobeMax, 108, 207, sc);
			StrobeProgress(so, $0 * ss, 0.5);
			sd = so.ef;
		)
		:(sc < 213)?	// open(?)
		(
			sd = 1;
		)
		:(sc < 252)?	// random
		(
			(sc < 226)?		// lo
			(
				ss = 0.25;
			)
			:(sc < 239)?	// med
			(
				ss = 0.25;
			)
			:				// hi
			(
				ss = 0.75;
			);
			ss = StrobeRandomSpeed(so, kStrobeMin, kStrobeMax, 0.5, ss);
			StrobeProgress(so, $0 * ss, kStrobeDur * ss);
			sd = so.ef;
		)
		:		// open(?)
		(
			sd = 1;
		);
	);
	sd
);


// common
kAllOn = Vec(1, 1, 1, 1);

gCommon;	// rgbw

CommonTime#
(
	(cCol < 1)?	// no macro (?)
	(
		r = 1 / (any(pMod, 1, 6)? 255 : 256);
		c = Vec(cR * r, cG * r, cB * r, cW * r);
		gCommon = c;
	)
	:	// macro (unknown)
	(
		gCommon = kAllOn;
	);
);


// pixel

gPix[0];	// rgbw[kPixs]

UseW#	// (rgb) rgbw  (utilize W by RGB)
(
	c = $0;
	c[3] = min(c[0], c[1], c[2]);
	c
);

PixTime#
(
	any(pMod, 3, 4)?		// rgb
	(
		@(i < kPixs)
		(
			c = cPix[i];
			p = gPix[i];

			p[0] = c.cR / 255;
			p[1] = c.cG / 255;
			p[2] = c.cB / 255;
			gPix[i] = UseW(p);
			++i;
		);
	)
	:any(pMod, 5, 6)?	// rgbw
	(
		@(i < kPixs)
		(
			c = cPix[i];
			p = gPix[i];

			p[0] = c.cR / 255;
			p[1] = c.cG / 255;
			p[2] = c.cB / 255;
			p[3] = c.cW / 255;
			gPix[i] = p;
			++i;
		);
	)
	:		// none
	(
		gPix = 0;
	);
);


// background

gBgColor;	// rgbw

BgColorTime#
(
	any(pMod, 1, 6)?
	(
		gBgColor[0] = cBR / 255;
		gBgColor[1] = cBG / 255;
		gBgColor[2] = cBB / 255;
		gBgColor[3] = cBW / 255;
	)
	:
	(
		gBgColor = 0;
	);
);


// static/dynamic fx
kDyFxSpeedMin = 1 / kPixs;	// (?)
kDyFxSpeedMax = 1;			// (?)
kStMaskAll  = Vec(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1);
kStMaskSome = Vec(1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1);

gStFxMask;	// (0/1)[kPixs]
gDyFxMask;	// (0/1)[kPixs]
gDyFxPh;

StFxTime#
(
	any(pMod, 1, 6)?
	(	// (pixel mask?)
		(cSFx < 10)?	// none
		(
			gStFxMask = kStMaskAll;
		)
		:
		(
			gStFxMask = kStMaskSome;
		);
	)
	:any(pMod, 2, 4)?
	(
		(
		 cEfc < 8 |
		 cEfc == 11 |
		 14 <= cEfc)?	// none
		(
			gStFxMask = kStMaskAll;
		)
		:
		(
			gStFxMask = kStMaskSome;
		);
	)
	:
	(
		gStFxMask = kStMaskAll;
	);
);


DyFxTime#
(
	any(pMod, 1, 6)?
	(	// (pixel mask?)
		(cDFx < 10)?	// none
		(
			gDyFxMask = kStMaskAll;
		)
		:
		(
			(cEfs < 127)?	// fwd
			(
				fs =  ParamSqr(kDyFxSpeedMin, kDyFxSpeedMax, 126, 0, cEfs);
			)
			:(cEfs < 129)?	// stop
			(
			)
			:	// rev
			(
				fs = -ParamSqr(kDyFxSpeedMin, kDyFxSpeedMax, 129, 255, cEfs);
			);
			gDyFxPh = Cycle(gDyFxPh + fs * $0);
			
			// ramp up pixels
			t = floor((kPixs + 1) * gDyFxPh);
			@(i < kPixs)
			(
				gDyFxMask[i] = (i < t)? 1 : 0;
				++i;
			);
		);
	)
	:any(pMod, 2, 4)?
	(
		(cEfc < 15 |
		 any(cEfc, 33, 34, 41, 43, 64, 69) |
		 71 <= cEfc)?	// none
		(
			gDyFxMask = kStMaskAll;
		)
		:
		(
			(cEfs < 64)?	// static (at phase 0?)
			(
				gDyFxPh = 0;
			)
			:
			(
				(cEfs < 159)?	// fwd
				(
					fs =  ParamSqr(kDyFxSpeedMin, kDyFxSpeedMax, 158, 64, cEfs);
				)
				:(cEfs < 161)?	// stop
				(
				)
				:	// rev
				(
					fs = -ParamSqr(kDyFxSpeedMin, kDyFxSpeedMax, 161, 255, cEfs);
				);
				gDyFxPh = Cycle(gDyFxPh + fs * $0);
			);
			
			// ramp up pixels
			t = floor((kPixs + 1) * gDyFxPh);
			@(i < kPixs)
			(
				gDyFxMask[i] = (i < t)? 1 : 0;
				++i;
			);
		);
	)
	:
	(
		gStFxMask = kStMaskAll;
	);
);


// final color
/*
Final color = max(Main, PerPix)

Mode 1, 6
Main = Fg(Common)/Bg(BgColor)
Fg = StFx & DyFx
Bg = !Fg

Mode 0, 2-5
Main = Fg(Effect)/Bg(Common)
Bg = StFx & DyFx
Fg = !Bg

*/


ColMax#	// (c0, c1) c
(
	c0 = $0;
	c1 = $1;
	Vec(
	max(c0[0], c1[0]),
	max(c0[1], c1[1]),
	max(c0[2], c1[2]),
	max(c0[3], c1[3]))
);


gCol;	// final color (average rgbw)
gStrobeFg[0];
gStrobeBg[0];


ColorTime#
(
	CommonTime($0);
	PixTime($0);
	BgColorTime($0);
	StFxTime($0);
	DyFxTime($0);

	any(pMod, 1, 6)?
	(
		m = VecMul(gStFxMask, gDyFxMask);

		// per pix fg/bg
		i = 0;
		@(i < kPixs)
		(
			cm[i] = m[i]? gCommon : gBgColor;
			++i;
		);
	)
	:any(pMod, 2, 4)?
	(
		fgcl = Vec(cEfR / 255, cEfG / 255, cEfB / 255, cEfW / 255);	// fx color
		dmfg = cEfD / 255;	// fg (fx) dimmer
		dmbg = cBDm / 255;	// bg dimmer
		shfg = ShutterTimeO($0, gStrobeFg, cFSt);	// fg strobe
		shbg = ShutterTimeO($0, gStrobeBg, cBSt);	// bg strobe
		fgc = VecScale(fgcl,    dmfg * shfg);		// fg color
		bgc = VecScale(gCommon, dmbg * shbg);		// bg color

		m = VecMul(gStFxMask, gDyFxMask);

		// per pix fg/bg
		i = 0;
		@(i < kPixs)
		(
			cm[i] = m[i]? bgc : fgc;
			++i;
		);
	)
	:
	(
		i = 0;
		@(i < kPixs)
		(
			cm[i] = gCommon;
			++i;
		);
	);

	// per pix htp
	i = 0;
	@(i < kPixs)
	(
		c[i] = ColMax(cm[i], gPix[i]);
		++i;
	);

	// average pix
	i = 0;
	@(i < kPixs)
	(
		s = VecAdd(s, c[i]);
		++i;
	);

	gCol = VecScale(s, 1 / kPixs);	// final color (average)
);


// zoom

kZoomSpeed = 1;	// (?)

gZoom;

ZoomTime#
(
	gZoom += LimitChange($0, cZom / 255 - gZoom, kZoomSpeed);
);


// shutter

gShutter;
gStrobe[0];

ShutterTime#
(
	gShutter = ShutterTimeO($0, gStrobe, cSht);
);


// dimmer

gDimmer;

DimmerTime#
(
	r = any(pMod, 1, 6)? 255 : 256;
	gDimmer = cDim / r;
);


// protocol methods

kMode = Vec(	// cMod1-3
Vec(0, 0, 0),
Vec(1, 0, 0),
Vec(0, 1, 0),
Vec(0, 0, 1),
Vec(0, 1, 1),
Vec(0, 0, 2),
Vec(1, 0, 2)
);


UPDATE#
(
	dir = PanTiltTime($0);

	ColorTime($0);
	ZoomTime($0);
	ShutterTime($0);
	DimmerTime($0);

	PRM?
	(
		m = kMode[pMod];
		cMod1 = m[0];
		cMod2 = m[1];
		cMod3 = m[2];
		CTLS = 1;
	);

	(dir | PRM)?
	(
		PAN = gPan;
		TILT = 90 + gTilt;
		SHAPE = MovingShape(kBaseWid, kBaseHi, kHeadWid, kHeadHi, kZFront, kZBack, kCenterHi, PAN, TILT, kBaseDep, kArmDep, kArmWid);

		// setup light
		l = LIGHT[0];
		l.pos[1] = kPosY;
		l.pos[2] = kZFront;
		l.dia = kDia;
		LIGHT[0] = l;
	);

	(
		fa = ParamLin(60, 4, 0, 1, gZoom);	// field angle(?)
		cent = ParamLin(kCnt, 0, 0, 1, gZoom);
		spread = Spread(cent, 0.1, fa);

		c = RGBfromRGBW(gCol, kWhite);

		// setup light
		l = LIGHT[0];
		l.color = VecScale(c, kSource * gDimmer * gShutter);
		l.spdh = spread;
		l.spdv = spread;
		l.cnt = cent;
		LIGHT[0] = l;
	);

	PRM = CTL = 0;
);

";


chmap:
"cR.w cG.w cB.w cW.w cCtc cCol cSht cDim.w cPan.w cTlt.w cFnc cRst cZom cRot",
"cPan.w cTlt.w cSpd cZom cRot cDim cSht cR cG cB cW cCtc cCol cSFx cDFx cEfs cBR cBG cBB cBW cRst",
"cR.w cG.w cB.w cW.w cCtc cCol cSht cDim.w cPan.w cTlt.w cFnc cRst cZom cRot cEfc cEfs cEff cEfR cEfG cEfB cEfW cEfD cBDm cEfT cEfp cFSt cBSt cBSl",
"cR.w cG.w cB.w cW.w cCtc cCol cSht cDim.w cPan.w cTlt.w cFnc cRst cZom cRot                                                                       cPix(cR cG cB)[19]",
"cR.w cG.w cB.w cW.w cCtc cCol cSht cDim.w cPan.w cTlt.w cFnc cRst cZom cRot cEfc cEfs cEff cEfR cEfG cEfB cEfW cEfD cBDm cEfT cEfp cFSt cBSt cBSl cPix(cR cG cB)[19]",
"cR.w cG.w cB.w cW.w cCtc cCol cSht cDim.w cPan.w cTlt.w cFnc cRst cZom cRot cEfc cEfs cEff cEfR cEfG cEfB cEfW cEfD cBDm cEfT cEfp cFSt cBSt cBSl cPix(cR cG cB cW)[19]",
"cPan.w cTlt.w cSpd cZom cRot cDim cSht cR cG cB cW cCtc cCol cSFx cDFx cEfs cBR cBG cBB cBW cRst cPix(cR cG cB cW)[19]";


ctl:
{
	controls:
	{
		type:mode;
		id:cMod1;
		modes:
		{
			controls:
			{
				type:numeric;
				id:cR;
				name:R;
				max:"255.9";
				step:"0.1";
				prec:1;
				ini:"255.9";
			},
			{
				type:numeric;
				id:cG;
				name:G;
				max:"255.9";
				step:"0.1";
				prec:1;
				ini:"255.9";
			},
			{
				type:numeric;
				id:cB;
				name:B;
				max:"255.9";
				step:"0.1";
				prec:1;
				ini:"255.9";
			},
			{
				type:numeric;
				id:cW;
				name:W;
				max:"255.9";
				step:"0.1";
				prec:1;
				ini:"255.9";
			},
			{
				type:numeric;
				id:cCtc;
				name:CTC;
				max:255;
			},
			{
				type:numeric;
				id:cCol;
				name:Color;
				max:255;
			},
			{
				type:numeric;
				id:cSht;
				name:Shutter;
				max:255;
				ini:255;
			},
			{
				type:numeric;
				id:cDim;
				name:Dimmer;
				max:"255.9";
				step:"0.1";
				prec:1;
				ini:"255.9";
			},
			{
				type:numeric;
				id:cPan;
				name:Pan;
				max:"255.9";
				step:"0.1";
				prec:1;
				ini:128;
			},
			{
				type:numeric;
				id:cTlt;
				name:Tilt;
				max:"255.9";
				step:"0.1";
				prec:1;
				ini:128;
			},
			{
				type:numeric;
				id:cFnc;
				name:Function;
				max:255;
			},
			{
				type:numeric;
				id:cRst;
				name:Reset;
				max:255;
			},
			{
				type:numeric;
				id:cZom;
				name:Zoom;
				max:255;
			},
			{
				type:numeric;
				id:cRot;
				name:Rotate;
				max:255;
			};
		},
		{
			controls:
			{
				type:numeric;
				id:cPan;
				name:Pan;
				max:"255.9";
				step:"0.1";
				prec:1;
				ini:128;
			},
			{
				type:numeric;
				id:cTlt;
				name:Tilt;
				max:"255.9";
				step:"0.1";
				prec:1;
				ini:128;
			},
			{
				type:numeric;
				id:cSpd;
				name:Speed;
				max:255;
			},
			{
				type:numeric;
				id:cZom;
				name:Zoom;
				max:255;
			},
			{
				type:numeric;
				id:cRot;
				name:Rotate;
				max:255;
			},
			{
				type:numeric;
				id:cDim;
				name:Dimmer;
				max:255;
				ini:255;
			},
			{
				type:numeric;
				id:cSht;
				name:Shutter;
				max:255;
			},
			{
				type:numeric;
				id:cR;
				name:R;
				max:255;
				ini:255;
			},
			{
				type:numeric;
				id:cG;
				name:G;
				max:255;
				ini:255;
			},
			{
				type:numeric;
				id:cB;
				name:B;
				max:255;
				ini:255;
			},
			{
				type:numeric;
				id:cW;
				name:W;
				max:255;
				ini:255;
			},
			{
				type:numeric;
				id:cCtc;
				name:CTC;
				max:255;
			},
			{
				type:numeric;
				id:cCol;
				name:Color;
				max:255;
			},
			{
				type:numeric;
				id:cSFx;
				name:Static;
				max:255;
			},
			{
				type:numeric;
				id:cDFx;
				name:Dynamic;
				max:255;
			},
			{
				type:numeric;
				id:cEfs;
				name:FxSpeed;
				max:255;
			},
			{
				type:numeric;
				id:cBR;
				name:Bg R;
				max:255;
			},
			{
				type:numeric;
				id:cBG;
				name:Bg G;
				max:255;
			},
			{
				type:numeric;
				id:cBB;
				name:Bg B;
				max:255;
			},
			{
				type:numeric;
				id:cBW;
				name:Bg W;
				max:255;
			},
			{
				type:numeric;
				id:cRst;
				name:Reset;
				max:255;
			};
		};
	},
	{
		type:mode;
		id:cMod2;
		modes:
		{
		},
		{
			controls:
			{
				type:numeric;
				id:cEfc;
				name:Fx;
				max:255;
			},
			{
				type:numeric;
				id:cEfs;
				name:FxSpeed;
				max:255;
			},
			{
				type:numeric;
				id:cEff;
				name:FxFade;
				max:255;
			},
			{
				type:numeric;
				id:cEfR;
				name:Fx R;
				max:255;
				ini:255;
			},
			{
				type:numeric;
				id:cEfG;
				name:Fx G;
				max:255;
				ini:255;
			},
			{
				type:numeric;
				id:cEfB;
				name:Fx B;
				max:255;
				ini:255;
			},
			{
				type:numeric;
				id:cEfW;
				name:Fx W;
				max:255;
				ini:255;
			},
			{
				type:numeric;
				id:cEfD;
				name:FxDimmer;
				max:255;
				ini:255;
			},
			{
				type:numeric;
				id:cBDm;
				name:BgDimmer;
				max:255;
				ini:255;
			},
			{
				type:numeric;
				id:cEfT;
				name:FxTrans;
				max:255;
			},
			{
				type:numeric;
				id:cEfp;
				name:FxSupp;
				max:255;
			},
			{
				type:numeric;
				id:cFSt;
				name:FgStrobe;
				max:255;
				ini:255;
			},
			{
				type:numeric;
				id:cBSt;
				name:BgStrobe;
				max:255;
				ini:255;
			},
			{
				type:numeric;
				id:cBSl;
				name:BgSel;
				max:255;
			};
		};
	},
	{
		type:mode;
		id:cMod3;
		modes:
		{
		},
		{
			controls:
			{
				type:array;
				id:cPixs;
				nam:Pixel;
				label:Pixel;
				base:1;
				elemid:cPix;
				fixed:1;
				max:19;
				ini:19;
				controls:
				{
					type:numeric;
					id:cR;
					name:R;
					max:255;
				},
				{
					type:numeric;
					id:cG;
					name:G;
					max:255;
				},
				{
					type:numeric;
					id:cB;
					name:B;
					max:255;
				};
			};
		},
		{
			controls:
			{
				type:array;
				id:cPixs;
				nam:Pixel;
				label:Pixel;
				base:1;
				elemid:cPix;
				fixed:1;
				max:19;
				ini:19;
				controls:
				{
					type:numeric;
					id:cR;
					name:R;
					max:255;
				},
				{
					type:numeric;
					id:cG;
					name:G;
					max:255;
				},
				{
					type:numeric;
					id:cB;
					name:B;
					max:255;
				},
				{
					type:numeric;
					id:cW;
					name:W;
					max:255;
				};
			};
		};
	};
};


prm:
{
	controls:
	{
		type:check;
		id:pRPn;
		name:"-Pan";
	},
	{
		type:check;
		id:pRTl;
		name:"-Tilt";
	},
	{
		type:check;
		id:pSwp;
		name:Swap;
	},
	{
		type:selector;
		id:pMod;
		name:Mode;
		ini:0;
		items:
		{
			text:21ch;
			value:0;
		},
		{
			text:23ch;
			value:1;
		},
		{
			text:35ch;
			value:2;
		},
		{
			text:78ch;
			value:3;
		},
		{
			text:92ch;
			value:4;
		},
		{
			text:97ch;
			value:5;
		},
		{
			text:99ch;
			value:6;
		};
	};
};

};
