<commit_before>/* 
	ej.function.js by <NAME>, Ircam - 03 2005
	multi bpf editor (compatible with Max standart function GUI)

*/

// global code
var ejies = new EjiesUtils();						// lien vers ejiesUtils.js

const FUNCTIONVERSION = 2;	// Depuis ejies version 1.52 le numro de version est 2 ( cause du domain qui a 2 bornes).
inlets = 1;
outlets = 5;
const INTERP_OUTLET = 0;
const LINE_OUTLET = 1;
const DUMP_OUTLET = 2;
const BANG_OUTLET = 3;
const DUMPOUT = 4;
setinletassist(0, "quite anything...");
setoutletassist(INTERP_OUTLET, "interpolated Y for input X");
setoutletassist(LINE_OUTLET, "points in line~ format");
setoutletassist(DUMP_OUTLET, "dump message output");
setoutletassist(BANG_OUTLET, "bang when changed with mouse");
setoutletassist(DUMPOUT, "dumpout");

var g = new Global("ej.function");	// utilis par dump & listdump
g["copy"] = new Array();			// Utilis pour le copier-coller
g["copycolors"] = new Array();		// Utilis pour le copier-coller des couleurs
var NbCourbes = 2;
var fctns = new Array();
var Tolerance = 4;
var Bordure = 4;
var LegendBordure = 12;
var BoxWidth = box.rect[2] - box.rect[0];
var BoxHeight = box.rect[3] - box.rect[1] ;
var PixelDomain;
var PixelRange;
var current;
var Legend;
var GridMode;
var Snap2GridState;
var HiddenPointDisplay;
var ClickAdd;
var ClickMove;
var AutoSustain;
var IdleMode = 0;
var SelectedPoint = -2;
var IdlePoint = -1;
var RedrawEnable;
var AllowEdit = 1;
var TimeFlag = 0;
var OpendialogPrepend = 0;
var DisplayOneTime;
var LectureInspectorFlag = 0;

RedrawEnable = 0;	// dsactivation de l'affichage pendant l'initialisation

if (max.version < 455)
	perror("MaxMSP 4.5.5 or higher is required.");

if (box.rect[2] - box.rect[0] == 64 && box.rect[3] - box.rect[1] == 64) {
	// numbox a t cre  partie de jsui : dimensions = 64*64
	init();
	onresize(200,100);
}

//////////////// Objets ///////////////
function Point(x, y, valx, valy)
{
	this.x = x;
	this.y = y;
	this.valx = valx;
	this.valy = valy;
	this.sustain = 0;
	this.fix = 0;
}

function Courbe(name)
{
	this.name = name;			// c'est assez clair...
	this.np = 0;				// nombre de points dans la courbe
	this.domain = [0., 1000.];	// domain de la courbe
	this.range = [0., 1.];		// range de la courbe
	this.ZoomX = [0., 1000.];	// domain de la courbe
	this.ZoomY = [0., 1.];		// range de la courbe
	this.pa = new Array();		// PointsArray
	this.brgb =[0.8,0.8,0.8];	// Couleur de fond
	this.frgb =[0.32,0.32,0.32];// Couleur des points
	this.rgb2 =[0.33,0.33,0.33];// Couleur des traits
	this.rgb3 =[1,0.,0.];		// Couleur sustain
	this.rgb4 =[0.2,0.2,0.2];	// couleur texte
	this.rgb5 =[0.5,0.5,0.5];	// Couleur grille
	this.display = 1;			// display while inactive ?
	this.GridStep = 100;		// tout est dans lenom
	this.PixelDomain;			// ...
	this.PixelRange;			// ...
	this.NextFrom = 0;			// utilis pour le message next
}


function init()
{
	var c;
	
	for (c = 0; c < NbCourbes; c++) {
		fctns[c] = new Courbe("function" + c);
	}
	
	// suppression des ventuelles courbes en trop (par resetall)
	while (fctns.length > NbCourbes) {
		fctns.splice(fctns.length - 1, 1);
	}
}



//////////////// Fonctions Affichage ///////////////
function draw()
{
	if (! RedrawEnable)
		return;
	
	var c, i, j;
	var tmpF = fctns[current]; // a prend moins de place

	with (sketch) {
		// on efface tout
		glclearcolor(tmpF.brgb);
		glclear();
		gldisable("lighting");

		// dessine la grille
		if ( GridMode ) {
			glcolor(tmpF["rgb5"], 0.2);
			
			for (j = 0; j < (((tmpF.ZoomX[1] - tmpF.ZoomX[0]) / tmpF.GridStep)+1); j++) {
			linesegment( screentoworld(val2x(tmpF, j*tmpF.GridStep), val2y(tmpF, tmpF.range[0]-Bordure) ),
				screentoworld( val2x(tmpF, j*tmpF.GridStep), val2y(tmpF, tmpF.range[1]) ) );
			}
		}
	
		for (c = 0; c < NbCourbes; c++) {
			if (fctns[c].np > 0) {

				// dessine les segments
				if ( fctns[c].display ) {
					glcolor(fctns[c]["rgb2"], ((c == current) * 0.5) + 0.2);
							
					moveto(screentoworld(fctns[c]["pa"][0].x,fctns[c]["pa"][0].y ));
					
					for (i = 0; i < (fctns[c].np - 1); i++) {
						lineto(screentoworld(fctns[c]["pa"][i+1].x,fctns[c]["pa"][i+1].y ));
					}
				}
				
				// dessine les points de la courbe active ou de toutes les courbes si HiddenPointDisplay est activ
				if ( fctns[c].display && ((c == current) || HiddenPointDisplay) ) {
					// dessine les points
					glcolor(fctns[c]["frgb"], ((c == current) * 0.5) + 0.2);
	
					for (i = 0; i < fctns[c].np; i++) {							
						moveto(screentoworld(fctns[c]["pa"][i].x,fctns[c]["pa"][i].y ));
						
						if ( fctns[c]["pa"][i].sustain) {
							glcolor(fctns[c]["rgb3"], ((c == current) * 0.5) + 0.2);
							circle(5/BoxHeight); // 5 pixels le point...
							glcolor(fctns[c]["frgb"], ((c == current) * 0.5) + 0.2);
						}
						else
							circle(5/BoxHeight); // 5 pixels le point...
					}
				}
				
			}

		}

		if ( Legend ) {
			// Nom de la Courbe
			moveto((BoxWidth - Bordure)/BoxHeight,(BoxHeight - LegendBordure - Bordure)/BoxHeight);
			font("Arial");
			fontsize(11);
			textalign("right","center");
			glcolor(tmpF.rgb4);
			
			if ( BoxWidth < 130 && (SelectedPoint >= 0  || IdlePoint >= 0) ) { ;} //
			else {
				if (tmpF.display)
					text(tmpF.name);
				else
					text("("+tmpF.name+")");
			}
	
			if (tmpF.np > 0 && (SelectedPoint >= 0  || IdlePoint >= 0)) {
				var WhichPoint = (SelectedPoint >=0 ) ? SelectedPoint : IdlePoint ;

				if ( WhichPoint < tmpF.np) {
					var sep = " ";
					if (tmpF.pa[WhichPoint].fix)
						sep = "=";

					fontsize(10);
					textalign("left","center");
					moveto(-(BoxWidth - Bordure)/BoxHeight,(BoxHeight - LegendBordure - Bordure)/BoxHeight);
					if (TimeFlag)
						text("X" + sep + MyDomain2String(tmpF["pa"][WhichPoint].valx) + " Y" + sep + tmpF["pa"][WhichPoint].valy.toFixed(2));
					else
						text("X" + sep + tmpF["pa"][WhichPoint].valx.toFixed(2) + " Y" + sep + tmpF["pa"][WhichPoint].valy.toFixed(2));
				}
			}
		}
	}
/* 	post("draw operation completed\n"); */
	refresh();
}


//////////////// Fonctions magiques ///////////////
function x2val(courbe, x) { return ((( x - Bordure)) * courbe.PixelDomain + courbe.ZoomX[0]); }
function y2val(courbe, y) {	return (courbe.ZoomY[1] - ((y  - Bordure - LegendBordure) * courbe.PixelRange)); }
function val2x(courbe, valx) {	return (( (valx - courbe.ZoomX[0]) / courbe.PixelDomain) + Bordure); }
function val2y(courbe, valy) {	return ( ((courbe.ZoomY[1] - valy) / courbe.PixelRange) + Bordure + LegendBordure); }


//////////////// Fonctions standart ///////////////
function bang()
{
	line(fctns[current]);
}
bang.immediate = 1;

function msg_float(v)
{
	Interpolation(fctns[current], v);
}
msg_float.immediate = 1;

function list()
{
	ArgsParser(fctns[current], "list", arguments)
	UpdateDisplay();
}

function anything()
{
	var c;
	var NeedUpdate = -1;
	
	for (c = 0; c < NbCourbes; c++) {
		if ( messagename == fctns[c].name ) {
			NeedUpdate = ArgsParser(fctns[c], messagename, arguments);
			break;
		}
	}
		
	if (NeedUpdate == -1) {	// si -1 c'est que a ne correspond pas  un nom de function
		perror("doesn't understand", messagename);
		return;
	}

	if (NeedUpdate & 2 == 2)
		notifyclients();
	if (NeedUpdate & 1 == 1)
		redrawon();
}

function onresize(w,h)
{
	box.size(ejies.clip(w, 100, 1024),ejies.clip(h, 50, 1024));	// taille minimum 100*50
	BoxWidth = box.rect[2] - box.rect[0];
	BoxHeight = box.rect[3] - box.rect[1] ;
	AllPixel2Machin();
	ValRecalculate();
	draw();
}


