summaryrefslogtreecommitdiff
path: root/apps/plugins/mpegplayer (follow)
Commit message (Collapse)AuthorAge
* Initial commit of the Samsung YP-R0 port.Thomas Martitz2011-12-24
| | | | | | | | | | | | | | | | | | | | | | | | | | | | This port is a hybrid native/RaaA port. It runs on a embedded linux system, but is the only application. It therefore can implement lots of stuff that native targets also implement, while leveraging the underlying linux kernel. The port is quite advanced. User interface, audio playback, plugins work mostly fine. Missing is e.g. power mangement and USB (see SamsungYPR0 wiki page). Included in utils/ypr0tools are scripts and programs required to generate a patched firmware. The patched firmware has the rootfs modified to load Rockbox. It includes a early/safe USB mode. This port needs a new toolchain, one that includes glibc headers and libraries. rockboxdev.sh can generate it, but e.g. codesourcey and distro packages may also work. Most of the initial effort is done by Lorenzo Miori and others (on ABI), including reverse engineering and patching of the original firmware, initial drivers, and more. Big thanks to you. Flyspray: FS#12348 Author: Lorenzo Miori, myself Merry christmas to ypr0 owners! :) git-svn-id: svn://svn.rockbox.org/rockbox/trunk@31415 a1c6a512-1295-4272-9138-f99709370657
* Commit to certain names for cache coherency APIs and discard the aliases.Michael Sevakis2011-12-17
| | | | | | | Wouldn't surprise me a bit to get some non-green. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@31339 a1c6a512-1295-4272-9138-f99709370657
* Sandisk Sansa Connect port (FS #12363)Tomasz Moń2011-11-16
| | | | | | | | Included are drivers for buttons, backlight, lcd, audio and storage. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@31000 a1c6a512-1295-4272-9138-f99709370657
* fix redRafaël Carré2011-10-10
| | | | git-svn-id: svn://svn.rockbox.org/rockbox/trunk@30740 a1c6a512-1295-4272-9138-f99709370657
* mpegplayer sync_audio_setting(): simplify / clarifyRafaël Carré2011-10-10
| | | | | | fix fuze+ yellow git-svn-id: svn://svn.rockbox.org/rockbox/trunk@30739 a1c6a512-1295-4272-9138-f99709370657
* fuze+°: add mpeg keymapAmaury Pouly2011-10-02
| | | | git-svn-id: svn://svn.rockbox.org/rockbox/trunk@30698 a1c6a512-1295-4272-9138-f99709370657
* MIPS uses GCC 4.1.2 which was wrongly detected as supporting ↵Andree Buschmann2011-09-08
| | | | | | '-fgnu89-inline'. Now only set this parameter if GCCNUM > 401. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@30471 a1c6a512-1295-4272-9138-f99709370657
* Fix FS#12261. Only set '-fgnu89-inline' if GCC >= 4.1.3 is used.Andree Buschmann2011-09-07
| | | | git-svn-id: svn://svn.rockbox.org/rockbox/trunk@30467 a1c6a512-1295-4272-9138-f99709370657
* Do not set '-fgnu89-inline' for MIPS targets.Andree Buschmann2011-09-03
| | | | git-svn-id: svn://svn.rockbox.org/rockbox/trunk@30421 a1c6a512-1295-4272-9138-f99709370657
* FS#12256: Use '-fgnu89-inline' for mpegplayer and doom. Resolves compiler ↵Andree Buschmann2011-09-03
| | | | | | warnings when using mingw32. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@30420 a1c6a512-1295-4272-9138-f99709370657
* GSoC/Buflib: Enable compaction in buflib.Thomas Martitz2011-08-30
| | | | | | | | | | | | | | | | | | | This enables the ability to allocate (and free) memory dynamically without fragmentation, through compaction. This means allocations can move and fragmentation be reduced. Most changes are preparing Rockbox for this, which many times means adding a move callback which can temporarily disable movement when the corresponding code is in a critical section. For now, the audio buffer allocation has a central role, because it's the one having allocated most. This buffer is able to shrink itself, for which it needs to stop playback for a very short moment. For this, audio_buffer_available() returns the size of the audio buffer which can possibly be used by other allocations because the audio buffer can shrink. lastfm scrobbling and timestretch can now be toggled at runtime without requiring a reboot. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@30381 a1c6a512-1295-4272-9138-f99709370657
* Cleanup tree.c cache handling a bit.Thomas Martitz2011-08-03
| | | | | | | | * Rename stuff to not re-use the term dircache * Move cache to own struct * Encapsulate retrieving entries a bit git-svn-id: svn://svn.rockbox.org/rockbox/trunk@30242 a1c6a512-1295-4272-9138-f99709370657
* Have mpegplayer use the mixer (the playback channel, since it's mutually ↵Michael Sevakis2011-07-08
| | | | | | exclusive to audio playback) so the clicks and skip beep can be used according to user settings. Introduce some system sound functions to make easier playing event sounds from various places and convert files calling 'beep_play' to use 'system_sound_play' and 'keyclick_click'. Event sound could be become themeable. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@30130 a1c6a512-1295-4272-9138-f99709370657
* mpegplayer: Adapt to new pause_rewind setting and apply it for allMichael Hohmuth2011-05-13
| | | | | | kinds of pause, not just pause on headphone unplug. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@29878 a1c6a512-1295-4272-9138-f99709370657
* Fix a bunch of 'variable set but not used' warnings reported from GCC 4.6.0.Andree Buschmann2011-05-08
| | | | git-svn-id: svn://svn.rockbox.org/rockbox/trunk@29841 a1c6a512-1295-4272-9138-f99709370657
* Anti-Aliased Fonts support.Thomas Martitz2011-03-05
| | | | | | | | | | | | | This enables Rockbox to render anti-aliased fonts using an alpha blending method. The input font bitmaps are 4bit, i.e. 4x larger, but the metadata size stays the same. A tool, convttf, for converting ttf fonts directly to the Rockbox fnt format is provided. It has a useful help output, but the parameter that works best is -c1 or -c2 (2 for larger font sizes). Flyspray: FS#8961 Author: Initial work by Jonas Hurrelmann, further work by Fred Bauer, Andrew Mahone, Teruaki Kawashima and myself. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@29523 a1c6a512-1295-4272-9138-f99709370657
* Change the thread api a bit.Thomas Martitz2011-03-05
| | | | | | | * Remove THREAD_ID_CURRENT macro in favor of a thread_self() function, this allows thread functions to be simpler. * thread_self_entry() shortcut for kernel.c. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@29521 a1c6a512-1295-4272-9138-f99709370657
* mpegplayer: Move libmpeg2 files into their own subdirectory so things are ↵Michael Sevakis2011-02-22
| | | | | | less cluttered. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@29381 a1c6a512-1295-4272-9138-f99709370657
* MPEGPlayer: Try out a different frame drop scheme meant to skip in a more ↵Michael Sevakis2011-02-03
| | | | | | uniform way rather than running up late and jumping forward; will often drop more in long term to keep up in short term. Some other obscure fixes included: wait for 2 ref pics before decoding B-pics again after P or I frame drop or seeking (issue with open GOPs); draw the frame the decoder already has when beginning playback after a seek; rename a few vars. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@29198 a1c6a512-1295-4272-9138-f99709370657
* MPEGPlayer: Account for drivers that pass pointers to their internal state ↵Michael Sevakis2011-01-25
| | | | | | variables to the audio callback. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@29135 a1c6a512-1295-4272-9138-f99709370657
* * Rename backlight_force_on to backlight_ignore_timeout to make it clear ↵Teruaki Kawashima2011-01-24
| | | | | | | | | | | what the function does. * Add backlight_force_on() which forces to turn on backlight even when the setting is set to Off. use this in lamp. No functional change except lamp. See aslo FS#9883. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@29128 a1c6a512-1295-4272-9138-f99709370657
* SA9200: Add YUV blitting, do full-screen updates more efficiently and ↵Michael Sevakis2011-01-11
| | | | | | properly clip partial updates. Plugin struct becomes incompatible for this target but leave version alone since it's still dev-only. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@29028 a1c6a512-1295-4272-9138-f99709370657
* Put illustrative switch case back, because it clarifies intention...maybe ;).Michael Sevakis2011-01-07
| | | | git-svn-id: svn://svn.rockbox.org/rockbox/trunk@28985 a1c6a512-1295-4272-9138-f99709370657
* MPEGPlyaer: A bit of audio mutation. Remove a useless thread state. Take ↵Michael Sevakis2011-01-07
| | | | | | some control over the buffer away from the audio thread itself. Some atomicity corrections. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@28984 a1c6a512-1295-4272-9138-f99709370657
* Fix another comment typoAlexander Levin2011-01-04
| | | | git-svn-id: svn://svn.rockbox.org/rockbox/trunk@28969 a1c6a512-1295-4272-9138-f99709370657
* MPEGPlayer: Give the parser a little stronger going over this time so it ↵Michael Sevakis2011-01-04
| | | | | | tolerates odd audio timestamps better. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@28965 a1c6a512-1295-4272-9138-f99709370657
* mpeg_parser: fix a typo in the commentAlexander Levin2011-01-04
| | | | git-svn-id: svn://svn.rockbox.org/rockbox/trunk@28964 a1c6a512-1295-4272-9138-f99709370657
* MPEGPlayer: Try a few timestamps for all streams at the beginning, not just ↵Michael Sevakis2011-01-03
| | | | | | video, to help deal with anomalous start PTS values in a stream. Hopefully the hardcoded number gives it enough tolerance. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@28961 a1c6a512-1295-4272-9138-f99709370657
* MPEGPlayer graphics mutation: Implement a more visible FPS display and ↵Michael Sevakis2011-01-03
| | | | | | remove the debugging info from it. Tweak thumbnailing and printing of unavailable frames. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@28960 a1c6a512-1295-4272-9138-f99709370657
* MPEGPlayer: Some UI tweaking and bugfixingMichael Sevakis2010-12-22
| | | | | | | | | | * Allow skip-to-beginning in single-play mode; there is no 3-second delay in that case. * Properly handle and keep track of pauses caused by headphone removal. * Improve skipping over bad files - search in skip direction and allow it to be ended with the stop key. * Add the system message processing done elsewhere to all button queue waits. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@28875 a1c6a512-1295-4272-9138-f99709370657
* MPEGPlayer: Fix a bug where seeking a stream into the same frame more than ↵Michael Sevakis2010-12-20
| | | | | | once in a row causes it to report itself at the end in error; to fix it, pause streams rather than stopping them if the playback isn't stopped. Also, add one check so the engine can at rewind unseekable files to time 0. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@28862 a1c6a512-1295-4272-9138-f99709370657
* MPEGPlayer: Move some code that's probably better situated in the stream ↵Michael Sevakis2010-12-19
| | | | | | manager rather than the parser. Fix visibility checking in video out. Extra message sending for new stream isn't needed; just do full decoder sequence reset when requesting dimensions. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@28855 a1c6a512-1295-4272-9138-f99709370657
* MPEGPlayer: Skip to next file when there is a problem with a video file in ↵Michael Sevakis2010-12-19
| | | | | | all-play mode, otherwise exit as usual. Only consider failures such as engine init issues or no file to view to be a plugin error but not problems with the video files themselves; the user is adequately informed already. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@28854 a1c6a512-1295-4272-9138-f99709370657
* MPEGPlayer: Fix leakage of file decriptors if file wasn't accepted by ↵Michael Sevakis2010-12-19
| | | | | | playback engine-- code is now getting tested that couldn't be before. Also reset parser in stream init when it doesn't like the file. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@28853 a1c6a512-1295-4272-9138-f99709370657
* MPEGPlayer playlist should as well support all viewer-handled file ↵Michael Sevakis2010-12-18
| | | | | | extensions...indeed. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@28850 a1c6a512-1295-4272-9138-f99709370657
* HD300 - plugins keymapsMarcin Bukat2010-11-30
| | | | git-svn-id: svn://svn.rockbox.org/rockbox/trunk@28712 a1c6a512-1295-4272-9138-f99709370657
* FS#8607: MPEG video playlistTeruaki Kawashima2010-11-26
| | | | | | | Add mode to play multiple mpeg files in the same directory in the order the file browser shows. In this mode, Mpegplayer exits after finishing the last .mpg file. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@28667 a1c6a512-1295-4272-9138-f99709370657
* These should be the last of the plugin keymaps requiredRobert Menes2010-11-03
| | | | | | | for the GoGear HDD6330. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@28467 a1c6a512-1295-4272-9138-f99709370657
* MPIO HD200: rename button defines to adhere how they are labeled on the device.Marcin Bukat2010-11-02
| | | | git-svn-id: svn://svn.rockbox.org/rockbox/trunk@28443 a1c6a512-1295-4272-9138-f99709370657
* Make disabling HAVE_PITCHSCREEN actually work without breaking the buildFrank Gevaerts2010-09-17
| | | | git-svn-id: svn://svn.rockbox.org/rockbox/trunk@28102 a1c6a512-1295-4272-9138-f99709370657
* Plugins: modify IRAM copying codeRafaël Carré2010-08-30
| | | | | | | | | Move to plugin_crt0.c, plugins don't need PLUGIN_IRAM_* macros anymore IRAM is no longered zeroed before copying (as it is at the same address than BSS) -> Fix FS#11581 Use cpucache_invalidate() (and not cpucache_flush), needed for self-modifying code on cached IRAM git-svn-id: svn://svn.rockbox.org/rockbox/trunk@27948 a1c6a512-1295-4272-9138-f99709370657
* Second try: Introduce plugin_crt0.c that every plugin links.Thomas Martitz2010-08-24
| | | | | | | | | | | | | It handles exit() properly, calling the handler also when the plugin returns normally (also make exit() more standard compliant while at it). It also holds PLUGIN_HEADER, so that it doesn't need to be in each plugin anymore. To work better together with callbacks passed to rb->default_event_handler_ex() introduce exit_on_usb() which will call the exit handler before showing the usb screen and exit() after it. In most cases rb->default_event_handler_ex() was passed a callback which was manually called at all other return points. This can now be done via atexit(). In future plugin_crt0.c could also handle clearing bss, initializing iram and more. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@27873 a1c6a512-1295-4272-9138-f99709370657
* Revert "Introduce plugin_crt0.c that every plugin links."Thomas Martitz2010-08-23
| | | | | | Too much errors and no time to fix them now. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@27863 a1c6a512-1295-4272-9138-f99709370657
* Introduce plugin_crt0.c that every plugin links.Thomas Martitz2010-08-23
| | | | | | | | | | | | | It handles exit() properly, calling the handler also when the plugin returns normally (also it makes exit() more standard compliant while at it). It also holds PLUGIN_HEADER, so that it doesn't need to be in each plugin anymore. To work better together with callbacks passed to rb->default_event_handler_ex introduce exit_on_usb() which will call the exit handler before showing the usb screen and exit() after it. In most cases it was passed a callback which was manually called at all other return points. This can now be done via atexit(). In future plugin_crt0.c could also handle clearing bss, initializing iram and more. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@27862 a1c6a512-1295-4272-9138-f99709370657
* mpegplayer mad: apply r27655 fixRafaël Carré2010-08-01
| | | | git-svn-id: svn://svn.rockbox.org/rockbox/trunk@27659 a1c6a512-1295-4272-9138-f99709370657
* Revert accidental commit of "%z" support in r26071 (the implementation ↵Frank Gevaerts2010-07-18
| | | | | | | | | assumed size_t==long, which is not always the case in hosted environments) Remove four remaining uses of %z, three of which were in DEBUGF git-svn-id: svn://svn.rockbox.org/rockbox/trunk@27479 a1c6a512-1295-4272-9138-f99709370657
* r27159 broke mpegplayer but not the mpa codec because its libmad init wasn't ↵Michael Sevakis2010-06-29
| | | | | | also updated to be compatible with the changes. Fix that. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@27171 a1c6a512-1295-4272-9138-f99709370657
* CPP substitution isn't made inside " ", but we need " " when using , in a ↵Rafaël Carré2010-06-11
| | | | | | | | | | gas macro argument Modify HIGH_REGS macro to store/load only one range of registers When the range isn't contigous (in MC_put_x_8*), shift registers to make it contigous (r4 and r5 are now unused by these functions) git-svn-id: svn://svn.rockbox.org/rockbox/trunk@26759 a1c6a512-1295-4272-9138-f99709370657
* FS#11335 by me: make ARM assembly functions thumb-friendlyRafaël Carré2010-06-11
| | | | | | | | | | | | | We can't pop into pc on ARMv4t when using thumb: the T bit won't be modified if we are returning to a thumb function Code running on ARMv4t should use the new ldrpc / ldmpc macros instead of ldr pc, [sp], #4 and ldm(cond) sp!, {regs, pc} No modification on pure ARM builds and ARMv5+ Note: USE_THUMB is currently never defined, no targets can currently be built with -mthumb, see FS#6734 git-svn-id: svn://svn.rockbox.org/rockbox/trunk@26756 a1c6a512-1295-4272-9138-f99709370657
* Add a wrapper header, mylcd.h, in the lib subdirectory, which lets plugins' ↵Michael Sevakis2010-06-04
| | | | | | code automatically call the proper functions depending if compilation is for greylib or color display, also forms proper call to grey_ and xlcd_. mylcd_ub_ call greylib unbuffered routines, regular lcd routines otherwise. Form is mylcd_<fnname>, <fnname> is the symbol name stripped of prefixes lcd_, grey_, or xlcd_. Convert a couple plugins I know well (easy job). git-svn-id: svn://svn.rockbox.org/rockbox/trunk@26542 a1c6a512-1295-4272-9138-f99709370657
>1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543
/* 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.  */

