/* Copyright (c) 1997-1999 Miller Puckette. * For information on usage and redistribution, and for a DISCLAIMER OF ALL * WARRANTIES, see the file, "LICENSE.txt," in this distribution. */ #include #include #include /* for read/write to files */ #include "m_pd.h" #include "g_canvas.h" #include /* see also the "plot" object in g_scalar.c which deals with graphing arrays which are fields in scalars. Someday we should unify the two, but how? */ /* aux routine to bash leading '#' to '$' for dialogs in u_main.tk which can't send symbols starting with '$' (because the Pd message interpreter would change them!) */ static t_symbol *sharptodollar(t_symbol *s) { if (*s->s_name == '#') { char buf[MAXPDSTRING]; strncpy(buf, s->s_name, MAXPDSTRING); buf[MAXPDSTRING-1] = 0; buf[0] = '$'; return (gensym(buf)); } else return (s); } /* --------- "pure" arrays with scalars for elements. --------------- */ /* Pure arrays have no a priori graphical capabilities. They are instantiated by "garrays" below or can be elements of other scalars (g_scalar.c); their graphical behavior is defined accordingly. */ t_array *array_new(t_symbol *templatesym, t_gpointer *parent) { t_array *x = (t_array *)getbytes(sizeof (*x)); t_template *template; t_gpointer *gp; template = template_findbyname(templatesym); x->a_templatesym = templatesym; x->a_n = 1; x->a_elemsize = sizeof(t_word) * template->t_n; x->a_vec = (char *)getbytes(x->a_elemsize); /* note here we blithely copy a gpointer instead of "setting" a new one; this gpointer isn't accounted for and needn't be since we'll be deleted before the thing pointed to gets deleted anyway; see array_free. */ x->a_gp = *parent; x->a_stub = gstub_new(0, x); word_init((t_word *)(x->a_vec), template, parent); return (x); } void array_resize(t_array *x, t_template *template, int n) { int elemsize, oldn; t_gpointer *gp; if (n < 1) n = 1; oldn = x->a_n; elemsize = sizeof(t_word) * template->t_n; x->a_vec = (char *)resizebytes(x->a_vec, oldn * elemsize, n * elemsize); x->a_n = n; if (n > oldn) { char *cp = x->a_vec + elemsize * oldn; int i = n - oldn; for (; i--; cp += elemsize) { t_word *wp = (t_word *)cp; word_init(wp, template, &x->a_gp); } } } void array_free(t_array *x) { /* we don't unset our gpointer here since it was never "set." */ /* gpointer_unset(&x->a_gp); */ gstub_cutoff(x->a_stub); freebytes(x->a_vec, x->a_elemsize * x->a_n); freebytes(x, sizeof *x); } /* --------------------- graphical arrays (garrays) ------------------- */ t_class *garray_class; static int gcount = 0; struct _garray { t_gobj x_gobj; t_glist *x_glist; t_array x_array; /* actual array; note only 4 fields used as below */ t_symbol *x_name; t_symbol *x_realname; /* name with "$" expanded */ t_float x_firstx; /* X value of first item */ t_float x_xinc; /* X increment */ char x_usedindsp; /* true if some DSP routine is using this */ char x_saveit; /* true if we should save this with parent */ }; /* macros to get into the "array" structure */ #define x_n x_array.a_n #define x_elemsize x_array.a_elemsize #define x_vec x_array.a_vec #define x_templatesym x_array.a_templatesym t_garray *graph_array(t_glist *gl, t_symbol *s, t_symbol *templatesym, t_floatarg f, t_floatarg saveit) { int n = f, i; int zz, nwords; t_garray *x; t_pd *x2; t_template *template; char *str; if (s == &s_) { char buf[40]; sprintf(buf, "array%d", ++gcount); s = gensym(buf); templatesym = &s_float; n = 100; } else if (!strncmp((str = s->s_name), "array", 5) && (zz = atoi(str + 5)) > gcount) gcount = zz; template = template_findbyname(templatesym); if (!template) { error("array: couldn't find template %s", templatesym->s_name); return (0); } nwords = template->t_n; for (i = 0; i < nwords; i++) { /* we can't have array or list elements yet because what scalar can act as their "parent"??? */ if (template->t_vec[i].ds_type == DT_ARRAY || template->t_vec[i].ds_type == DT_LIST) { error("array: template %s can't have sublists or arrays", templatesym->s_name); return (0); } } x = (t_garray *)pd_new(garray_class); if (n <= 0) n = 100; x->x_n = n; x->x_elemsize = nwords * sizeof(t_word); x->x_vec = getbytes(x->x_n * x->x_elemsize); memset(x->x_vec, 0, x->x_n * x->x_elemsize); /* LATER should check that malloc */ x->x_name = s; x->x_realname = canvas_realizedollar(gl, s); pd_bind(&x->x_gobj.g_pd, x->x_realname); x->x_templatesym = templatesym; x->x_firstx = 0; x->x_xinc = 1; /* LATER make methods to set this... */ glist_add(gl, &x->x_gobj); x->x_glist = gl; x->x_usedindsp = 0; x->x_saveit = (saveit != 0); if (x2 = pd_findbyclass(gensym("#A"), garray_class)) pd_unbind(x2, gensym("#A")); pd_bind(&x->x_gobj.g_pd, gensym("#A")); return (x); } /* called from array menu item to create a new one */ void canvas_menuarray(t_glist *canvas) { t_glist *x = (t_glist *)canvas; char cmdbuf[200]; sprintf(cmdbuf, "pdtk_array_dialog %%s array%d 100 1 1\n", ++gcount); gfxstub_new(&x->gl_pd, x, cmdbuf); } /* called from graph_dialog to set properties */ void garray_properties(t_garray *x) { char cmdbuf[200]; gfxstub_deleteforkey(x); /* create dialog window. LATER fix this to escape '$' properly; right now we just detect a leading '$' and escape it. There should be a systematic way of doing this. */ if (x->x_name->s_name[0] == '$') sprintf(cmdbuf, "pdtk_array_dialog %%s \\%s %d %d 0\n", x->x_name->s_name, x->x_n, x->x_saveit); else sprintf(cmdbuf, "pdtk_array_dialog %%s %s %d %d 0\n", x->x_name->s_name, x->x_n, x->x_saveit); gfxstub_new(&x->x_gobj.g_pd, x, cmdbuf); } /* this is called back from the dialog window to create a garray. The otherflag requests that we find an existing graph to put it in. */ void glist_arraydialog(t_glist *parent, t_symbol *name, t_floatarg size, t_floatarg saveit, t_floatarg otherflag) { t_glist *gl; t_garray *a; if (size < 1) size = 1; if (otherflag == 0 || (!(gl = glist_findgraph(parent)))) gl = glist_addglist(parent, &s_, 0, 1, (size > 1 ? size-1 : size), -1, 0, 0, 0, 0); a = graph_array(gl, sharptodollar(name), &s_float, size, saveit); } /* this is called from the properties dialog window for an existing array */ void garray_arraydialog(t_garray *x, t_symbol *name, t_floatarg fsize, t_floatarg saveit, t_floatarg deleteit) { if (deleteit != 0) { glist_delete(x->x_glist, &x->x_gobj); } else { int size; t_symbol *argname = sharptodollar(name); if (argname != x->x_name) { x->x_name = argname; pd_unbind(&x->x_gobj.g_pd, x->x_realname); x->x_realname = canvas_realizedollar(x->x_glist, argname); pd_bind(&x->x_gobj.g_pd, x->x_realname); } size = fsize; if (size < 1) size = 1; if (size != x->x_n) garray_resize(x, size); garray_setsaveit(x, (saveit != 0)); garray_redraw(x); } } static void garray_free(t_garray *x) { t_pd *x2; gfxstub_deleteforkey(x); pd_unbind(&x->x_gobj.g_pd, x->x_realname); /* LATER find a way to get #A unbound earlier (at end of load?) */ while (x2 = pd_findbyclass(gensym("#A"), garray_class)) pd_unbind(x2, gensym("#A")); freebytes(x->x_vec, x->x_n * x->x_elemsize); } /* ------------- code used by both array and plot widget functions ---- */ /* routine to get screen coordinates of a point in an array */ void array_getcoordinate(t_glist *glist, char *elem, int xonset, int yonset, int wonset, int indx, float basex, float basey, float xinc, float *xp, float *yp, float *wp) { float xval, yval, ypix, wpix; if (xonset >= 0) xval = *(float *)(elem + xonset); else xval = indx * xinc; if (yonset >= 0) yval = *(float *)(elem + yonset); else yval = 0; ypix = glist_ytopixels(glist, basey + yval); if (wonset >= 0) { /* found "w" field which controls linewidth. */ float wval = *(float *)(elem + wonset); wpix = glist_ytopixels(glist, basey + yval + wval) - ypix; if (wpix < 0) wpix = -wpix; } else wpix = 1; *xp = glist_xtopixels(glist, basex + xval); *yp = ypix; *wp = wpix; } static float array_motion_xcumulative; static float array_motion_ycumulative; static t_symbol *array_motion_xfield; static t_symbol *array_motion_yfield; static t_glist *array_motion_glist; static t_gobj *array_motion_gobj; static t_word *array_motion_wp; static t_template *array_motion_template; static int array_motion_npoints; static int array_motion_elemsize; static int array_motion_altkey; static float array_motion_initx; static float array_motion_xperpix; static float array_motion_yperpix; static int array_motion_lastx; static int array_motion_fatten; /* LATER protect against the template changing or the scalar disappearing probably by attaching a gpointer here ... */ static void array_motion(void *z, t_floatarg dx, t_floatarg dy) { array_motion_xcumulative += dx * array_motion_xperpix; array_motion_ycumulative += dy * array_motion_yperpix; if (*array_motion_xfield->s_name) { /* it's an x, y plot; can drag many points at once */ int i; char *charword = (char *)array_motion_wp; for (i = 0; i < array_motion_npoints; i++) { t_word *thisword = (t_word *)(charword + i * array_motion_elemsize); if (*array_motion_xfield->s_name) { float xwas = template_getfloat(array_motion_template, array_motion_xfield, thisword, 1); template_setfloat(array_motion_template, array_motion_xfield, thisword, xwas + dx, 1); } if (*array_motion_yfield->s_name) { float ywas = template_getfloat(array_motion_template, array_motion_yfield, thisword, 1); if (array_motion_fatten) { if (i == 0) { float newy = ywas + dy; if (newy < 0) newy = 0; template_setfloat(array_motion_template, array_motion_yfield, thisword, newy, 1); } } else { template_setfloat(array_motion_template, array_motion_yfield, thisword, ywas + dy, 1); } } } } else { /* a y-only plot. */ int thisx = array_motion_initx + array_motion_xcumulative, x2; int increment, i, nchange; char *charword = (char *)array_motion_wp; float newy = array_motion_ycumulative, oldy = template_getfloat( array_motion_template, array_motion_yfield, (t_word *)(charword + array_motion_elemsize * array_motion_lastx), 1); float ydiff = newy - oldy; if (thisx < 0) thisx = 0; else if (thisx >= array_motion_npoints) thisx = array_motion_npoints - 1; increment = (thisx > array_motion_lastx ? -1 : 1); nchange = 1 + increment * (array_motion_lastx - thisx); for (i = 0, x2 = thisx; i < nchange; i++, x2 += increment) { template_setfloat(array_motion_template, array_motion_yfield, (t_word *)(charword + array_motion_elemsize * x2), newy, 1); if (nchange > 1) newy -= ydiff * (1./(nchange - 1)); } array_motion_lastx = thisx; } glist_redrawitem(array_motion_glist, array_motion_gobj); } int array_doclick(t_array *array, t_glist *glist, t_gobj *gobj, t_symbol *elemtemplatesym, float linewidth, float xloc, float xinc, float yloc, int xpix, int ypix, int shift, int alt, int dbl, int doit) { t_canvas *elemtemplatecanvas; t_template *elemtemplate; int elemsize, yonset, wonset, xonset, i; if (!array_getfields(elemtemplatesym, &elemtemplatecanvas, &elemtemplate, &elemsize, &xonset, &yonset, &wonset)) { float best = 100; int incr; /* if it has more than 2000 points, just check 300 of them. */ if (array->a_n < 2000) incr = 1; else incr = array->a_n / 300; for (i = 0; i < array->a_n; i += incr) { float pxpix, pypix, pwpix, dx, dy; array_getcoordinate(glist, (char *)(array->a_vec) + i * elemsize, xonset, yonset, wonset, i, xloc, yloc, xinc, &pxpix, &pypix, &pwpix); if (pwpix < 4) pwpix = 4; dx = pxpix - xpix; if (dx < 0) dx = -dx; if (dx > 8) continue; dy = pypix - ypix; if (dy < 0) dy = -dy; if (dx + dy < best) best = dx + dy; if (wonset >= 0) { dy = (pypix + pwpix) - ypix; if (dy < 0) dy = -dy; if (dx + dy < best) best = dx + dy; dy = (pypix - pwpix) - ypix; if (dy < 0) dy = -dy; if (dx + dy < best) best = dx + dy; } } if (best > 8) return (0); best += 0.001; /* add truncation error margin */ for (i = 0; i < array->a_n; i += incr) { float pxpix, pypix, pwpix, dx, dy, dy2, dy3; array_getcoordinate(glist, (char *)(array->a_vec) + i * elemsize, xonset, yonset, wonset, i, xloc, yloc, xinc, &pxpix, &pypix, &pwpix); if (pwpix < 4) pwpix = 4; dx = pxpix - xpix; if (dx < 0) dx = -dx; dy = pypix - ypix; if (dy < 0) dy = -dy; if (wonset >= 0) { dy2 = (pypix + pwpix) - ypix; if (dy2 < 0) dy2 = -dy2; dy3 = (pypix - pwpix) - ypix; if (dy3 < 0) dy3 = -dy3; if (yonset <= 0) dy = 100; } else dy2 = dy3 = 100; if (dx + dy <= best || dx + dy2 <= best || dx + dy3 <= best) { if (dy < dy2 && dy < dy3) array_motion_fatten = 0; else if (dy2 < dy3) array_motion_fatten = -1; else array_motion_fatten = 1; if (doit) { char *elem = (char *)array->a_vec; array_motion_elemsize = elemsize; array_motion_glist = glist; array_motion_gobj = gobj; array_motion_template = elemtemplate; array_motion_xperpix = glist_dpixtodx(glist, 1); array_motion_yperpix = glist_dpixtody(glist, 1); if (alt && xpix < pxpix) /* delete a point */ { if (array->a_n <= 1) return (0); memmove((char *)(array->a_vec) + elemsize * i, (char *)(array->a_vec) + elemsize * (i+1), (array->a_n - 1 - i) * elemsize); array_resize(array, elemtemplate, array->a_n - 1); glist_redrawitem(array_motion_glist, array_motion_gobj); return (0); } else if (alt) { /* add a point (after the clicked-on one) */ array_resize(array, elemtemplate, array->a_n + 1); elem = (char *)array->a_vec; memmove(elem + elemsize * (i+1), elem + elemsize * i, (array->a_n - i) * elemsize); i++; (array->a_n)++; } if (xonset >= 0) { array_motion_xfield = gensym("x"); array_motion_xcumulative = *(float *)((elem + elemsize * i) + xonset); array_motion_wp = (t_word *)(elem + i * elemsize); array_motion_npoints = array->a_n - i; } else { array_motion_xfield = &s_; array_motion_xcumulative = 0; array_motion_wp = (t_word *)elem; array_motion_npoints = array->a_n; array_motion_initx = i; array_motion_lastx = i; array_motion_xperpix *= (xinc == 0 ? 1 : 1./xinc); } if (array_motion_fatten) { array_motion_yfield = gensym("w"); array_motion_ycumulative = *(float *)((elem + elemsize * i) + wonset); array_motion_xperpix *= array_motion_fatten; } else if (yonset >= 0) { array_motion_yfield = gensym("y"); array_motion_ycumulative = *(float *)((elem + elemsize * i) + yonset); } else { array_motion_yfield = &s_; array_motion_ycumulative = 0; } glist_grab(glist, 0, array_motion, 0, xpix, ypix); } if (alt) { if (xpix < pxpix) return (CURSOR_EDITMODE_DISCONNECT); else return (CURSOR_RUNMODE_ADDPOINT); } else return (array_motion_fatten ? CURSOR_RUNMODE_THICKEN : CURSOR_RUNMODE_CLICKME); } } } return (0); } /* -------------------- widget behavior for garray ------------ */ static void garray_getrect(t_gobj *z, t_glist *glist, int *xp1, int *yp1, int *xp2, int *yp2) { t_garray *x = (t_garray *)z; float x1 = 0x7fffffff, y1 = 0x7fffffff, x2 = -0x7fffffff, y2 = -0x7fffffff; t_canvas *elemtemplatecanvas; t_template *elemtemplate; int elemsize, yonset, wonset, xonset, i; if (!array_getfields(x->x_templatesym, &elemtemplatecanvas, &elemtemplate, &elemsize, &xonset, &yonset, &wonset)) { int incr; /* if it has more than 2000 points, just check 300 of them. */ if (x->x_array.a_n < 2000) incr = 1; else incr = x->x_array.a_n / 300; for (i = 0; i < x->x_array.a_n; i += incr) { float pxpix, pypix, pwpix, dx, dy; array_getcoordinate(glist, (char *)(x->x_array.a_vec) + i * elemsize, xonset, yonset, wonset, i, 0, 0, 1, &pxpix, &pypix, &pwpix); if (pwpix < 2) pwpix = 2; if (pxpix < x1) x1 = pxpix; if (pxpix > x2) x2 = pxpix; if (pypix - pwpix < y1) y1 = pypix - pwpix; if (pypix + pwpix > y2) y2 = pypix + pwpix; } } *xp1 = x1; *yp1 = y1; *xp2 = x2; *yp2 = y2; } static void garray_displace(t_gobj *z, t_glist *glist, int dx, int dy) { /* refuse */ } static void garray_select(t_gobj *z, t_glist *glist, int state) { t_garray *x = (t_garray *)z; /* fill in later */ } static void garray_activate(t_gobj *z, t_glist *glist, int state) { } static void garray_delete(t_gobj *z, t_glist *glist) { /* nothing to do */ } static void garray_vis(t_gobj *z, t_glist *glist, int vis) { t_garray *x = (t_garray *)z; if (vis) { int i, xonset, yonset, type; t_symbol *arraytype; t_template *template = template_findbyname(x->x_templatesym); if (!template) return; if (!template_find_field(template, gensym("y"), &yonset, &type, &arraytype) || type != DT_FLOAT) { error("%s: needs floating-point 'y' field", x->x_templatesym->s_name); sys_vgui(".x%x.c create text 50 50 -text foo\ -tags .x%x.a%x\n", glist_getcanvas(glist), glist_getcanvas(glist), x); } else if (!template_find_field(template, gensym("x"), &xonset, &type, &arraytype) || type != DT_FLOAT) { float firsty, xcum = x->x_firstx; int lastpixel = -1, ndrawn = 0; float yval = 0, xpix; int ixpix = 0; sys_vgui(".x%x.c create line \\\n", glist_getcanvas(glist)); for (i = 0; i < x->x_n; i++) { yval = *(float *)(x->x_vec + template->t_n * i * sizeof (t_word) + yonset); xpix = glist_xtopixels(glist, xcum); ixpix = xpix + 0.5; if (ixpix != lastpixel) { sys_vgui("%d %f \\\n", ixpix, glist_ytopixels(glist, yval)); ndrawn++; } lastpixel = ixpix; if (ndrawn >= 1000) break; xcum += x->x_xinc; } /* TK will complain if there aren't at least 2 points... */ if (ndrawn == 0) sys_vgui("0 0 0 0 \\\n"); else if (ndrawn == 1) sys_vgui("%d %f \\\n", ixpix, glist_ytopixels(glist, yval)); sys_vgui("-tags .x%x.a%x\n", glist_getcanvas(glist), x); firsty = *(float *)(x->x_vec + yonset); sys_vgui(".x%x.c create text %f %f -text {%s} -anchor e\ -font -*-courier-bold--normal--%d-* -tags .x%x.a%x\n", glist_getcanvas(glist), glist_xtopixels(glist, x->x_firstx) - 5., glist_ytopixels(glist, firsty), x->x_name->s_name, glist_getfont(glist), glist_getcanvas(glist), x); } else { post("x, y arrays not yet supported"); } } else { sys_vgui(".x%x.c delete .x%x.a%x\n", glist_getcanvas(glist), glist_getcanvas(glist), x); } } static int garray_click(t_gobj *z, struct _glist *glist, int xpix, int ypix, int shift, int alt, int dbl, int doit) { t_garray *x = (t_garray *)z; return (array_doclick(&x->x_array, glist, z, x->x_templatesym, 1, 0, 1, 0, xpix, ypix, shift, alt, dbl, doit)); } #define ARRAYWRITECHUNKSIZE 1000 static void garray_save(t_gobj *z, t_binbuf *b) { t_garray *x = (t_garray *)z; binbuf_addv(b, "sssisi;", gensym("#X"), gensym("array"), x->x_name, x->x_n, x->x_templatesym, x->x_saveit); if (x->x_saveit) { int n = x->x_n, n2 = 0; if (x->x_templatesym != &s_float) { pd_error(x, "sorry, you can only save 'float' arrays now"); return; } if (n > 200000) post("warning: I'm saving an array with %d points!\n", n); while (n2 < n) { int chunk = n - n2, i; if (chunk > ARRAYWRITECHUNKSIZE) chunk = ARRAYWRITECHUNKSIZE; binbuf_addv(b, "si", gensym("#A"), n2); for (i = 0; i < chunk; i++) binbuf_addv(b, "f", ((float *)(x->x_vec))[n2+i]); binbuf_addv(b, ";"); n2 += chunk; } } } t_widgetbehavior garray_widgetbehavior = { garray_getrect, garray_displace, garray_select, garray_activate, garray_delete, garray_vis, garray_click, garray_save, 0 }; /* ----------------------- public functions -------------------- */ void garray_usedindsp(t_garray *x) { x->x_usedindsp = 1; } void garray_redraw(t_garray *x) { if (glist_isvisible(x->x_glist)) { garray_vis(&x->x_gobj, x->x_glist, 0); garray_vis(&x->x_gobj, x->x_glist, 1); } } /* This functiopn gets the template of an array; if we can't figure out what template an array's elements belong to we're in grave trouble when it's time to free or resize it. */ t_template *garray_template(t_garray *x) { t_template *template = template_findbyname(x->x_templatesym); if (!template) bug("garray_template"); return (template); } int garray_npoints(t_garray *x) /* get the length */ { return (x->x_n); } char *garray_vec(t_garray *x) /* get the contents */ { return ((char *)(x->x_vec)); } /* routine that checks if we're just an array of floats and if so returns the goods */ int garray_getfloatarray(t_garray *x, int *size, t_float **vec) { t_template *template = garray_template(x); int yonset, type; t_symbol *arraytype; if (!template_find_field(template, gensym("y"), &yonset, &type, &arraytype) || type != DT_FLOAT) error("%s: needs floating-point 'y' field", x->x_templatesym->s_name); else if (template->t_n != 1) error("%s: has more than one field", x->x_templatesym->s_name); else { *size = garray_npoints(x); *vec = (float *)garray_vec(x); return (1); } return (0); } /* get any floating-point field of any element of an array */ float garray_get(t_garray *x, t_symbol *s, t_int indx) { t_template *template = garray_template(x); int yonset, type; t_symbol *arraytype; if (!template_find_field(template, gensym("y"), &yonset, &type, &arraytype) || type != DT_FLOAT) { error("%s: needs floating-point '%s' field", x->x_templatesym->s_name, s->s_name); return (0); } if (indx < 0) indx = 0; else if (indx >= x->x_n) indx = x->x_n - 1; return (*(float *)((x->x_vec + sizeof(t_word) * indx) + yonset)); } /* set the "saveit" flag */ void garray_setsaveit(t_garray *x, int saveit) { if (x->x_saveit && !saveit) post("warning: array %s: clearing save-in-patch flag", x->x_name->s_name); x->x_saveit = saveit; } /*------------------- Pd messages ------------------------ */ static void garray_const(t_garray *x, t_floatarg g) { t_template *template = garray_template(x); int yonset, type, i; t_symbol *arraytype; if (!template_find_field(template, gensym("y"), &yonset, &type, &arraytype) || type != DT_FLOAT) error("%s: needs floating-point 'y' field", x->x_templatesym->s_name); else for (i = 0; i < x->x_n; i++) *(float *)(((char *)x->x_vec + sizeof(t_word) * i) + yonset) = g; garray_redraw(x); } /* sum of Fourier components; called from routines below */ static void garray_dofo(t_garray *x, int npoints, float dcval, int nsin, t_float *vsin, int sineflag) { t_template *template = garray_template(x); int yonset, type, i, j; t_symbol *arraytype; double phase, phaseincr, fj; if (npoints == 0) npoints = 512; /* dunno what a good default would be... */ if (npoints != (1 << ilog2(npoints))) post("%s: rounnding to %d points", x->x_templatesym->s_name, (npoints = (1<x_templatesym->s_name); return; } for (i = 0, phase = -phaseincr; i < x->x_n; i++, phase += phaseincr ) { double sum = dcval; if (sineflag) for (j = 0, fj = phase; j < nsin; j++, fj += phase) sum += vsin[j] * sin(fj); else for (j = 0, fj = 0; j < nsin; j++, fj += phase) sum += vsin[j] * cos(fj); *(float *)((x->x_vec + sizeof(t_word) * i) + yonset) = sum; } garray_redraw(x); } static void garray_sinesum(t_garray *x, t_symbol *s, int argc, t_atom *argv) { t_template *template = garray_template(x); t_float *svec = (t_float *)t_getbytes(sizeof(t_float) * argc); int npoints, i; if (argc < 2) { error("sinesum: %s: need number of points and partial strengths", x->x_templatesym->s_name); return; } npoints = atom_getfloatarg(0, argc, argv); argv++, argc--; svec = (t_float *)t_getbytes(sizeof(t_float) * argc); if (!svec) return; for (i = 0; i < argc; i++) svec[i] = atom_getfloatarg(i, argc, argv); garray_dofo(x, npoints, 0, argc, svec, 1); t_freebytes(svec, sizeof(t_float) * argc); } static void garray_cosinesum(t_garray *x, t_symbol *s, int argc, t_atom *argv) { t_template *template = garray_template(x); t_float *svec = (t_float *)t_getbytes(sizeof(t_float) * argc); int npoints, i; if (argc < 2) { error("sinesum: %s: need number of points and partial strengths", x->x_templatesym->s_name); return; } npoints = atom_getfloatarg(0, argc, argv); argv++, argc--; svec = (t_float *)t_getbytes(sizeof(t_float) * argc); if (!svec) return; for (i = 0; i < argc; i++) svec[i] = atom_getfloatarg(i, argc, argv); garray_dofo(x, npoints, 0, argc, svec, 0); t_freebytes(svec, sizeof(t_float) * argc); } static void garray_normalize(t_garray *x, t_float f) { t_template *template = garray_template(x); int yonset, type, npoints, i; double maxv, renormer; t_symbol *arraytype; if (f <= 0) f = 1; if (!template_find_field(template, gensym("y"), &yonset, &type, &arraytype) || type != DT_FLOAT) { error("%s: needs floating-point 'y' field", x->x_templatesym->s_name); return; } for (i = 0, maxv = 0; i < x->x_n; i++) { double v = *(float *)((x->x_vec + sizeof(t_word) * i) + yonset); if (v > maxv) maxv = v; if (-v > maxv) maxv = -v; } if (maxv >= 0) { renormer = f / maxv; for (i = 0; i < x->x_n; i++) { *(float *)((x->x_vec + sizeof(t_word) * i) + yonset) *= renormer; } } garray_redraw(x); } /* list -- the first value is an index; subsequent values are put in the "y" slot of the array. This generalizes Max's "table", sort of. */ static void garray_list(t_garray *x, t_symbol *s, int argc, t_atom *argv) { t_template *template = garray_template(x); int yonset, type, i; t_symbol *arraytype; if (!template_find_field(template, gensym("y"), &yonset, &type, &arraytype) || type != DT_FLOAT) error("%s: needs floating-point 'y' field", x->x_templatesym->s_name); else if (argc < 2) return; else { int firstindex = atom_getfloat(argv); argc--; argv++; /* drop negative x values */ if (firstindex < 0) { argc += firstindex; argv -= firstindex; firstindex = 0; if (argc <= 0) return; } if (argc + firstindex > x->x_n) { argc = x->x_n - firstindex; if (argc <= 0) return; } for (i = 0; i < argc; i++) *(float *)((x->x_vec + sizeof(t_word) * (i + firstindex)) + yonset) = atom_getfloat(argv + i); } garray_redraw(x); } /* forward a "bounds" message to the owning graph */ static void garray_bounds(t_garray *x, t_floatarg x1, t_floatarg y1, t_floatarg x2, t_floatarg y2) { vmess(&x->x_glist->gl_pd, gensym("bounds"), "ffff", x1, y1, x2, y2); } /* same for "xticks", etc */ static void garray_xticks(t_garray *x, t_floatarg point, t_floatarg inc, t_floatarg f) { vmess(&x->x_glist->gl_pd, gensym("xticks"), "fff", point, inc, f); } static void garray_yticks(t_garray *x, t_floatarg point, t_floatarg inc, t_floatarg f) { vmess(&x->x_glist->gl_pd, gensym("yticks"), "fff", point, inc, f); } static void garray_xlabel(t_garray *x, t_symbol *s, int argc, t_atom *argv) { typedmess(&x->x_glist->gl_pd, s, argc, argv); } static void garray_ylabel(t_garray *x, t_symbol *s, int argc, t_atom *argv) { typedmess(&x->x_glist->gl_pd, s, argc, argv); } /* change the name of a garray. */ static void garray_rename(t_garray *x, t_symbol *s) { pd_unbind(&x->x_gobj.g_pd, x->x_realname); pd_bind(&x->x_gobj.g_pd, x->x_realname = x->x_name = s); garray_redraw(x); } static void garray_read(t_garray *x, t_symbol *filename) { int nelem = x->x_n, filedesc; FILE *fd; char buf[MAXPDSTRING], *bufptr; t_template *template = garray_template(x); int yonset, type, i; t_symbol *arraytype; if (!template_find_field(template, gensym("y"), &yonset, &type, &arraytype) || type != DT_FLOAT) { error("%s: needs floating-point 'y' field", x->x_templatesym->s_name); return; } if ((filedesc = open_via_path( canvas_getdir(glist_getcanvas(x->x_glist))->s_name, filename->s_name, "", buf, &bufptr, MAXPDSTRING, 0)) < 0 || !(fd = fdopen(filedesc, "r"))) { error("%s: can't open", filename->s_name); return; } for (i = 0; i < nelem; i++) { if (!fscanf(fd, "%f", (float *)((x->x_vec + sizeof(t_word) * i) + yonset))) { post("%s: read %d elements into table of size %d", filename->s_name, i, nelem); break; } } while (i < nelem) *(float *)((x->x_vec + sizeof(t_word) * i) + yonset) = 0, i++; fclose(fd); garray_redraw(x); } /* this should be renamed and moved... */ int garray_ambigendian(void) { unsigned short s = 1; unsigned char c = *(char *)(&s); return (c==0); } #define BINREADMODE "rb" #define BINWRITEMODE "wb" static void garray_read16(t_garray *x, t_symbol *filename, t_symbol *endian, t_floatarg fskip) { int skip = fskip, filedesc; int i, nelem; float *vec; FILE *fd; char buf[MAXPDSTRING], *bufptr; short s; int cpubig = garray_ambigendian(), swap = 0; char c = endian->s_name[0]; if (c == 'b') { if (!cpubig) swap = 1; } else if (c == 'l') { if (cpubig) swap = 1; } else if (c) { error("array_read16: endianness is 'l' (low byte first ala INTEL)"); post("... or 'b' (high byte first ala MIPS,DEC,PPC)"); } if (!garray_getfloatarray(x, &nelem, &vec)) { error("%s: not a float array", x->x_templatesym->s_name); return; } if ((filedesc = open_via_path( canvas_getdir(glist_getcanvas(x->x_glist))->s_name, filename->s_name, "", buf, &bufptr, MAXPDSTRING, 1)) < 0 || !(fd = fdopen(filedesc, BINREADMODE))) { error("%s: can't open", filename->s_name); return; } if (skip) { long pos = fseek(fd, (long)skip, SEEK_SET); if (pos < 0) { error("%s: can't seek to byte %d", buf, skip); fclose(fd); return; } } for (i = 0; i < nelem; i++) { if (fread(&s, sizeof(s), 1, fd) < 1) { post("%s: read %d elements into table of size %d", filename->s_name, i, nelem); break; } if (swap) s = ((s & 0xff) << 8) | ((s & 0xff00) >> 8); vec[i] = s * (1./32768.); } while (i < nelem) vec[i++] = 0; fclose(fd); garray_redraw(x); } static void garray_write(t_garray *x, t_symbol *filename) { FILE *fd; char buf[MAXPDSTRING]; t_template *template = garray_template(x); int yonset, type, i; t_symbol *arraytype; if (!template_find_field(template, gensym("y"), &yonset, &type, &arraytype) || type != DT_FLOAT) { error("%s: needs floating-point 'y' field", x->x_templatesym->s_name); return; } canvas_makefilename(glist_getcanvas(x->x_glist), filename->s_name, buf, MAXPDSTRING); sys_bashfilename(buf, buf); if (!(fd = fopen(buf, "w"))) { error("%s: can't create", buf); return; } for (i = 0; i < x->x_n; i++) { if (fprintf(fd, "%g\n", *(float *)((x->x_vec + sizeof(t_word) * i) + yonset)) < 1) { post("%s: write error", filename->s_name); break; } } fclose(fd); } static unsigned char waveheader[] = { 0x52, 0x49, 0x46, 0x46, 0x00, 0x00, 0x00, 0x00, 0x57, 0x41, 0x56, 0x45, 0x66, 0x6d, 0x74, 0x20, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x44, 0xac, 0x00, 0x00, 0x88, 0x58, 0x01, 0x00, 0x02, 0x00, 0x10, 0x00, 0x64, 0x61, 0x74, 0x61, 0x00, 0x00, 0x00, 0x00, }; /* wave format only so far */ static void garray_write16(t_garray *x, t_symbol *filename, t_symbol *format) { t_template *template = garray_template(x); int yonset, type, i; t_symbol *arraytype; FILE *fd; int aiff = (format == gensym("aiff")); char filenamebuf[MAXPDSTRING], buf2[MAXPDSTRING]; int swap = garray_ambigendian(); /* wave is only little endian */ int intbuf; strncpy(filenamebuf, filename->s_name, MAXPDSTRING-10); filenamebuf[MAXPDSTRING-10] = 0; if (sizeof(int) != 4) post("write16: only works on 32-bit machines"); if (aiff) { if (strcmp(filenamebuf + strlen(filenamebuf)-5, ".aiff")) strcat(filenamebuf, ".aiff"); } else { if (strcmp(filenamebuf + strlen(filenamebuf)-4, ".wav")) strcat(filenamebuf, ".wav"); } if (!template_find_field(template, gensym("y"), &yonset, &type, &arraytype) || type != DT_FLOAT) { error("%s: needs floating-point 'y' field", x->x_templatesym->s_name); return; } canvas_makefilename(glist_getcanvas(x->x_glist), filenamebuf, buf2, MAXPDSTRING); sys_bashfilename(buf2, buf2); if (!(fd = fopen(buf2, BINWRITEMODE))) { error("%s: can't create", buf2); return; } intbuf = 2 * x->x_n + 36; if (swap) { unsigned char *foo = (unsigned char *)&intbuf, xxx; xxx = foo[0]; foo[0] = foo[3]; foo[3] = xxx; xxx = foo[1]; foo[1] = foo[2]; foo[2] = xxx; } memcpy((void *)(waveheader + 4), (void *)(&intbuf), 4); intbuf = 2 * x->x_n; if (swap) { unsigned char *foo = (unsigned char *)&intbuf, xxx; xxx = foo[0]; foo[0] = foo[3]; foo[3] = xxx; xxx = foo[1]; foo[1] = foo[2]; foo[2] = xxx; } memcpy((void *)(waveheader + 40), (void *)(&intbuf), 4); if (fwrite(waveheader, sizeof(waveheader), 1, fd) < 1) { post("%s: write error", buf2); goto closeit; } for (i = 0; i < x->x_n; i++) { float f = 32767. * *(float *)((x->x_vec + sizeof(t_word) * i) + yonset); short sh; if (f < -32768) f = -32768; else if (f > 32767) f = 32767; sh = f; if (swap) { unsigned char *foo = (unsigned char *)&sh, xxx; xxx = foo[0]; foo[0] = foo[1]; foo[1] = xxx; } if (fwrite(&sh, sizeof(sh), 1, fd) < 1) { post("%s: write error", buf2); goto closeit; } } closeit: fclose(fd); } void garray_resize(t_garray *x, t_floatarg f) { int was = x->x_n, elemsize; t_glist *gl; int dspwas; int n = f; char *nvec; if (n < 1) n = 1; elemsize = template_findbyname(x->x_templatesym)->t_n * sizeof(t_word); nvec = t_resizebytes(x->x_vec, was * elemsize, n * elemsize); if (!nvec) { pd_error(x, "array resize failed: out of memory"); return; } x->x_vec = nvec; /* LATER should check t_resizebytes result */ if (n > was) memset(x->x_vec + was*elemsize, 0, (n - was) * elemsize); x->x_n = n; /* if this is the only array in the graph, reset the graph's coordinates */ gl = x->x_glist; if (gl->gl_list == &x->x_gobj && !x->x_gobj.g_next) { vmess(&gl->gl_pd, gensym("bounds"), "ffff", 0., gl->gl_y1, (double)(n > 1 ? n-1 : 1), gl->gl_y2); /* close any dialogs that might have the wrong info now... */ gfxstub_deleteforkey(gl); } else garray_redraw(x); if (x->x_usedindsp) canvas_update_dsp(); } static void garray_print(t_garray *x) { post("garray %s: template %s, length %d", x->x_name->s_name, x->x_templatesym->s_name, x->x_n); } void g_array_setup(void) { garray_class = class_new(gensym("array"), 0, (t_method)garray_free, sizeof(t_garray), CLASS_GOBJ, 0); class_setwidget(garray_class, &garray_widgetbehavior); class_addmethod(garray_class, (t_method)garray_const, gensym("const"), A_DEFFLOAT, A_NULL); class_addlist(garray_class, garray_list); class_addmethod(garray_class, (t_method)garray_bounds, gensym("bounds"), A_FLOAT, A_FLOAT, A_FLOAT, A_FLOAT, A_NULL); class_addmethod(garray_class, (t_method)garray_xticks, gensym("xticks"), A_FLOAT, A_FLOAT, A_FLOAT, 0); class_addmethod(garray_class, (t_method)garray_xlabel, gensym("xlabel"), A_GIMME, 0); class_addmethod(garray_class, (t_method)garray_yticks, gensym("yticks"), A_FLOAT, A_FLOAT, A_FLOAT, 0); class_addmethod(garray_class, (t_method)garray_ylabel, gensym("ylabel"), A_GIMME, 0); class_addmethod(garray_class, (t_method)garray_rename, gensym("rename"), A_SYMBOL, 0); class_addmethod(garray_class, (t_method)garray_read, gensym("read"), A_SYMBOL, A_NULL); class_addmethod(garray_class, (t_method)garray_read16, gensym("read16"), A_SYMBOL, A_DEFFLOAT, A_DEFSYM, A_NULL); class_addmethod(garray_class, (t_method)garray_write, gensym("write"), A_SYMBOL, A_NULL); class_addmethod(garray_class, (t_method)garray_write16, gensym("write16"), A_SYMBOL, A_DEFSYM, A_NULL); class_addmethod(garray_class, (t_method)garray_resize, gensym("resize"), A_FLOAT, A_NULL); class_addmethod(garray_class, (t_method)garray_print, gensym("print"), A_NULL); class_addmethod(garray_class, (t_method)garray_sinesum, gensym("sinesum"), A_GIMME, 0); class_addmethod(garray_class, (t_method)garray_cosinesum, gensym("cosinesum"), A_GIMME, 0); class_addmethod(garray_class, (t_method)garray_normalize, gensym("normalize"), A_DEFFLOAT, 0); class_addmethod(garray_class, (t_method)garray_arraydialog, gensym("arraydialog"), A_SYMBOL, A_FLOAT, A_FLOAT, A_FLOAT, A_NULL); }