//////////////// Fonctions Internes ///////////////
function LectureInspector()
{
	if (LectureInspectorFlag)
		return;
	
	var i, j;
	var idx = 1; // jsarguments[0] == ej.function.js
	if (jsarguments.length > 2) {
		NbCourbes = jsarguments[idx++];

		init();	// cration des courbes
		
		if (jsarguments.length != (10 + NbCourbes*18) ) {
			perror("bad number of arguments in the inspector");
			return;
		}
		Legend = jsarguments[idx++];
		GridMode = jsarguments[idx++];
		Snap2GridState = jsarguments[idx++];
		HiddenPointDisplay = jsarguments[idx++];
		ClickAdd = jsarguments[idx++];
		ClickMove = jsarguments[idx++];
		AutoSustain = jsarguments[idx++];
		TimeFlag = jsarguments[idx++];
		
		for (i = 0; i < NbCourbes; i++) {
			for (j=0; j < 3; j++) { fctns[i].brgb[j] = jsarguments[idx++] / 255; }
			for (j=0; j < 3; j++) { fctns[i].frgb[j] = jsarguments[idx++] / 255; }
			for (j=0; j < 3; j++) { fctns[i].rgb2[j] = jsarguments[idx++] / 255; }
			for (j=0; j < 3; j++) { fctns[i].rgb3[j] = jsarguments[idx++] / 255; }
			for (j=0; j < 3; j++) { fctns[i].rgb4[j] = jsarguments[idx++] / 255; }
			for (j=0; j < 3; j++) { fctns[i].rgb5[j] = jsarguments[idx++] / 255; }
		}
		return;
	}
	else {
/* 		NbCourbes = 2; // C'est dj dfini et a fout la merde avec l'initialisation save() */
		init();	// cration des courbes
	}
}
LectureInspector.local = 1;

function UpdateDisplay()
{
	notifyclients();
	draw();
}
UpdateDisplay.local = 1;

function LectureNextLigne(f)
{
	// ignore les lignes qui contiennt mois de 4 caractres ou //
	var tmp = new String();

	do {
		tmp = f.readline();
	} while	(tmp.length < 4 || (tmp.search("//") != -1));

	return tmp;
}
LectureNextLigne.local = 1;

function EditedWithMouse()
{
	if (EditedWithMouse.state)
		outlet(BANG_OUTLET, "bang");
	
	EditedWithMouse.state = 0;
	notifyclients();
}
EditedWithMouse.local = 1;

function DisplayCursor(v)
{
	if (this.box.ignoreclick == 0 && CursorChange == 0)		// Si la bote est ignore click ou on ne change pas le cursor
		return;
	
	if (v != DisplayCursor.state) {
		setcursor(v);
		DisplayCursor.state = v
	}
}
DisplayCursor.local = 1;

function PattrInterpError(v)
{
	if (PattrInterpError.flag)
		return;
	
	if (v == 0 )
		perror("interpolation isn't possible (different number of function)");
	else
		perror("interpolation isn't possible (different number of points)");

	PattrInterpError.flag = 1;
}
PattrInterpError.local = 1;

function PattrTooMany(v)
{
	if (v >= 2048) {
		if (PattrTooMany.flag) {
			perror("too many functions/points... pattr won't work...");
			PattrTooMany.flag = 0;
		}
	} else
		PattrTooMany.flag = 1;
}
PattrTooMany.local = 1;

function MyDomain2String(v)
{
	var tmpMinute, tmpSecond, tmpMs;
	var tmpStr = new String();
	
	if (v < 1000) {
		tmpStr = "0\"" + v.toFixed(0);
	
	} else {
		tmpMinute = Math.floor(v / 60000);
		tmpStr+= tmpMinute + "\'";
		
		tmpSecond = Math.floor(v / 1000);

		if (tmpSecond % 60 < 10)
			tmpStr += "0" + tmpSecond % 60 + "\"";
		else
			tmpStr += tmpSecond % 60 + "\"";

		tmpMS = Math.floor(v - (tmpSecond*1000));

		if (tmpMS < 10)
			tmpStr += "00" + tmpMS;
		else if (tmpMS < 100)
			tmpStr += "0" + tmpMS;
		else
			tmpStr += tmpMS;
	}
	return tmpStr;
}
MyDomain2String.local = 1;

function line(courbe)
{
	var tmpF = courbe;

	var tmpArray = new Array();
	var i, idx;
	
	if ( ! tmpF.np)
		return;		// si pas de point... pas de line... pas de bras... pas de chocolat...
	
	tmpF.NextFrom = 0;

	for (i = 1, idx = 0; i < tmpF.np; i++) {
		tmpArray[idx++] = tmpF.pa[i].valy;
		tmpArray[idx++] = tmpF.pa[i].valx - tmpF.pa[i-1].valx;
		if (tmpF.pa[i].sustain) {
			tmpF.NextFrom = i;
			break;
		}
	}
	
	outlet(LINE_OUTLET, tmpF.name, tmpF.pa[0].valy);
	outlet(LINE_OUTLET, tmpF.name, tmpArray);
}
line.local = 1;

function Interpolation(courbe, v)
{
	var tmpF = courbe;
	var i, a, tmpRange, tmpDomain;
	
	if ( tmpF.np < 2 )
		return;
	
	if ( v < tmpF.pa[0].valx ) {	// v est plus petit que le premier point
		outlet(INTERP_OUTLET, tmpF.name, tmpF.pa[0].valy);
		return;
	}

	if ( v > tmpF.pa[tmpF.np - 1].valx ) {	// v est plus grand que le dernier point
		outlet(INTERP_OUTLET, tmpF.name, tmpF.pa[tmpF.np - 1].valy);
		return;
	}
	
	for (i = 0, a = 0; i < tmpF.np; i++) {
		if (v > tmpF.pa[i].valx)
			a = i;
		else
			break;
	}

	tmpRange = tmpF.pa[a+1].valy - tmpF.pa[a].valy;
	tmpDomain = tmpF.pa[a+1].valx - tmpF.pa[a].valx;
	
	outlet(INTERP_OUTLET, tmpF.name, ((v - tmpF.pa[a].valx) / tmpDomain) * tmpRange + tmpF.pa[a].valy);
}
Interpolation.local = 1;

function ValRecalculate()
{
	// recalcule la position en fonction des valeurs
	var c,i;
	
	for (c = 0; c < NbCourbes; c++) {
		for (i = 0; i < fctns[c].np; i++) {
			fctns[c]["pa"][i].x = val2x(fctns[c], fctns[c]["pa"][i].valx);
			fctns[c]["pa"][i].y = val2y(fctns[c], fctns[c]["pa"][i].valy);
		}
	}
}
ValRecalculate.local = 1;

function ApplyAutoSustain()
{
	var i, c;
	if ( AutoSustain ) {
		for (c = 0; c < NbCourbes; c++) {
			for (i = 0; i < fctns[c].np; i++) {
				if (fctns[c].np < 3)
					break;

				if (i == fctns[c].np - 2)
					fctns[c].pa[i].sustain = 1;
				else
					fctns[c].pa[i].sustain = 0;
			}
		}
	}
}
ApplyAutoSustain.local = 1;

function MyDomain(start, stop, courbe)
{
	var i;

	courbe.domain[0] = start;
	courbe.domain[1] = stop;

	MyDomain2Zoom(courbe);
	pixel2machin(courbe);
	
	for (i = 0; i < courbe.np; i++) {
		courbe.pa[i].x = val2x(courbe, courbe.pa[i].valx);
	}
}
MyDomain.local = 1;

function MyDomain2Zoom(courbe)
{
	courbe.ZoomX[0] = courbe.domain[0];
	courbe.ZoomX[1] = courbe.domain[1];
}
MyDomain2Zoom.local = 1;

function MyRange2Zoom(courbe)
{
	courbe.ZoomY[0] = courbe.range[0];
	courbe.ZoomY[1] = courbe.range[1];
}
MyRange2Zoom.local = 1;

function MyThings2Zoom(courbe)
{
	MyDomain2Zoom(courbe);
	MyRange2Zoom(courbe);
}
MyThings2Zoom.local = 1;

function MySetDomain(start, stop, courbe)
{
	var i;
	
	courbe.domain[0] = start;
	courbe.domain[1] = stop;

	MyDomain2Zoom(courbe);
	pixel2machin(courbe);
	
	for (i = 0; i < courbe.np; i++) {
		courbe.pa[i].valx = x2val(courbe, courbe.pa[i].x);
	}

}
MySetDomain.local = 1;

function MyAutoRange(courbe)
{
	var i, min, max;

	if (courbe.np < 2 )
		return;

	min = courbe.pa[0].valy;
	max = courbe.pa[0].valy;
	for(i = 1; i < courbe.np; i++) {
		if (courbe.pa[i].valy > max)
			max = courbe.pa[i].valy;
		if (courbe.pa[i].valy < min)
			min = courbe.pa[i].valy;
	}
	if (min == max)	// si le minimum et le maximum sont identiques... on arrte.
		return;

	range(min, max, courbe);
}
MyAutoRange.local = 1;

function MyAutoDomain(courbe)
{
	var i, min, max;

	if (courbe.np < 2 )
		return;

	min = courbe.pa[0].valx;
	max = courbe.pa[0].valx;
	for(i = 1; i < courbe.np; i++) {
		if (courbe.pa[i].valx > max)
			max = courbe.pa[i].valx;
		if (courbe.pa[i].valx < min)
			min = courbe.pa[i].valx;
	}
	if (min == max)	// si le minimum et le maximum sont identiques... on arrte.
		return;

	MyDomain(min, max, courbe);
}
MyAutoDomain.local = 1;

function MyPasteColors(courbe)
{
	var c, j, idx;
	var cp = g["copycolors"];
	idx = 0;
	
	if (cp.length == 0) {
		perror("before pasting colors, you have to copy something");
		return;
	}
	
	for (j=0; j < 3; j++) { courbe.brgb[j] = cp[idx++]; }
	for (j=0; j < 3; j++) { courbe.frgb[j] = cp[idx++]; }
	for (j=0; j < 3; j++) { courbe.rgb2[j] = cp[idx++]; }
	for (j=0; j < 3; j++) { courbe.rgb3[j] = cp[idx++]; }
	for (j=0; j < 3; j++) { courbe.rgb4[j] = cp[idx++]; }
	for (j=0; j < 3; j++) { courbe.rgb5[j] = cp[idx++]; }
}
MyPasteColors.local = 1;