#ifdef ROCKBOX
#include "plugin.h"
#include "../../pdbox.h"
#include "m_pd.h"
#include "g_canvas.h"
#ifdef SIMULATOR
int printf(const char *fmt, ...);
#endif /* SIMULATOR */
#else /* ROCKBOX */
#include <stdlib.h>
#include <string.h>
#include <stdio.h>  	/* for read/write to files */
#include "m_pd.h"
#include "g_canvas.h"
#include <math.h>
#endif /* ROCKBOX */

/* 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;
#ifndef ROCKBOX
    t_gpointer *gp;
#endif
    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;
#ifndef ROCKBOX
    t_gpointer *gp;
#endif

    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 word_free(t_word *wp, t_template *template);

void array_free(t_array *x)
{
    int i;
    t_template *scalartemplate = template_findbyname(x->a_templatesym);
	/* we don't unset our gpointer here since it was never "set." */
    /* gpointer_unset(&x->a_gp); */
    gstub_cutoff(x->a_stub);
    for (i = 0; i < x->a_n; i++)
    {
    	t_word *wp = (t_word *)(x->a_vec + x->a_elemsize * i);
	word_free(wp, scalartemplate);
    }
    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];
#ifdef ROCKBOX
        snprintf(buf, sizeof(buf), "array%d", ++gcount);
