diff options
author | Alessio Treglia <quadrispro@ubuntu.com> | 2009-10-19 09:55:11 +0200 |
---|---|---|
committer | Alessio Treglia <quadrispro@ubuntu.com> | 2009-10-19 09:55:11 +0200 |
commit | 5cd66eecc95be11cacc5aaf4db8c67a499bb2d4d (patch) | |
tree | f9fe35437c9a69b886676bbdeff692ebc728bec2 /snd-marks.c |
Imported Upstream version 11
Diffstat (limited to 'snd-marks.c')
-rw-r--r-- | snd-marks.c | 3040 |
1 files changed, 3040 insertions, 0 deletions
diff --git a/snd-marks.c b/snd-marks.c new file mode 100644 index 0000000..bcec2ec --- /dev/null +++ b/snd-marks.c @@ -0,0 +1,3040 @@ +#include "snd.h" + +struct mark { + mus_long_t samp; + char *name; + int id, sync; + bool visible; + XEN properties; + int properties_gc_loc; +}; + +static int mark_id_counter = 0; + +int mark_to_int(mark *m) {return(m->id);} + + +mus_long_t mark_sample(mark *m) {return(m->samp);} + + +static int sync_max = 0; + +int mark_sync_max(void) +{ + return(sync_max); +} + + +int mark_sync(mark *m) {return(m->sync);} + +void set_mark_sync(mark *m, int val) +{ + m->sync = val; + if (val > sync_max) + sync_max = val; +} + + +static mark *make_mark_1(mus_long_t samp, const char *name, int id, int sc) +{ + mark *mp; + mp = (mark *)calloc(1, sizeof(mark)); + if (name) mp->name = mus_strdup(name); else mp->name = NULL; + mp->samp = samp; + mp->id = id; + set_mark_sync(mp, sc); + mp->properties_gc_loc = NOT_A_GC_LOC; + mp->properties = XEN_FALSE; + return(mp); +} + + +static mark *make_mark(mus_long_t samp, const char *name) +{ + return(make_mark_1(samp, name, mark_id_counter++, 0)); +} + + +static mark *copy_mark(mark *m) +{ + return(make_mark_1(m->samp, m->name, m->id, m->sync)); +} + + +static mark *free_mark(mark *mp) +{ + if (mp) + { + if (mp->name) free(mp->name); + if (mp->properties_gc_loc != NOT_A_GC_LOC) + { + snd_unprotect_at(mp->properties_gc_loc); + mp->properties_gc_loc = NOT_A_GC_LOC; + mp->properties = XEN_FALSE; + } + free(mp); + } + return(NULL); +} + + +static mark *map_over_marks(chan_info *cp, mark *(*func)(chan_info *ncp, mark *mp1, void *p1), void *m, read_direction_t direction) +{ + ed_list *ed; + ed = cp->edits[cp->edit_ctr]; + if ((ed) && (ed->marks)) + { + int marks; + mark **mps; + mps = ed->marks; + marks = ed->mark_ctr; + if (mps) + { + mark *mp; + int i; + if (direction == READ_FORWARD) + { + for (i = 0; i <= marks; i++) + if (mps[i]) /* can be null if we're running delete_marks at a higher level and draw-mark-hook is active */ + { + mp = (*func)(cp, mps[i], m); + if (mp) return(mp); + } + } + else + { + for (i = marks; i >= 0; i--) + if (mps[i]) + { + mp = (*func)(cp, mps[i], m); + if (mp) return(mp); + } + } + } + } + return(NULL); +} + + +static mark *map_over_marks_with_int(chan_info *cp, mark *(*func)(chan_info *ncp, mark *mp1, int val1), int value, read_direction_t direction) +{ + ed_list *ed; + ed = cp->edits[cp->edit_ctr]; + if (ed->marks) + { + int marks; + mark **mps; + mps = ed->marks; + marks = ed->mark_ctr; + if (mps) + { + mark *mp; + int i; + if (direction == READ_FORWARD) + { + for (i = 0; i <= marks; i++) + if (mps[i]) /* can be null if we're running delete_marks at a higher level and draw-mark-hook is active */ + { + mp = (*func)(cp, mps[i], value); + if (mp) return(mp); + } + } + else + { + for (i = marks; i >= 0; i--) + if (mps[i]) + { + mp = (*func)(cp, mps[i], value); + if (mp) return(mp); + } + } + } + } + return(NULL); +} + + +static mark *find_mark_id_1(chan_info *cp, mark *mp, int id) +{ + if (mp->id == id) + return(mp); + return(NULL); +} + + +static mark *find_mark_from_id(int id, chan_info **cps, int pos) +{ + int i; + for (i = 0; i < ss->max_sounds; i++) + { + chan_info *cp; + snd_info *sp; + int j; + sp = ss->sounds[i]; + if ((sp) && (sp->inuse == SOUND_NORMAL)) + for (j = 0; j<(sp->nchans); j++) + if ((cp = ((chan_info *)(sp->chans[j])))) + { + if (pos < cp->edit_size) /* pos can be -1 */ + { + int old_pos; + mark *mp; + old_pos = cp->edit_ctr; + if (pos >= 0) cp->edit_ctr = pos; + /* memoization would have to be done here where we know cp->edit_ctr */ + mp = map_over_marks_with_int(cp, find_mark_id_1, id, READ_FORWARD); + cp->edit_ctr = old_pos; + if (mp) + { + if (cps) cps[0] = cp; + return(mp); + } + } + } + } + return(NULL); +} + + +mus_long_t mark_id_to_sample(int id) +{ + mark *m; + m = find_mark_from_id(id, NULL, AT_CURRENT_EDIT_POSITION); + if (m) + return(m->samp); + return(-1); +} + + +static mark *find_named_mark_1(chan_info *cp, mark *mp, void *uname) +{ + char *name = (char *)uname; + if ((mp->name) && (mus_strcmp(mp->name, name))) return(mp); + else return(NULL); +} + + +static mark *find_named_mark(chan_info *cp, const char *name) +{ + return(map_over_marks(cp, find_named_mark_1, (void *)name, READ_FORWARD)); +} + + +static mark *find_previous_mark_1(chan_info *cp, mark *mp, void *m) +{ + if (mp->samp < (*((mus_long_t *)m))) + return(mp); + return(NULL); +} + + +static mark *find_previous_mark(mus_long_t current_sample, chan_info *cp) +{ + return(map_over_marks(cp, find_previous_mark_1, (void *)(¤t_sample), READ_BACKWARD)); +} + + +static mark *find_next_mark_1(chan_info *cp, mark *mp, void *m) +{ + if (mp->samp > (*((mus_long_t *)m))) + return(mp); + return(NULL); +} + + +static mark *find_next_mark(mus_long_t current_sample, chan_info *cp) +{ + return(map_over_marks(cp, find_next_mark_1, (void *)(¤t_sample), READ_FORWARD)); +} + + +static mark* marks_off_1(chan_info *cp, mark *mp, void *ignore) +{ + mp->visible = false; + return(NULL); +} + + +void marks_off(chan_info *cp) +{ + map_over_marks(cp, marks_off_1, NULL, READ_FORWARD); +} + + +static XEN draw_mark_hook; + +static void draw_mark_1(chan_info *cp, axis_info *ap, mark *mp, bool show) +{ + /* fields are samp and name */ + if (!(cp->graph_time_p)) return; + if (XEN_HOOKED(draw_mark_hook)) + { + XEN res = XEN_FALSE; + res = run_progn_hook(draw_mark_hook, + XEN_LIST_1(new_xen_mark(mp->id)), + S_draw_mark_hook); + if (XEN_TRUE_P(res)) + { + mp->visible = show; + return; + } + } + show_mark(cp, ap, mp, show); +} + + +static void draw_mark(chan_info *cp, axis_info *ap, mark *mp) +{ + if (!(mp->visible)) draw_mark_1(cp, ap, mp, true); +} + + +static void erase_mark(chan_info *cp, axis_info *ap, mark *mp) +{ + if (mp->visible) draw_mark_1(cp, ap, mp, false); +} + + +typedef struct { + int x, y; + mark *all_done; +} mdata; + +static mark *hit_mark_1(chan_info *cp, mark *mp, void *m) +{ + int mx; + mdata *md = (mdata *)m; + axis_info *ap; + + ap = cp->axis; + if (mp->samp < ap->losamp) return(NULL); + if (mp->samp > ap->hisamp) return(md->all_done); /* grf_x clips so we can be confused by off-screen marks */ + + mx = grf_x((double)(mp->samp) / (double)SND_SRATE(cp->sound), cp->axis); + if (mx > (md->x + mark_tag_width(ss))) return(md->all_done); /* past it */ + if (mx < (md->x - mark_tag_width(ss))) return(NULL); /* before it */ + if (mp->name == NULL) /* check y if unnamed */ + { + if ((md->y >= ap->y_axis_y1) && + (md->y <= (ap->y_axis_y1 + mark_tag_height(ss)))) + return(mp); + else return(NULL); + } + else return(mp); +} + + +static mark *hit_triangle_1(chan_info *cp, mark *mp, void *m) +{ + /* m->samp = raw x mouse position */ + /* we're going left to right, so after passing x, give up */ + int mx, y; + mdata *md = (mdata *)m; + axis_info *ap; + + ap = cp->axis; + if (mp->samp < ap->losamp) return(NULL); + if (mp->samp > ap->hisamp) return(md->all_done); /* grf_x clips so we can be confused by off-screen marks */ + + mx = grf_x((double)(mp->samp) / (double)SND_SRATE(cp->sound), cp->axis); + if (mx > md->x) return(md->all_done); + if ((mx + MARK_PLAY_ARROW_SIZE) < md->x) return(NULL); + y = md->y - ap->y_axis_y0 - MARK_PLAY_ARROW_SIZE; + if (y < 0) y = -y; + if ((mx + MARK_PLAY_ARROW_SIZE - y) >= md->x) return(mp); + /* the last is assuming the triangle shape for hit detection */ + return(NULL); +} + + +mark *hit_triangle(chan_info *cp, int x, int y) +{ + if (cp->edits[cp->edit_ctr]->marks) + { + axis_info *ap; + ap = cp->axis; + /* first check that we're in the bottom portion of the graph where the mark triangles are */ + if ((y >= ap->y_axis_y0) && + (y <= (ap->y_axis_y0 + 2 * MARK_PLAY_ARROW_SIZE))) + { + mark *mp; + mdata *md; + md = (mdata *)calloc(2, sizeof(mdata)); + md->x = x; + md->y = y; + md->all_done = (mark *)1; + mp = map_over_marks(cp, hit_triangle_1, (void *)md, READ_FORWARD); + if (mp == (mark *)1) mp = NULL; + free(md); + return(mp); + } + } + return(NULL); +} + + +static XEN mark_drag_hook; +static XEN mark_hook; /* add, delete, move */ + +static bool watching_mouse = false; /* this is tracking axis moves */ +static int last_mouse_x = 0; +static mark *moving_mark = NULL; /* used only while "off-screen" during axis moves */ + +static timeout_result_t watch_mouse_button = 0; + +#if (!USE_NO_GUI) +static void move_axis_to_track_mark(chan_info *cp); + +static TIMEOUT_TYPE watch_mouse(TIMEOUT_ARGS) +{ + chan_info *cp = (chan_info *)context; + if (watch_mouse_button) + { + move_axis_to_track_mark(cp); + watch_mouse_button = CALL_TIMEOUT(watch_mouse, 50, cp); + } + TIMEOUT_RESULT +} +#endif + + +static void start_mark_watching(chan_info *cp, mark *mp) +{ + moving_mark = mp; + watch_mouse_button = CALL_TIMEOUT(watch_mouse, 50, cp); + watching_mouse = true; +} + + +static void cancel_mark_watch(chan_info *cp) +{ + if (watch_mouse_button) TIMEOUT_REMOVE(watch_mouse_button); + watch_mouse_button = 0; + watching_mouse = false; + moving_mark = NULL; +} + + +static bool move_mark_1(chan_info *cp, mark *mp, int x) +{ + axis_info *ap; + int nx; + mus_long_t samps; + bool redraw; + ap = cp->axis; + redraw = (!watching_mouse); + if ((x > ap->x_axis_x1) || (x < ap->x_axis_x0)) + { + if (watching_mouse) + { + if (((x < ap->x_axis_x0) && (ap->x0 == ap->xmin)) || + ((x > ap->x_axis_x1) && (ap->x1 == ap->xmax))) + return(false); + } + nx = move_axis(cp, ap, x); + if (!watching_mouse) start_mark_watching(cp, mp); + } + else + { + erase_mark(cp, ap, mp); + nx = x; + if (watching_mouse) + { + cancel_mark_watch(cp); + redraw = false; + } + } + mp->samp = (mus_long_t)(ungrf_x(ap, nx) * SND_SRATE(cp->sound)); + if (mp->samp < 0) mp->samp = 0; + samps = CURRENT_SAMPLES(cp); + if (mp->samp > samps) mp->samp = samps; + if (XEN_HOOKED(mark_drag_hook)) + run_hook(mark_drag_hook, + XEN_LIST_1(new_xen_mark(mp->id)), + S_mark_drag_hook); + return(redraw); +} + + +static int compare_mark_samps(const void *mp1, const void *mp2) +{ + mark *m1, *m2; + m1 = (mark *)(*((mark **)mp1)); + m2 = (mark *)(*((mark **)mp2)); + if (m1->samp < m2->samp) return(-1); + if (m1->samp == m2->samp) return(0); + return(1); +} + + +static void sort_marks(chan_info *cp) +{ + ed_list *ed; + ed = cp->edits[cp->edit_ctr]; + qsort((void *)(ed->marks), ed->mark_ctr + 1, sizeof(mark *), compare_mark_samps); +} + + +static int prev_cx = -1; + +mus_long_t move_play_mark(chan_info *cp, mus_long_t *mc, int cx) +{ + /* mc = mouse loc sampwise return samps updating mc */ + mus_long_t cur_mc; + axis_info *ap; + ap = cp->axis; + if (prev_cx > 0) show_mark_triangle(cp, prev_cx); + prev_cx = cx; + show_mark_triangle(cp, cx); + cur_mc = (*mc); + (*mc) = (mus_long_t)(ungrf_x(ap, cx) * SND_SRATE(cp->sound)); + return((*mc) - cur_mc); +} + + +void finish_moving_play_mark(chan_info *cp) +{ + snd_info *sp; + sp = cp->sound; + show_mark_triangle(cp, prev_cx); + prev_cx = -1; + sp->speed_control = 1.0; +} + + +typedef enum {MARK_ADD, MARK_DELETE, MARK_MOVE, MARKS_DELETE, MARK_RELEASE} mark_hook_reason_t; + +static void run_mark_hook(chan_info *cp, int id, mark_hook_reason_t reason) +{ + /* called after the mark list has been made consistent */ + if (XEN_HOOKED(mark_hook)) + run_hook(mark_hook, + XEN_LIST_4(new_xen_mark(id), + C_INT_TO_XEN_SOUND(cp->sound->index), + C_TO_XEN_INT(cp->chan), + C_TO_XEN_INT((int)reason)), + S_mark_hook); + run_watchers(); +} + + +#define MARKS_ALLOC_SIZE 4 + +mark *add_mark(mus_long_t samp, const char *name, chan_info *cp) +{ + int i, med; + mark **mps; + ed_list *ed; + + ed = cp->edits[cp->edit_ctr]; + if (!(ed->marks)) + { + ed->mark_size = MARKS_ALLOC_SIZE; + ed->mark_ctr = -1; + ed->marks = (mark **)calloc(MARKS_ALLOC_SIZE, sizeof(mark *)); + } + + ed->mark_ctr++; + if (ed->mark_ctr >= ed->mark_size) + { + ed->mark_size += MARKS_ALLOC_SIZE; + ed->marks = (mark **)realloc(ed->marks, ed->mark_size * sizeof(mark *)); + for (i = ed->mark_size - MARKS_ALLOC_SIZE; i < ed->mark_size; i++) ed->marks[i] = NULL; + } + + mps = ed->marks; + med = ed->mark_ctr; + + if (med == 0) + { + if (mps[0]) free_mark(mps[0]); + mps[0] = make_mark(samp, name); + run_mark_hook(cp, mps[0]->id, MARK_ADD); + return(mps[0]); + } + else + { + for (i = 0; i < med; i++) /* not <= because we pre-incremented above */ + { + mark *mp; + mp = mps[i]; + if (samp < mp->samp) + { + int j; + if (mps[med]) free_mark(mps[med]); + for (j = med; j > i; j--) + mps[j] = mps[j - 1]; + mps[i] = make_mark(samp, name); + run_mark_hook(cp, mps[i]->id, MARK_ADD); + return(mps[i]); + } + } + /* insert at end */ + if (mps[med]) free_mark(mps[med]); + mps[med] = make_mark(samp, name); + run_mark_hook(cp, mps[med]->id, MARK_ADD); + return(mps[med]); + } +} + + +bool delete_mark_samp(mus_long_t samp, chan_info *cp) +{ + if (cp) + { + ed_list *ed; + ed = cp->edits[cp->edit_ctr]; + if (ed->marks) + { + int i; + mark **mps; + mps = ed->marks; + if (mps) + { + int edm; + edm = ed->mark_ctr; + for (i = 0; i <= edm; i++) + { + mark *mp; + mp = mps[i]; + if (mp->samp == samp) + { + axis_info *ap; + int id = -1; + ap = cp->axis; + if ((mp->samp >= ap->losamp) && (mp->samp <= ap->hisamp)) erase_mark(cp, ap, mp); + id = mp->id; + free_mark(mp); + mps[i] = NULL; + if (i < edm) + { + int j; + for (j = i; j < edm; j++) mps[j] = mps[j + 1]; + mps[edm] = NULL; + } + ed->mark_ctr--; + run_mark_hook(cp, id, MARK_DELETE); + return(true); + } + } + } + } + } + return(false); +} + + +static bool delete_mark_id(int id, chan_info *cp) +{ + if (cp) + { + ed_list *ed; + ed = cp->edits[cp->edit_ctr]; + if (ed->marks) + { + mark **mps; + mps = ed->marks; + if (mps) + { + int i, edm; + edm = ed->mark_ctr; + for (i = 0; i <= edm; i++) + { + mark *mp; + mp = mps[i]; + if (mp->id == id) + { + axis_info *ap; + ap = cp->axis; + if ((mp->samp >= ap->losamp) && (mp->samp <= ap->hisamp)) erase_mark(cp, ap, mp); + free_mark(mp); + mps[i] = NULL; + if (i < edm) + { + int j; + for (j = i; j < edm; j++) mps[j] = mps[j + 1]; + mps[edm] = NULL; + } + ed->mark_ctr--; + run_mark_hook(cp, id, MARK_DELETE); + return(true); + } + } + } + } + } + return(false); +} + + +static void delete_marks(chan_info *cp) +{ + if (cp) + { + ed_list *ed; + ed = cp->edits[cp->edit_ctr]; + if (ed->marks) + { + int i; + mark **mps; + mps = ed->marks; + if (mps) + { + for (i = 0; i < ed->mark_size; i++) + { + mark *mp; + mp = mps[i]; + if (mp) + { + axis_info *ap; + ap = cp->axis; + if ((mp->samp >= ap->losamp) && (mp->samp <= ap->hisamp)) erase_mark(cp, ap, mp); + free_mark(mp); + mps[i] = NULL; + } + } + ed->mark_ctr = -1; + run_mark_hook(cp, -1, MARKS_DELETE); + } + } + } +} + + +void free_mark_list(ed_list *ed) +{ + if (ed->marks) + { + int j; + for (j = 0; j < ed->mark_size; j++) + { + mark *mp; + mp = ed->marks[j]; + if (mp) free_mark(mp); + ed->marks[j] = NULL; + } + free(ed->marks); + ed->marks = NULL; + ed->mark_size = 0; + } +} + + +/* save and restore across update-sound */ + +typedef struct { + mark **marks; + int ctr; + int size; +} mark_info; + +typedef struct { + mark_info **ms; + int size; +} marks_info; + +void *sound_store_marks(snd_info *sp) +{ + int i; + mark_info **res = NULL; + marks_info *rtn = NULL; + res = (mark_info **)calloc(sp->nchans, sizeof(mark_info *)); + rtn = (marks_info *)calloc(1, sizeof(marks_info)); + rtn->ms = res; + rtn->size = sp->nchans; + for (i = 0; i < sp->nchans; i++) + { + chan_info *cp; + cp = sp->chans[i]; + if (cp) + { + ed_list *ed; + ed = cp->edits[cp->edit_ctr]; + if (ed) + { + res[i] = (mark_info *)calloc(1, sizeof(mark_info)); + res[i]->marks = ed->marks; + res[i]->ctr = ed->mark_ctr; + res[i]->size = ed->mark_size; + ed->marks = NULL; + ed->mark_size = 0; + ed->mark_ctr = -1; + } + } + } + return((void *)rtn); +} + + +void sound_restore_marks(snd_info *sp, void *mrk) +{ + marks_info *mrks = (marks_info *)mrk; + if (mrks) + { + int i, lim; + mark_info **marks; + marks = mrks->ms; + lim = mrks->size; + if (sp->nchans < lim) lim = sp->nchans; /* update can change channel number either way */ + for (i = 0; i < lim; i++) + { + if (marks[i]) + { + ed_list *ed; + chan_info *cp; + cp = sp->chans[i]; + ed = cp->edits[0]; + if (ed) + { + ed->marks = marks[i]->marks; + ed->mark_ctr = marks[i]->ctr; + ed->mark_size = marks[i]->size; + } + } + } + for (i = 0; i < mrks->size; i++) + if (marks[i]) free(marks[i]); + /* possible memleak here if chan num has lessened */ + free(marks); + free(mrks); + } +} + + +static mark *find_nth_mark(chan_info *cp, int count) +{ + int i, c; + mus_long_t samp; + mark *mp = NULL; + if ((!cp) || (!cp->edits[cp->edit_ctr]->marks)) return(NULL); + if (count > 0) c = count; else c = -count; + samp = CURSOR(cp); + for (i = 0; i < c; i++) + { + if (count > 0) mp = find_next_mark(samp, cp); + else mp = find_previous_mark(samp, cp); + if (!mp) break; + samp = mp->samp; + } + return(mp); +} + + +bool goto_mark(chan_info *cp, int count) +{ + mark *mp; + if ((!cp) || (!cp->edits[cp->edit_ctr]->marks)) return(false); + mp = find_nth_mark(cp, count); + if (!mp) return(false); + cursor_moveto(cp, mp->samp); + return(true); +} + + +void goto_named_mark(chan_info *cp, const char *name) +{ + mark *mp; + mp = find_named_mark(cp, name); + if (mp) cursor_moveto(cp, mp->samp); +} + + +static mark *active_mark_1(chan_info *cp, mark *mp, void *ignore) +{ + axis_info *ap; + ap = cp->axis; + if ((mp->samp >= ap->losamp) && (mp->samp <= ap->hisamp)) return(mp); + return(NULL); +} + + +mark *active_mark(chan_info *cp) +{ + return(map_over_marks(cp, active_mark_1, NULL, READ_FORWARD)); +} + + +mus_long_t mark_beg(chan_info *cp) +{ + /* called only in snd-chn.c for active zoom */ + mark *mp; + mp = active_mark(cp); + if (mp) return(mp->samp); + return(-1); +} + + +typedef struct { + mus_long_t last_samp; +} dpy_last; + +static mark *display_channel_marks_1(chan_info *cp, mark *mp, void *m) +{ + axis_info *ap; + dpy_last *ls = (dpy_last *)m; + ap = cp->axis; + if (mp->samp > ap->hisamp) return(mp); /* terminates loop */ + if (mp->samp == ls->last_samp) + return(NULL); + /* actually this should notice how wide in samples the mark stem is and avoid any redraw until we get to the next clear pixel */ + else ls->last_samp = mp->samp; /* avoid drawing twice at same point == erase */ + if ((mp->samp >= ap->losamp) && + (mp->samp <= ap->hisamp) && + (mp != moving_mark)) + draw_mark(cp, ap, mp); + return(NULL); +} + + +void display_channel_marks(chan_info *cp) +{ + if ((cp->edits[cp->edit_ctr]->marks) && (cp->show_marks)) + { + dpy_last ls; + ls.last_samp = -1; + map_over_marks(cp, display_channel_marks_1, (void *)(&ls), READ_FORWARD); + } +} + + +void ripple_marks(chan_info *cp, mus_long_t beg, mus_long_t change) +{ + /* if change = 0, just set ptr, else copy and fixup with deletions */ + /* this is called after the tree has been pushed forward, so edit_ctr is ahead of us */ + /* but we don't do anything if no marks */ + + if ((cp) && (cp->edit_ctr > 0)) + { + ed_list *old_ed, *new_ed; + new_ed = cp->edits[cp->edit_ctr]; + old_ed = cp->edits[cp->edit_ctr - 1]; + + if (new_ed->marks) + { + /* release current */ + free_mark_list(new_ed); + } + + /* copy old with position change */ + new_ed->mark_ctr = old_ed->mark_ctr; + new_ed->mark_size = old_ed->mark_size; + if (new_ed->mark_size > 0) + { + new_ed->marks = (mark **)calloc(new_ed->mark_size, sizeof(mark *)); + if (new_ed->mark_ctr >= 0) + { + int i; + mark **mps, **mpo; + mark *mp; + mps = new_ed->marks; + mpo = old_ed->marks; + for (i = 0; i <= new_ed->mark_ctr; i++) + mps[i] = copy_mark(mpo[i]); + if (change < 0) + { + mus_long_t end; + /* if (change < 0) and any marks are between beg and beg+change, they must be deleted */ + end = beg - change - 1; + i = 0; + while (i <= new_ed->mark_ctr) + { + mp = mps[i]; + if ((mp->samp >= beg) && (mp->samp <= end)) /* was mp->samp > beg, ditto end, beg can = end */ + delete_mark_samp(mp->samp, cp); /* changes cp->mark_ctr, hence the while loop? */ + else + { + if (mp->samp > beg) /* don't change marks that precede the point of the change */ + mp->samp += change; + i++; + } + } + } + else + { + if (change > 0) + for (i = 0; i <= new_ed->mark_ctr; i++) + { + mp = mps[i]; + if (mp->samp > beg) mp->samp += change; + } + } + } + } + } +} + + +bool mark_define_region(chan_info *cp, int count) +{ + if ((cp) && (max_regions(ss) > 0)) + { + ed_list *ed; + ed = cp->edits[cp->edit_ctr]; + if (ed->marks) + { + mus_long_t beg; + mark *mp; + beg = CURSOR(cp); + mp = find_nth_mark(cp, count); + if (mp) + { + mus_long_t end; + end = mp->samp; + if (end != beg) + { + mus_long_t ends[1]; + sync_info *si; + int i; + ends[0] = end; + if (end < beg) + { + ends[0] = beg; + beg = end; + } + deactivate_selection(); + si = sync_to_chan(cp); + si->begs[0] = beg; + define_region(si, ends); + for (i = 0; i < si->chans; i++) + { + reactivate_selection(si->cps[i], beg, ends[0]); + update_graph(si->cps[i]); + } + si = free_sync_info(si); + return(true); + } + } + } + } + return(false); +} + + +static mark *reverse_mark_1(chan_info *cp, mark *mp, void *um) +{ + mark *m = (mark *)um; + mp->samp = m->samp - mp->samp; + return(NULL); +} + + +void reverse_marks(chan_info *cp, mus_long_t beg, mus_long_t dur) /* beg -1 for full sound */ +{ + ed_list *ed; + ed = cp->edits[cp->edit_ctr]; + if (ed->marks) + { + mark *m; + mark **mps; + mps = ed->marks; + if (beg == -1) + { + m = make_mark_1(CURRENT_SAMPLES(cp) - 1, NULL, 0, 0); + map_over_marks(cp, reverse_mark_1, (void *)m, READ_FORWARD); + free_mark(m); + } + else + { + mus_long_t end; + int i, marks; + end = beg + dur - 1; + marks = ed->mark_ctr; + for (i = 0; i <= marks; i++) + { + m = mps[i]; + if ((m->samp >= beg) && (m->samp <= end)) + m->samp = end - (m->samp - beg); + } + } + if (ed->mark_ctr >= 0) + qsort((void *)mps, ed->mark_ctr + 1, sizeof(mark *), compare_mark_samps); + } +} + + +void src_marks(chan_info *cp, mus_float_t ratio, mus_long_t old_samps, mus_long_t new_samps, mus_long_t beg, bool over_selection) +{ + ed_list *ed; + ed = cp->edits[cp->edit_ctr]; + if ((ed->marks) && (ed->mark_ctr >= 0)) + { + int i, marks; + mark *m; + mark **mps; + mps = ed->marks; + marks = ed->mark_ctr; + if (!over_selection) + { + for (i = 0; i <= marks; i++) + { + m = mps[i]; + if (ratio > 0.0) + m->samp = (mus_long_t)(m->samp / ratio); + else m->samp = (mus_long_t)((old_samps - 1 - m->samp) / (-ratio)); /* ratio < 0 here */ + } + } + else + { + mus_long_t end; + end = beg + old_samps - 1; + for (i = 0; i <= marks; i++) + { + m = mps[i]; + if ((m->samp >= beg) && (m->samp <= end)) + { + if (ratio > 0.0) + m->samp = beg + (mus_long_t)((m->samp - beg) / ratio); + else m->samp = beg + (mus_long_t)((old_samps - 1 - (m->samp - beg)) / (-ratio)); + } + else + { + if (m->samp > end) + m->samp += (new_samps - old_samps); + } + } + } + if (ratio < 0.0) qsort((void *)mps, marks + 1, sizeof(mark *), compare_mark_samps); + } +} + + +void reset_marks(chan_info *cp, int cur_marks, mus_long_t *samps, mus_long_t end, mus_long_t extension, bool over_selection) +{ + ed_list *ed; + ed = cp->edits[cp->edit_ctr]; + if ((ed->marks) && (ed->mark_ctr >= 0)) + { + int i, marks; + mark *m; + mark **mps; + mps = ed->marks; + marks = ed->mark_ctr; + if (over_selection) + for (i = 0; i <= marks; i++) + { + m = mps[i]; + if (m->samp > end) m->samp += extension; + } + for (i = 0; (i <= marks) && (i < cur_marks); i++) + { + m = mps[i]; + if (samps[i] >= 0) m->samp = samps[i]; + } + qsort((void *)mps, marks + 1, sizeof(mark *), compare_mark_samps); + } +} + + +void ripple_trailing_marks(chan_info *cp, mus_long_t beg, mus_long_t old_len, mus_long_t new_len) +{ + ed_list *ed; + ed = cp->edits[cp->edit_ctr]; + if ((ed->marks) && (ed->mark_ctr >= 0)) + { + int i, marks; + mark **mps; + ripple_marks(cp, 0, 0); + mps = ed->marks; + marks = ed->mark_ctr; + for (i = 0; i <= marks; i++) + { + mark *m; + m = mps[i]; + if (m->samp > (beg + old_len)) m->samp += (new_len - old_len); + } + } +} + + +void swap_marks(chan_info *cp0, chan_info *cp1) +{ + ed_list *ed0, *ed1; + ed0 = cp0->edits[cp0->edit_ctr]; + ed1 = cp1->edits[cp1->edit_ctr]; + if ((ed0->marks) || (ed1->marks)) + { + mark **mps0 = NULL, **mps1 = NULL; + int ctr0 = -1, ctr1 = -1; + int size0 = 0, size1 = 0; + if (ed0->marks) + { + mps0 = ed0->marks; + ctr0 = ed0->mark_ctr; + size0 = ed0->mark_size; + } + if (ed1->marks) + { + mps1 = ed1->marks; + ctr1 = ed1->mark_ctr; + size1 = ed1->mark_size; + } + ed0->marks = mps1; + ed0->mark_ctr = ctr1; + ed0->mark_size = size1; + ed1->marks = mps0; + ed1->mark_ctr = ctr0; + ed1->mark_size = size0; + } +} + + +/* -------------------------------- SYNCD AND DRAGGED MARKS -------------------------------- */ + +typedef struct { + mark **marks; + chan_info **chans; + int marks_size; + int mark_ctr; + int sync; + mus_long_t *initial_samples; +} syncdata; + +static syncdata *make_syncdata(int sync) +{ + syncdata *sd; + sd = (syncdata *)calloc(1, sizeof(syncdata)); + sd->sync = sync; + sd->mark_ctr = 0; + sd->marks_size = 8; + sd->marks = (mark **)calloc(sd->marks_size, sizeof(mark *)); + sd->chans = (chan_info **)calloc(sd->marks_size, sizeof(chan_info *)); + sd->initial_samples = (mus_long_t *)calloc(sd->marks_size, sizeof(mus_long_t)); + return(sd); +} + + +static void add_syncd_mark(syncdata *sd, mark *mp, chan_info *cp) +{ + sd->marks[sd->mark_ctr] = mp; + sd->initial_samples[sd->mark_ctr] = mp->samp; + sd->chans[sd->mark_ctr++] = cp; + if (sd->mark_ctr == sd->marks_size) + { + int i; + sd->marks = (mark **)realloc(sd->marks, sd->marks_size * 2 * sizeof(mark *)); + sd->chans = (chan_info **)realloc(sd->chans, sd->marks_size * 2 * sizeof(chan_info *)); + /* why was initial_samples missing? 2-May-02 */ + sd->initial_samples = (mus_long_t *)realloc(sd->initial_samples, sd->marks_size * 2 * sizeof(mus_long_t)); + for (i = sd->marks_size; i < sd->marks_size * 2; i++) {sd->marks[i] = NULL; sd->chans[i] = NULL;} + sd->marks_size *= 2; + } +} + + +static mark *gather_local_syncd_marks(chan_info *cp, mark *mp, void *usd) +{ + syncdata *sd = (syncdata *)usd; + if (sd->sync == mp->sync) + add_syncd_mark(sd, mp, cp); + return(NULL); +} + + +static void gather_chan_syncd_marks(chan_info *cp, void *sd) +{ + map_over_marks(cp, gather_local_syncd_marks, sd, READ_FORWARD); +} + + +static syncdata *gather_syncd_marks(int sync) +{ + syncdata *sd; + sd = make_syncdata(sync); + for_each_normal_chan_with_void(gather_chan_syncd_marks, (void *)sd); + return(sd); +} + + +static syncdata *free_syncdata(syncdata *sd) +{ + if (sd) + { + if (sd->marks) free(sd->marks); + if (sd->initial_samples) free(sd->initial_samples); + if (sd->chans) free(sd->chans); + free(sd); + } + return(NULL); +} + + +static bool mark_control_clicked = false; /* C-click of mark -> drag data as mark is dragged */ +static mus_long_t mark_initial_sample = 0; +static syncdata *mark_sd = NULL; + +typedef struct { + widget_t graph; + point_t *p0, *p1; + int lastpj; + color_t color; +} mark_context; + +static mark_context **mark_movers = NULL; + +static mark_context *make_mark_context(chan_info *cp) +{ + mark_context *g; + g = (mark_context *)calloc(1, sizeof(mark_context)); + g->graph = channel_graph(cp); + g->color = ss->sgx->mark_color; + return(g); +} + + +static mark_context *free_mark_context(mark_context *ms) +{ + if (ms->p0) {free(ms->p0); ms->p0 = NULL;} + if (ms->p1) {free(ms->p1); ms->p1 = NULL;} + free(ms); + return(NULL); +} + + +static void mark_save_graph(mark_context *ms, int j); +static void make_mark_graph(chan_info *cp, mus_long_t initial_sample, mus_long_t current_sample, int which); + +static void initialize_md_context(int size, chan_info **cps) +{ + int i; + mark_movers = (mark_context **)calloc(size, sizeof(mark_context *)); + for (i = 0; i < size; i++) + { + mark_context *ms; + mark_movers[i] = make_mark_context(cps[i]); + ms = mark_movers[i]; + ms->lastpj = make_graph(cps[i]); + mark_save_graph(ms, ms->lastpj); + } +} + + +static void finalize_md_context(int size) +{ + if (mark_movers) + { + int i; + for (i = 0; i < size; i++) + if (mark_movers[i]) + free_mark_context(mark_movers[i]); + free(mark_movers); + mark_movers = NULL; + } +} + + +mark *hit_mark(chan_info *cp, int x, int y, int key_state) +{ + ed_list *ed; + ed = cp->edits[cp->edit_ctr]; + if ((ed->marks) && (ed->mark_ctr >= 0)) + { + axis_info *ap; + ap = cp->axis; + /* first check that we're in the top portion of the graph where the mark tabs are */ + if ((y >= ap->y_axis_y1) && + (y <= (ap->y_axis_y1 + mark_tag_height(ss) + 10))) /* + 10 for named marks -- checked again later */ + { + mark *mp; + mdata *md; + md = (mdata *)calloc(1, sizeof(mdata)); + md->x = x; + md->y = y; + md->all_done = (mark *)1; + mp = map_over_marks(cp, hit_mark_1, (void *)md, READ_FORWARD); + if (mp == (mark *)1) mp = NULL; + free(md); + if (mp) + { + mark_control_clicked = (key_state & snd_ControlMask); + if (mp->sync != 0) + { + if (mark_sd) mark_sd = free_syncdata(mark_sd); + mark_sd = gather_syncd_marks(mp->sync); + } + if (mark_control_clicked) + { + mark_initial_sample = mp->samp; + if (mark_sd) + { + if ((mark_sd->mark_ctr > 1) && + (mark_sd->marks[0] != mp)) + { + mark *tm; + int loc = 1; + mus_long_t ts; + chan_info *tc; + for (loc = 1; loc < mark_sd->mark_ctr; loc++) + if (mark_sd->marks[loc] == mp) break; + if (loc < mark_sd->mark_ctr) + { + tm = mark_sd->marks[0]; + ts = mark_sd->initial_samples[0]; + tc = mark_sd->chans[0]; + mark_sd->marks[0] = mark_sd->marks[loc]; + mark_sd->initial_samples[0] = mark_sd->initial_samples[loc]; + mark_sd->chans[0] = mark_sd->chans[loc]; + mark_sd->marks[loc] = tm; + mark_sd->initial_samples[loc] = ts; + mark_sd->chans[loc] = tc; + } + } + initialize_md_context(mark_sd->mark_ctr, mark_sd->chans); + } + else initialize_md_context(1, &cp); + } + } + return(mp); + } + } + return(NULL); +} + + +#if (!USE_NO_GUI) + +static void allocate_erase_grf_points(mark_context *ms) +{ + if (ms->p0 == NULL) + { + ms->p0 = (point_t *)calloc(POINT_BUFFER_SIZE, sizeof(point_t)); + ms->p1 = (point_t *)calloc(POINT_BUFFER_SIZE, sizeof(point_t)); + } +} + + +static void backup_erase_grf_points(mark_context *ms, int nj) +{ + ms->lastpj = nj; + memcpy((void *)(ms->p0), (void *)get_grf_points(), nj * sizeof(point_t)); + memcpy((void *)(ms->p1), (void *)get_grf_points1(), nj * sizeof(point_t)); +} + + +static void mark_save_graph(mark_context *ms, int j) +{ + allocate_erase_grf_points(ms); + backup_erase_grf_points(ms, j); +} + + +static void erase_and_draw_grf_points(mark_context *ms, chan_info *cp, int nj) +{ + chan_context *cx; + axis_context *ax; + point_t *points; +#if USE_MOTIF + GC draw_gc, undraw_gc; +#else + gc_t *draw_gc, *undraw_gc; +#endif + points = get_grf_points(); + cx = cp->tcgx; + if (!cx) cx = cp->cgx; + ax = cx->ax; + undraw_gc = erase_GC(cp); + draw_gc = copy_GC(cp); + if (cp->time_graph_style == GRAPH_LINES) + { + ax->gc = undraw_gc; + draw_lines(ax, ms->p0, ms->lastpj); + ax->gc = draw_gc; + draw_lines(ax, points, nj); + } + else + { + ax->gc = undraw_gc; + draw_points(ax, ms->p0, ms->lastpj, cp->dot_size); + ax->gc = draw_gc; + draw_points(ax, points, nj, cp->dot_size); + } + backup_erase_grf_points(ms, nj); + ax->gc = draw_gc; +} + + +static void erase_and_draw_both_grf_points(mark_context *ms, chan_info *cp, int nj) +{ + chan_context *cx; + axis_context *ax; + point_t *points, *points1; +#if USE_MOTIF + GC draw_gc, undraw_gc; +#else + gc_t *draw_gc, *undraw_gc; +#endif + points = get_grf_points(); + points1 = get_grf_points1(); + cx = cp->tcgx; + if (!cx) cx = cp->cgx; + ax = cx->ax; + undraw_gc = erase_GC(cp); + draw_gc = copy_GC(cp); + if (cp->time_graph_style == GRAPH_LINES) + { + ax->gc = undraw_gc; + draw_lines(ax, ms->p0, ms->lastpj); + draw_lines(ax, ms->p1, ms->lastpj); + ax->gc = draw_gc; + draw_lines(ax, points, nj); + draw_lines(ax, points1, nj); + } + else + { + ax->gc = undraw_gc; + draw_points(ax, ms->p0, ms->lastpj, cp->dot_size); + draw_points(ax, ms->p1, ms->lastpj, cp->dot_size); + ax->gc = draw_gc; + draw_points(ax, points, nj, cp->dot_size); + draw_points(ax, points1, nj, cp->dot_size); + } + backup_erase_grf_points(ms, nj); + ax->gc = draw_gc; +} +#else +static void mark_save_graph(mark_context *ms, int j) {} +#endif + + +static bool move_syncd_mark(chan_info *cp, mark *m, int x) +{ + mus_long_t old_samp, diff; + bool redraw; + old_samp = m->samp; + redraw = move_mark_1(cp, m, x); + diff = m->samp - old_samp; + if (diff != 0) + { + if ((mark_sd) && (mark_sd->mark_ctr > 1)) + { + int i; + for (i = 0; i < mark_sd->mark_ctr; i++) + { + mark *mp; + mp = mark_sd->marks[i]; + if (mp != m) + { + axis_info *ap; + mus_long_t samps; + chan_info *ncp; + ncp = mark_sd->chans[i]; + ap = ncp->axis; + if ((mp->samp >= ap->losamp) && + (mp->samp <= ap->hisamp)) + erase_mark(ncp, ap, mp); + mp->samp += diff; + if (mp->samp < 0) mp->samp = 0; + samps = CURRENT_SAMPLES(ncp); + if (mp->samp > samps) mp->samp = samps; + if (mark_control_clicked) + make_mark_graph(ncp, mark_sd->initial_samples[i], mp->samp, i); + if ((mp->samp >= ap->losamp) && + (mp->samp <= ap->hisamp)) + draw_mark(ncp, ap, mp); + } + } + } + } + return(redraw); +} + + +#if (!USE_NO_GUI) +static void move_axis_to_track_mark(chan_info *cp) +{ + if (moving_mark) + { + bool redraw; + if (moving_mark->sync) + redraw = move_syncd_mark(cp, moving_mark, last_mouse_x); + else redraw = move_mark_1(cp, moving_mark, last_mouse_x); + if (redraw) draw_mark(cp, cp->axis, moving_mark); + } +} +#endif + + +void move_mark(chan_info *cp, mark *mp, int x) /* from mouse drag callback in snd-chn.c, called whenever mark is visible */ +{ + bool redraw; + last_mouse_x = x; + if (mp->sync) + redraw = move_syncd_mark(cp, mp, x); + else redraw = move_mark_1(cp, mp, x); + if (mark_control_clicked) + make_mark_graph(cp, mark_initial_sample, mp->samp, 0); + if (redraw) draw_mark(cp, cp->axis, mp); +} + + +static void edit_dragged_mark(chan_info *cp, mark *m, mus_long_t initial_sample) +{ + /* edit -- initial_sample is where we were when the drag started, ended at m->samp */ + mus_long_t num, mark_final_sample; + int id; + mark *new_m; + + mark_final_sample = m->samp; + num = mark_final_sample - initial_sample; + m->samp = initial_sample; + id = m->id; + + if (num > 0) + extend_with_zeros(cp, initial_sample, num, cp->edit_ctr, "drag mark"); + /* at this point, old mark pointer is irrelevant (it lives in the previous edit history list) */ + /* but since the ripple didn't touch it, we need to move it forward to reflect the insertion */ + else + if (num < 0) + { + new_m = map_over_marks_with_int(cp, find_mark_id_1, id, READ_FORWARD); + new_m->samp = initial_sample; + delete_samples(mark_final_sample, -num, cp, cp->edit_ctr); + } + + if (num != 0) + { + new_m = map_over_marks_with_int(cp, find_mark_id_1, id, READ_FORWARD); + new_m->samp = mark_final_sample; + update_graph(cp); + } +} + + +void finish_moving_mark(chan_info *cp, mark *m) /* button release called from snd-chn.c */ +{ + if (watching_mouse) cancel_mark_watch(cp); + if ((m->sync != 0) && (mark_sd)) + { + int i; + if (XEN_HOOKED(mark_hook)) + for (i = 0; i < mark_sd->mark_ctr; i++) + run_mark_hook(mark_sd->chans[i], mark_sd->marks[i]->id, MARK_RELEASE); + if (mark_control_clicked) + { + for (i = mark_sd->mark_ctr - 1; i >= 0; i--) + { + /* do the edits in reverse order on the assumption that marks sharing a channel were ordered to begin with, + * so they'll happen in reverse order here, so the lower sample edits in rippling won't affect the higher + */ + mark *sdm; + sdm = mark_sd->marks[i]; + edit_dragged_mark(mark_sd->chans[i], sdm, mark_sd->initial_samples[i]); + } + finalize_md_context(mark_sd->mark_ctr); + } + for (i = 0; i < mark_sd->mark_ctr; i++) + if (mark_sd->chans[i]) + { + int j; + sort_marks(mark_sd->chans[i]); /* resort marks in case movement scrambled them */ + for (j = i + 1; j < mark_sd->mark_ctr; j++) /* only sort each channel once */ + if (mark_sd->chans[j] == mark_sd->chans[i]) + mark_sd->chans[j] = NULL; + } + } + else + { + run_mark_hook(cp, m->id, MARK_RELEASE); + if (mark_control_clicked) + { + edit_dragged_mark(cp, m, mark_initial_sample); + finalize_md_context(1); + } + sort_marks(cp); + } + if (mark_sd) mark_sd = free_syncdata(mark_sd); +} + + +void play_syncd_mark(chan_info *cp, mark *m) +{ + syncdata *sd; + sd = gather_syncd_marks(m->sync); + if ((sd) && (sd->mark_ctr > 0)) + play_channels(sd->chans, sd->mark_ctr, sd->initial_samples, NULL, IN_BACKGROUND, + C_TO_XEN_INT(AT_CURRENT_EDIT_POSITION), false, "play sync'd mark", 0); + if (sd) free_syncdata(sd); +} + + +static void make_mark_graph(chan_info *cp, mus_long_t initial_sample, mus_long_t current_sample, int which) +{ +#if (!USE_NO_GUI) + snd_info *sp; + int j = 0; + mus_long_t i, k, samps; + axis_info *ap; + double samples_per_pixel, samp, x, incr; + int pixels; + snd_fd *sf = NULL; + int x_start, x_end; + double start_time = 0.0, cur_srate = 1.0; + + sp = cp->sound; + ap = cp->axis; + cur_srate = (double)SND_SRATE(sp); + ap->losamp = (mus_long_t)(ap->x0 * cur_srate); + if (ap->losamp < 0) ap->losamp = 0; + if (ap->x0 != ((double)(ap->losamp) / cur_srate)) ap->losamp++; + start_time = (double)(ap->losamp) / cur_srate; + ap->hisamp = (mus_long_t)(ap->x1 * cur_srate); + if ((ap->losamp == 0) && (ap->hisamp == 0)) return; + x_start = ap->x_axis_x0; + x_end = ap->x_axis_x1; + samps = ap->hisamp - ap->losamp + 1; + if ((x_start == x_end) && (samps > 10)) return; /* must be too-tiny graph */ + pixels = x_end - x_start; + if (pixels >= POINT_BUFFER_SIZE) pixels = POINT_BUFFER_SIZE - 1; + if ((x_start == x_end) || (samps <= 1)) + samples_per_pixel = 0.01; /* any non-zero value < 1.0 should be ok here */ + else samples_per_pixel = (mus_float_t)((double)(samps - 1) / (mus_float_t)pixels); + + /* this is assuming one dragged mark per channel */ + if ((samples_per_pixel < 5.0) && + (samps < POINT_BUFFER_SIZE)) + { + sf = init_sample_read(ap->losamp, cp, READ_FORWARD); + if (sf == NULL) return; + incr = (double)1.0 / cur_srate; + if (current_sample < initial_sample) + { + for (j = 0, i = ap->losamp, x = start_time; i <= ap->hisamp; i++, j++, x += incr) + { + if (i == current_sample) + for (k = current_sample; k < initial_sample; k++) + read_sample(sf); + set_grf_point(grf_x(x, ap), j, grf_y(read_sample(sf), ap)); + } + } + else + { + for (j = 0, i = ap->losamp, x = start_time; i <= ap->hisamp; i++, j++, x += incr) + { + if ((i < initial_sample) || (i >= current_sample)) + samp = read_sample(sf); + else samp = 0.0; + set_grf_point(grf_x(x, ap), j, grf_y(samp, ap)); + } + } + erase_and_draw_grf_points(mark_movers[which], cp, j); + } + else + { + mus_float_t ymin, ymax, msamp; + int xi; + double xf; + if (peak_env_usable(cp, samples_per_pixel, ap->hisamp, false, cp->edit_ctr, (samps > PEAK_ENV_CUTOFF))) + { + /* needs two sets of pointers and a frame within the amp env: + * sample given mark edit: i and xk + * sample within (original, unedited) amp env: ii and xki (xf) + * frame bounds within amp env if relevant: k and kk + * this is confusing code! + */ + double step, xk, xki; + mus_long_t ii, kk; + peak_env_info *ep; + ep = cp->edits[cp->edit_ctr]->peak_env; + step = samples_per_pixel / (mus_float_t)(ep->samps_per_bin); + xf = (double)(ap->losamp) / (double)(ep->samps_per_bin); + j = 0; + x = ap->x0; + xi = grf_x(x, ap); + i = ap->losamp; + ii = ap->losamp; + xk = i; + xki = (double)(ap->losamp); + while (i <= ap->hisamp) + { + k = (mus_long_t)xf; + kk = (mus_long_t)(xf + step); + if (((current_sample >= initial_sample) && + (i >= initial_sample) && + (i < current_sample)) || + (kk >= ep->peak_env_size)) + { + ymin = 0.0; + ymax = 0.0; + } + else + { + ymin = ep->fmax; + ymax = ep->fmin; + for (; k <= kk; k++) + { + if (ep->data_min[k] < ymin) ymin = ep->data_min[k]; + if (ep->data_max[k] > ymax) ymax = ep->data_max[k]; + } + } + set_grf_points(xi++, j++, + grf_y(ymin, ap), + grf_y(ymax, ap)); + xk += samples_per_pixel; + i = (mus_long_t)xk; + if ((current_sample < initial_sample) && + (ii >= current_sample) && + (ii < initial_sample)) + { + xf = (mus_float_t)((double)initial_sample / (mus_float_t)ep->samps_per_bin); + ii = initial_sample; + xki = (double)initial_sample; + } + else + { + if ((current_sample < initial_sample) || + (i >= current_sample) || + (i < initial_sample)) + { + xf += step; + xki += samples_per_pixel; + ii = (mus_long_t)xki; + } + } + } + erase_and_draw_both_grf_points(mark_movers[which], cp, j); + } + else + { + sf = init_sample_read(ap->losamp, cp, READ_FORWARD); + if (sf == NULL) return; + j = 0; /* graph point counter */ + x = ap->x0; + xi = grf_x(x, ap); + xf = 0.0; /* samples per pixel counter */ + ymin = MIN_INIT; + ymax = MAX_INIT; + if (current_sample < initial_sample) + { + for (i = ap->losamp, xf = 0.0; i <= ap->hisamp; i++) + { + if (i == current_sample) + for (k = current_sample; k < initial_sample; k++) + read_sample(sf); + msamp = read_sample(sf); + if (msamp > ymax) ymax = msamp; + if (msamp < ymin) ymin = msamp; + xf += 1.0; + if (xf > samples_per_pixel) + { + set_grf_points(xi, j, + grf_y(ymin, ap), + grf_y(ymax, ap)); + xi++; + j++; + xf -= samples_per_pixel; + ymin = MIN_INIT; + ymax = MAX_INIT; + } + } + } + else + { + for (i = ap->losamp, xf = 0.0; i <= ap->hisamp; i++) + { + if ((i < initial_sample) || (i >= current_sample)) + msamp = read_sample(sf); + else msamp = 0.0; + if (msamp > ymax) ymax = msamp; + if (msamp < ymin) ymin = msamp; + xf += 1.0; + if (xf > samples_per_pixel) + { + set_grf_points(xi, j, + grf_y(ymin, ap), + grf_y(ymax, ap)); + xi++; + j++; + xf -= samples_per_pixel; + ymin = MIN_INIT; + ymax = MAX_INIT; + } + } + } + erase_and_draw_both_grf_points(mark_movers[which], cp, j); + } + } + free_snd_fd(sf); +#endif +} + + +#if (!USE_NO_GUI) + +/* -------------------------------- display mark -------------------------------- */ + +void show_mark(chan_info *cp, axis_info *ap, mark *mp, bool show) +{ + int len, top, cx, y0, y1; + axis_context *ax; + +#if USE_MOTIF + #define STRING_Y_OFFSET 6 +#else + #define STRING_Y_OFFSET -6 +#endif + + top = ap->y_axis_y1; + y1 = top; + y0 = ap->y_axis_y0; + if (mp->name) top += 10; + cx = grf_x((double)(mp->samp) / (double)SND_SRATE(cp->sound), ap); + + /* split into 3 cases to try to make it more readable */ +#if USE_MOTIF + + ax = mark_tag_context(cp); + if (mp->name) + { + ax->current_font = ss->sgx->peaks_fontstruct->fid; + XSetFont(ax->dp, ax->gc, ss->sgx->peaks_fontstruct->fid); + len = mark_name_width(mp->name); + draw_string(ax, (int)(cx - 0.5 * len), y1 + STRING_Y_OFFSET, mp->name, strlen(mp->name)); + } + fill_rectangle(ax, + cx - mark_tag_width(ss), top, + 2 * mark_tag_width(ss), mark_tag_height(ss)); + draw_line(ax, cx, top + 4, cx, y0); + fill_polygon(ax, 4, + cx, y0, + cx + MARK_PLAY_ARROW_SIZE, y0 + MARK_PLAY_ARROW_SIZE, + cx, y0 + 2 * MARK_PLAY_ARROW_SIZE, + cx, y0); + mp->visible = show; + +#else + /* gtk / cairo */ + +#if USE_CAIRO + { + color_t bg_color, old_color; + int slop = 0; + ax = mark_tag_context(cp); + if (mp->name) + { + ax->current_font = PEAKS_FONT(ss); + len = mark_name_width(mp->name); + if (!show) /* erase mark */ + { + ax = erase_context(cp); + fill_rectangle(ax, (int)(cx - 0.5 * len - 1), top - 15, len + 3, 16); + ax = mark_tag_context(cp); + } + else draw_string(ax, (int)(cx - 0.5 * len), y1 + STRING_Y_OFFSET, mp->name, strlen(mp->name)); + } + if (ax->cr) cairo_destroy(ax->cr); + ax->cr = gdk_cairo_create(ax->wn); + + old_color = ax->gc->fg_color; + if (show) + bg_color = ss->sgx->red; + else + { + if (cp->cgx->selected) + bg_color = ss->sgx->selected_graph_color; + else bg_color = ss->sgx->graph_color; + slop = 1; + } + set_foreground_color(ax, bg_color); + + fill_rectangle(ax, + cx - mark_tag_width(ss), top, + 2 * mark_tag_width(ss), mark_tag_height(ss) + slop); + draw_line(ax, cx, top + 4, cx, y0); + fill_polygon(ax, 4, + cx, y0, + cx + MARK_PLAY_ARROW_SIZE + slop, y0 + MARK_PLAY_ARROW_SIZE, + cx, y0 + 2 * MARK_PLAY_ARROW_SIZE + slop, + cx, y0); + mp->visible = show; + + set_foreground_color(ax, old_color); + make_graph(cp); + } + + +#else + + /* gtk without cairo */ + if (mp->name) + { + len = mark_name_width(mp->name); + if (!show) /* erase mark */ + { + ax = erase_context(cp); + /* gtk and cairo cases need to be separate because we're using XOR (red) in the non-cairo case (so the tag needs to be left unerased) */ + fill_rectangle(ax, (int)(cx - 0.5 * len), top - 15, len + 1, 13); /* this should depend on TINY_FONT height */ + } + else + { + ax = copy_context(cp); + ax->current_font = PEAKS_FONT(ss); + draw_string(ax, (int)(cx - 0.5 * len), y1 + STRING_Y_OFFSET, mp->name, strlen(mp->name)); + } + } + ax = mark_tag_context(cp); + fill_rectangle(ax, + cx - mark_tag_width(ss), top, + 2 * mark_tag_width(ss), mark_tag_height(ss)); + draw_line(ax, cx, top + 4, cx, y0); + fill_polygon(ax, 4, + cx, y0, + cx + MARK_PLAY_ARROW_SIZE, y0 + MARK_PLAY_ARROW_SIZE, + cx, y0 + 2 * MARK_PLAY_ARROW_SIZE, + cx, y0); + mp->visible = show; + +#endif +#endif +} + + +void show_mark_triangle(chan_info *cp, int x) +{ + int y0; + y0 = ((axis_info *)(cp->axis))->y_axis_y0; + draw_polygon(mark_tag_context(cp), 4, + x, y0, + x + MARK_PLAY_ARROW_SIZE, y0 + MARK_PLAY_ARROW_SIZE, + x, y0 + 2 * MARK_PLAY_ARROW_SIZE, + x, y0); +} + +#endif + + + +/* ---------------------------------------- mark object ---------------------------------------- */ + +typedef struct { + int n; +} xen_mark; + + +#define XEN_TO_XEN_MARK(arg) ((xen_mark *)XEN_OBJECT_REF(arg)) + +int xen_mark_to_int(XEN n) +{ + xen_mark *mx; + mx = XEN_TO_XEN_MARK(n); + return(mx->n); +} + + +static XEN_OBJECT_TYPE xen_mark_tag; + +bool xen_mark_p(XEN obj) +{ + return(XEN_OBJECT_TYPE_P(obj, xen_mark_tag)); +} + + +static void xen_mark_free(xen_mark *v) {if (v) free(v);} + +XEN_MAKE_OBJECT_FREE_PROCEDURE(xen_mark, free_xen_mark, xen_mark_free) + + +static char *xen_mark_to_string(xen_mark *v) +{ + #define XEN_MARK_PRINT_BUFFER_SIZE 64 + char *buf; + if (v == NULL) return(NULL); + buf = (char *)calloc(XEN_MARK_PRINT_BUFFER_SIZE, sizeof(char)); + sprintf(buf, "#<mark %d>", v->n); + return(buf); +} + +XEN_MAKE_OBJECT_PRINT_PROCEDURE(xen_mark, print_xen_mark, xen_mark_to_string) + + +#if HAVE_FORTH || HAVE_RUBY +static XEN g_xen_mark_to_string(XEN obj) +{ + char *vstr; + XEN result; + #define S_xen_mark_to_string "mark->string" + + XEN_ASSERT_TYPE(XEN_MARK_P(obj), obj, XEN_ONLY_ARG, S_xen_mark_to_string, "a mark"); + + vstr = xen_mark_to_string(XEN_TO_XEN_MARK(obj)); + result = C_TO_XEN_STRING(vstr); + free(vstr); + return(result); +} +#endif + + +#if (!HAVE_S7) +static bool xen_mark_equalp(xen_mark *v1, xen_mark *v2) +{ + return((v1 == v2) || + (v1->n == v2->n)); +} + +static XEN equalp_xen_mark(XEN obj1, XEN obj2) +{ + if ((!(XEN_MARK_P(obj1))) || (!(XEN_MARK_P(obj2)))) return(XEN_FALSE); + return(xen_return_first(C_TO_XEN_BOOLEAN(xen_mark_equalp(XEN_TO_XEN_MARK(obj1), XEN_TO_XEN_MARK(obj2))), obj1, obj2)); +} +#endif + + +static xen_mark *xen_mark_make(int n) +{ + xen_mark *new_v; + new_v = (xen_mark *)malloc(sizeof(xen_mark)); + new_v->n = n; + return(new_v); +} + + +XEN new_xen_mark(int n) +{ + xen_mark *mx; + if (n < 0) + return(XEN_FALSE); + + mx = xen_mark_make(n); + XEN_MAKE_AND_RETURN_OBJECT(xen_mark_tag, mx, 0, free_xen_mark); +} + + +#if HAVE_S7 +static bool s7_xen_mark_equalp(void *obj1, void *obj2) +{ + return((obj1 == obj2) || + (((xen_mark *)obj1)->n == ((xen_mark *)obj2)->n)); +} +#endif + + +static void init_xen_mark(void) +{ +#if HAVE_S7 + xen_mark_tag = XEN_MAKE_OBJECT_TYPE("<mark>", print_xen_mark, free_xen_mark, s7_xen_mark_equalp, NULL, NULL, NULL, NULL, NULL, NULL); +#else +#if HAVE_RUBY + xen_mark_tag = XEN_MAKE_OBJECT_TYPE("XenMark", sizeof(xen_mark)); +#else + xen_mark_tag = XEN_MAKE_OBJECT_TYPE("Mark", sizeof(xen_mark)); +#endif +#endif + +#if HAVE_GUILE + scm_set_smob_print(xen_mark_tag, print_xen_mark); + scm_set_smob_free(xen_mark_tag, free_xen_mark); + scm_set_smob_equalp(xen_mark_tag, equalp_xen_mark); +#endif + +#if HAVE_FORTH + fth_set_object_inspect(xen_mark_tag, print_xen_mark); + fth_set_object_dump(xen_mark_tag, g_xen_mark_to_string); + fth_set_object_equal(xen_mark_tag, equalp_xen_mark); + fth_set_object_free(xen_mark_tag, free_xen_mark); +#endif + +#if HAVE_RUBY + rb_define_method(xen_mark_tag, "to_s", XEN_PROCEDURE_CAST print_xen_mark, 0); + rb_define_method(xen_mark_tag, "eql?", XEN_PROCEDURE_CAST equalp_xen_mark, 1); + rb_define_method(xen_mark_tag, "==", XEN_PROCEDURE_CAST equalp_xen_mark, 1); + rb_define_method(xen_mark_tag, "to_str", XEN_PROCEDURE_CAST g_xen_mark_to_string, 0); +#endif +} +/* -------------------------------------------------------------------------------- */ + + +static XEN g_integer_to_mark(XEN n) +{ + #define H_integer_to_mark "(" S_integer_to_mark " n) returns a mark object corresponding to the given integer" + XEN_ASSERT_TYPE(XEN_INTEGER_P(n), n, XEN_ONLY_ARG, S_integer_to_mark, "an integer"); + return(new_xen_mark(XEN_TO_C_INT(n))); +} + + +static XEN g_mark_to_integer(XEN n) +{ + #define H_mark_to_integer "(" S_mark_to_integer " id) returns the integer corresponding to the given mark object" + XEN_ASSERT_TYPE(XEN_MARK_P(n), n, XEN_ONLY_ARG, S_mark_to_integer, "a mark"); + return(C_TO_XEN_INT(xen_mark_to_int(n))); +} + + +static XEN snd_no_such_mark_error(const char *caller, XEN id) +{ + XEN_ERROR(XEN_ERROR_TYPE("no-such-mark"), + XEN_LIST_2(C_TO_XEN_STRING(caller), + id)); + return(XEN_FALSE); +} + + +#if MUS_DEBUGGING && HAVE_SCHEME +/* too hard to do this via mouse events in snd-test, so do it by hand here */ +static XEN g_test_control_drag_mark(XEN snd, XEN chn, XEN mid) +{ + int x, y; + mus_long_t cx; + chan_info *cp; + mark *m = NULL, *m1 = NULL; + cp = get_cp(snd, chn, "test-C-mark"); + if (!cp) return(XEN_FALSE); + m = find_mark_from_id(XEN_MARK_TO_C_INT(mid), NULL, AT_CURRENT_EDIT_POSITION); + if (m == NULL) + return(snd_no_such_mark_error("test-C-mark", mid)); + y = cp->axis->y_axis_y1; + if (m->name) y += 10; + x = grf_x((double)(m->samp) / (double)SND_SRATE(cp->sound), cp->axis); + m1 = hit_mark(cp, x, y + 1, snd_ControlMask); + if (m != m1) + { + fprintf(stderr, "ah rats! "); + abort(); + } + move_mark(cp, m, x - 50); + finish_moving_mark(cp, m); + x = grf_x((double)(m->samp) / (double)SND_SRATE(cp->sound), cp->axis); + y = cp->axis->y_axis_y0 + 2; + hit_triangle(cp, x, y); + cx = m->samp + 50; + move_play_mark(cp, &cx, x + 50); + finish_moving_play_mark(cp); + return(mid); +} +#endif + + +typedef enum {MARK_SAMPLE, MARK_NAME, MARK_SYNC, MARK_HOME} mark_field_t; + +static XEN mark_get(XEN n, mark_field_t fld, XEN pos_n, const char *caller) +{ + int pos; + chan_info *ncp[1]; + mark *m = NULL; + + pos = XEN_TO_C_INT_OR_ELSE(pos_n, AT_CURRENT_EDIT_POSITION); + + m = find_mark_from_id(XEN_MARK_TO_C_INT(n), ncp, pos); + if (m == NULL) + return(snd_no_such_mark_error(caller, n)); + + switch (fld) + { + case MARK_SAMPLE: + return(C_TO_XEN_INT64_T(m->samp)); + break; + + case MARK_SYNC: + return(C_TO_XEN_INT(m->sync)); + break; + + case MARK_NAME: + if (m->name) + return(C_TO_XEN_STRING(m->name)); + else return(C_TO_XEN_STRING("")); + break; + + case MARK_HOME: + return(XEN_LIST_2(C_INT_TO_XEN_SOUND((ncp[0]->sound)->index), + C_TO_XEN_INT(ncp[0]->chan))); + break; + } + return(XEN_FALSE); +} + + +static XEN mark_set(XEN mark_n, XEN val, mark_field_t fld, const char *caller) +{ + chan_info *cp[1]; + mark *m; + + m = find_mark_from_id(XEN_MARK_TO_C_INT(mark_n), cp, AT_CURRENT_EDIT_POSITION); + if (m == NULL) + return(snd_no_such_mark_error(caller, mark_n)); + + switch (fld) + { + case MARK_SAMPLE: + m->samp = mus_oclamp(0, + XEN_TO_C_INT64_T_OR_ELSE(val, 0), + CURRENT_SAMPLES(cp[0])); + sort_marks(cp[0]); /* update and re-sort current mark list */ + run_mark_hook(cp[0], m->id, MARK_MOVE); + update_graph(cp[0]); + break; + + case MARK_SYNC: + if (XEN_INTEGER_P(val)) + set_mark_sync(m, XEN_TO_C_INT(val)); + else set_mark_sync(m, (int)XEN_TO_C_BOOLEAN(val)); + break; + + case MARK_NAME: + if (m->name) free(m->name); + if (XEN_FALSE_P(val)) + m->name = NULL; + else m->name = mus_strdup(XEN_TO_C_STRING(val)); + update_graph(cp[0]); + break; + + default: + break; + } + return(val); +} + + +static XEN g_mark_p(XEN id_n) +{ + #define H_mark_p "(" S_mark_p " id): " PROC_TRUE " if mark is active" + if (XEN_MARK_P(id_n)) + return(C_TO_XEN_BOOLEAN(find_mark_from_id(XEN_MARK_TO_C_INT(id_n), NULL, AT_CURRENT_EDIT_POSITION))); + return(XEN_FALSE); +} + + +static XEN g_mark_sample(XEN mark_n, XEN pos_n) +{ + #define H_mark_sample "(" S_mark_sample " id :optional pos): mark's location (sample number) at edit history pos" + XEN_ASSERT_TYPE(XEN_MARK_P(mark_n), mark_n, XEN_ARG_1, S_mark_sample, "a mark"); + XEN_ASSERT_TYPE(XEN_INTEGER_IF_BOUND_P(pos_n), pos_n, XEN_ARG_2, S_mark_sample, "an integer"); + return(mark_get(mark_n, MARK_SAMPLE, pos_n, S_mark_sample)); +} + +static XEN g_set_mark_sample(XEN mark_n, XEN samp_n) +{ + XEN_ASSERT_TYPE(XEN_MARK_P(mark_n), mark_n, XEN_ARG_1, S_setB S_mark_sample, "a mark"); + XEN_ASSERT_TYPE(XEN_INT64_T_P(samp_n) || XEN_NOT_BOUND_P(samp_n), samp_n, XEN_ARG_2, S_setB S_mark_sample, "an integer"); + return(mark_set(mark_n, samp_n, MARK_SAMPLE, S_setB S_mark_sample)); +} + + +XEN g_mark_sync(XEN mark_n) +{ + #define H_mark_sync "(" S_mark_sync " id): mark's sync value (default: 0)" + XEN_ASSERT_TYPE(XEN_MARK_P(mark_n), mark_n, XEN_ONLY_ARG, S_mark_sync, "a mark"); + return(mark_get(mark_n, MARK_SYNC, XEN_UNDEFINED, S_mark_sync)); +} + +XEN g_set_mark_sync(XEN mark_n, XEN sync_n) +{ + XEN_ASSERT_TYPE(XEN_MARK_P(mark_n), mark_n, XEN_ARG_1, S_setB S_mark_sync, "a mark"); + XEN_ASSERT_TYPE(XEN_INTEGER_OR_BOOLEAN_P(sync_n), sync_n, XEN_ARG_2, S_setB S_mark_sync, "an integer"); + return(mark_set(mark_n, sync_n, MARK_SYNC, S_setB S_mark_sync)); +} + + +static XEN g_mark_name(XEN mark_n) +{ + #define H_mark_name "(" S_mark_name " id): mark's name" + XEN_ASSERT_TYPE(XEN_MARK_P(mark_n), mark_n, XEN_ONLY_ARG, S_mark_name, "a mark"); + return(mark_get(mark_n, MARK_NAME, XEN_UNDEFINED, S_mark_name)); +} + +static XEN g_set_mark_name(XEN mark_n, XEN name) +{ + XEN_ASSERT_TYPE(XEN_MARK_P(mark_n), mark_n, XEN_ARG_1, S_setB S_mark_name, "a mark"); + XEN_ASSERT_TYPE(XEN_STRING_P(name) || XEN_FALSE_P(name), name, XEN_ARG_2, S_setB S_mark_name, "a string"); + return(mark_set(mark_n, name, MARK_NAME, S_setB S_mark_name)); +} + + +static XEN g_mark_sync_max(void) +{ + #define H_mark_sync_max "(" S_mark_sync_max "): max mark sync value seen so far" + return(C_TO_XEN_INT(mark_sync_max())); +} + + +static XEN g_mark_home(XEN mark_n) +{ + #define H_mark_home "(" S_mark_home " id): the sound (index) and channel that hold mark id" + XEN_ASSERT_TYPE(XEN_MARK_P(mark_n), mark_n, XEN_ONLY_ARG, S_mark_home, "a mark"); + return(mark_get(mark_n, MARK_HOME, XEN_UNDEFINED, S_mark_home)); +} + + +static XEN g_find_mark(XEN samp_n, XEN snd, XEN chn_n, XEN edpos) +{ + #define H_find_mark "(" S_find_mark " samp-or-name :optional snd chn edpos): \ +find the mark in snd's channel chn at samp (if a number) or with the given name (if a string); return the mark or " PROC_FALSE " if no mark found." + + mark **mps; + int pos; + chan_info *cp = NULL; + + XEN_ASSERT_TYPE((XEN_NUMBER_P(samp_n) || XEN_STRING_P(samp_n) || (XEN_FALSE_P(samp_n))), samp_n, XEN_ARG_1, S_find_mark, "a number or string or " PROC_FALSE); + ASSERT_CHANNEL(S_find_mark, snd, chn_n, 2); + + cp = get_cp(snd, chn_n, S_find_mark); + if (!cp) return(XEN_FALSE); + pos = to_c_edit_position(cp, edpos, S_find_mark, 4); + + mps = cp->edits[pos]->marks; + if (mps) + { + int i; + mus_long_t samp = 0; + const char *name = NULL; + if (XEN_STRING_P(samp_n)) + name = XEN_TO_C_STRING(samp_n); + else samp = XEN_TO_C_INT64_T_OR_ELSE(samp_n, 0); + if (name) + { + for (i = 0; i <= cp->edits[pos]->mark_ctr; i++) + if ((mps[i]) && + (mus_strcmp(name, mps[i]->name))) + return(new_xen_mark(mps[i]->id)); + } + else + { + for (i = 0; i <= cp->edits[pos]->mark_ctr; i++) + if ((mps[i]) && + (mps[i]->samp == samp)) + return(new_xen_mark(mps[i]->id)); + } + } + return(XEN_FALSE); +} + + +static XEN g_add_mark_1(XEN samp_n, XEN snd, XEN chn_n, XEN name, XEN sync, bool check_sample) +{ + #define H_add_mark "(" S_add_mark " samp :optional snd chn name (sync 0)): add a mark at sample samp returning the mark." + mark *m = NULL; + chan_info *cp; + mus_long_t loc; + int msync = 0; + const char *mname = NULL; + + XEN_ASSERT_TYPE(XEN_INT64_T_P(samp_n) || XEN_NOT_BOUND_P(samp_n), samp_n, XEN_ARG_1, S_add_mark, "an integer"); + XEN_ASSERT_TYPE(XEN_STRING_IF_BOUND_P(name) || XEN_FALSE_P(name), name, XEN_ARG_4, S_add_mark, "a string"); + XEN_ASSERT_TYPE(XEN_INTEGER_IF_BOUND_P(sync), sync, XEN_ARG_5, S_add_mark, "an integer"); + ASSERT_CHANNEL(S_add_mark, snd, chn_n, 2); + + cp = get_cp(snd, chn_n, S_add_mark); + if (!cp) return(XEN_FALSE); + + loc = XEN_TO_C_INT64_T_OR_ELSE(samp_n, 0); + + if ((!check_sample) && + (loc >= CURRENT_SAMPLES(cp))) + return(XEN_FALSE); + + if ((loc < 0) || + (loc >= CURRENT_SAMPLES(cp))) + XEN_ERROR(NO_SUCH_SAMPLE, + XEN_LIST_2(C_TO_XEN_STRING(S_add_mark), + samp_n)); + + if (XEN_STRING_P(name)) mname = XEN_TO_C_STRING(name); + if (XEN_INTEGER_P(sync)) msync = XEN_TO_C_INT(sync); + + m = add_mark(loc, mname, cp); + if (m) + { + if (msync != 0) set_mark_sync(m, msync); + update_graph(cp); + return(new_xen_mark(m->id)); + } + return(XEN_FALSE); +} + + +static XEN g_add_mark(XEN samp_n, XEN snd, XEN chn_n, XEN name, XEN sync) +{ + return(g_add_mark_1(samp_n, snd, chn_n, name, sync, true)); +} + + +static XEN g_add_mark_unchecked(XEN samp_n, XEN snd, XEN chn_n, XEN name, XEN sync) +{ + return(g_add_mark_1(samp_n, snd, chn_n, name, sync, false)); +} + + +static XEN g_delete_mark(XEN id_n) +{ + #define H_delete_mark "(" S_delete_mark " id): delete mark id" + chan_info *cp[1]; + mark *m; + int id; + + XEN_ASSERT_TYPE(XEN_MARK_P(id_n), id_n, XEN_ONLY_ARG, S_delete_mark, "a mark"); + id = XEN_MARK_TO_C_INT(id_n); + + m = find_mark_from_id(id, cp, AT_CURRENT_EDIT_POSITION); + if (m == NULL) + return(snd_no_such_mark_error(S_delete_mark, id_n)); + + if (delete_mark_id(id, cp[0])) + update_graph(cp[0]); + else return(snd_no_such_mark_error(S_delete_mark, id_n)); + + return(id_n); +} + + +static XEN g_delete_marks(XEN snd, XEN chn_n) +{ + #define H_delete_marks "(" S_delete_marks " :optional snd chn): delete all marks in snd's channel chn" + chan_info *cp; + ASSERT_CHANNEL(S_delete_marks, snd, chn_n, 1); + cp = get_cp(snd, chn_n, S_delete_marks); + if (!cp) return(XEN_FALSE); + delete_marks(cp); + return(XEN_FALSE); +} + + +static XEN int_array_to_mark_list(int *arr, int i, int len) +{ + if (i < len) + return(XEN_CONS(new_xen_mark(arr[i]), int_array_to_mark_list(arr, i + 1, len))); + return(XEN_CONS(new_xen_mark(arr[i]), XEN_EMPTY_LIST)); +} + + +static int *syncd_marks(int sync) +{ + syncdata *sd; + int *ids; + int i; + sd = make_syncdata(sync); + for_each_normal_chan_with_void(gather_chan_syncd_marks, (void *)sd); + ids = (int *)calloc(1 + sd->mark_ctr, sizeof(int)); + ids[0] = sd->mark_ctr; + for (i = 0; i < sd->mark_ctr; i++) ids[i + 1] = sd->marks[i]->id; + free_syncdata(sd); + return(ids); +} + + +static XEN g_syncd_marks(XEN sync) +{ + #define H_syncd_marks "(" S_syncd_marks " sync): list of marks that share a given sync value (" S_mark_sync ")" + int *ids; + XEN res; + + XEN_ASSERT_TYPE(XEN_INTEGER_P(sync), sync, XEN_ONLY_ARG, S_syncd_marks, "an integer"); + ids = syncd_marks(XEN_TO_C_INT(sync)); + + if (ids == NULL) return(XEN_EMPTY_LIST); + if (ids[0] == 0) {free(ids); return(XEN_EMPTY_LIST);} + + res = int_array_to_mark_list(ids, 1, ids[0]); + + free(ids); + return(res); +} + + +static XEN g_mark_tag_width(void) {return(C_TO_XEN_INT(mark_tag_width(ss)));} + +static XEN g_set_mark_tag_width(XEN val) +{ + #define H_mark_tag_width "(" S_mark_tag_width "): width (pixels) of mark tags (10)" + int width; + XEN_ASSERT_TYPE(XEN_INTEGER_P(val), val, XEN_ONLY_ARG, S_setB S_mark_tag_width, "an integer"); + width = mus_iclamp(0, XEN_TO_C_INT(val), LOTSA_PIXELS); + set_mark_tag_width(width); + for_each_normal_chan(update_graph); + return(C_TO_XEN_INT(mark_tag_width(ss))); +} + + +static XEN g_mark_tag_height(void) {return(C_TO_XEN_INT(mark_tag_height(ss)));} + +static XEN g_set_mark_tag_height(XEN val) +{ + #define H_mark_tag_height "(" S_mark_tag_height "): height (pixels) of mark tags (4)" + int height; + XEN_ASSERT_TYPE(XEN_INTEGER_P(val), val, XEN_ONLY_ARG, S_setB S_mark_tag_height, "an integer"); + height = mus_iclamp(0, XEN_TO_C_INT(val), LOTSA_PIXELS); + set_mark_tag_height(height); + for_each_normal_chan(update_graph); + return(C_TO_XEN_INT(mark_tag_height(ss))); +} + + +static int *channel_marks(chan_info *cp, int pos) +{ + int *ids = NULL; + ed_list *ed; + ed = cp->edits[pos]; + if (ed->marks) + { + mark **mps; + int marks; + mps = ed->marks; + marks = ed->mark_ctr; + if (mps) + { + int i; + ids = (int *)calloc(marks + 2, sizeof(int)); /* 1 for size, 1 because mark_ctr is current count */ + ids[0] = marks + 1; + for (i = 0; i <= marks; i++) + ids[i + 1] = mps[i]->id; + } + } + return(ids); +} + + +static XEN g_marks(XEN snd, XEN chn_n, XEN pos_n) +{ + #define H_marks "(" S_marks " :optional snd chn edpos): list of marks in snd/chn at edit history position pos. \ +mark list is: channel given: (id id ...), snd given: ((id id) (id id ...)), neither given: (((id ...) ...) ...)." + + chan_info *cp; + snd_info *sp; + XEN res1 = XEN_EMPTY_LIST; + + ASSERT_CHANNEL(S_marks, snd, chn_n, 0); + + if (XEN_INTEGER_P(snd) || XEN_SOUND_P(snd)) + { + int i, pos; + int *ids; + XEN res; + if (XEN_INTEGER_P(chn_n)) + { + cp = get_cp(snd, chn_n, S_marks); + if (!cp) return(XEN_FALSE); + if (XEN_INTEGER_P(pos_n)) + { + pos = XEN_TO_C_INT(pos_n); + if (pos == AT_CURRENT_EDIT_POSITION) + pos = cp->edit_ctr; + if ((pos < 0) || (pos >= cp->edit_size) || (cp->edits[pos] == NULL)) + XEN_ERROR(NO_SUCH_EDIT, + XEN_LIST_2(C_TO_XEN_STRING(S_marks), + pos_n)); + } + else pos = cp->edit_ctr; + ids = channel_marks(cp, pos); + if (ids == NULL) return(XEN_EMPTY_LIST); + if (ids[0] == 0) + { + free(ids); + return(XEN_EMPTY_LIST); + } + res = int_array_to_mark_list(ids, 1, ids[0]); + free(ids); + return(res); + } + else + { + sp = get_sp(snd); + if (sp == NULL) + return(snd_no_such_sound_error(S_marks, snd)); + for (i = sp->nchans - 1; i >= 0; i--) + { + cp = sp->chans[i]; + ids = channel_marks(cp, cp->edit_ctr); + if ((ids == NULL) || (ids[0] == 0)) + res1 = XEN_CONS(XEN_EMPTY_LIST, res1); + else res1 = XEN_CONS(int_array_to_mark_list(ids, 1, ids[0]), + res1); + if (ids) free(ids); + } + } + } + else + { + int j; + /* all marks */ + for (j = ss->max_sounds - 1; j >= 0; j--) + { + sp = ss->sounds[j]; + if ((sp) && (sp->inuse == SOUND_NORMAL)) + res1 = XEN_CONS(g_marks(C_TO_XEN_INT(j), XEN_UNDEFINED, XEN_UNDEFINED), + res1); + } + } + return(res1); +} + + +/* ---------------- saved marks ---------------- + * + * upon restoration, pre-existing marks can collide both in mark-id and mark-sync + * with the saved marks, so these need to be fixed-up as they are encountered. + * Also mark-sync-max needs to reflect the newly chosen sync values. + * + * We used to try to save the entire mark history, but I can't see how that can work + * in general -- the save-marks output can be loaded at any time, so we can't + * rely on anything in regard to the edit history. + */ + +static bool find_any_marks(chan_info *cp) +{ + ed_list *ed; + ed = cp->edits[cp->edit_ctr]; + return((ed->marks) && (ed->mark_ctr >= 0)); /* initialized to -1 -- 0 is first mark */ +} + +typedef struct { + FILE *fd; + int size; + int *syncs; +} save_mark_info; + +#define SYNC_BASE "_sync_" +#define SYNC_NAME_SIZE 32 + +static char *mark_sync_name(int cur_sync) +{ + char *result; + result = (char *)calloc(SYNC_NAME_SIZE, sizeof(char)); + mus_snprintf(result, SYNC_NAME_SIZE, "%s%d", SYNC_BASE, cur_sync); + return(result); +} + + +static char *map_mark_sync(chan_info *cp, mark *m, save_mark_info *sv) +{ + /* if sync 0 just return "0", else if already in syncs array, use _sync_n_ as name, + * else open a let, declare _sync_n_ with new safe value, + * add current int value to syncs array (can't assume unshared here will not collide later) + */ + + int i, cur_sync; + cur_sync = m->sync; + if (cur_sync == 0) + return(mus_strdup("0")); + + if (sv->size > 0) + for (i = 0; i < sv->size; i++) + if (cur_sync == sv->syncs[i]) + return(mark_sync_name(cur_sync)); + + /* add sync to current set (protect against later collisions, take current shared-syncs into account) */ + if (sv->size == 0) + sv->syncs = (int *)calloc(1, sizeof(int)); + else sv->syncs = (int *)realloc(sv->syncs, (sv->size + 1) * sizeof(int)); + sv->syncs[sv->size++] = cur_sync; + +#if (!HAVE_FORTH) + fprintf(sv->fd, " "); + for (i = 0; i < sv->size - 1; i++) fprintf(sv->fd, " "); /* previous lets */ +#endif + +#if HAVE_SCHEME + fprintf(sv->fd, "(let ((%s%d (1+ (mark-sync-max))))\n", SYNC_BASE, cur_sync); +#endif + +#if HAVE_RUBY + fprintf(sv->fd, "begin\n %s%d = mark_sync_max + 1\n", SYNC_BASE, cur_sync); +#endif + +#if HAVE_FORTH + fprintf(sv->fd, "mark-sync-max 1 + value %s%d\n", SYNC_BASE, cur_sync); +#endif + + return(mark_sync_name(cur_sync)); +} + + +static mark *save_mark(chan_info *cp, mark *m, void *info) +{ + /* we're called within "sound_block" where "sfile" = current sound index */ + + save_mark_info *sv = (save_mark_info *)info; + char *mapped_sync; + int i; + + mapped_sync = map_mark_sync(cp, m, sv); + + fprintf(sv->fd, " "); + for (i = 0; i < sv->size; i++) fprintf(sv->fd, " "); /* lets */ + + /* here we need to use the "!" form of add-mark to ignore bad sample numbers -- these can come about during + * the save-state process when redoable edits causing extensions are undone before marks are added, + * possibly to the extended portion -- we'll simply drop those marks, rather than wrecking the restore + * process with an error. + */ + +#if HAVE_SCHEME + if (m->name) + fprintf(sv->fd, "(add-mark! " MUS_LD " sfile %d \"%s\" %s)\n", m->samp, cp->chan, m->name, mapped_sync); + else fprintf(sv->fd, "(add-mark! " MUS_LD " sfile %d #f %s)\n", m->samp, cp->chan, mapped_sync); +#endif + +#if HAVE_RUBY + if (m->name) + fprintf(sv->fd, "add_mark!(" MUS_LD ", sfile, %d, \"%s\", %s)\n", m->samp, cp->chan, m->name, mapped_sync); + else fprintf(sv->fd, "add_mark!(" MUS_LD ", sfile, %d, false, %s)\n", m->samp, cp->chan, mapped_sync); +#endif + +#if HAVE_FORTH + if (m->name) + fprintf(sv->fd, MUS_LD " sfile %d \"%s\" %s add-mark! drop\n", m->samp, cp->chan, m->name, mapped_sync); + else fprintf(sv->fd, MUS_LD " sfile %d #f %s add-mark! drop\n", m->samp, cp->chan, mapped_sync); +#endif + + free(mapped_sync); + return(NULL); /* returning a mark here breaks out of the map mark loop */ +} + + +void save_mark_list(FILE *fd, chan_info *cp, bool all_chans) +{ + /* used in save-marks (below) and the edit history stuff in snd-edits.c + * changed 23-Sep-06 -- restore-marks is now a no-op, and no attempt is made to save the entire mark history. + */ + + save_mark_info *sv; + if ((!all_chans) && (!(find_any_marks(cp)))) return; /* in the sound (all_chans) case, this has been checked already */ + sv = (save_mark_info *)calloc(1, sizeof(save_mark_info)); + sv->fd = fd; + sv->size = 0; + sv->syncs = NULL; + if (all_chans) + { + int i; + snd_info *sp; + sp = cp->sound; + for (i = 0; i < sp->nchans; i++) + map_over_marks(sp->chans[i], save_mark, (void *)sv, READ_FORWARD); + } + else map_over_marks(cp, save_mark, (void *)sv, READ_FORWARD); + if (sv->size > 0) + { +#if HAVE_SCHEME || HAVE_RUBY + int i; +#endif + fprintf(fd, " "); + +#if HAVE_SCHEME + for (i = 0; i < sv->size; i++) fprintf(fd, ")"); +#endif + +#if HAVE_RUBY + for (i = 0; i < sv->size; i++) fprintf(fd, "end\n"); +#endif + + fprintf(fd, "\n"); + } + if (sv->syncs) free(sv->syncs); + free(sv); +} + + +static XEN g_save_marks(XEN snd, XEN filename) +{ + #define H_save_marks "(" S_save_marks " :optional snd (filename \"<snd-file-name>.marks\")): save snd's marks in filename. \ +The saved file is " XEN_LANGUAGE_NAME " code, so to restore the marks, load that file." + + snd_info *sp; + XEN res = XEN_FALSE; + + ASSERT_SOUND(S_save_marks, snd, 1); + XEN_ASSERT_TYPE(XEN_STRING_IF_BOUND_P(filename), filename, XEN_ARG_2, S_save_marks, "a string"); + + sp = get_sp(snd); + if (sp == NULL) + return(snd_no_such_sound_error(S_save_marks, snd)); + + if (map_over_sound_chans(sp, find_any_marks)) /* are there any marks? */ + { + char *newname = NULL; + int i, len; + FILE *fd; + if (XEN_STRING_P(filename)) + newname = mus_strdup(XEN_TO_C_STRING(filename)); + else + { + len = strlen(sp->filename); + newname = (char *)calloc(len + 7, sizeof(char)); + strcpy(newname, sp->filename); + for (i = len - 1; i > 0; i--) + if (newname[i] == '.') + break; + if (i > 0) len = i; + newname[len] = '\0'; + strcat(newname, ".marks"); + } + fd = FOPEN(newname, "w"); + if (fd == NULL) + { + XEN lname; + lname = C_TO_XEN_STRING(newname); + free(newname); + XEN_ERROR(CANNOT_SAVE, + XEN_LIST_3(C_TO_XEN_STRING(S_save_marks), + C_TO_XEN_STRING("open ~A: ~A"), + XEN_LIST_2(lname, + C_TO_XEN_STRING(snd_open_strerror())))); + } + else + { +#if HAVE_FORTH + fprintf(fd, "#f value sfile\n"); +#endif + open_save_sound_block(sp, fd, false); /* find-sound or open it with enclosing let */ + save_mark_list(fd, sp->chans[0], true); /* true -> save all chans, matching cross-channel syncs */ + close_save_sound_block(fd, false); /* close the let form */ + snd_fclose(fd, newname); + res = C_TO_XEN_STRING(newname); + } + free(newname); + } + return(res); +} + + +static XEN g_mark_properties(XEN n) +{ + mark *m; + #define H_mark_properties "(" S_mark_properties " id): A property list associated with the given mark. \ +The accessor mark-property is provided in mark." XEN_FILE_EXTENSION "." + + XEN_ASSERT_TYPE(XEN_MARK_P(n), n, XEN_ARG_1, S_mark_properties, "a mark"); + + m = find_mark_from_id(XEN_MARK_TO_C_INT(n), NULL, AT_CURRENT_EDIT_POSITION); + if (m == NULL) + return(snd_no_such_mark_error(S_mark_properties, n)); + + if (!(XEN_VECTOR_P(m->properties))) + { + m->properties = XEN_MAKE_VECTOR(1, XEN_EMPTY_LIST); + m->properties_gc_loc = snd_protect(m->properties); + } + return(XEN_VECTOR_REF(m->properties, 0)); +} + + +static XEN g_set_mark_properties(XEN n, XEN val) +{ + mark *m; + XEN_ASSERT_TYPE(XEN_MARK_P(n), n, XEN_ARG_1, S_mark_properties, "a mark"); + + m = find_mark_from_id(XEN_MARK_TO_C_INT(n), NULL, AT_CURRENT_EDIT_POSITION); + if (m == NULL) + return(snd_no_such_mark_error(S_setB S_mark_properties, n)); + + if (!(XEN_VECTOR_P(m->properties))) + { + m->properties = XEN_MAKE_VECTOR(1, XEN_EMPTY_LIST); + m->properties_gc_loc = snd_protect(m->properties); + } + + XEN_VECTOR_SET(m->properties, 0, val); + return(XEN_VECTOR_REF(m->properties, 0)); +} + + + + + +#ifdef XEN_ARGIFY_1 +XEN_ARGIFY_2(g_mark_sample_w, g_mark_sample) +XEN_NARGIFY_2(g_set_mark_sample_w, g_set_mark_sample) +XEN_NARGIFY_1(g_mark_sync_w, g_mark_sync) +XEN_NARGIFY_2(g_set_mark_sync_w, g_set_mark_sync) +XEN_NARGIFY_1(g_mark_name_w, g_mark_name) +XEN_NARGIFY_2(g_set_mark_name_w, g_set_mark_name) +XEN_NARGIFY_0(g_mark_sync_max_w, g_mark_sync_max) +XEN_NARGIFY_1(g_mark_home_w, g_mark_home) +XEN_ARGIFY_3(g_marks_w, g_marks) +XEN_ARGIFY_5(g_add_mark_w, g_add_mark) +XEN_ARGIFY_5(g_add_mark_unchecked_w, g_add_mark_unchecked) +XEN_NARGIFY_1(g_delete_mark_w, g_delete_mark) +XEN_ARGIFY_2(g_delete_marks_w, g_delete_marks) +XEN_NARGIFY_1(g_syncd_marks_w, g_syncd_marks) +XEN_NARGIFY_0(g_mark_tag_width_w, g_mark_tag_width) +XEN_NARGIFY_1(g_set_mark_tag_width_w, g_set_mark_tag_width) +XEN_NARGIFY_0(g_mark_tag_height_w, g_mark_tag_height) +XEN_NARGIFY_1(g_set_mark_tag_height_w, g_set_mark_tag_height) +XEN_ARGIFY_4(g_find_mark_w, g_find_mark) +XEN_ARGIFY_2(g_save_marks_w, g_save_marks) +XEN_NARGIFY_1(g_mark_p_w, g_mark_p) +XEN_NARGIFY_1(g_integer_to_mark_w, g_integer_to_mark) +XEN_NARGIFY_1(g_mark_to_integer_w, g_mark_to_integer) +XEN_NARGIFY_1(g_mark_properties_w, g_mark_properties) +XEN_NARGIFY_2(g_set_mark_properties_w, g_set_mark_properties) +#if MUS_DEBUGGING && HAVE_SCHEME + XEN_NARGIFY_3(g_test_control_drag_mark_w, g_test_control_drag_mark) +#endif +#else +#define g_mark_sample_w g_mark_sample +#define g_set_mark_sample_w g_set_mark_sample +#define g_mark_sync_w g_mark_sync +#define g_set_mark_sync_w g_set_mark_sync +#define g_mark_name_w g_mark_name +#define g_set_mark_name_w g_set_mark_name +#define g_mark_sync_max_w g_mark_sync_max +#define g_mark_home_w g_mark_home +#define g_marks_w g_marks +#define g_add_mark_w g_add_mark +#define g_add_mark_unchecked_w g_add_mark_unchecked +#define g_delete_mark_w g_delete_mark +#define g_delete_marks_w g_delete_marks +#define g_syncd_marks_w g_syncd_marks +#define g_mark_tag_width_w g_mark_tag_width +#define g_set_mark_tag_width_w g_set_mark_tag_width +#define g_mark_tag_height_w g_mark_tag_height +#define g_set_mark_tag_height_w g_set_mark_tag_height +#define g_find_mark_w g_find_mark +#define g_save_marks_w g_save_marks +#define g_mark_p_w g_mark_p +#define g_integer_to_mark_w g_integer_to_mark +#define g_mark_to_integer_w g_mark_to_integer +#define g_mark_properties_w g_mark_properties +#define g_set_mark_properties_w g_set_mark_properties +#if MUS_DEBUGGING && HAVE_SCHEME + #define g_test_control_drag_mark_w g_test_control_drag_mark +#endif +#endif + +void g_init_marks(void) +{ + #define H_mark_drag_hook S_mark_drag_hook " (id): called when a mark is dragged" + #define H_mark_hook S_mark_hook " (id snd chn reason): called when a mark added, deleted, or moved. \ +'Reason' can be 0: add, 1: delete, 2: move, 3: delete all marks" + + init_xen_mark(); + + mark_drag_hook = XEN_DEFINE_HOOK(S_mark_drag_hook, 1, H_mark_drag_hook); /* arg = id */ + mark_hook = XEN_DEFINE_HOOK(S_mark_hook, 4, H_mark_hook); /* args = id snd chn reason */ + + XEN_DEFINE_PROCEDURE_WITH_SETTER(S_mark_sample, g_mark_sample_w, H_mark_sample, + S_setB S_mark_sample, g_set_mark_sample_w, 1, 1, 2, 0); + + XEN_DEFINE_PROCEDURE_WITH_SETTER(S_mark_sync, g_mark_sync_w, H_mark_sync, + S_setB S_mark_sync, g_set_mark_sync_w, 1, 0, 2, 0); + + XEN_DEFINE_PROCEDURE_WITH_SETTER(S_mark_name, g_mark_name_w, H_mark_name, + S_setB S_mark_name, g_set_mark_name_w, 1, 0, 2, 0); + + XEN_DEFINE_PROCEDURE(S_mark_sync_max, g_mark_sync_max_w, 0, 0, 0, H_mark_sync_max); + XEN_DEFINE_PROCEDURE(S_mark_home, g_mark_home_w, 1, 0, 0, H_mark_home); + XEN_DEFINE_PROCEDURE(S_marks, g_marks_w, 0, 3, 0, H_marks); + XEN_DEFINE_PROCEDURE(S_add_mark, g_add_mark_w, 0, 5, 0, H_add_mark); + XEN_DEFINE_PROCEDURE(S_add_mark "!", g_add_mark_unchecked_w, 0, 5, 0, H_add_mark); + XEN_DEFINE_PROCEDURE(S_delete_mark, g_delete_mark_w, 1, 0, 0, H_delete_mark); + XEN_DEFINE_PROCEDURE(S_delete_marks, g_delete_marks_w, 0, 2, 0, H_delete_marks); + XEN_DEFINE_PROCEDURE(S_syncd_marks, g_syncd_marks_w, 1, 0, 0, H_syncd_marks); + XEN_DEFINE_PROCEDURE(S_find_mark, g_find_mark_w, 1, 3, 0, H_find_mark); + XEN_DEFINE_PROCEDURE(S_save_marks, g_save_marks_w, 0, 2, 0, H_save_marks); + XEN_DEFINE_PROCEDURE(S_mark_p, g_mark_p_w, 1, 0, 0, H_mark_p); + XEN_DEFINE_PROCEDURE(S_integer_to_mark, g_integer_to_mark_w, 1, 0, 0, H_integer_to_mark); + XEN_DEFINE_PROCEDURE(S_mark_to_integer, g_mark_to_integer_w, 1, 0, 0, H_mark_to_integer); + + XEN_DEFINE_PROCEDURE_WITH_SETTER(S_mark_tag_width, g_mark_tag_width_w, H_mark_tag_width, + S_setB S_mark_tag_width, g_set_mark_tag_width_w, 0, 0, 1, 0); + + XEN_DEFINE_PROCEDURE_WITH_SETTER(S_mark_tag_height, g_mark_tag_height_w, H_mark_tag_height, + S_setB S_mark_tag_height, g_set_mark_tag_height_w, 0, 0, 1, 0); + + XEN_DEFINE_PROCEDURE_WITH_SETTER(S_mark_properties, g_mark_properties_w, H_mark_properties, + S_setB S_mark_properties, g_set_mark_properties_w, 1, 0, 2, 0); + + #define H_draw_mark_hook S_draw_mark_hook " (mark-id): called before a mark is drawn (in XOR mode). \ +If the hook returns " PROC_TRUE ", the mark is not drawn." + + draw_mark_hook = XEN_DEFINE_HOOK(S_draw_mark_hook, 1, H_draw_mark_hook); /* arg = mark-id */ + +#if MUS_DEBUGGING && HAVE_SCHEME + XEN_DEFINE_PROCEDURE("internal-test-control-drag-mark", g_test_control_drag_mark_w, 3, 0, 0, "internal testing func"); +#endif +} + |