function WriteDialog()
{
	// open a standart dialog box to save the file
	var savedialog, prepend;
	savedialog = this.patcher.newdefault(box.rect[0], box.rect[1] - 50, "savedialog", "TEXT");
	prepend = this.patcher.newdefault(box.rect[0], box.rect[1] - 25, "prepend", "write");
	savedialog.varname = "temp-savedialog";
	prepend.varname = "temp-prepend";
	savedialog.hidden = 1;
	prepend.hidden = 1;
	
	// pour connecter en hidden, il faut que la fonction soit nomme
	if (! this.box.varname)
		this.box.varname = "tmp-function";

	this.patcher.script("hidden", "connect", "temp-savedialog", 0, "temp-prepend", 0);
	this.patcher.script("hidden", "connect", "temp-prepend", 0, this.box.varname, 0);
	
	// dnommage de la fonction si elle s'appelle tmp-function
	if (this.box.varname == "tmp-function")
		this.box.varname = "";

	savedialog.message("bang");
	this.patcher.script("delete", "temp-savedialog");
	this.patcher.script("delete", "temp-prepend");
}
WriteDialog.local = 1;

function ReadDialog()
{
	var opendialog, prepend, prepend2;
	opendialog = this.patcher.newdefault(box.rect[0], box.rect[1] - 50, "opendialog", "TEXT");
	prepend = this.patcher.newdefault(box.rect[0], box.rect[1] - 25, "prepend", "read");
	prepend2 = this.patcher.newdefault(box.rect[0] + 50, box.rect[1] - 25, "prepend", "DeleteReadThings");
	opendialog.varname = "temp-opendialog";
	prepend.varname = "temp-prepend";
	prepend2.varname = "temp-prepend2";
	opendialog.hidden = 1;
	prepend.hidden = 1;
	prepend2.hidden = 1;

	// pour connecter en hidden, il faut que la fonction soit nomme
	if (! this.box.varname)
		this.box.varname = "tmp-function";

	this.patcher.script("hidden", "connect", "temp-opendialog", 0, "temp-prepend", 0);
	this.patcher.script("hidden", "connect", "temp-prepend", 0, this.box.varname, 0);
	this.patcher.script("hidden", "connect", "temp-opendialog", 1, "temp-prepend2", 0);
	this.patcher.script("hidden", "connect", "temp-prepend2", 0, this.box.varname, 0);
	
	OpendialogPrepend = 1;
	
	
	// dnommage de la fonction si elle s'appelle tmp-function
	if (this.box.varname == "tmp-function")
		this.box.varname = "";

	opendialog.message("bang");
}
ReadDialog.local = 1;

function DeleteReadThings()
{
	// pour l'opration de suppression quand on vient de read...
	// pour une raison qui m'chappe...
	// il faut dlayer...
	tsk = new Task(function() { if ( ! tsk.running) { this.patcher.script("delete", "temp-opendialog"); this.patcher.script("delete", "temp-prepend"); this.patcher.script("delete", "temp-prepend2"); } }, this);
	tsk.interval = 100;
	tsk.repeat(1);
	
	OpendialogPrepend = 0;	// rinitialisation du flag
}

function MyRemoveDuplicate(courbe)
{
	var i, flag;
	
	do
	{
		flag = 0;
		for (i = 1; i < (courbe.np - 1); i++) {
			// suppression du point du milieu s'il est encadr par 2 valeurs identiques
			if ( courbe.pa[i-1].valy == courbe.pa[i].valy && courbe.pa[i+1].valy == courbe.pa[i-1].valy) {
				DeletePoint(courbe, i);
				flag = 1;
			}
		}
	} while (flag)
}
MyRemoveDuplicate.local = 1;

function MySmooth(courbe)
{
	var i;
	
	for (i = 1; i < (courbe.np - 1); i++) {
		courbe.pa[i].valy = courbe.pa[i-1].valy*0.2 + courbe.pa[i].valy*0.6 + courbe.pa[i+1].valy*0.2;
		courbe.pa[i].y = val2y(courbe, courbe.pa[i].valy);
	}
}
MySmooth.local = 1;

function pixel2machin(courbe)
{
	courbe.PixelDomain = (courbe.ZoomX[1] - courbe.ZoomX[0]) / (BoxWidth-(Bordure*2));
	courbe.PixelRange = (courbe.ZoomY[1] - courbe.ZoomY[0]) / (BoxHeight-((Bordure*2) + LegendBordure));
/* 	MyPointsInside(courbe); */
}
pixel2machin.local = 1.;

function AllPixel2Machin()
{
	var c;
	for (c = 0; c < NbCourbes; c++) {
		pixel2machin(fctns[c]);
	}
}
AllPixel2Machin.local = 1;

function AddPoint(courbe, x, y)
{
	var tmp = courbe.np;	// si tmp n'est pas modifi c'est que c'est la plus grande valeur...
	var i;

	if (courbe.np == 0) {	// cas particulier quand il n'y a pas de point dans la courbe
		courbe.pa[0] = new Point(x,y, x2val(courbe, x), y2val(courbe, y));
		tmp = 0;
	}
	else {
		for (i = 0; i < courbe.np; i++) {
			if (courbe.pa[i].x >= x) {
				tmp = i;
				break;
			}
		}
		courbe.pa.splice(tmp, 0, new Point(x,y, x2val(courbe, x), y2val(courbe, y)) );	// insre un point
	}
	
	courbe.np++;	// on incrmente car il y a un point supplmentaire dans la courbe

	return tmp;		// valeur de retour utilise comme seleected point
}
AddPoint.local = 1;

function DeletePoint(courbe, lequel)
{
 	if (! courbe.pa[lequel].fix) {
		courbe.pa.splice(lequel, 1);
		courbe.np--;
	}
}
DeletePoint.local = 1;

function MovePoint(courbe, lequel, newx, newy)
{
	if (lequel >= courbe.np) {
		perror("no point", lequel,"in function", courbe.name);
		return;
	}
	courbe.pa[lequel].valx = newx;
	courbe.pa[lequel].valy = newy;					
	courbe.pa[lequel].x = val2x(courbe, newx);
	courbe.pa[lequel].y = val2y(courbe, newy);

	SortBulle(courbe);	// il faut maintenant remettre tous les points dans l'ordre
	ApplyAutoSustain();
}
MovePoint.local = 1;

function SwitchCurrent(sens)
{
		if( sens )		// avec shift on tourne dans l'autre sens
			current = (current - 1 + NbCourbes) % NbCourbes;
		else
			current = (current + 1) % NbCourbes;
		
		outlet(DUMPOUT, "display", current);
		draw();
}
SwitchCurrent.local = 1;

function CurrentOrArgument(c, a, combien)
{
	if ( a.length > combien)
		return c;
	else
		return fctns[current];
}
CurrentOrArgument.local = 1;

function SortBulle(courbe)
{
	// la mthode bulle pour mettre dans l'ordre... :-)
	var i, bulle;
	var max = courbe.np - 2;
	var tmp = new Point();
	
	do {
		bulle = 0;
		
		for (i=0; i <= max; i++) {
			if ( courbe.pa[i].x > courbe.pa[i+1].x ) {
				tmp = courbe.pa[i];
				courbe.pa[i] = courbe.pa[i+1];
				courbe.pa[i+1] = tmp;
				bulle = 1;
			}
		}
	} while (bulle);
	
	ApplyAutoSustain();
}
SortBulle.local = 1;

function RedrawOrNot(v)
{
	if (v != RedrawOrNot.LastValue) {
		RedrawOrNot.LastValue = v;
		v == -1 ? WaitALittleBit() : draw() ; // si pas de point slectionn on attend un peu avant de faire draw()
	}
}
RedrawOrNot.local = 1;