#else
    	sprintf(buf, "array%d", ++gcount);
#endif
    	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)
{
#ifdef ROCKBOX
    (void) canvas;
#else /* ROCKBOX */
    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);
#endif /* ROCKBOX */
}

    /* called from graph_dialog to set properties */
void garray_properties(t_garray *x)
{
#ifdef ROCKBOX
    (void) x;
#else /* ROCKBOX */
    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);
#endif /* ROCKBOX */
}

    /* 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;
#ifndef ROCKBOX
    gfxstub_deleteforkey(x);
#endif
    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 = fixtof(*(t_sample *)(elem + xonset));
    else xval = indx * xinc;
    if (yonset >= 0)
    	yval = fixtof(*(t_sample *)(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;
#ifndef ROCKBOX
static int array_motion_altkey;
#endif
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)
{
#ifdef ROCKBOX
    (void) z;
#endif
    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 * array_motion_yperpix;
			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 * array_motion_yperpix, 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;

#ifdef ROCKBOX
    (void) linewidth;
    (void) shift;
    (void) dbl;
#endif

    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 - 1) * elemsize);
			i++;
		    }
		    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_yperpix *= 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)
    	{
#ifdef ROCKBOX
            float pxpix, pypix, pwpix;
#else /* ROCKBOX */
	    float pxpix, pypix, pwpix, dx, dy;
