/* WaVeVieW - waveform viewer widget for pd (c) 2004 Tom Schouten This program is free software covered under version 2 of the GPL. See the file COPYING included in the distribution. */ #include #include #include #include #include #include #include "m_pd.h" /* util & debug */ // and i though i wouldn't need this.. tsss.. #ifdef __unix__ #include #include #include #define ASSERT(x) if (!(x)){\ post("ASSERT ( %s ) failed in %s, line %d", #x, __FILE__, __LINE__);\ kill (getpid(), SIGTRAP);\ } #else #define ASSERT(x) #endif static inline int max(int a, int b) {return (a < b) ? b : a;} static inline int min(int a, int b) {return (a > b) ? b : a;} static inline float fmax(float a, float b) {return (a < b) ? b : a;} static inline float fmin(float a, float b) {return (a > b) ? b : a;} static int clip(int x, int m, int M){return max(m, (min(M, x)));} static float fclip(float x, float m, float M){return fmax(m, (fmin(M, x)));} /* MULTIRES CACHE OBJECT this object is constructed from an array and can be used to generate a peak structure (min/max lines) for drawing the waveform */ typedef struct { float min; float max; } pair_t; typedef struct { float *vec; // the vector we are going to represent int elements; int top_level; // the finest level in the hierarchy, (level == log2 of subsampling factor) int bottom_level; // the coarsest level int blocks; // nb of blocks per level. for each level, the block contains half of it's parents pairs int nb_top_pairs; // nb of pairs in top level blocks (bottom level blocks contains 1 pair) int total_nb_pairs; // total number of pairs over all levels and block (cache size upper bound) int samples_per_block; // each block contains info for this many samples pair_t *multi; // the multires array. [ top_level_blocks ... bottom_level_blocks ] } mcache_t; /* get nb of pairs at level */ static inline int mcache_nb_pairs(mcache_t *m, int level){ ASSERT (level >= m->top_level); // not represented (to fine) ASSERT (level <= m->bottom_level); // .. (to coarse) return (1 << (m->bottom_level - level)); } /* get first cache block at level */ static inline pair_t *mcache_level(mcache_t *m, int level){ ASSERT (level >= m->top_level); // not represented (to fine) ASSERT (level <= m->bottom_level); // .. (to coarse) int index = m->total_nb_pairs - (m->total_nb_pairs >> (level - m->top_level)); ASSERT (index >= 0); ASSERT (index < m->total_nb_pairs); return m->multi + index; } /* utility functions for multires stuff */ static inline int mcache_in_vector(mcache_t *m, float *f){ return ((f >= m->vec) && (f < (m->vec + m->elements))); } /* scan a part of the vector */ static inline void mcache_scan_bound(mcache_t *m, float *f, int elements, int stride, pair_t *pair){ pair->min = 0.0f; pair->max = 0.0f; if (!elements) return; if (mcache_in_vector(m, f)){ pair->min = f[0]; pair->max = f[0]; elements--; f += stride; } while (elements--){ float x = mcache_in_vector(m, f) ? f[0] : 0.0f; pair->min = fmin(pair->min, x); pair->max = fmax(pair->max, x); f += stride; } } /* compute all lower levels from top level */ static void mcache_refine(mcache_t *m, int block){ int l; for (l = m->top_level; l < m->bottom_level; l++){ int src_pairs = mcache_nb_pairs(m, l); int dst_pairs = mcache_nb_pairs(m, l+1); pair_t *src_pair = mcache_level(m, l) + block * src_pairs; pair_t *dst_pair = mcache_level(m, l+1) + block * dst_pairs; while (dst_pairs--){ dst_pair->min = fmin(src_pair[0].min, src_pair[1].min); dst_pair->max = fmax(src_pair[0].max, src_pair[1].max); dst_pair++; src_pair+=2; } } } /* build top level the only sour point here is that the last block does not have proper size, so we workaround it by NOT computing it until it works (FXME) */ static void mcache_build_top(mcache_t *m, int block){ int point = block * m->samples_per_block; ASSERT(point >= 0); int i = m->nb_top_pairs; int chunk = 1 << m->top_level; // sample chunk size that will be used for top level pair float *vec = m->vec + point; pair_t *p = mcache_level(m, m->top_level) + block * m->nb_top_pairs; while (i--){ mcache_scan_bound(m, vec, chunk, 1, p); vec += chunk; p++; } } static void mcache_build(mcache_t *m){ int i; /* for each block */ for(i=0; iblocks; i++){ /* generate top level */ mcache_build_top(m, i); /* refine */ mcache_refine(m, i); } } static mcache_t *mcache_new(float *vec, int elements, int top_level, int bottom_level){ mcache_t *m = malloc(sizeof(*m)); ASSERT(vec); ASSERT(elements); /* init state */ m->vec = vec; m->elements = elements; m->top_level = top_level; m->bottom_level = bottom_level; m->nb_top_pairs = 1 << (bottom_level - top_level); m->samples_per_block = 1 << bottom_level; m->blocks = ((elements - 1) / m->samples_per_block) + 1; m->total_nb_pairs = m->blocks * m->nb_top_pairs * 2; // 2 = 1 + 1/2 + 1/4 + ... m->multi = calloc(m->total_nb_pairs, sizeof(pair_t)); /* build cache */ mcache_build(m); return m; } static void mcache_free(mcache_t *m){ free(m->multi); free(m); } /* note: floats are not deep enough for using incremental updates recomputing everything from ints would be a better approach, but it is slower. not that it matters that much though. */ static void mcache_peaks_direct(mcache_t *m, pair_t *p, int nb_pairs, double fpoint, double fsamples_per_pixel){ int samples_per_pixel = fsamples_per_pixel; while (nb_pairs--){ int point = fpoint; mcache_scan_bound(m, m->vec + point, samples_per_pixel, 1, p++); // get pair fpoint += fsamples_per_pixel; // move point } } /* note: if fpairs_per_pixel >= 1.0f subsequent steps do not introduce aliasing instead of respecting this, we allow aliasing to occur for very small window size and full view. */ static int mcache_peaks_multires(mcache_t *m, pair_t *p, int nb_pairs, double fpoint, double fsamples_per_pixel, double flevel){ /* check if valid */ int level = ceil(flevel); // round up to lessen aliasing if (level < ((double)m->top_level)) return 0; // can't use cache /* prepare for traversing cache level */ double subsamp = ((double)(1 << level)); // subsampling factor for this level double fpairs_per_pixel = fsamples_per_pixel / subsamp; // get pair increment double fpair = fpoint / subsamp; // get pair point int level_start = mcache_level(m, level) - m->multi; // start index of this level's pairs int level_endx = level_start + m->blocks * mcache_nb_pairs(m, level); // end index /* fill peak array */ while (nb_pairs--){ int pair = ((int)fpair) + level_start; if ((pair >= level_start) && (pair < level_endx)){ p->min = m->multi[pair].min; p->max = m->multi[pair].max; } else { // no data == 0.0 p->min = 0.0f; p->max = 0.0f; } p++; fpair += fpairs_per_pixel; } return 1; } /* fill an array of peaks */ static void mcache_peaks(mcache_t *m, int start, int endx, pair_t *p, int nb_pairs) { ASSERT(start <= endx); ASSERT(nb_pairs > 0); double fsamples_per_pixel = fmax(1.0f, ((double)(endx - start)) / ((double)nb_pairs)); // maybe this is not necessary. however, the entire widget respects this ASSERT(fsamples_per_pixel >= 1.0); double flevel = log(fsamples_per_pixel) / log(2); double fpoint = start; /* compute from peak cache */ if (!mcache_peaks_multires(m, p, nb_pairs, fpoint, fsamples_per_pixel, flevel)) /* if failed (level too fine), compute directly from array */ mcache_peaks_direct(m, p, nb_pairs, fpoint, fsamples_per_pixel); } /* WAVE EDITING WIDGET */ // widget event #define NONE 0 #define MOTION 1 #define PRESS 2 #define RELEASE 3 #define KPRESS 4 #define KRELEASE 5 #define PD_VIEW 6 #define PD_SELECT 7 /* global window stuff: for simplicity only one window.. */ static pthread_t thread; static float main_width = 800; static float main_height = 200; static int need_reshape = 0; // dimensions have changed static int need_redraw = 0; // data or view has changed static float waveform_color[] = {1.0, 1.0, 1.0, 1.0}; static float selection_color[] = {0.1, 0.2, 0.3, 1.0}; static float needle_color[] = {1.0, 0.0, 0.0, 1.0}; struct widget_; typedef struct widget_ widget_t; typedef struct wevent_ { widget_t *w; int type; int button; int key; float x; // rel coords or pd float args float y; } wevent_t; typedef struct pdevent_ { int type; float arg1; float arg2; } pdevent_t; struct widget_ { /* pd */ t_object obj; t_outlet *outlet; t_symbol *name; // array name widget_t *next; /* subframe */ int x; int y; int width; int height; /* array */ float *vec; int elements; /* waveform viewport this can be larger than the file. left and right will be padded with red lines */ int view_start; int view_endx; /* selection must be a valid subrange of array */ int sel_start; int sel_endx; /* motion handler */ void *motionhandler; float last_press_x; // press's coords float last_press_y; int point; // press's point float zoom; // last zoom state float last_zoom; // press's zoom (incremental instead of absolute) /* scroll button amplitude gain (incremental) */ float gain; /* peak cache */ mcache_t *peaks; int need_refresh; // rebuild cache /* comm: no queues, just a single event buffer data will be dropped when when selection/view events are sent too fast. */ /* communication thread->pd */ int key; // thread event handler unbound key sent as event t_clock *clock; // polling clock /* communication pd->thread */ pdevent_t pd_event; /* needle */ int needle; }; /* widget list */ static widget_t *widgets = 0; static int nb_widgets = 0; static int widget_update(widget_t *w); //static void widget_setup(widget_t *w); static void widget_free_multires(widget_t *w); static widget_t * widget_init(widget_t *w, t_symbol *name){ w->name = name; w->motionhandler = 0; w->zoom = 1.0; // full scale w->last_zoom = 1.0; w->gain = 1.0; w->peaks = 0; w->need_refresh = 0; w->vec = 0; w->elements = 0; w->key = 0; w->needle = -1; //disabled w->width = main_width; w->pd_event.type = 0; widget_update(w); // get array params and set defaults return w; } /* set/get operations preserving invariants: there's no fine grained locking because of the spaghetti factor. valid selection valid view (samples per pixel >= 1.0) */ static void widget_set_selection(widget_t *w, int start, int endx){ start = clip(start, 0, w->elements); endx = clip(endx , 0, w->elements); if (endx < start) endx = start; w->sel_start = start; w->sel_endx = endx; } // map current view to zoom static float widget_unzoom(widget_t *w){ ASSERT(w->width > 0); ASSERT(w->elements > 0); float fullscale = ((float)w->elements) / ((float)w->width); float samples_per_pixel = ((float)(w->view_endx - w->view_start)) / ((float)w->width); float zoom = log(samples_per_pixel) / log(fullscale); ASSERT(samples_per_pixel >= 1.0f); ASSERT(zoom >= 0.0f); ASSERT(zoom <= 1.0f); return zoom; } static void widget_set_view(widget_t *w, int start, int endx){ ASSERT(w->width > 0); /* limit zoom to 1 sample/pixel */ int min_endx = start + w->width; endx = max(min_endx, endx); w->view_start = start; w->view_endx = endx; w->zoom = widget_unzoom(w); } static widget_t *widget_register(widget_t *w){ w->next = widgets; widgets = w; nb_widgets++; need_reshape = 1; return w; } static void widget_deinit(widget_t *w){ widget_free_multires(w); } static widget_t *widget_by_index(int i){ if (i < 0) goto error; if (i >= nb_widgets) goto error; widget_t *w = widgets; while(i--) w = w->next; return w; error: return 0; } static void widget_unregister(widget_t *deadman){ /* BEGIN */ widget_t head, *w = &head; w->next = widgets; while(w->next){ if (w->next == deadman){ w->next = w->next->next; nb_widgets--; goto done; } w = w->next; } post("edit: widget not found while unregistering"); done: /* END */ widgets = head.next; need_reshape = 1; } static void widget_realestate(widget_t *w){ /* setup dims */ glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluOrtho2D(0, 1, 0, 1); glViewport(w->x, w->y, w->width, w->height); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); } // clear cache static void widget_free_multires(widget_t *w){ if (w->peaks) mcache_free(w->peaks); w->peaks = 0; } // make sure theres a cache present static void widget_build_multires(widget_t *w){ /* need refresh -> delete old */ if (w->need_refresh) widget_free_multires(w); /* check if we have one */ if (w->peaks) return; /* build new */ w->peaks = mcache_new(w->vec, w->elements, 4, 16); ASSERT(w->peaks); w->need_refresh = 0; } static void widget_set_defaults(widget_t *w){ ASSERT(w->elements > 0); widget_set_selection(w, 0, 0); widget_set_view(w, 0, w->elements); } // get array and update cache if necessary static int widget_update(widget_t *w){ int retval; float old_elements = w->elements; float *old_vec = w->vec; /* get pd array. strange: locking does not work properly.. */ //sys_lock(); t_garray *a = (t_garray *)pd_findbyclass(w->name, garray_class); if ((!a) || (!garray_getfloatarray(a, &(w->elements), &(w->vec)))){ //post("edit: can't find array %s", w->name->s_name); w->vec = 0; w->elements = 0; } //sys_unlock(); //post("got array %x %d", w->vec, w->elements); /* no array */ if (!w->vec){ widget_free_multires(w); retval = 0; goto done; } /* check if it's a new one */ if ((old_elements != w->elements) || (old_vec != w->vec)){ ASSERT(w->elements); w->need_refresh = 1; widget_set_defaults(w); // set defaults } /* check cache */ widget_build_multires(w); retval = 1; done: return retval; } // set default values for view and selection //static void widget_setup(widget_t *w){ // if (widget_update(w)) // get pd array // widget_set_defaults(w); // set default selection and view if valid array //} // map 'highlevel' zoom to samples/pixel : (fullscale)^zoom static float widget_samples_per_pixel(widget_t *w){ ASSERT(w->zoom >= 0); ASSERT(w->zoom <= 1.0f); float fullscale = fmax(1.0f, ((float)w->elements) / ((float)w->width)); return pow(fullscale, w->zoom); } static inline void widget_vertex(widget_t *w, int i, float x){ glVertex2f(((float)i) / ((float)w->width), (x + 1.0f) * 0.5f); } // draw a single peak line (min / max) static void widget_draw_peakline(widget_t *w, float *f, int stride){ int i; glColor4fv(waveform_color); glBegin(GL_LINE_STRIP); //glBegin(GL_POINTS); for (i=0; iwidth; i++){ widget_vertex(w, i, f[0] * w->gain); f += stride; } glEnd(); } static void widget_draw_waveform_lines(widget_t *w, pair_t *peaks){ widget_draw_peakline(w, (float *)peaks, 2); widget_draw_peakline(w, ((float *)peaks) + 1, 2); } static void widget_draw_waveform_solid(widget_t *w, pair_t *peaks){ int i; glColor4fv(waveform_color); glBegin(GL_TRIANGLE_STRIP); for (i=0; iwidth; i++){ widget_vertex(w, i, peaks[i].max * w->gain); widget_vertex(w, i, peaks[i].min * w->gain); } glEnd(); } static void widget_get_peaks(widget_t *w, pair_t *peaks, int nb_peaks){ mcache_peaks(w->peaks, w->view_start, w->view_endx, peaks, nb_peaks); } // get peak array and draw static void widget_draw_waveform(widget_t *w){ if (w->width <= 0) return; // no valid window int nb_peaks = w->width; pair_t p[nb_peaks]; // peaks to draw ASSERT(w->peaks); // make sure we have a cache // get peak array widget_get_peaks(w, p, nb_peaks); switch (1){ case 1: widget_draw_waveform_lines(w, p); break; case 2: widget_draw_waveform_solid(w, p); break; // doesn't look very nice } } // draw waveform selection rectangle static void widget_draw_selection(widget_t *w){ float fsel_start = ((float)(w->sel_start - w->view_start)) / ((float)(w->view_endx - w->view_start)); float fsel_endx = ((float)(w->sel_endx - w->view_start)) / ((float)(w->view_endx - w->view_start)); glColor4fv(selection_color); glEnable(GL_COLOR_LOGIC_OP); glLogicOp(GL_OR); glBegin(GL_QUADS); { glVertex2f(fsel_start, 0); glVertex2f(fsel_endx, 0); glVertex2f(fsel_endx, 1); glVertex2f(fsel_start, 1); } glDisable(GL_COLOR_LOGIC_OP); glEnd(); } static void widget_draw_needle(widget_t *w){ if (w->needle >= 0){ float fneedle = ((float)(w->needle - w->view_start)) / ((float)(w->view_endx - w->view_start)); glColor4fv(needle_color); glBegin(GL_LINES); { //post("fneedle = %f", fneedle); glVertex2f(fneedle, 0); glVertex2f(fneedle, 1); } glEnd(); } } static void widget_event(widget_t *w, wevent_t *we); static void widget_pd_event(widget_t *w){ pdevent_t *pe = &w->pd_event; if (pe->type){ int start = pe->arg1; int length = (pe->arg2 < 0.0f) ? w->elements : pe->arg2; switch(pe->type){ case PD_VIEW: widget_set_view(w, start, start+length); break; case PD_SELECT: widget_set_selection(w, start, start+length); break; } } w->pd_event.type = 0; } // display widget in subframe static void widget_display(widget_t *w){ widget_pd_event(w); // get pending pd event widget_realestate(w); // opengl coord stuff if (widget_update(w)){ // make sure array and cache are in place widget_draw_waveform(w); // waveform widget_draw_selection(w); // selection widget_draw_needle(w); // needle } } /* mouse event handler motion: left button = selection start right button = selection end middle button: x = move point, y = zoom */ static float interpolate(float left, float right, float fraction){ return left * (1.0 - fraction) + right * fraction; } static float widget_point(widget_t *w, float fraction){ return interpolate(w->view_start, w->view_endx, fraction); } static void widget_left_motion_handler(widget_t *w, wevent_t *we){ int p = widget_point(w, we->x); p = clip(p, 0, w->elements); // clip to valid range if (p > w->sel_endx) w->sel_endx = p; // check order w->sel_start = p; } static void widget_right_motion_handler(widget_t *w, wevent_t *we){ int p = widget_point(w, we->x); p = clip(p, 0, w->elements); // clip to valid range if (p < w->sel_start) w->sel_start = p; // check order w->sel_endx = p; } // set viewport static void widget_viewport(widget_t *w, int start, int endx){ if (start > endx){ post("WARNING: start > endx"); } else { w->view_start = start; w->view_endx = endx; } } static void widget_middle_motion_handler(widget_t *w, wevent_t *we){ /* compute zoom : (old) map (bottom->top) == (0->1) to (1 -> full_level) similar relative map */ w->zoom = fclip(w->last_zoom + (we->y - w->last_press_y)/4, 0, 1); float scale = widget_samples_per_pixel(w); //post("zoom %f, scale = %f", w->zoom, scale); /* point from click */ int point = w->point; /* compute new viewport */ float range = ((float)(w->width)) * scale; int o1 = range * we->x; int o2 = range - o1; int sel_start = point - o1; int sel_endx = point + o2; widget_viewport(w, sel_start, sel_endx); } static void widget_motionhandler(widget_t *w, wevent_t *we){ if (w->motionhandler) ((void(*)(widget_t *w, wevent_t *we))w->motionhandler)(w, we); } /* event handler left button = selection start right button = selection end middle button = move (x) / zoom (y) */ static void widget_event(widget_t *w, wevent_t *we){ switch(we->type){ case PRESS: /* store coordinates */ w->last_press_x = we->x; w->last_press_y = we->y; w->point = interpolate(w->view_start, w->view_endx, we->x); w->last_zoom = w->zoom; /* link handler */ switch(we->button){ case 0: w->motionhandler = widget_left_motion_handler; break; case 2: w->motionhandler = widget_right_motion_handler; break; case 1: w->motionhandler = widget_middle_motion_handler; break; default: w->motionhandler = 0; break; case 4: w->gain *= (95.0 / 100.0); goto done; case 3: w->gain *= (100.0 / 95.0); goto done; } /* fallthrough */ case MOTION: widget_motionhandler(w, we); break; case KPRESS: //post("key %d", we->key); switch(we->key){ case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': break; case 'r': /* refresh cache on next redraw */ widget_free_multires(w); break; case 'a': /* select all */ w->sel_start = 0; w->sel_endx = w->elements; break; case 'f': /* full view */ widget_set_view(w, 0, w->elements); break; case 's': /* selection view */ if (w->sel_start != w->sel_endx){ // ignore if no selection widget_set_view(w, w->sel_start, w->sel_endx); } break; case 'F': glutFullScreen(); break; case 'm': /* move selection */ { int length = w->sel_endx - w->sel_start; int start = interpolate(w->view_start, w->view_endx, we->x); widget_set_selection(w, start, start+length); } break; default: // HACK: this is really ugly, but serves well until pd release // which has thread compatible stack checking w->key = we->key; break; } } done: need_redraw = 1; } /* EVENT->WIDGET DISPATCHING */ // translate window (pixel) coordinates to relative floats static void wevent_wcoord(wevent_t *we, float x, float y){ we->x = x / main_width; we->y = (main_height - y) / main_height; } // dispatch to widget based on y coord // and re-adjust y-coord to full widget scale static void wevent_dispatch(wevent_t *we){ static int last_widget = 0; widget_t *w = 0; int i = 0; int widget; /* find widget */ if (we->type == MOTION){ widget = last_widget; } else { widget = (int)(((float)nb_widgets) * we->y); } last_widget = widget; w = widget_by_index(widget); if (!w) goto error; we->w = w; /* transform event coordinates */ we->y -= ((float)widget) / ((float)nb_widgets); we->y *= ((float)nb_widgets); /* call handler */ widget_event(w, we); return; error: we->w = 0; } /* GLUT CALLBACKS */ static void do_display(void){ widget_t *w = widgets; glClear(GL_COLOR_BUFFER_BIT); while(w){ widget_display(w); w = w->next; } glFlush(); glutSwapBuffers(); need_redraw = 0; } static void do_reshape(int width, int height){ /* get args */ main_width = width; main_height = height; if (!nb_widgets) goto done; int x = 0; int y = 0; int slice = main_height / nb_widgets; widget_t *w = widgets; for(;w;w=w->next){ w->x = x; w->y = y; w->width = width; w->height = (w->next) ? slice : (height - y); y += slice; } done: need_reshape = 0; } static void do_idle(void){ if (need_reshape) { do_reshape(main_width, main_height); need_redraw = 1; } if (need_redraw){ do_display(); } usleep(20000); // 50 fps } static void do_mouse(int button, int state, int x, int y) { wevent_t we; we.type = state ? RELEASE : PRESS; we.button = button; wevent_wcoord(&we, x, y); wevent_dispatch(&we); } static void do_moved_mouse(int x, int y) { wevent_t we; we.type = MOTION; wevent_wcoord(&we, x, y); // translate coordinates wevent_dispatch(&we); // translate and dispatch event } static void do_keyboard(unsigned char thekey, int mousex, int mousey){ wevent_t we; we.type = KPRESS; we.key = thekey; wevent_wcoord(&we, mousex, mousey); wevent_dispatch(&we); } /* GLUT THREAD */ void *thread_main(void *x){ char *argv[] = {"editor-window"}; int argc = 1; glutInit(&argc, argv); glutInitDisplayMode(GLUT_DOUBLE|GLUT_RGB); glutInitWindowSize(main_width, main_height); glutCreateWindow("wvvw"); glutDisplayFunc(do_display); glutReshapeFunc(do_reshape); glutMouseFunc(do_mouse); glutMotionFunc(do_moved_mouse); glutKeyboardFunc(do_keyboard); glutIdleFunc(do_idle); glutMainLoop(); return 0; } /* PD OBJECT */ static void wvvw_poll(widget_t *x){ if (x->key){ char sym[] = {x->key, 0}; t_atom argv[2]; SETFLOAT(argv+0, x->sel_start); SETFLOAT(argv+1, (x->sel_endx - x->sel_start)); /* send out */ outlet_anything(x->outlet, gensym(sym), 2, argv); /* mark read */ x->key = 0; } clock_delay(x->clock, 10.f); } t_class *wvvw_class = 0; static void wvvw_free(widget_t *x){ clock_unset(x->clock); clock_free(x->clock); widget_unregister(x); widget_deinit(x); } static void *wvvw_new(t_symbol *name){ widget_t *x = (widget_t *)pd_new(wvvw_class); widget_init(x, name); x->outlet = outlet_new(&x->obj, gensym("anything")); x->clock = clock_new(x, (t_method)wvvw_poll); wvvw_poll(x); widget_register(x); return x; } static void wvvw_redraw(widget_t *w){ w->need_refresh = 1; need_redraw = 1; } /* setters */ static void wvvw_array(widget_t *w, t_symbol *s){ w->name = s; wvvw_redraw(w); } static void wvvw_needle(widget_t *w, float f){ w->needle = f; need_redraw = 1; } static void wvvw_view(widget_t *w, float fstart, float flength){ if (!w->pd_event.type){ // drop if prev event not yet read w->pd_event.arg1 = fstart; w->pd_event.arg2 = flength; w->pd_event.type = PD_VIEW; wvvw_redraw(w); } } static void wvvw_select(widget_t *w, float fstart, float flength){ if (!w->pd_event.type){ w->pd_event.arg1 = fstart; w->pd_event.arg2 = flength; w->pd_event.type = PD_SELECT; wvvw_redraw(w); } } /* GLOBAL SETUP */ void wvvw_setup(void){ wvvw_class = class_new(gensym("wvvw"), (t_newmethod)wvvw_new, (t_method)wvvw_free, sizeof(widget_t), 0, A_DEFSYMBOL, 0); class_addmethod(wvvw_class, (t_method)wvvw_array, gensym("array"), A_SYMBOL, 0); class_addmethod(wvvw_class, (t_method)wvvw_redraw, gensym("refresh"), 0); class_addmethod(wvvw_class, (t_method)wvvw_needle, gensym("needle"), A_FLOAT, 0); class_addmethod(wvvw_class, (t_method)wvvw_view, gensym("view"), A_FLOAT, A_FLOAT, 0); class_addmethod(wvvw_class, (t_method)wvvw_select, gensym("select"), A_FLOAT, A_FLOAT, 0); pthread_create(&thread, 0, thread_main, 0); post("WVVW: version " VERSION); }