function ArgsParser(courbe, msg, a) 
{
	// en fonction du nombre d'arguments 1 (interpolationX-Y) 2 (AddPoint) 3 (MovePoint)
	var tmpReturn = -1;
	var NeedDraw = 0;	// pour savoir si on doit
	var NeedNotify = 0;

	if ( typeof(a[0]) == "number") {

		switch (a.length) {
			case 1: Interpolation(courbe, a[0]); break;
			case 2: AddPoint(courbe, val2x(courbe, a[0]), val2y(courbe, a[1])); NeedDraw++; NeedNotify++; break;
			case 3: MovePoint(courbe, a[0], a[1], a[2]); NeedDraw++; NeedNotify++; break;
			default: perror("too many arguments for message", msg); break;
		}
		return ( (NeedNotify > 0 ? 2 : 0) + (NeedDraw > 0 ? 1 : 0) );	// sort de la fonction
	}

	switch (a[0]) {
		case "bang":		line(courbe); tmpReturn++; break;
		case "clear":		redrawoff(); MyClear(courbe); NeedDraw++; NeedNotify++; tmpReturn++; break;
		case "clearsustain":	redrawoff(); MyClearSustain(courbe); NeedDraw++; NeedNotify++; tmpReturn++; break;
		case "dump":		a.length == 2 ? MyDump(courbe, a[1]) : MyDump(courbe); tmpReturn++; break;
		case "listdump":	a.length == 2 ? MyListDump(courbe, a[1]) : MyListDump(courbe); tmpReturn++; break;
		case "fix":			if (a.length == 3) { FixPoint(courbe, a[1], a[2]); } ; NeedNotify++; tmpReturn++; break;
		case "unfix":		MyUnfix(courbe); NeedNotify++; tmpReturn++; break;
		case "name":		redrawoff(); if (a.length == 2) { MyName(courbe, a[1]);} ; getname(); NeedDraw++; NeedNotify++; tmpReturn++; break;
		case "next":		MyNext(courbe); tmpReturn++; break;
		case "nth":			if (a.length == 2) { MyNth(a[1], courbe); } ; tmpReturn++; break;
		case "sustain":		redrawoff(); if (a.length == 3) { MySustain(a[1], a[2], courbe); } ; NeedDraw++; NeedNotify++; tmpReturn++; break;
		case "domain":
							redrawoff(); 
							if (a.length == 2)
								MyDomain(0, a[1], courbe);
							else if (a.length == 3)
								MyDomain(a[1], a[2], courbe);
							else
								perror("bad argument(s) for message domain"); 
							NeedDraw++; NeedNotify++; tmpReturn++; break;
		case "setdomain":	
							redrawoff(); 
							if (a.length == 2)
								MySetDomain(0, a[1], courbe);
							else if (a.length == 3)
								MySetDomain(a[1], a[2], courbe);
							else
								perror("bad argument(s) for message setdomain"); 
							NeedDraw++; NeedNotify++; tmpReturn++; break;
		case "range":		redrawoff(); if (a.length == 3) { range(a[1], a[2], courbe);};  NeedDraw++; NeedNotify++; tmpReturn++; break;
		case "setrange":	redrawoff(); if (a.length == 3) { setrange(a[1], a[2], courbe);} ; NeedDraw++; NeedNotify++; tmpReturn++; break;
		case "zoom_x":		redrawoff(); zoom_x(a[1], a[2], courbe); NeedDraw++; tmpReturn++; break;
		case "zoom_y":		redrawoff(); zoom_y(a[1], a[2], courbe); NeedDraw++; tmpReturn++; break;
		case "zoomout":		redrawoff(); MyZoomOut(courbe); NeedDraw++; tmpReturn++; break;
		case "autodomain":	redrawoff(); MyAutoDomain(courbe); NeedDraw++; NeedNotify++; tmpReturn++; break;
		case "autorange":	redrawoff(); MyAutoRange(courbe); NeedDraw++; NeedNotify++; tmpReturn++; break;
		case "removeduplicate": 	redrawoff(); MyRemoveDuplicate(courbe); NeedDraw++; NeedNotify++; tmpReturn++; break;
		case "smooth":		redrawoff(); MySmooth(courbe); NeedDraw++; NeedNotify++; tmpReturn++; break;
		case "gridstep":	redrawoff(); if (a.length == 2) { MyGridStep(courbe, a[1]); }; NeedDraw++; NeedNotify++; tmpReturn++; break;
		case "brgb":		redrawoff(); SetColor(courbe, "brgb", a[1], a[2], a[3]); NeedDraw++; tmpReturn++; break;
		case "frgb":		redrawoff(); SetColor(courbe, "frgb", a[1], a[2], a[3]); NeedDraw++; tmpReturn++; break;
		case "rgb2":		redrawoff(); SetColor(courbe, "rgb2", a[1], a[2], a[3]); NeedDraw++; tmpReturn++; break;
		case "rgb3":		redrawoff(); SetColor(courbe, "rgb3", a[1], a[2], a[3]); NeedDraw++; tmpReturn++; break;
		case "rgb4":		redrawoff(); SetColor(courbe, "rgb4", a[1], a[2], a[3]); NeedDraw++; tmpReturn++; break;
		case "rgb5":		redrawoff(); SetColor(courbe, "rgb5", a[1], a[2], a[3]); NeedDraw++; tmpReturn++; break;

		case "pastefunction":	redrawoff(); MyPasteFunction(courbe); NeedDraw++; NeedNotify++; tmpReturn++; break;
		case "pastecolors":	redrawoff(); MyPasteColors(courbe); NeedDraw++; tmpReturn++; break;

		// get things
		case "getdomain":	getdomain(courbe); break;
		case "getrange":	getrange(courbe); break;
		case "getfix":		getfix(courbe); break;
		case "getsustain":	getsustain(courbe); break;
		case "getgridstep":	getgridstep(courbe); break;
		case "getbrgb":		GetColor(courbe, "brgb"); break;
		case "getfrgb":		GetColor(courbe, "frgb"); break;
		case "getrgb2":		GetColor(courbe, "rgb2"); break;
		case "getrgb3":		GetColor(courbe, "rgb3"); break;
		case "getrgb4":		GetColor(courbe, "rgb4"); break;
		case "getrgb5":		GetColor(courbe, "rgb5"); break;
		case "getnbpoints":	getnbpoints(courbe); break;
		case "getzoom_x":	getzoom_x(courbe); break;
		case "getzoom_y":	getzoom_y(courbe); break;

		default: 			// message d'erreurs en fonction du type de message all ou pas
							if (msg == "all") {
								if (DisplayOneTime == 1) {
									perror("bad arguments for message all");
									DisplayOneTime = 0;
								}
							} else
								perror("doesn't understand", msg);
							tmpReturn++;
							break;
	}
	
	if (tmpReturn == -1)
		return -1;
	else
		return ( (NeedNotify > 0 ? 2 : 0) + (NeedDraw > 0 ? 1 : 0) );
}
ArgsParser.local = 1;

function WaitALittleBit()
{
	// attend 750ms avant de redessiner
	tsk = new Task(function() { if (! tsk.running) { draw(); } }, this);
	tsk.interval = 750;
	tsk.repeat(1);
}
WaitALittleBit.local = 1;

function FixPoint(courbe, WhichPoint, state)
{
	if ( (state == 0 || state == 1 ) && (WhichPoint >= 0 && WhichPoint < courbe.np) )
		courbe.pa[WhichPoint].fix = state;
	else
		perror("bad argument for message fix");
}
FixPoint.local = 1;

function MyNext(courbe)
{
	var i, idx;
	var tmpArray = new Array();
	
	var OldNextFrom = courbe.NextFrom;
	
	if (courbe.NextFrom == 0) {
		line(courbe);
		return;
	}
	
	for (i = (courbe.NextFrom + 1), idx = 0; i < courbe.np; i++) {
		tmpArray[idx++] = courbe.pa[i].valy;
		tmpArray[idx++] = courbe.pa[i].valx - courbe.pa[i-1].valx;
		if (courbe.pa[i].sustain) {
			courbe.NextFrom = i;
			break;
		}
	}
	
	if (OldNextFrom == courbe.NextFrom)	// il n'y a plus de sustain...
		courbe.NextFrom = 0;
	
	outlet(LINE_OUTLET, courbe.name, tmpArray);
}
MyNext.local = 1;

function MyNth(courbe, v)
{
	if (v >= 0 && v < courbe.np)
		outlet(INTERP_OUTLET, courbe.name, courbe.pa[v].valy)
}
MyNth.local = 1;

function MySustain(point, state, courbe)
{
	if ( state == 1 || state == 0 ) {
		if (point >= 0 && point < courbe.np)
			courbe.pa[point].sustain = state;
		else
			perror("no point", point, "(sustain operation aborted)" );
	} else
		perror("bad arguments for message sustain");
}
MySustain.local = 1;

function MyClear(courbe)
{
	courbe.pa.splice(0, courbe.np - 1);
	courbe.np = 0;
}
MyClear.local = 1;

function MyClearSustain(courbe)
{
	var i;
	for (i = 0; i < courbe.np; i++)
		courbe.pa[i].sustain = 0;
}
MyClearSustain.local = 1;

function MyColor2Float(r, g, b)
{
	var tmpArray = new Array(3);
	tmpArray[0] = r/255.;
	tmpArray[1] = g/255.;
	tmpArray[2] = b/255.;	
	return tmpArray;
}
MyColor2Float.local = 1;

function MyFloat2Color(qui)
{
	var tmpArray = new Array(3);
	tmpArray[0] = Math.round(qui[0]*255.);
	tmpArray[1] = Math.round(qui[1]*255.);
	tmpArray[2] = Math.round(qui[2]*255.);
	return tmpArray; 
}
MyFloat2Color.local = 1;

function MyGridStep(courbe, v)
{
	if (typeof(v) == "number" && v > 0) {
		if ( ((courbe.ZoomX[1] - courbe.ZoomX[0]) / v) < (BoxWidth-(Bordure*2) / 4) )
			courbe.GridStep = v;
	} else
		perror("bad argument for message gridstep");
}
MyGridStep.local = 1;


function MyDump(courbe, sendname)
{
	var tmpF = courbe;
	var i, str;
	
	if (! tmpF.np)
		return;

	if (arguments.length == 1) {
		for (i = 0; i < tmpF.np; i++) {
			outlet(DUMP_OUTLET, tmpF.name, tmpF.pa[i].valx, tmpF.pa[i].valy);
		}
		return;
	}
	//else -> on envoie vers un send
	for (i = 0; i < tmpF.np; i++) {
		str = tmpF.name + " " + tmpF.pa[i].valx.toFixed(6) + " " + tmpF.pa[i].valy.toFixed(6);
		g.dump = str.split(" ");	// String -> Array
		g.sendnamed(sendname,"dump");
	}
}
MyDump.local = 1;

function MyListDump(courbe, sendname)
{
	var tmpF = courbe;
	var tmpArray = new Array();
	var i, idx, str;
	
	if (! tmpF.np)
		return;
	
	for (i = 0, idx = 0; i < tmpF.np; i++) {
		tmpArray[idx++] = tmpF.pa[i].valx;
		tmpArray[idx++] = tmpF.pa[i].valy;
	}

	if (tmpArray.length > 255) {
		perror("listdump aborted: too many points... use dump instead for now.");
		return;
	}
	
	if (arguments.length == 1) {
		outlet(DUMP_OUTLET, tmpF.name, tmpArray);
		return;
	}

	//else -> on envoie vers un send
	str = tmpF.name;
	for (i = 0; i < tmpArray.length; i++) {
		str += " " + tmpArray[i].toFixed(6);
	}
	g.listdump = str.split(" ");	// String -> Array
	g.sendnamed(sendname,"listdump");
}
MyListDump.local = 1;

function MyName(courbe, name)
{
	courbe.name = name;
}
MyName.local = 1;