#endif /* ROCKBOX */
    	    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)
{
#ifdef ROCKBOX
    (void) z;
    (void) glist;
    (void) dx;
    (void) dy;
#endif
    /* refuse */
}

static void garray_select(t_gobj *z, t_glist *glist, int state)
{
#ifdef ROCKBOX
    (void) z;
    (void) glist;
    (void) state;
#else /* ROCKBOX */
    t_garray *x = (t_garray *)z;
#endif /* ROCKBOX */
    /* fill in later */
}

static void garray_activate(t_gobj *z, t_glist *glist, int state)
{
#ifdef ROCKBOX
    (void) z;
    (void) glist;
    (void) state;
#endif
}

static void garray_delete(t_gobj *z, t_glist *glist)
{
#ifdef ROCKBOX
    (void) z;
    (void) glist;
#endif
    /* 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);
#ifndef ROCKBOX
    	    sys_vgui(".x%x.c create text 50 50 -text foo\
    	    	-tags .x%x.a%x\n",
    	    	glist_getcanvas(glist), glist_getcanvas(glist), x);
#endif
    	}
    	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;
#ifndef ROCKBOX
    	    sys_vgui(".x%x.c create line \\\n", glist_getcanvas(glist));
#endif
    	    for (i = 0; i < x->x_n; i++)
    	    {
    	    	yval = fixtof(*(t_sample *)(x->x_vec +
		    template->t_n * i * sizeof (t_word) + yonset));
    	    	xpix = glist_xtopixels(glist, xcum);
    	    	ixpix = xpix + 0.5;
    	    	if (ixpix != lastpixel)
    	    	{
#ifndef ROCKBOX
    	    	    sys_vgui("%d %f \\\n", ixpix,
    	    	    	glist_ytopixels(glist, yval));
#endif
    	    	    ndrawn++;
    	    	}
    	    	lastpixel = ixpix;
    	    	if (ndrawn >= 1000) break;
    	    	xcum += x->x_xinc;
    	    }
    	    	/* TK will complain if there aren't at least 2 points... */
#ifndef ROCKBOX
    	    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);
#endif
    	    firsty = fixtof(*(t_sample *)(x->x_vec + yonset));
#ifndef ROCKBOX
    	    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);
#endif
    	}
    	else
    	{
    	    post("x, y arrays not yet supported");
    	}
    }
    else
    {
#ifndef ROCKBOX
    	sys_vgui(".x%x.c delete .x%x.a%x\n",
    	    glist_getcanvas(glist), glist_getcanvas(glist), x);
#endif
    }
}

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);
#ifdef ROCKBOX
#ifdef SIMULATOR
    printf("array save\n");
#endif /* SIMULATOR */
#else /* ROCKBOX */
    fprintf(stderr,"array save\n");
#endif /* ROCKBOX */
    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", fixtof(((t_sample *)(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
};

/* ----------------------- 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_sample **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 =  (t_sample *)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<<ilog2(npoints))));
    garray_resize(x, npoints + 3);
    phaseincr = 2. * 3.14159 / npoints;
    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, 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)
{
#ifdef ROCKBOX
    (void) s;
#else
    t_template *template = garray_template(x);
#endif
    
    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)
{
#ifdef ROCKBOX
    (void) s;
#else
    t_template *template = garray_template(x);
#endif
    
    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);
#ifdef ROCKBOX
    int yonset, type, i;
#else
    int yonset, type, npoints, i;
#endif
    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;
#ifdef ROCKBOX
    (void) s;
#endif
    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++)
    	    *(t_sample *)((x->x_vec + sizeof(t_word) * (i + firstindex)) + yonset) = 
    	    	ftofix(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;
#ifdef ROCKBOX
    int fd = 0;
#else
    FILE *fd;
#endif
    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 
#ifdef ROCKBOX
                || !(fd = filedesc))
#else
	    	|| !(fd = fdopen(filedesc, "r")))
#endif
    {
    	error("%s: can't open", filename->s_name);
    	return;
    }
    for (i = 0; i < nelem; i++)
    {
#ifdef ROCKBOX
        if(rb_fscanf_f(fd, (float*)((x->x_vec + sizeof(t_word) * i) + yonset)))
#else
    	if (!fscanf(fd, "%f", (float *)((x->x_vec + sizeof(t_word) * i) +
	    yonset)))
#endif
    	{
    	    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++;
#ifdef ROCKBOX
    close(fd);
#else
    fclose(fd);
#endif
    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;
    t_sample *vec;
#ifdef ROCKBOX
    int fd = 0;
#else
    FILE *fd;
#endif
    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 
#ifdef ROCKBOX
        || !(fd = filedesc))
#else
	    	|| !(fd = fdopen(filedesc, BINREADMODE)))
#endif
    {
    	error("%s: can't open", filename->s_name);
    	return;
    }
    if (skip)
    {
#ifdef ROCKBOX
        long pos = lseek(fd, (long)skip, SEEK_SET);
#else
    	long pos = fseek(fd, (long)skip, SEEK_SET);
#endif
    	if (pos < 0)
    	{
    	    error("%s: can't seek to byte %d", buf, skip);
#ifdef ROCKBOX
            close(fd);
#else
    	    fclose(fd);
#endif
    	    return;
    	}
    }

    for (i = 0; i < nelem; i++)
    {
#ifdef ROCKBOX
        if(read(fd, &s, sizeof(s)) < (ssize_t) sizeof(s))
#else
    	if (fread(&s, sizeof(s), 1, fd) < 1)
#endif
    	{
    	    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;
#ifdef ROCKBOX
    close(fd);
#else
    fclose(fd);
#endif
    garray_redraw(x);
}

static void garray_write(t_garray *x, t_symbol *filename)
{
#ifdef ROCKBOX
    int fd;
#else
    FILE *fd;
#endif
    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);
#ifdef ROCKBOX
    if(!(fd = open(buf, O_WRONLY|O_CREAT|O_TRUNC, 0666)))
#else
    if (!(fd = fopen(buf, "w")))
#endif
    {
    	error("%s: can't create", buf);
    	return;
    }
    for (i = 0; i < x->x_n; i++)
    {
#ifdef ROCKBOX
        if(rb_fprintf_f(fd, 
#else /* ROCKBOX */
    	if (fprintf(fd, "%g\n",
#endif /* ROCKBOX */
    	    *(float *)((x->x_vec + sizeof(t_word) * i) + yonset)) < 1)
    	{
    	    post("%s: write error", filename->s_name);
    	    break;
    	}
    }