function perror()
{
	ejies.scriptname = "ej.function.js";<commit_msg>bugfix for listdump: display an error message if the list contents more than 4094 values<commit_after>/* 
	ej.function.js by <NAME>, Ircam - 03 2005
	multi bpf editor (compatible with Max standart function GUI)

*/

// global code
var ejies = new EjiesUtils(); // lien vers ejiesUtils.js

const FUNCTIONVERSION = 2; // Depuis ejies version 1.52 le numro de version est 2 ( cause du domain qui a 2 bornes).
inlets = 1;
outlets = 5;
const INTERP_OUTLET = 0;
const LINE_OUTLET = 1;
const DUMP_OUTLET = 2;
const BANG_OUTLET = 3;
const DUMPOUT = 4;
setinletassist(0, "quite anything...");
setoutletassist(INTERP_OUTLET, "interpolated Y for input X");
setoutletassist(LINE_OUTLET, "points in line~ format");
setoutletassist(DUMP_OUTLET, "dump message output");
setoutletassist(BANG_OUTLET, "bang when changed with mouse");
setoutletassist(DUMPOUT, "dumpout");

var g = new Global("ej.function");	// utilis par dump & listdump
g["copy"] = new Array();			// Utilis pour le copier-coller
g["copycolors"] = new Array();		// Utilis pour le copier-coller des couleurs
var NbCourbes = 2;
var fctns = new Array();
var Tolerance = 4;
var Bordure = 4;
var LegendBordure = 12;
var BoxWidth = box.rect[2] - box.rect[0];
var BoxHeight = box.rect[3] - box.rect[1] ;
var PixelDomain;
var PixelRange;
var current;
var Legend;
var GridMode;
var Snap2GridState;
var HiddenPointDisplay;
var ClickAdd;
var ClickMove;
var AutoSustain;
var IdleMode = 0;
var SelectedPoint = -2;
var IdlePoint = -1;
var RedrawEnable;
var AllowEdit = 1;
var TimeFlag = 0;
var OpendialogPrepend = 0;
var DisplayOneTime;
var LectureInspectorFlag = 0;

RedrawEnable = 0;	// dsactivation de l'affichage pendant l'initialisation

if (max.version < 455)
	perror("MaxMSP 4.5.5 or higher is required.");

if (box.rect[2] - box.rect[0] == 64 && box.rect[3] - box.rect[1] == 64) {
	// numbox a t cre  partie de jsui : dimensions = 64*64
	init();
	onresize(200,100);
}

//////////////// Objets ///////////////
function Point(x, y, valx, valy)
{
	this.x = x;
	this.y = y;
	this.valx = valx;
	this.valy = valy;
	this.sustain = 0;
	this.fix = 0;
}

function Courbe(name)
{
	this.name = name;			// c'est assez clair...
	this.np = 0;				// nombre de points dans la courbe
	this.domain = [0., 1000.];	// domain de la courbe
	this.range = [0., 1.];		// range de la courbe
	this.ZoomX = [0., 1000.];	// domain de la courbe
	this.ZoomY = [0., 1.];		// range de la courbe
	this.pa = new Array();		// PointsArray
	this.brgb =[0.8,0.8,0.8];	// Couleur de fond
	this.frgb =[0.32,0.32,0.32];// Couleur des points
	this.rgb2 =[0.33,0.33,0.33];// Couleur des traits
	this.rgb3 =[1,0.,0.];		// Couleur sustain
	this.rgb4 =[0.2,0.2,0.2];	// couleur texte
	this.rgb5 =[0.5,0.5,0.5];	// Couleur grille
	this.display = 1;			// display while inactive ?
	this.GridStep = 100;		// tout est dans lenom
	this.PixelDomain;			// ...
	this.PixelRange;			// ...
	this.NextFrom = 0;			// utilis pour le message next
}


function init()
{
	var c;
	
	for (c = 0; c < NbCourbes; c++) {
		fctns[c] = new Courbe("function" + c);
	}
	
	// suppression des ventuelles courbes en trop (par resetall)
	while (fctns.length > NbCourbes) {
		fctns.splice(fctns.length - 1, 1);
	}
}


//////////////// Fonctions Affichage ///////////////
function draw()
{
	if (! RedrawEnable)
		return;
	
	var c, i, j;
	var tmpF = fctns[current]; // a prend moins de place

	with (sketch) {
		// on efface tout
		glclearcolor(tmpF.brgb);
		glclear();
		gldisable("lighting");

		// dessine la grille
		if ( GridMode ) {
			glcolor(tmpF["rgb5"], 0.2);
			
			for (j = 0; j < (((tmpF.ZoomX[1] - tmpF.ZoomX[0]) / tmpF.GridStep)+1); j++) {
			linesegment( screentoworld(val2x(tmpF, j*tmpF.GridStep), val2y(tmpF, tmpF.range[0]-Bordure) ),
				screentoworld( val2x(tmpF, j*tmpF.GridStep), val2y(tmpF, tmpF.range[1]) ) );
			}
		}
	
		for (c = 0; c < NbCourbes; c++) {
			if (fctns[c].np > 0) {

				// dessine les segments
				if ( fctns[c].display ) {
					glcolor(fctns[c]["rgb2"], ((c == current) * 0.5) + 0.2);
							
					moveto(screentoworld(fctns[c]["pa"][0].x,fctns[c]["pa"][0].y ));
					
					for (i = 0; i < (fctns[c].np - 1); i++) {
						lineto(screentoworld(fctns[c]["pa"][i+1].x,fctns[c]["pa"][i+1].y ));
					}
				}
				
				// dessine les points de la courbe active ou de toutes les courbes si HiddenPointDisplay est activ
				if ( fctns[c].display && ((c == current) || HiddenPointDisplay) ) {
					// dessine les points
					glcolor(fctns[c]["frgb"], ((c == current) * 0.5) + 0.2);
	
					for (i = 0; i < fctns[c].np; i++) {							
						moveto(screentoworld(fctns[c]["pa"][i].x,fctns[c]["pa"][i].y ));
						
						if ( fctns[c]["pa"][i].sustain) {
							glcolor(fctns[c]["rgb3"], ((c == current) * 0.5) + 0.2);
							circle(5/BoxHeight); // 5 pixels le point...
							glcolor(fctns[c]["frgb"], ((c == current) * 0.5) + 0.2);
						}
						else
							circle(5/BoxHeight); // 5 pixels le point...
					}
				}
				
			}

		}

		if ( Legend ) {
			// Nom de la Courbe
			moveto((BoxWidth - Bordure)/BoxHeight,(BoxHeight - LegendBordure - Bordure)/BoxHeight);
			font("Arial");
			fontsize(11);
			textalign("right","center");
			glcolor(tmpF.rgb4);
			
			if ( BoxWidth < 130 && (SelectedPoint >= 0  || IdlePoint >= 0) ) { ;} //
			else {
				if (tmpF.display)
					text(tmpF.name);
				else
					text("("+tmpF.name+")");
			}
	
			if (tmpF.np > 0 && (SelectedPoint >= 0  || IdlePoint >= 0)) {
				var WhichPoint = (SelectedPoint >=0 ) ? SelectedPoint : IdlePoint ;

				if ( WhichPoint < tmpF.np) {
					var sep = " ";
					if (tmpF.pa[WhichPoint].fix)
						sep = "=";

					fontsize(10);
					textalign("left","center");
					moveto(-(BoxWidth - Bordure)/BoxHeight,(BoxHeight - LegendBordure - Bordure)/BoxHeight);
					if (TimeFlag)
						text("X" + sep + MyDomain2String(tmpF["pa"][WhichPoint].valx) + " Y" + sep + tmpF["pa"][WhichPoint].valy.toFixed(2));
					else
						text("X" + sep + tmpF["pa"][WhichPoint].valx.toFixed(2) + " Y" + sep + tmpF["pa"][WhichPoint].valy.toFixed(2));
				}
			}
		}
	}
/* 	post("draw operation completed\n"); */
	refresh();
}


//////////////// Fonctions magiques ///////////////
function x2val(courbe, x) { return ((( x - Bordure)) * courbe.PixelDomain + courbe.ZoomX[0]); }
function y2val(courbe, y) {	return (courbe.ZoomY[1] - ((y  - Bordure - LegendBordure) * courbe.PixelRange)); }
function val2x(courbe, valx) {	return (( (valx - courbe.ZoomX[0]) / courbe.PixelDomain) + Bordure); }
function val2y(courbe, valy) {	return ( ((courbe.ZoomY[1] - valy) / courbe.PixelRange) + Bordure + LegendBordure); }


//////////////// Fonctions standart ///////////////
function bang()
{
	line(fctns[current]);
}
bang.immediate = 1;

function msg_float(v)
{
	Interpolation(fctns[current], v);
}
msg_float.immediate = 1;

function list()
{
	ArgsParser(fctns[current], "list", arguments)
	UpdateDisplay();
}

function anything()
{
	var c;
	var NeedUpdate = -1;
	
	for (c = 0; c < NbCourbes; c++) {
		if ( messagename == fctns[c].name ) {
			NeedUpdate = ArgsParser(fctns[c], messagename, arguments);
			break;
		}
	}
		
	if (NeedUpdate == -1) {	// si -1 c'est que a ne correspond pas  un nom de function
		perror("doesn't understand", messagename);
		return;
	}

	if (NeedUpdate & 2 == 2)
		notifyclients();
	if (NeedUpdate & 1 == 1)
		redrawon();
}

function onresize(w,h)
{
	box.size(ejies.clip(w, 100, 1024),ejies.clip(h, 50, 1024));	// taille minimum 100*50
	BoxWidth = box.rect[2] - box.rect[0];
	BoxHeight = box.rect[3] - box.rect[1] ;
	AllPixel2Machin();
	ValRecalculate();
	draw();
}


//////////////// Fonctions Internes ///////////////
function LectureInspector()
{
	if (LectureInspectorFlag)
		return;
	
	var i, j;
	var idx = 1; // jsarguments[0] == ej.function.js
	if (jsarguments.length > 2) {
		NbCourbes = jsarguments[idx++];

		init();	// cration des courbes
		
		if (jsarguments.length != (10 + NbCourbes*18) ) {
			perror("bad number of arguments in the inspector");
			return;
		}
		Legend = jsarguments[idx++];
		GridMode = jsarguments[idx++];
		Snap2GridState = jsarguments[idx++];
		HiddenPointDisplay = jsarguments[idx++];
		ClickAdd = jsarguments[idx++];
		ClickMove = jsarguments[idx++];
		AutoSustain = jsarguments[idx++];
		TimeFlag = jsarguments[idx++];
		
		for (i = 0; i < NbCourbes; i++) {
			for (j=0; j < 3; j++) { fctns[i].brgb[j] = jsarguments[idx++] / 255; }
			for (j=0; j < 3; j++) { fctns[i].frgb[j] = jsarguments[idx++] / 255; }
			for (j=0; j < 3; j++) { fctns[i].rgb2[j] = jsarguments[idx++] / 255; }
			for (j=0; j < 3; j++) { fctns[i].rgb3[j] = jsarguments[idx++] / 255; }
			for (j=0; j < 3; j++) { fctns[i].rgb4[j] = jsarguments[idx++] / 255; }
			for (j=0; j < 3; j++) { fctns[i].rgb5[j] = jsarguments[idx++] / 255; }
		}
		return;
	}
	else {
/* 		NbCourbes = 2; // C'est dj dfini et a fout la merde avec l'initialisation save() */
		init();	// cration des courbes
	}
}
LectureInspector.local = 1;

function UpdateDisplay()
{
	notifyclients();
	draw();
}
UpdateDisplay.local = 1;

function LectureNextLigne(f)
{
	// ignore les lignes qui contiennt mois de 4 caractres ou //
	var tmp = new String();

	do {
		tmp = f.readline();
	} while	(tmp.length < 4 || (tmp.search("//") != -1));

	return tmp;
}
LectureNextLigne.local = 1;

function EditedWithMouse()
{
	if (EditedWithMouse.state)
		outlet(BANG_OUTLET, "bang");
	
	EditedWithMouse.state = 0;
	notifyclients();
}
EditedWithMouse.local = 1;

function DisplayCursor(v)
{
	if (this.box.ignoreclick == 0 && CursorChange == 0)		// Si la bote est ignore click ou on ne change pas le cursor
		return;
	
	if (v != DisplayCursor.state) {
		setcursor(v);
		DisplayCursor.state = v
	}
}
DisplayCursor.local = 1;

function PattrInterpError(v)
{
	if (PattrInterpError.flag)
		return;
	
	if (v == 0 )
		perror("interpolation isn't possible (different number of function)");
	else
		perror("interpolation isn't possible (different number of points)");

	PattrInterpError.flag = 1;
}
PattrInterpError.local = 1;

function PattrTooMany(v)
{
	if (v >= 2048) {
		if (PattrTooMany.flag) {
			perror("too many functions/points... pattr won't work...");
			PattrTooMany.flag = 0;
		}
	} else
		PattrTooMany.flag = 1;
}
PattrTooMany.local = 1;

function MyDomain2String(v)
{
	var tmpMinute, tmpSecond, tmpMs;
	var tmpStr = new String();
	
	if (v < 1000) {
		tmpStr = "0\"" + v.toFixed(0);
	
	} else {
		tmpMinute = Math.floor(v / 60000);
		tmpStr+= tmpMinute + "\'";
		
		tmpSecond = Math.floor(v / 1000);

		if (tmpSecond % 60 < 10)
			tmpStr += "0" + tmpSecond % 60 + "\"";
		else
			tmpStr += tmpSecond % 60 + "\"";

		tmpMS = Math.floor(v - (tmpSecond*1000));

		if (tmpMS < 10)
			tmpStr += "00" + tmpMS;
		else if (tmpMS < 100)
			tmpStr += "0" + tmpMS;
		else
			tmpStr += tmpMS;
	}
	return tmpStr;
}
MyDomain2String.local = 1;

function line(courbe)
{
	var tmpF = courbe;

	var tmpArray = new Array();
	var i, idx;
	
	if ( ! tmpF.np)
		return;		// si pas de point... pas de line... pas de bras... pas de chocolat...
	
	tmpF.NextFrom = 0;

	for (i = 1, idx = 0; i < tmpF.np; i++) {
		tmpArray[idx++] = tmpF.pa[i].valy;
		tmpArray[idx++] = tmpF.pa[i].valx - tmpF.pa[i-1].valx;
		if (tmpF.pa[i].sustain) {
			tmpF.NextFrom = i;
			break;
		}
	}
	
	outlet(LINE_OUTLET, tmpF.name, tmpF.pa[0].valy);
	outlet(LINE_OUTLET, tmpF.name, tmpArray);
}
line.local = 1;

function Interpolation(courbe, v)
{
	var tmpF = courbe;
	var i, a, tmpRange, tmpDomain;
	
	if ( tmpF.np < 2 )
		return;
	
	if ( v < tmpF.pa[0].valx ) {	// v est plus petit que le premier point
		outlet(INTERP_OUTLET, tmpF.name, tmpF.pa[0].valy);
		return;
	}

	if ( v > tmpF.pa[tmpF.np - 1].valx ) {	// v est plus grand que le dernier point
		outlet(INTERP_OUTLET, tmpF.name, tmpF.pa[tmpF.np - 1].valy);
		return;
	}
	
	for (i = 0, a = 0; i < tmpF.np; i++) {
		if (v > tmpF.pa[i].valx)
			a = i;
		else
			break;
	}

	tmpRange = tmpF.pa[a+1].valy - tmpF.pa[a].valy;
	tmpDomain = tmpF.pa[a+1].valx - tmpF.pa[a].valx;
	
	outlet(INTERP_OUTLET, tmpF.name, ((v - tmpF.pa[a].valx) / tmpDomain) * tmpRange + tmpF.pa[a].valy);
}
Interpolation.local = 1;

function ValRecalculate()
{
	// recalcule la position en fonction des valeurs
	var c,i;
	
	for (c = 0; c < NbCourbes; c++) {
		for (i = 0; i < fctns[c].np; i++) {
			fctns[c]["pa"][i].x = val2x(fctns[c], fctns[c]["pa"][i].valx);
			fctns[c]["pa"][i].y = val2y(fctns[c], fctns[c]["pa"][i].valy);
		}
	}
}
ValRecalculate.local = 1;

function ApplyAutoSustain()
{
	var i, c;
	if ( AutoSustain ) {
		for (c = 0; c < NbCourbes; c++) {
			for (i = 0; i < fctns[c].np; i++) {
				if (fctns[c].np < 3)
					break;

				if (i == fctns[c].np - 2)
					fctns[c].pa[i].sustain = 1;
				else
					fctns[c].pa[i].sustain = 0;
			}
		}
	}
}
ApplyAutoSustain.local = 1;

function MyDomain(start, stop, courbe)
{
	var i;

	courbe.domain[0] = start;
	courbe.domain[1] = stop;

	MyDomain2Zoom(courbe);
	pixel2machin(courbe);
	
	for (i = 0; i < courbe.np; i++) {
		courbe.pa[i].x = val2x(courbe, courbe.pa[i].valx);
	}
}
MyDomain.local = 1;

function MyDomain2Zoom(courbe)
{
	courbe.ZoomX[0] = courbe.domain[0];
	courbe.ZoomX[1] = courbe.domain[1];
}
MyDomain2Zoom.local = 1;

function MyRange2Zoom(courbe)
{
	courbe.ZoomY[0] = courbe.range[0];
	courbe.ZoomY[1] = courbe.range[1];
}
MyRange2Zoom.local = 1;

function MyThings2Zoom(courbe)
{
	MyDomain2Zoom(courbe);
	MyRange2Zoom(courbe);
}
MyThings2Zoom.local = 1;

function MySetDomain(start, stop, courbe)
{
	var i;
	
	courbe.domain[0] = start;
	courbe.domain[1] = stop;

	MyDomain2Zoom(courbe);
	pixel2machin(courbe);
	
	for (i = 0; i < courbe.np; i++) {
		courbe.pa[i].valx = x2val(courbe, courbe.pa[i].x);
	}

}
MySetDomain.local = 1;

function MyAutoRange(courbe)
{
	var i, min, max;

	if (courbe.np < 2 )
		return;

	min = courbe.pa[0].valy;
	max = courbe.pa[0].valy;
	for(i = 1; i < courbe.np; i++) {
		if (courbe.pa[i].valy > max)
			max = courbe.pa[i].valy;
		if (courbe.pa[i].valy < min)
			min = courbe.pa[i].valy;
	}
	if (min == max)	// si le minimum et le maximum sont identiques... on arrte.
		return;

	range(min, max, courbe);
}
MyAutoRange.local = 1;

function MyAutoDomain(courbe)
{
	var i, min, max;

	if (courbe.np < 2 )
		return;

	min = courbe.pa[0].valx;
	max = courbe.pa[0].valx;
	for(i = 1; i < courbe.np; i++) {
		if (courbe.pa[i].valx > max)
			max = courbe.pa[i].valx;
		if (courbe.pa[i].valx < min)
			min = courbe.pa[i].valx;
	}
	if (min == max)	// si le minimum et le maximum sont identiques... on arrte.
		return;

	MyDomain(min, max, courbe);
}
MyAutoDomain.local = 1;

function MyPasteColors(courbe)
{
	var c, j, idx;
	var cp = g["copycolors"];
	idx = 0;
	
	if (cp.length == 0) {
		perror("before pasting colors, you have to copy something");
		return;
	}
	
	for (j=0; j < 3; j++) { courbe.brgb[j] = cp[idx++]; }
	for (j=0; j < 3; j++) { courbe.frgb[j] = cp[idx++]; }
	for (j=0; j < 3; j++) { courbe.rgb2[j] = cp[idx++]; }
	for (j=0; j < 3; j++) { courbe.rgb3[j] = cp[idx++]; }
	for (j=0; j < 3; j++) { courbe.rgb4[j] = cp[idx++]; }
	for (j=0; j < 3; j++) { courbe.rgb5[j] = cp[idx++]; }
}
MyPasteColors.local = 1;

function WriteDialog()
{
	// open a standart dialog box to save the file
	var savedialog, prepend;
	savedialog = this.patcher.newdefault(box.rect[0], box.rect[1] - 50, "savedialog", "TEXT");
	prepend = this.patcher.newdefault(box.rect[0], box.rect[1] - 25, "prepend", "write");
	savedialog.varname = "temp-savedialog";
	prepend.varname = "temp-prepend";
	savedialog.hidden = 1;
	prepend.hidden = 1;
	
	// pour connecter en hidden, il faut que la fonction soit nomme
	if (! this.box.varname)
		this.box.varname = "tmp-function";

	this.patcher.script("hidden", "connect", "temp-savedialog", 0, "temp-prepend", 0);
	this.patcher.script("hidden", "connect", "temp-prepend", 0, this.box.varname, 0);
	
	// dnommage de la fonction si elle s'appelle tmp-function
	if (this.box.varname == "tmp-function")
		this.box.varname = "";

	savedialog.message("bang");
	this.patcher.script("delete", "temp-savedialog");
	this.patcher.script("delete", "temp-prepend");
}
WriteDialog.local = 1;

function ReadDialog()
{
	var opendialog, prepend, prepend2;
	opendialog = this.patcher.newdefault(box.rect[0], box.rect[1] - 50, "opendialog", "TEXT");
	prepend = this.patcher.newdefault(box.rect[0], box.rect[1] - 25, "prepend", "read");
	prepend2 = this.patcher.newdefault(box.rect[0] + 50, box.rect[1] - 25, "prepend", "DeleteReadThings");
	opendialog.varname = "temp-opendialog";
	prepend.varname = "temp-prepend";
	prepend2.varname = "temp-prepend2";
	opendialog.hidden = 1;
	prepend.hidden = 1;
	prepend2.hidden = 1;

	// pour connecter en hidden, il faut que la fonction soit nomme
	if (! this.box.varname)
		this.box.varname = "tmp-function";

	this.patcher.script("hidden", "connect", "temp-opendialog", 0, "temp-prepend", 0);
	this.patcher.script("hidden", "connect", "temp-prepend", 0, this.box.varname, 0);
	this.patcher.script("hidden", "connect", "temp-opendialog", 1, "temp-prepend2", 0);
	this.patcher.script("hidden", "connect", "temp-prepend2", 0, this.box.varname, 0);
	
	OpendialogPrepend = 1;
	
	
	// dnommage de la fonction si elle s'appelle tmp-function
	if (this.box.varname == "tmp-function")
		this.box.varname = "";

	opendialog.message("bang");
}
ReadDialog.local = 1;

function DeleteReadThings()
{
	// pour l'opration de suppression quand on vient de read...
	// pour une raison qui m'chappe...
	// il faut dlayer...
	tsk = new Task(function() { if ( ! tsk.running) { this.patcher.script("delete", "temp-opendialog"); this.patcher.script("delete", "temp-prepend"); this.patcher.script("delete", "temp-prepend2"); } }, this);
	tsk.interval = 100;
	tsk.repeat(1);
	
	OpendialogPrepend = 0;	// rinitialisation du flag
}

function MyRemoveDuplicate(courbe)
{
	var i, flag;
	
	do
	{
		flag = 0;
		for (i = 1; i < (courbe.np - 1); i++) {
			// suppression du point du milieu s'il est encadr par 2 valeurs identiques
			if ( courbe.pa[i-1].valy == courbe.pa[i].valy && courbe.pa[i+1].valy == courbe.pa[i-1].valy) {
				DeletePoint(courbe, i);
				flag = 1;
			}
		}
	} while (flag)
}
MyRemoveDuplicate.local = 1;

function MySmooth(courbe)
{
	var i;
	
	for (i = 1; i < (courbe.np - 1); i++) {
		courbe.pa[i].valy = courbe.pa[i-1].valy*0.2 + courbe.pa[i].valy*0.6 + courbe.pa[i+1].valy*0.2;
		courbe.pa[i].y = val2y(courbe, courbe.pa[i].valy);
	}
}
MySmooth.local = 1;

function pixel2machin(courbe)
{
	courbe.PixelDomain = (courbe.ZoomX[1] - courbe.ZoomX[0]) / (BoxWidth-(Bordure*2));
	courbe.PixelRange = (courbe.ZoomY[1] - courbe.ZoomY[0]) / (BoxHeight-((Bordure*2) + LegendBordure));
/* 	MyPointsInside(courbe); */
}
pixel2machin.local = 1.;

function AllPixel2Machin()
{
	var c;
	for (c = 0; c < NbCourbes; c++) {
		pixel2machin(fctns[c]);
	}
}
AllPixel2Machin.local = 1;

function AddPoint(courbe, x, y)
{
	var tmp = courbe.np;	// si tmp n'est pas modifi c'est que c'est la plus grande valeur...
	var i;

	if (courbe.np == 0) {	// cas particulier quand il n'y a pas de point dans la courbe
		courbe.pa[0] = new Point(x,y, x2val(courbe, x), y2val(courbe, y));
		tmp = 0;
	}
	else {
		for (i = 0; i < courbe.np; i++) {
			if (courbe.pa[i].x >= x) {
				tmp = i;
				break;
			}
		}
		courbe.pa.splice(tmp, 0, new Point(x,y, x2val(courbe, x), y2val(courbe, y)) );	// insre un point
	}
	
	courbe.np++;	// on incrmente car il y a un point supplmentaire dans la courbe

	return tmp;		// valeur de retour utilise comme seleected point
}
AddPoint.local = 1;

function DeletePoint(courbe, lequel)
{
 	if (! courbe.pa[lequel].fix) {
		courbe.pa.splice(lequel, 1);
		courbe.np--;
	}
}
DeletePoint.local = 1;

function MovePoint(courbe, lequel, newx, newy)
{
	if (lequel >= courbe.np) {
		perror("no point", lequel,"in function", courbe.name);
		return;
	}
	courbe.pa[lequel].valx = newx;
	courbe.pa[lequel].valy = newy;					
	courbe.pa[lequel].x = val2x(courbe, newx);
	courbe.pa[lequel].y = val2y(courbe, newy);

	SortBulle(courbe);	// il faut maintenant remettre tous les points dans l'ordre
	ApplyAutoSustain();
}
MovePoint.local = 1;

function SwitchCurrent(sens)
{
		if( sens )		// avec shift on tourne dans l'autre sens
			current = (current - 1 + NbCourbes) % NbCourbes;
		else
			current = (current + 1) % NbCourbes;
		
		outlet(DUMPOUT, "display", current);
		draw();
}
SwitchCurrent.local = 1;

function CurrentOrArgument(c, a, combien)
{
	if ( a.length > combien)
		return c;
	else
		return fctns[current];
}
CurrentOrArgument.local = 1;

function SortBulle(courbe)
{
	// la mthode bulle pour mettre dans l'ordre... :-)
	var i, bulle;
	var max = courbe.np - 2;
	var tmp = new Point();
	
	do {
		bulle = 0;
		
		for (i=0; i <= max; i++) {
			if ( courbe.pa[i].x > courbe.pa[i+1].x ) {
				tmp = courbe.pa[i];
				courbe.pa[i] = courbe.pa[i+1];
				courbe.pa[i+1] = tmp;
				bulle = 1;
			}
		}
	} while (bulle);
	
	ApplyAutoSustain();
}
SortBulle.local = 1;

function RedrawOrNot(v)
{
	if (v != RedrawOrNot.LastValue) {
		RedrawOrNot.LastValue = v;
		v == -1 ? WaitALittleBit() : draw() ; // si pas de point slectionn on attend un peu avant de faire draw()
	}
}
RedrawOrNot.local = 1;

function ArgsParser(courbe, msg, a) 
{
	// en fonction du nombre d'arguments 1 (interpolationX-Y) 2 (AddPoint) 3 (MovePoint)
	var tmpReturn = -1;
	var NeedDraw = 0;	// pour savoir si on doit
	var NeedNotify = 0;

	if ( typeof(a[0]) == "number") {

		switch (a.length) {
			case 1: Interpolation(courbe, a[0]); break;
			case 2: AddPoint(courbe, val2x(courbe, a[0]), val2y(courbe, a[1])); NeedDraw++; NeedNotify++; break;
			case 3: MovePoint(courbe, a[0], a[1], a[2]); NeedDraw++; NeedNotify++; break;
			default: perror("too many arguments for message", msg); break;
		}
		return ( (NeedNotify > 0 ? 2 : 0) + (NeedDraw > 0 ? 1 : 0) );	// sort de la fonction
	}

	switch (a[0]) {
		case "bang":		line(courbe); tmpReturn++; break;
		case "clear":		redrawoff(); MyClear(courbe); NeedDraw++; NeedNotify++; tmpReturn++; break;
		case "clearsustain":	redrawoff(); MyClearSustain(courbe); NeedDraw++; NeedNotify++; tmpReturn++; break;
		case "dump":		a.length == 2 ? MyDump(courbe, a[1]) : MyDump(courbe); tmpReturn++; break;
		case "listdump":	a.length == 2 ? MyListDump(courbe, a[1]) : MyListDump(courbe); tmpReturn++; break;
		case "fix":			if (a.length == 3) { FixPoint(courbe, a[1], a[2]); } ; NeedNotify++; tmpReturn++; break;
		case "unfix":		MyUnfix(courbe); NeedNotify++; tmpReturn++; break;
		case "name":		redrawoff(); if (a.length == 2) { MyName(courbe, a[1]);} ; getname(); NeedDraw++; NeedNotify++; tmpReturn++; break;
		case "next":		MyNext(courbe); tmpReturn++; break;
		case "nth":			if (a.length == 2) { MyNth(a[1], courbe); } ; tmpReturn++; break;
		case "sustain":		redrawoff(); if (a.length == 3) { MySustain(a[1], a[2], courbe); } ; NeedDraw++; NeedNotify++; tmpReturn++; break;
		case "domain":
							redrawoff(); 
							if (a.length == 2)
								MyDomain(0, a[1], courbe);
							else if (a.length == 3)
								MyDomain(a[1], a[2], courbe);
							else
								perror("bad argument(s) for message domain"); 
							NeedDraw++; NeedNotify++; tmpReturn++; break;
		case "setdomain":	
							redrawoff(); 
							if (a.length == 2)
								MySetDomain(0, a[1], courbe);
							else if (a.length == 3)
								MySetDomain(a[1], a[2], courbe);
							else
								perror("bad argument(s) for message setdomain"); 
							NeedDraw++; NeedNotify++; tmpReturn++; break;
		case "range":		redrawoff(); if (a.length == 3) { range(a[1], a[2], courbe);};  NeedDraw++; NeedNotify++; tmpReturn++; break;
		case "setrange":	redrawoff(); if (a.length == 3) { setrange(a[1], a[2], courbe);} ; NeedDraw++; NeedNotify++; tmpReturn++; break;
		case "zoom_x":		redrawoff(); zoom_x(a[1], a[2], courbe); NeedDraw++; tmpReturn++; break;
		case "zoom_y":		redrawoff(); zoom_y(a[1], a[2], courbe); NeedDraw++; tmpReturn++; break;
		case "zoomout":		redrawoff(); MyZoomOut(courbe); NeedDraw++; tmpReturn++; break;
		case "autodomain":	redrawoff(); MyAutoDomain(courbe); NeedDraw++; NeedNotify++; tmpReturn++; break;
		case "autorange":	redrawoff(); MyAutoRange(courbe); NeedDraw++; NeedNotify++; tmpReturn++; break;
		case "removeduplicate": 	redrawoff(); MyRemoveDuplicate(courbe); NeedDraw++; NeedNotify++; tmpReturn++; break;
		case "smooth":		redrawoff(); MySmooth(courbe); NeedDraw++; NeedNotify++; tmpReturn++; break;
		case "gridstep":	redrawoff(); if (a.length == 2) { MyGridStep(courbe, a[1]); }; NeedDraw++; NeedNotify++; tmpReturn++; break;
		case "brgb":		redrawoff(); SetColor(courbe, "brgb", a[1], a[2], a[3]); NeedDraw++; tmpReturn++; break;
		case "frgb":		redrawoff(); SetColor(courbe, "frgb", a[1], a[2], a[3]); NeedDraw++; tmpReturn++; break;
		case "rgb2":		redrawoff(); SetColor(courbe, "rgb2", a[1], a[2], a[3]); NeedDraw++; tmpReturn++; break;
		case "rgb3":		redrawoff(); SetColor(courbe, "rgb3", a[1], a[2], a[3]); NeedDraw++; tmpReturn++; break;
		case "rgb4":		redrawoff(); SetColor(courbe, "rgb4", a[1], a[2], a[3]); NeedDraw++; tmpReturn++; break;
		case "rgb5":		redrawoff(); SetColor(courbe, "rgb5", a[1], a[2], a[3]); NeedDraw++; tmpReturn++; break;

		case "pastefunction":	redrawoff(); MyPasteFunction(courbe); NeedDraw++; NeedNotify++; tmpReturn++; break;
		case "pastecolors":	redrawoff(); MyPasteColors(courbe); NeedDraw++; tmpReturn++; break;

		// get things
		case "getdomain":	getdomain(courbe); break;
		case "getrange":	getrange(courbe); break;
		case "getfix":		getfix(courbe); break;
		case "getsustain":	getsustain(courbe); break;
		case "getgridstep":	getgridstep(courbe); break;
		case "getbrgb":		GetColor(courbe, "brgb"); break;
		case "getfrgb":		GetColor(courbe, "frgb"); break;
		case "getrgb2":		GetColor(courbe, "rgb2"); break;
		case "getrgb3":		GetColor(courbe, "rgb3"); break;
		case "getrgb4":		GetColor(courbe, "rgb4"); break;
		case "getrgb5":		GetColor(courbe, "rgb5"); break;
		case "getnbpoints":	getnbpoints(courbe); break;
		case "getzoom_x":	getzoom_x(courbe); break;
		case "getzoom_y":	getzoom_y(courbe); break;

		default: 			// message d'erreurs en fonction du type de message all ou pas
							if (msg == "all") {
								if (DisplayOneTime == 1) {
									perror("bad arguments for message all");
									DisplayOneTime = 0;
								}
							} else
								perror("doesn't understand", msg);
							tmpReturn++;
							break;
	}
	
	if (tmpReturn == -1)
		return -1;
	else
		return ( (NeedNotify > 0 ? 2 : 0) + (NeedDraw > 0 ? 1 : 0) );
}
ArgsParser.local = 1;

function WaitALittleBit()
{
	// attend 750ms avant de redessiner
	tsk = new Task(function() { if (! tsk.running) { draw(); } }, this);
	tsk.interval = 750;
	tsk.repeat(1);
}
WaitALittleBit.local = 1;

function FixPoint(courbe, WhichPoint, state)
{
	if ( (state == 0 || state == 1 ) && (WhichPoint >= 0 && WhichPoint < courbe.np) )
		courbe.pa[WhichPoint].fix = state;
	else
		perror("bad argument for message fix");
}
FixPoint.local = 1;

function MyNext(courbe)
{
	var i, idx;
	var tmpArray = new Array();
	
	var OldNextFrom = courbe.NextFrom;
	
	if (courbe.NextFrom == 0) {
		line(courbe);
		return;
	}
	
	for (i = (courbe.NextFrom + 1), idx = 0; i < courbe.np; i++) {
		tmpArray[idx++] = courbe.pa[i].valy;
		tmpArray[idx++] = courbe.pa[i].valx - courbe.pa[i-1].valx;
		if (courbe.pa[i].sustain) {
			courbe.NextFrom = i;
			break;
		}
	}
	
	if (OldNextFrom == courbe.NextFrom)	// il n'y a plus de sustain...
		courbe.NextFrom = 0;
	
	outlet(LINE_OUTLET, courbe.name, tmpArray);
}
MyNext.local = 1;

function MyNth(courbe, v)
{
	if (v >= 0 && v < courbe.np)
		outlet(INTERP_OUTLET, courbe.name, courbe.pa[v].valy)
}
MyNth.local = 1;

function MySustain(point, state, courbe)
{
	if ( state == 1 || state == 0 ) {
		if (point >= 0 && point < courbe.np)
			courbe.pa[point].sustain = state;
		else
			perror("no point", point, "(sustain operation aborted)" );
	} else
		perror("bad arguments for message sustain");
}
MySustain.local = 1;

function MyClear(courbe)
{
	courbe.pa.splice(0, courbe.np - 1);
	courbe.np = 0;
}
MyClear.local = 1;

function MyClearSustain(courbe)
{
	var i;
	for (i = 0; i < courbe.np; i++)
		courbe.pa[i].sustain = 0;
}
MyClearSustain.local = 1;

function MyColor2Float(r, g, b)
{
	var tmpArray = new Array(3);
	tmpArray[0] = r/255.;
	tmpArray[1] = g/255.;
	tmpArray[2] = b/255.;	
	return tmpArray;
}
MyColor2Float.local = 1;

function MyFloat2Color(qui)
{
	var tmpArray = new Array(3);
	tmpArray[0] = Math.round(qui[0]*255.);
	tmpArray[1] = Math.round(qui[1]*255.);
	tmpArray[2] = Math.round(qui[2]*255.);
	return tmpArray; 
}
MyFloat2Color.local = 1;

function MyGridStep(courbe, v)
{
	if (typeof(v) == "number" && v > 0) {
		if ( ((courbe.ZoomX[1] - courbe.ZoomX[0]) / v) < (BoxWidth-(Bordure*2) / 4) )
			courbe.GridStep = v;
	} else
		perror("bad argument for message gridstep");
}
MyGridStep.local = 1;


function MyDump(courbe, sendname)
{
	var tmpF = courbe;
	var i, str;
	
	if (! tmpF.np)
		return;

	if (arguments.length == 1) {
		for (i = 0; i < tmpF.np; i++) {
			outlet(DUMP_OUTLET, tmpF.name, tmpF.pa[i].valx, tmpF.pa[i].valy);
		}
		return;
	}
	//else -> on envoie vers un send
	for (i = 0; i < tmpF.np; i++) {
		str = tmpF.name + " " + tmpF.pa[i].valx.toFixed(6) + " " + tmpF.pa[i].valy.toFixed(6);
		g.dump = str.split(" ");	// String -> Array
		g.sendnamed(sendname,"dump");
	}
}
MyDump.local = 1;

function MyListDump(courbe, sendname)
{
	var tmpF = courbe;
	var tmpArray = new Array();
	var i, idx, str;
	
	if (! tmpF.np)
		return;
	
	for (i = 0, idx = 0; i < tmpF.np; i++) {
		tmpArray[idx++] = tmpF.pa[i].valx;
		tmpArray[idx++] = tmpF.pa[i].valy;
	}

	if (tmpArray.length > 4094) {
		perror("listdump aborted: too many points... use dump instead for now.");
		return;
	}
	
	if (arguments.length == 1) {
		outlet(DUMP_OUTLET, tmpF.name, tmpArray);
		return;
	}

	//else -> on envoie vers un send
	str = tmpF.name;
	for (i = 0; i < tmpArray.length; i++) {
		str += " " + tmpArray[i].toFixed(6);
	}
	g.listdump = str.split(" ");	// String -> Array
	g.sendnamed(sendname,"listdump");
}
MyListDump.local = 1;

function MyName(courbe, name)
{
	courbe.name = name;
}
MyName.local = 1;

function perror()
{
	ejies.scriptname = "ej.function.js";
{'fraction_non_alphanumeric': 0.18847617140568682, 'fraction_numerical': 0.02484648244560139, 'mean_word_length': 3.4172624751234615, 'pattern_counts': {'":': 90, '<': 123, '<?xml version=': 0, '>': 67, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2818212', 'n_tokens_mistral': 27285, 'n_tokens_neox': 24500, 'n_words': 5711}