#ifdef ROCKBOX
    close(fd);
#else
    fclose(fd);
#endif
}

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;
#ifdef ROCKBOX
    int fd;
#else
    FILE *fd;
#endif
    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);
#ifdef ROCKBOX
    if(!(fd = open(buf2, O_WRONLY|O_CREAT|O_TRUNC, 0666)))
#else
    if (!(fd = fopen(buf2, BINWRITEMODE)))
#endif
    {
    	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);
#ifdef ROCKBOX
    if(write(fd, waveheader, sizeof(waveheader)) < (ssize_t) sizeof(waveheader))
#else
    if (fwrite(waveheader, sizeof(waveheader), 1, fd) < 1)
#endif
    {
    	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;
    	}
#ifdef ROCKBOX
        if(write(fd, &sh, sizeof(sh)) < (ssize_t) sizeof(sh))
#else
	if (fwrite(&sh, sizeof(sh), 1, fd) < 1)
#endif
	{
    	    post("%s: write error", buf2);
    	    goto closeit;
	}
    }
closeit:
#ifdef ROCKBOX
    close(fd);
#else
    fclose(fd);
#endif
}

void garray_resize(t_garray *x, t_floatarg f)
{
    int was = x->x_n, elemsize;
    t_glist *gl;
#ifndef ROCKBOX
    int dspwas;
#endif
    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... */
#ifndef ROCKBOX
    	gfxstub_deleteforkey(gl);
#endif
    }
    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);
    class_setsavefn(garray_class, garray_save);
}