Subversion Repositories DIN Is Noise

Rev

Rev 1495 | Rev 1533 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
* curve_editor.cc
* DIN Is Noise is copyright (c) 2006-2020 Jagannathan Sampath
* For more information, please visit https://dinisnoise.org/
*/


#include "input.h"
#include "curve_editor.h"
#include "curve_library.h"
#include "console.h"
#include "font.h"
#include "utils.h"
#include "random.h"
#include "sine_mixer.h"
#include "beat2value.h"
#include "vector2d.h"
#include "fft.h"
#include "main.h"
#include "ui_list.h"
#include "chrono.h"
#include "viewwin.h"
#include "spiraler.h"
#include "rose_milker.h"
#include "instrument.h"
#include "capturer.h"

using namespace std;

extern int lmb, rmb;
extern ofstream dlog;
extern string user_data_dir;
extern int SAMPLE_RATE;
extern int FPS;
extern double TIME_PER_FRAME;
extern const float TWO_PI;
extern int GOT_FPS;
extern char BUFFER [];
extern instrument* get_current_instrument ();

void show_curve_picker () {
  curve_picker.show ();
}

curve_editor::curve_editor (const std::string& settingsf, const std::string& helpf) : helptext (helpf) {

  curcrv = 0;

  do_nothing ();

  is_waveform_editor = sine_enabled = fft_enabled = samples_enabled = draw_curve_only = 0;

  load  (settingsf);

  lmb_move = 0;
  lmb_clicked = 0;

  angle = 0;
  last_rpm = 0;

  gl_pts = 0;
  n_pts = 0;

  hlabel_only = 0;
 
  mark_segments = 0;

}

curve_editor::~curve_editor () {
  save ();
  dlog << "-- destroyed curve editor: " << name << " ---" << endl;
}

void curve_editor::load (const std::string& fname) {

  // load settings from file
  std::string feditor = user_data_dir + fname;
  ifstream file (feditor.c_str (), ios::in);
  if (!file) {
    dlog << "!!! cannot open curve editor settings from: " << fname << " !!!" << endl;
    return;
  } else dlog << "<<< loading curve editor settings from: " << feditor;

  settings_filename = fname;

  std::string ignore;

  basic_editor::load (file);

  int ct; file >> ignore >> ct;
  carry_tangents = ct;

  int mt; file >> ignore >> mt;
  mirror_tangents = mt;

  file >> ignore >> sine_enabled;

  file >> ignore >> label_vertices;

  tb.load (file);

  file >> ignore >> fft_enabled;
  file >> ignore >> samples_enabled;
  file >> ignore >> hz >> ignore >> nperiods;
  cs.set (hz, nperiods);
  file >> ignore >> is_waveform_editor;
  file >> ignore >> draw_curve_only;
  file >> ignore >> kbkb_attack_editor;
  file >> ignore >> overlay;
  file >> ignore >> curcrv;
  file >> ignore >> axis;
  file >> ignore >> draw_plugin_output;

  dlog << ", done >>>" << endl;

}

void curve_editor::save () {

  // save settings to file
  //
  ofstream file ((user_data_dir + settings_filename).c_str(), ios::out);
  if (!file) {
    dlog << "cannot save " << settings_filename << endl;
    return;
  }

  basic_editor::save (file);

  file << "carry_tangents " << carry_tangents << endl;
  file << "mirror_tangents " << mirror_tangents << endl;
  file << "sine_enabled " << sine_enabled << endl;
  file << "label_vertex " << label_vertices << endl;

  tb.clear ();
  tb.save (file);

  file << "fft_enabled " << fft_enabled << endl;
  file << "samples_enabled " << samples_enabled << endl;
  file << "hz " << hz << endl;
  file << "nperiods " << nperiods << endl;
  file << "is_waveform_editor " << is_waveform_editor << endl;
  file << "draw_curve_only " << draw_curve_only << endl;
  file << "sustain_editing " << kbkb_attack_editor << endl;
  file << "overlay " << overlay << endl;
  file << "curcrv " << curcrv << endl;
  file << "mirror_axis " << axis << endl;
  file << "draw_plugin_output " << draw_plugin_output << endl;

}

void curve_editor::prep_move () {
  undos.push_back (undo_t (pik.crv_id, *pik.crv, win));
  curveinfo[pik.crv_id].lisner->selected (pik.crv_id);
  if (keydown (SDLK_SPACE)) {
    cons << YELLOW << "Moving whole curve";
    todo = MOVE_ALL;
  } else {
    todo = MOVE_PICKED;
    cons << YELLOW << "Moving " << pik;
  }
  cons << eol;
  lmb_move = PREP;
}

int curve_editor::handle_input () {

  basic_editor::handle_input (); // pan, zoom, snap etc

  if (lmb) {

    if (lmb_clicked == 0) { // first lmb

      lmb_clicked = 1;

      if (lmb_move == FINISH_ON_CLICK) {
        cons << RED << "Finished moving " << pik << eol;
        do_nothing ();
        return 1;
      }

      if (todo == NOTHING) { // try to hit something to move
        hittest ();
        if (pik()) { // hit
          if (pomo.hit (pik))
            ;
          else
            prep_move ();
          return 1;
        }
      } else {
        switch (todo) {
          case ADD_VERTEX:
            add_vertex ();
            return 1;
          case INSERT_VERTEX:
            if (!pik()) pik = pick_cur_curve ();
            insert ();
            return 1;
          case REMOVE_VERTEX:
            hittest ();
            if (pik ()) remove ();
            return 1;
          case MODULATE_POINT:
            hittest ();
            modulate_point (1);
            return 1;
          case FOLD_VERTEX:
            hittest ();
            if (pik()) fold_tangents_of_vertex (pik);
            return 1;
          case UNFOLD_VERTEX:
            hittest ();
            if (pik()) unfold_tangents_of_vertex (pik);
            return 1;
          case MIRROR_VERTEX:
            hittest ();
            if (pik()) mirror ();
            return 1;
          case START_CAPTURE:
            do_nothing ();
            return 1;
          case ASSIGN_CAPTURE:
            hittest ();
            assign_mouse_capture ();
            return 1;
          case PICK_CURVE:
            hittest ();
            if (pik()) curve_picked ();
            return 1;
        }
      }
    }
  } else {
    lmb_clicked = 0;
    if (lmb_move && !MENU.show) {
      if (todo == MOVE_PICKED) move (); else
      if (todo == MOVE_ALL) move (1); // all
      lmb_move = FINISH_ON_CLICK;
      return 1;
    }
  }
 
  if (library) {
    if (library->num_curves ()) {
      if (keypressedd (SDLK_9)) { load_curve (-1); return 1; } else
      if (keypressedd (SDLK_0)) { load_curve (+1); return 1; } else
      if (keypressed (SDLK_MINUS)) { delete_curve (); return 1;}
    }
    if (keypressed (SDLK_EQUALS)) { add_curve (); return 1; }
  }

  if (keypressed (SDLK_i)) insert_using_menu ();
  else if (keypressed (SDLK_v)) {
    if (CTRL) { // ctrl + v ie paste
      paste_using_menu ();
    } else { // delete vertex or tanget
      todo = REMOVE_VERTEX;
        hittest ();
        if (pik()) remove ();
      todo = NOTHING;
    }
  }
  else if (keypressedd (SDLK_z)) { // undo & redo
    if (SHIFT) // l_shift + z - redo
      do_redo ();
    else
      do_undo ();
  }
  else if (keypressedd (SDLK_r)) apply_plugin (uis.plugin__browser.get_cur ()); // apply current plugin
  else if (keypressed (SDLK_m)) { // mirror tangents ie move one tangent and the other mirrors it
    mirror_tangents = !mirror_tangents;
    if (mirror_tangents) cons << YELLOW << "tangents are mirrored" << eol; else cons << YELLOW<< "tangents are not mirrored" << eol;
    MENU.set_mirror_tangents (mirror_tangents);
  } else if (keypressed (SDLK_COMMA)) {
    mark_segments = !mark_segments;
    dont_call_listener (MENU.cb_mark_segments, mark_segments);
  }
  else if (keypressedd (SDLK_PERIOD)) {
    draw_curve_only = !draw_curve_only;
    dont_call_listener (MENU.cb_draw_curve, draw_curve_only);
  }
  else if (keypressed (SDLK_c)) {
    if (CTRL) { // l_ctrl + c - copy curve
      if (num_curves == 1) {
        pik = pick_cur_curve ();
        copy_curve ();
      } else {
        hittest ();
        if (pik()) copy_curve (); else if (hitlist.size()) copy_using_menu ();
      }
    } else { // carry tangents?
      carry_tangents = !carry_tangents;
      if (carry_tangents)
        cons << YELLOW << "vertices carry their tangents" << eol;
      else
        cons << YELLOW << "vertices desert their tangents" << eol;
      MENU.set_vertices_carry_tangents (carry_tangents);
    }
  }
  else if (keypressed (SDLK_SLASH)) {
    toggle_waveform_samples_display ();
  } else if (keypressed (SDLK_l)) {
    label_vertices = !label_vertices;
    dont_call_listener (MENU.cb_label_vertices, label_vertices);
  }
  else if (keypressed (SDLK_F8)) { // apply mouse capture
    if (mocap0()) {
      hittest ();
      todo = ASSIGN_CAPTURE;
      next_todo = NOTHING;
      assign_mouse_capture ();
    }
  } else if (keypressed (SDLK_o)) { // toggle overlay of current instrument
    overlay = !overlay;
    if (overlay)
      cons << GREEN << "Overlaid the " << get_current_instrument()->name << eol;  
    else
      cons << GREEN << "Removed the " << get_current_instrument()->name << " from overlay" << eol;  
    MENU.cb_overlay_instrument.set_state (overlay, 0);
  }
  else if (keypressed (SDLK_F1)) helptext(); // show help

  return 1;

}

void curve_editor::apply_mocap () {
  float x, y;
  int vx, vy;
  for (int i = 0, j = macros.size(); i < j; ++i) {
    mouse_macro& m = macros[i];
    if (m.paused == 0) {
      hit_t& h = m.hit;
      if (h(1)) {
        m.mo.get (x, y);
        move (h, x, y);
        obj2view (x, y, vx, vy);
        capturer.set (m, vx, vy);
      }
    }
  }
}

void curve_editor::pick (int i) {
  int ncurves = curveinfo.size ();
  if (i > -1 && i < ncurves) {
    curve_info& ci = curveinfo[i];
    ci.lisner->selected (i);
  }
}

void curve_editor::calc_hit_params (hit_t& hit) {
  if (!hit()) return;
  multi_curve* crv = get_curve (hit.crv_id);
  float vx, vy, lx, ly, rx, ry;
  int id = hit.id;
  crv->get_vertex (id, vx, vy);
  crv->get_left_tangent (id, lx, ly);
  crv->get_right_tangent (id, rx, ry);
  direction (hit.left_tangent.x, hit.left_tangent.y, vx, vy, lx, ly);
  direction (hit.right_tangent.x, hit.right_tangent.y, vx, vy, rx, ry);
  hit.left_tangent_magnitude = (float) magnitude (hit.left_tangent.x, hit.left_tangent.y);
  hit.right_tangent_magnitude = (float) magnitude (hit.right_tangent.x, hit.right_tangent.y);
  const point<float>& hp = hit.get (); obj2mouse (hp.x, hp.y);
  curcrv = hit.crv_id;
}

void curve_editor::hittest () {

  hitlist.clear ();
  for (int i = 0, j = curveinfo.size (); i < j; ++i) {
    curve_info& ci = curveinfo[i];
    multi_curve* crv = ci.curve;
    hittest (crv, i, crv->vertices, hit_t::VERTEX);
    hittest (crv, i, crv->left_tangents, hit_t::LEFT_TANGENT);
    hittest (crv, i, crv->right_tangents, hit_t::RIGHT_TANGENT);
  }

  int n = hitlist.size();
  if (n) n = filter_hitlist ();
  if (n == 1) {
    pik = hitlist [0];
    calc_hit_params (pik);
  } else {
    pik = hit_t ();
    if (n > 1) show_curve_picker ();
  }
}

void curve_editor::hittest (multi_curve* crv, int crv_id, const points_array& points, unsigned int what) {
  for (int i = 0, j = points.size (); i < j; ++i) {
    const point<float>& v = points[i];
    float vx, vy; obj2win (v, vx, vy);
    float d2 = magnitude2<float> (vx, vy, win.mousex, win.mousey);
    if (d2 <= win.handle_radius2) hitlist.push_back (hit_t (crv, crv_id, what, i));
  }
}

int curve_editor::filter_hitlist () {

  for (int i = 0, j = curveinfo.size (); i < j; ++i) curveinfo[i].picked = 0;
  for (vector<hit_t>::iterator i = hitlist.begin (), j = hitlist.end (); i != j;) {
    hit_t& hit = *i;
    if (todo == PICK_CURVE) {
      int& picked = curveinfo[hit.crv_id].picked;
      if (picked) { // picked already, so remove
        hitlist.erase (i);
        j = hitlist.end ();
      } else { // pick one item per curve
        picked = 1;
        ++i;
      }
    } else
    if (todo == REMOVE_VERTEX && hit.what != hit_t::VERTEX) { // have to select vertex to delete
      i = hitlist.erase (i);
      j = hitlist.end ();
    } else
    if ( (hit.what == hit_t::LEFT_TANGENT && hit.id == 0) || (hit.what == hit_t::RIGHT_TANGENT && hit.id == (hit.crv->last_vertex)) ) {
      // remove 1st vertex's left tangent and last vertex's right tangent
      i = hitlist.erase (i);
      j = hitlist.end ();
    }
    else {
      ++i;
    }
  }

  return hitlist.size ();
}

void curve_editor::set_pick_from_hitlist (int i) {
  pik = hitlist [i];
  calc_hit_params (pik);
  curveinfo [curcrv].lisner->selected (curcrv);
  cons << GREEN << "Picked " << pik << eol;
}

int curve_editor::move (hit_t& hit, float x, float y, int eval_now) {

  curve_info& ci = curveinfo [hit.crv_id];
  multi_curve* crv = ci.curve;

  int need_eval = 0;

  switch (hit.what) {

    case hit_t::VERTEX:
        need_eval = crv->set_vertex (hit.id, x, y, carry_tangents);
      break;

    case hit_t::LEFT_TANGENT:
        need_eval = crv->set_left_tangent (hit.id, x, y);
        if (mirror_tangents && need_eval) {
          float vx, vy, lvx, lvy;
          crv->get_vertex (hit.id, vx, vy);
          unit_vector (lvx, lvy, vx, vy, x, y);
          crv->set_right_tangent (hit.id, vx - hit.right_tangent_magnitude * lvx, vy - hit.right_tangent_magnitude * lvy);
        }
      break;

    case hit_t::RIGHT_TANGENT:
        need_eval = crv->set_right_tangent (hit.id, x, y);
        if (mirror_tangents && need_eval) {
          float vx, vy, rvx, rvy;
          crv->get_vertex (hit.id, vx, vy);
          unit_vector (rvx, rvy, vx, vy, x, y);
          crv->set_left_tangent (hit.id, vx - hit.left_tangent_magnitude * rvx, vy - hit.left_tangent_magnitude * rvy);
        }
      break;

  }

  if (need_eval && eval_now) {
    crv->evaluate();
    ci.lisner->edited (this, hit.crv_id);
  }

  return need_eval;

}

int curve_editor::move () {
  float sx, sy; snap (sx, sy);
  float cx, cy; win2obj (sx, sy, cx, cy);
  return move (pik, cx, cy);
}

int curve_editor::move (int) {

  float sx, sy; snap (sx, sy);

  float cx, cy;
  win2obj (sx, sy, cx, cy);

  curve_info& ci = curveinfo [pik.crv_id];
  multi_curve* crv = ci.curve;

  float vx, vy;
  float dx, dy;

  switch (pik.what) {
    case hit_t::VERTEX:
        crv->get_vertex (pik.id, vx, vy);
        break;
    case hit_t::LEFT_TANGENT:
        crv->get_left_tangent (pik.id, vx, vy);
        break;
    case hit_t::RIGHT_TANGENT:
        crv->get_right_tangent (pik.id, vx, vy);
        break;
  }

  dx = cx - vx;
  dy = cy - vy;

  int result = 0;
  for (int i = 0, j = crv->num_vertices; i < j; ++i) {
    crv->get_vertex (i, vx, vy);
    result |= crv->set_vertex (i, vx + dx, vy + dy);
    crv->get_left_tangent (i, vx, vy);
    result |= crv->set_left_tangent (i, vx + dx, vy + dy);
    crv->get_right_tangent (i, vx, vy);
    result |= crv->set_right_tangent (i, vx + dx, vy + dy);
  }

  if (result) {
    crv->evaluate();
    ci.lisner->edited (this, pik.crv_id);
  }

  return result;

}

void curve_editor::remove () {
  curve_info& ci = curveinfo [pik.crv_id];
  multi_curve* crv = ci.curve;
  mix = *crv;
  undos.push_back (undo_t (pik.crv_id, *crv, win));
  if (crv->remove (pik.id)) {
    pomo.remove (pik);
    crv->evaluate();
    ci.lisner->edited (this, pik.crv_id);
  } else {
    cons << RED << "cant delete curve: " << crv->name << eol;
    undos.pop_back ();
  }
  clear_hit (pik);
}

void curve_editor::swap () {
  if (num_curves == 2) {

    multi_curve& crv0 = *get_curve (0);
    multi_curve& crv1 = *get_curve (1);

    multi_curve swp (crv0);

    undos.push_back (undo_t (0, crv0, win));
    string name0 (crv0.name);
    float r0 = crv0.r, g0 = crv0.g, b0 = crv0.b;
      crv0 = crv1;
    crv0.name = name0;
    crv0.set_color (r0, g0, b0);

    undos.push_back (undo_t (1, crv1, win));
    string name1 (crv1.name);
    float r1 = crv1.r, g1 = crv1.g, b1 = crv1.b;
      crv1 = swp;
    crv1.name = name1;
    crv1.set_color (r1, g1, b1);

    cons << "Swapped curve " << crv0.name << " with " << crv1.name << eol;
    curve_info &ci0 = get_curve_info (0), &ci1 = get_curve_info (1);

    pomo.validate ();
    ci0.lisner->edited (this, 0);
    ci1.lisner->edited (this, 1);


  }
}

void curve_editor::insert () {
  curve_info& ci = curveinfo [pik.crv_id];
  multi_curve* crv = ci.curve;
  float sx, sy; snap (sx, sy);
  float cx, cy; win2obj (sx, sy, cx, cy);
  mix = *crv;
  undos.push_back (undo_t (pik.crv_id, *crv, win));
  const point<float>& obj = get_obj_chunk ();
  if (crv->insert (cx, cy, obj.x, obj.y)) {
    crv->evaluate();
    ci.lisner->edited (this, pik.crv_id);
  } else undos.pop_back ();
}

void curve_editor::replace () {
  if (curv_scratch_points.size () > 1) {
    curve_info& ci = curveinfo [pik.crv_id];
    multi_curve& crv = *ci.curve;
    int shapeform = crv.shapeform;
    mix = crv;
    undos.push_back (undo_t (pik.crv_id, crv, win));
    create_polyline (crv, curv_scratch_points);
    if (shapeform) crv.set_shapeform (1);
    pomo.validate ();
    ci.lisner->edited (this, pik.crv_id);
    cons << GREEN << "Replaced curve." << eol;
  } else cons << RED << "Aborted drawn curve!" << eol;

  clear_scratch_curve ();
}

void curve_editor::mirror (int whole_curve) {

  // mirrors vertex (or curve) about global x-axis, or local axes of bounding box
  curve_info& ci = curveinfo [pik.crv_id];
  multi_curve* crv = ci.curve;
  undos.push_back (undo_t (pik.crv_id, *crv, win));

  box<float> bbox;
  if (axis > MIRROR_Y) crv->calc_bbox (bbox);

  float xx, yy, dx, dy, _2mx = 2 * bbox.midx, _2my = 2 * bbox.midy;
  if (whole_curve) {
    if ((axis == MIRROR_Y) || (axis == MIRROR_BBY)) {
      for (int i = 0; i < crv->num_vertices; ++i) {
        crv->get_vertex (i, xx, yy);  dx = _2mx - xx; crv->set_vertex (i, dx, yy);
        crv->get_left_tangent (i, xx, yy); dx = _2mx - xx; crv->set_left_tangent (i, dx, yy);
        crv->get_right_tangent (i, xx, yy); dx = _2mx - xx; crv->set_right_tangent (i, dx, yy);
      }
    } else {
      for (int i = 0; i < crv->num_vertices; ++i) {
        crv->get_vertex (i, xx, yy); dy = _2my - yy; crv->set_vertex (i, xx, dy);
        crv->get_left_tangent (i, xx, yy); dy = _2my - yy; crv->set_left_tangent (i, xx, dy);
        crv->get_right_tangent (i, xx, yy); dy = _2my - yy; crv->set_right_tangent (i, xx, dy);
      }
    }
    mix = *crv;
    crv->evaluate();
    ci.lisner->edited (this, pik.crv_id);
  } else {
    switch (pik.what) {
      case hit_t::VERTEX:
        crv->get_vertex (pik.id, xx, yy);
        break;
      case hit_t::LEFT_TANGENT:
        crv->get_left_tangent (pik.id, xx, yy);
        break;
      case hit_t::RIGHT_TANGENT:
        crv->get_right_tangent (pik.id, xx, yy);
        break;
    }
    mix = *crv;
    if ((axis == MIRROR_Y) || (axis == MIRROR_BBY)) {
      dx = _2mx - xx;
      move (pik, dx, yy);
    } else {
      dy = _2my - yy;
      move (pik, xx, dy);
    }
  }
}

void curve_editor::draw () {
  project ();
    basic_editor::draw ();
    draw_all ();
    if (draw_plugin_output) uis.plugin__browser.draw (this);
    if (show_scratch_curve) draw_scratch_curve ();
    if (curve_picker.visible) hilite_item (curve_picker.id);
  unproject ();
  if (overlay) overlay_instrument ();
}

void curve_editor::draw_scratch_curve () {
  glColor3f (1, 1, 0.7f);
  int n = win_scratch_points.size () + 1;
  alloc_gl_pts (n);
  int k = 0;
  for (int i = 0, j = n - 1; i < j; ++i) {
    const point<float>& p = win_scratch_points[i];
    gl_pts[k++]=p.x;
    gl_pts[k++]=p.y;
  }
  float sx, sy; snap (sx, sy);
  gl_pts[k++]=sx;
  gl_pts[k++]=sy;
  glVertexPointer (2, GL_FLOAT, 0, gl_pts);
  glDrawArrays (GL_LINE_STRIP, 0, n);
}

void curve_editor::draw_all () {

  // draw labels
  tb.draw (hlabel_only);
  tb.clear ();

  if (mark_segments) mark_curve_segments ();

  // draw curve profile, vertices, tangents and handles
  int j = curveinfo.size ();
  int j1 = j > 1;
  for (int i = 0; i < j; ++i) {
    curve_info& ci = curveinfo[i];
    multi_curve* crv = ci.curve;
    if (i == curcrv && j1) glLineWidth (2); // hilite current
      if (draw_curve_only) {
        glColor3f (crv->r, crv->g, crv->b);
        draw_curve (crv);
      } else {
        glColor3f (crv->rt, crv->gt, crv->bt);
        draw_tangents (crv);
        glColor3f (crv->r, crv->g, crv->b);
        draw_vertices (crv);
        draw_curve (crv);
      }
    glLineWidth (1);
  }

  // mark beat/value (gater, fm/am, octave shift)
  //
  int n = bv.size ();
  if (n) {
    float nowx, nowy;
    float x, y;
    static const int marker_size = 35;
    for (int i = 0; i < n; ++i) {
      beat2value* bvi = bv[i];
      multi_curve& crv = *bvi->crv;
      if (crv.shapeform) crv.get_xy (bvi->now, nowx, nowy);
      else {
        nowx = bvi->now;
        nowy = bvi->sol (nowx);
      }
      obj2win (nowx, nowy, x, y);
      glColor3f (crv.r, crv.g, crv.b);
      glLineWidth (2);
      mkr[0]=x-marker_size;mkr[1]=y;
      mkr[2]=x;mkr[3]=y-marker_size;
      mkr[4]=x+marker_size;mkr[5]=y;
      mkr[6]=x;mkr[7]=y+marker_size;
      glVertexPointer (2, GL_INT, 0, mkr);
      glDrawArrays (GL_LINE_LOOP, 0, 4);
      glLineWidth (1);
     
    }
  }

  // label vertices
  if (label_vertices) {
    for (int i = 0, j = curveinfo.size (); i < j; ++i) {
      curve_info& ci = curveinfo[i];
      multi_curve* crv = ci.curve;
      for (int m = 0, n = crv->num_vertices; m < n; ++m) {
        point<float>& vm = crv->vertices[m];
        sprintf (BUFFER, "%d", m);
        tb.add (text (BUFFER, vm.x, vm.y, vtxlbl.r, vtxlbl.g, vtxlbl.b));
      }
      tb.refresh (this);
    }
  }

  if (samples_enabled && curveinfo.size()) cs.draw (this, curveinfo[0].curve);

  if (kbkb_attack_editor) {
    mkr[0]=susx;mkr[1]=susbox.bottom;
    mkr[2]=susx;mkr[3]=win.bottom;
    glEnable (GL_LINE_STIPPLE);
    glLineStipple (1, 0x00ff);
    glColor3f (0, 0.5, 1);
    glVertexPointer (2, GL_INT, 0, mkr);
    glDrawArrays (GL_LINES, 0, 2);
    glDisable (GL_LINE_STIPPLE);
    mkr[0]=susbox.left;mkr[1]=susbox.top;
    mkr[2]=susbox.midx;mkr[3]=susbox.bottom;
    mkr[4]=susbox.right;mkr[5]=susbox.top;
    glVertexPointer (2, GL_INT, 0, mkr);
    glDrawArrays (GL_LINE_LOOP, 0, 3);
  }

  draw_cursor ();

}


void curve_editor::mark_curve_segments () {

  // drop vertical lines from current curve's profile points
  glColor3f (0.2f, 0.2f, 0.2f);
  curve_info& ci = curveinfo [curcrv];
  multi_curve* crv = ci.curve;
  vector<curve>& curv = crv->curv;
  int m = 0;
  for (int i = 0, j = curv.size(); i < j; ++i) m += curv[i].vpts.size ();
  int e = 2 * m;
  int n = 2 * e;
  alloc_gl_pts (n);
  for (int i = 0, j = curv.size(), k = 0; i < j; ++i) {
    vector<crvpt>& vpts = curv[i].vpts;
    for (int p = 0, q = vpts.size (); p < q; ++p) {
      float dx, dy; obj2win (vpts[p].x, vpts[p].y, dx, dy);
      gl_pts [k++]=dx;
      gl_pts [k++]=dy;
      gl_pts [k++]=dx;
      gl_pts [k++]=0;
    }
  }
  glVertexPointer (2, GL_FLOAT, 0, gl_pts);
  glDrawArrays (GL_LINES, 0, e);
}

void curve_editor::draw_handle (const point<float>& p) {
  float x, y; obj2win (p, x, y);
  float handle [] = {x - win.handle_radius, y, x, y + win.handle_radius, x + win.handle_radius, y, x, y - win.handle_radius};
  glVertexPointer (2, GL_FLOAT, 0, handle);
  glDrawArrays (GL_LINE_LOOP, 0, 4);
}

void curve_editor::draw_tangent (const point<float>& p, const point<float>& t) {
  float x, y; obj2win (p, x, y);
  float tx, ty; obj2win (t, tx, ty);
  float pts [4] = {x, y, tx, ty};
  glVertexPointer (2, GL_FLOAT, 0, pts);
  glDrawArrays (GL_LINES, 0, 2);
}

void curve_editor::draw_curve (multi_curve* crv) {

  int r = 0, n = crv->get_total_points ();
  alloc_gl_pts (n);

  vector<curve>& curv = crv->curv;
  int j = curv.size (), k = j - 1;
  float dx, dy;
  for (int i = 0; i < j; ++i) {
    vector<crvpt>& vpts = curv[i].vpts;
    for (int p = 0, q = vpts.size () - 1; p < q; ++p) {
      obj2win (vpts[p].x, vpts[p].y, dx, dy);
      gl_pts[r++] = dx;
      gl_pts[r++] = dy;
    }
  }

  // last point
  int l = curv[k].vpts.size () - 1;
  crvpt& pl = curv[k].vpts[l];
  obj2win (pl.x, pl.y, dx, dy);
  gl_pts[r++]=dx;
  gl_pts[r++]=dy;

  glVertexPointer (2, GL_FLOAT, 0, gl_pts);
  glDrawArrays (GL_LINE_STRIP, 0, n);

}

void curve_editor::draw_vertices (multi_curve* crv) {
  const points_array& vertices = crv->vertices;
  for (int p = 0, q = vertices.size(); p < q; ++p) {
    const point<float>& v = vertices[p];
    draw_handle (v);
  }
}

void curve_editor::draw_tangents (multi_curve* crv) {
  // draw tangents of ith curve
  const points_array& vertices = crv->vertices;
  const points_array& left_tangents = crv->left_tangents;
  const points_array& right_tangents = crv->right_tangents;
  for (int p = 0, q = vertices.size(); p < q; ++p) {
    const point<float>& v = vertices[p];
    const point<float>& lt = left_tangents[p];
    const point<float>& rt = right_tangents[p];
    draw_tangent (v, lt);
    draw_handle (lt);
    draw_tangent (v, rt);
    draw_handle (rt);
  }
}

void curve_editor::dodo (list<undo_t>& do1, list<undo_t>& do2, std::string mesg) {
  if (do1.size() > 0) {
    undo_t& last = do1.back ();
    if (last.i < num_curves) {
      curve_info& ci = curveinfo[last.i];
      multi_curve* crv = ci.curve;
      mix = *crv;
      do2.push_back (undo_t (last.i, *crv, win));
        float r, g, b;
        crv->get_color (r, g, b);
          *crv = last.curve;
        crv->set_color (r, g, b);
      pomo.validate ();
      ci.lisner->edited (this, last.i);
      win = last.win;
      do1.pop_back ();
    }
    hitlist.clear ();
    calc_visual_params ();
  } else cons << RED << mesg << eol;
}

void curve_editor::add (multi_curve* crv, curve_listener* lsnr) {
  curveinfo.push_back (curve_info (crv, lsnr));
  if (++num_curves == 1) {
    render_curve_samples ();
    hlabel_only = crv->shapeform;
  }
}

void curve_editor::clear () {
  curveinfo.clear ();
  hitlist.clear ();
  clear_hit (pik);
  num_curves = 0;
}

multi_curve* curve_editor::get_curve (int i) {
  if (i > -1 && i < (int) curveinfo.size()) {
    curve_info& ci = curveinfo [i];
    return ci.curve;
  }
  return 0;
}

curve_info& curve_editor::get_curve_info (int i) {
  return curveinfo[i];
}

void curve_editor::enter () {
  calc_visual_params ();
  if (is_waveform_editor) uis.dofft ();
  setup_tools_menu ();
  MENU.set_snap (snap_what);
  MENU.set_vertices_carry_tangents (carry_tangents);
  MENU.set_mirror_tangents (mirror_tangents);
  MENU.sp_waveform_hz.set_value (hz);
  MENU.sp_waveform_periods.set_value (nperiods);
  MENU.sp_curve_rpm.set_value (last_rpm);
  dont_call_listener (MENU.cb_show_waveform_samples, samples_enabled);
  dont_call_listener (MENU.cb_label_vertices, label_vertices);
  dont_call_listener (MENU.cb_draw_curve, draw_curve_only);
  dont_call_listener (MENU.cb_overlay_instrument, overlay);
  if (uis.is_widget_on_screen (&uis.plugin__browser, this)) {
    uis.plugin__browser.set_ed (this);
    uis.plugin__browser.set_fold (!draw_plugin_output);
  }
}

void curve_editor::calc_visual_params () {
  float var = view.height * 1.0 / view.width;
  float wh = win.width * var, wh2 = wh / 2.0f;
  win.set (win.left, win.midy - wh2, win.right, win.midy + wh2);
  win.calc ();
  basic_editor::calc_visual_params ();
}

void curve_editor::clear_scratch_curve () {
  win_scratch_points.clear ();
  curv_scratch_points.clear ();
  scratch_curve.clear ();
  show_scratch_curve = 0;
}

void curve_editor::attach_library (curve_library* lib) {
  library = lib;
}

void curve_editor::bg () {
  apply_mocap ();
  rotate ();
}

curve_info::curve_info  (multi_curve* c, curve_listener* l, int p) {
  curve = c;
  lisner = l;
  picked = p;
}

curve_info::curve_info () {
  curve = 0;
  lisner = 0;
  picked = 0;
}


void curve_editor::clear_hit (hit_t& h) {
  h.clear ();
}

void curve_editor::load_curve (int dir) {
  if (!pik()) pik = pick_cur_curve ();
  multi_curve& crv = *pik.crv;
  mix = crv;
  undos.push_back (undo_t (pik.crv_id, crv, win));
  if (dir == 1) crv = library->next (); else crv = library->prev();
  crv.set_color (mix.r, mix.g, mix.b);
  crv.name = mix.name;
  setup_tools_menu ();
  pomo.validate ();
  curveinfo[pik.crv_id].lisner->edited (this, pik.crv_id);
  cons.rollup (1);
}

void curve_editor::add_curve () {
  if (!library) return;
  if (!pik()) pik = pick_cur_curve ();
  curve_info& ci = curveinfo [pik.crv_id];
  library->add (*ci.curve);
}

void curve_editor::replace_curve () {
  if (!library) return;
  if (!pik()) pik = pick_cur_curve ();
  library->replace (*get_curve (pik.crv_id));
}

void curve_editor::insert_curve () {
  if (!library) return;
  if (!pik()) pik = pick_cur_curve ();
  library->insert (*get_curve (pik.crv_id));
}

void curve_editor::paste (hit_t& hit) {
  if (hit()) {
    curve_info& ci = curveinfo [hit.crv_id];
    multi_curve* crv = ci.curve;
    if (copy.num_vertices) {
      mix = *crv;
      undos.push_back (undo_t (hit.crv_id, *crv, win));
        std::string crv_name (crv->name);
        float cr, cg, cb;
        crv->get_color (cr, cg, cb);
          *crv = copy;
        crv->set_color (cr, cg, cb);
        crv->name = crv_name;
      pomo.validate ();
      ci.lisner->edited (this, hit.crv_id);
      cons << GREEN << "Pasted into " << crv->name << eol;
    } else cons << RED << "Nothing to paste!" << eol;
    do_nothing ();
  } else cons << RED << "Pick a curve to paste." << eol;
}

std::string curve_editor::selection () {
  std::stringstream ss;
  const char* what [] = {"invalid", "v", "lt", "rt"};
  for (int i = 0, j = hitlist.size (); i < j; ++i) {
    hit_t& h = hitlist[i];
    multi_curve* c = h.crv;
    float x, y;
    switch (h.what) {
      case hit_t::VERTEX:
        c->get_vertex (h.id, x, y);
        break;
      case hit_t::LEFT_TANGENT:
        c->get_left_tangent (h.id, x, y);
        break;
      case hit_t::RIGHT_TANGENT:
        c->get_right_tangent (h.id, x, y);
        break;
    }
    ss << '{' << c->name << ' ' << what[h.what] << ' ' << h.id << ' ' << x << ' ' << y << "} ";
  }
  return ss.str ();
}


curve_samples::curve_samples () {
  m = 0;
  dm = 0;
  n = 0;
  n_reached = 0;
  x = xp = y = xy = 0;
  hz = step = 0;
  nperiods = 3;
}

curve_samples::~curve_samples () {
  if (x) delete[] x;
  if (xp) delete[] xp;
  if (y) delete[] y;
  if (xy) delete[] xy;
}

/*void curve_samples::calc_dm (int fps, int prev_fps) {
  if (prev_fps == 0) prev_fps = fps;
  const int dm0 = 2;
  dm = max (dm0, int (dm0 * prev_fps * 1.0f / fps));
  cons << "prev: " << prev_fps << " fps: " << fps << " dm = " << dm << eol;
}*/


void curve_samples::render (multi_curve* crv) {

  solver ss (crv); 
  float dxx = step, xx = -dxx;
  ss (xx, dxx, n, y);

  float dx = step;
  box<float> bbox; crv->calc_bbox (bbox);
  for (int i = 0; i < n; ++i) {
    x[i] = bbox.right + dx;
    dx += step;
  }

  if (crv->shapeform) {
    vector<curve>& curv = crv->curv;
    int ncrvs = curv.size ();
    int pid = 0, cid = 0;
    double dxp = 0;
    for (int i = 0; i < n;) {
      vector<crvpt>& vpts = curv[cid].vpts;
      vector<crvpt>& dpts = curv[cid].dpts;
      int pid1 = pid+1;
      crvpt& pp = dpts [pid];
      crvpt& pp1 = dpts [pid1];
      double left = pp.x, right = pp1.x;
      if (dxp <= right) {
        float delta = right - left;
        float amt = (dxp - pp.x) * 1. / delta;
        crvpt& vp = vpts [pid];
        crvpt& vp1 = vpts [pid1];
        xp[i++] = vp.x + amt * (vp1.x - vp.x);
        dxp += step;
      } else {
        int last2 = dpts.size () - 2;
        if (++pid > last2) {
          double lastx = curv[cid].dpts[pid].x;
          pid = 0;
          if (++cid >= ncrvs) {
            cid = 0;
            dxp -= lastx;
          }
        }
      }
    }
  }
}

void curve_samples::draw (curve_editor* ed, multi_curve* crv) {

  glColor3f (1, 0.25, 0.25);
  float dx, dxp, dy;
  float ox, oy;
  ed->obj2win (0, 0, ox, oy);

  dm = n * 1.0f / (nperiods * nsec * min (FPS, GOT_FPS)); // 1 period in n seconds

  m += dm;
  if (m >= n) {
    if (n_reached == 0) {
      m = n - 1;
      n_reached = 1;
    } else {
      m = 0;
      n_reached = 0;
    }
  }

  int j = 0, k = m;
  for (int i = 0; i <= k; ++i) {
    ed->obj2win (x[i], y[i], dx, dy);
    xy[j++] = dx;
    xy[j++] = dy;
  }
  glVertexPointer (2, GL_FLOAT, 0, xy);
  glDrawArrays (GL_LINE_STRIP, 0, k+1);

  if (crv->shapeform) { // mark position

    ed->obj2win (xp[k], y[k], dxp, dy);

    float sh[4];

    sh[0]=dxp;sh[1]=dy;
    glPointSize (6);
      glVertexPointer (2, GL_FLOAT, 0, sh);
      glDrawArrays (GL_POINTS, 0, 1);
    glPointSize (1);
    sh[0]=dxp;sh[1]=dy;sh[2]=dx;sh[3]=dy;

    glVertexPointer (2, GL_FLOAT, 0, sh);
    glDrawArrays (GL_LINES, 0, 2);

  }

}

void curve_samples::set (float zh, int p) {

  if (zh <= 0) zh = 440;
  if (p <= 0) p = 1;

  nperiods = p;

  hz = zh;
  hz2step (hz, step);

  int oldn = n;
  n = p * (1 + 1.0f / step) + 0.5;


  if (x) {
    if (n > oldn) {
      delete[] x; x = new float [n];
      delete[] xp; xp = new float [n];
      delete[] y; y = new float [n];
      delete[] xy; xy = new float [2 * n];
    }
  } else {
    x = new float [n];
    xp = new float [n];
    y = new float [n];
    xy = new float [2 * n];
  }
  int nsz = sizeof (float) * n;
  memset (x, 0, nsz);
  memset (xp, 0, nsz);
  memset (y, 0, nsz);
  memset (xy, 0, 2 * nsz);
}

void curve_editor::picked_using_picker (int id) {
  set_pick_from_hitlist (id);
  switch (todo) {
    case NOTHING:
      prep_move ();
      break;
    case PICK_CURVE:
      curve_picked ();
      break;
    case REMOVE_VERTEX:
      remove ();
      break;
    case FOLD_VERTEX:
      fold_tangents_of_vertex (pik);
      break;
    case UNFOLD_VERTEX:
      unfold_tangents_of_vertex (pik);
      break;
    case MIRROR_VERTEX:
      mirror ();
      break;
    case ASSIGN_CAPTURE:
      assign_mouse_capture ();
      break;
    case MODULATE_POINT:
      modulate_point (1);
      break;
  }
}

void curve_editor::remove_using_menu () {
  todo = REMOVE_VERTEX;
  cursor_mesg = " Click on vertex to delete. ESC to stop.";
}

void curve_editor::modulate_point () {
  todo = MODULATE_POINT;
  cursor_mesg = " Click on vertex or tangent to modulate. ESC to stop.";
}

void curve_editor::modulate_point (int) {
  if (pik()) {
    undos.push_back (undo_t (pik.crv_id, *pik.crv, win));
    pomo.add (pik);
  }
}

void curve_editor::insert_using_menu () {
  if (num_curves == 1) {
    todo = INSERT_VERTEX;
    cursor_mesg = " Click to insert vertex. ESC to stop.";
  } else {
    todo = PICK_CURVE;
    cursor_mesg = " Pick curve to insert vertex. ESC to abort!";
    next_todo = INSERT_VERTEX;
    next_cursor_mesg = " Click to insert vertex. ESC to stop.";
  }
}

void curve_editor::fold_tangents_using_menu () {
  int fold_vertex = MENU.cb_selection_only.state;
  if (fold_vertex) {
    todo = FOLD_VERTEX;
    cursor_mesg = " Click on vertex to fold tangents. ESC to stop.";
  } else {
    if (num_curves == 1) {
      pik = pick_cur_curve ();
      fold_all_tangents (pik);
      do_nothing ();
    } else {
      todo = PICK_CURVE;
      next_todo = FOLD_ALL;
      next_cursor_mesg = "";
      cursor_mesg = " Pick curve to fold tangents. ESC to stop.";
    }
  }
}

void curve_editor::unfold_tangents_using_menu () {
  int unfold_vertex = MENU.cb_selection_only.state;
  if (unfold_vertex) {
    todo = UNFOLD_VERTEX;
    cursor_mesg = " Click on vertex to unfold tangents. ESC to stop.";
  } else {
    if (num_curves == 1) {
      pik = pick_cur_curve ();
      unfold_all_tangents (pik);
      do_nothing ();
    } else {
      todo = PICK_CURVE;
      cursor_mesg = " Pick curve to unfold tangents. ESC to stop.";
      next_todo = UNFOLD_ALL;
      next_cursor_mesg = "";
    }
  }
}

void curve_editor::mirror_using_menu () {
  int mirror_vertex = MENU.cb_selection_only.state;
  if (mirror_vertex) {
    todo = MIRROR_VERTEX;
    cursor_mesg = " Click on vertex or tangent to flip. ESC to stop.";
  } else {
    if (num_curves == 1) {
      pik = pick_cur_curve ();
      int whole_curve = 1;
      mirror (whole_curve);
      do_nothing ();
    } else {
      todo = PICK_CURVE;
      cursor_mesg = " Pick the curve to flip. ESC to stop.";
      next_todo = MIRROR_ALL;
      next_cursor_mesg = "";
    }
  }
}

void curve_editor::copy_using_menu () {
  if (num_curves == 1) {
    copy = *curveinfo[0].curve;
    cons << GREEN << "Copied " << copy.name << eol;
  } else {
    todo = PICK_CURVE;
    cursor_mesg = " Pick the curve to copy.  ESC to stop.";
    next_todo = COPY;
    next_cursor_mesg = "";
  }
}

void curve_editor::paste_using_menu () {
  if (num_curves == 1) {
    pik = pick_cur_curve ();
    paste (pik);
  } else {
    todo = PICK_CURVE;
    cursor_mesg = " Pick the curve to paste.  ESC to stop.";
    next_todo = PASTE;
    next_cursor_mesg = "";
  }
}

void curve_editor::fold_all_tangents (hit_t& hit) {
  curve_info& ci = curveinfo [hit.crv_id];
  multi_curve& crv = *ci.curve;
  mix = crv;
  undos.push_back (undo_t (hit.crv_id, crv, win));
  convert2_polyline (crv);
  ci.lisner->edited (this, hit.crv_id);
}

void curve_editor::unfold_all_tangents (hit_t& hit) {
  curve_info& ci = curveinfo [hit.crv_id];
  multi_curve& crv = *ci.curve;
  mix = crv;
  undos.push_back (undo_t (hit.crv_id, crv, win));
  const point<float>& obj = get_obj_chunk ();
  convert2_catmull_rom (crv, max (obj.x, obj.y)); // turn bezier curve into 'catmull-rom' spline; still a bezier curve
  ci.lisner->edited (this, hit.crv_id);
}

void curve_editor::fold_tangents_of_vertex (hit_t& hit) {
  if (hit()) {
    curve_info& ci = curveinfo[hit.crv_id];
    multi_curve& crv = *ci.curve;
    mix = crv;
    undos.push_back (undo_t (hit.crv_id, crv, win));
    float vx, vy; crv.get_vertex (hit.id, vx, vy);
    crv.set_left_tangent (hit.id, vx, vy);
    crv.set_right_tangent (hit.id, vx, vy);
    crv.evaluate ();
    ci.lisner->edited (this, hit.crv_id);
    do_nothing ();
  }
}

void curve_editor::unfold_tangents_of_vertex (hit_t& hit) {
  if (hit()) {
    curve_info& ci = curveinfo [hit.crv_id];
    multi_curve& crv = *ci.curve;
    mix = crv;
    undos.push_back (undo_t (hit.crv_id, crv, win));
    const point<float>& obj = get_obj_chunk ();
    float vx, vy; crv.get_vertex (hit.id, vx, vy);
    crv.set_left_tangent (hit.id, vx - obj.x, vy);
    crv.set_right_tangent (hit.id, vx + obj.x, vy);
    crv.evaluate ();
    ci.lisner->edited (this, hit.crv_id);
    do_nothing ();
  }
}

void curve_editor::render_curve_samples () {
  if (samples_enabled) {
    if (!pik()) pik = pick_cur_curve ();
    cs.render (pik.crv);
  }
}

void curve_editor::toggle_waveform_samples_display () {
  if (is_waveform_editor) {
    samples_enabled = !samples_enabled;
    render_curve_samples ();
    dont_call_listener (MENU.cb_show_waveform_samples, samples_enabled);
  }
}

void curve_editor::set_hz (float zh) {
  hz = zh;
  if (hz < 0) hz = SAMPLE_RATE / 100;
  cs.set (hz, cs.nperiods);
  render_curve_samples ();
}

void curve_editor::set_periods (int p) {
  cs.set (cs.hz, p);
  render_curve_samples ();
}

void curve_editor::do_nothing () {
  stop_mouse_capture ();
  lmb_move = 0;
  if (todo == ADD_VERTEX) {
    if (MENU.show == 0)
      replace ();
    else
      clear_scratch_curve ();
  }
  todo = next_todo = NOTHING;
  cursor_mesg = next_cursor_mesg = "";
}

void curve_editor::copy_curve () {
  if (pik()) {
    copy = *get_curve(pik.crv_id);
    cons << GREEN << "Copied "<< copy.name << "." << eol;
    do_nothing ();
  }
}

void curve_editor::do_load_curve (int dir) {
  if (library && library->num_curves()) load_curve (dir);
}

void curve_editor::do_redo () {
  dodo (redos, undos, "no more redos.");
}

void curve_editor::do_undo () {
  dodo (undos, redos, "no more undos.");
}

void curve_editor::do_pick_curve () {
  clear_hit (pik);
  todo = PICK_CURVE;
  cursor_mesg = " Click to pick curve, ESC to abort!";
  next_todo = NOTHING;
  next_cursor_mesg = "";
}

hit_t curve_editor::pick_cur_curve () {
  multi_curve* crv = curveinfo[curcrv].curve;
  return hit_t (crv, 0);
}

multi_curve* curve_editor::get_picked_curve () {
  if (!pik()) {
    pik = pick_cur_curve ();
    return pik.crv;
  } else
    return pik.crv;
}

void curve_editor::set_picked_curve_name (const string& name) {
  if (pik()) pik.crv->set_name (name);
}

void curve_editor::delete_curve () {
  if (library) library->del ();
}

void curve_editor::draw_replacement_curve_using_menu () {
  if (num_curves == 1) {
    cons << YELLOW << "Replacing the viewed curve" << eol;
    pik = pick_cur_curve ();
    show_scratch_curve = 1;
    todo = ADD_VERTEX;
    cursor_mesg = " Click to add vertex. ESC to stop.";
  } else {
    todo = PICK_CURVE;
    cursor_mesg = " Pick curve to replace. ESC to abort!";
    next_todo = ADD_VERTEX;
    next_cursor_mesg = " Click to add vertex. ESC to abort!";
  }
}

void curve_editor::add_vertex () {
  show_scratch_curve = 1;
  float sx, sy; snap (sx, sy);
  win_scratch_points.push_back (point<float> (sx, sy));
  float curvx, curvy; win2obj (sx, sy, curvx, curvy);
  curv_scratch_points.push_back (point<float>(curvx, curvy));
  if (curv_scratch_points.size () < 2) cursor_mesg = "Click to add vertex. ESC to abort!"; else cursor_mesg = "Click to add vertex. ESC to complete.";
}

void curve_editor::assign_mouse_capture () {
  if (pik()) {
    undos.push_back (undo_t (pik.crv_id, *get_curve(pik.crv_id), win));
    macros.push_back (mouse_macro (mocap0, pik, capturer.add ()));
    cons << GREEN << "Assigned mouse capture to " << pik << eol;
    do_nothing ();
  }
}

void curve_editor::assign_mouse_capture_from_menu () {
  if (mocap0()) {
    todo = ASSIGN_CAPTURE;
    next_todo = NOTHING;
    cursor_mesg = " Pick vertex or tangent to assign mouse capture. ESC to abort!";
  } else cons << RED << "No mouse capture found!" << eol;
}

void curve_editor::remove_mouse_capture (state_button* sb) {
  for (vector<mouse_macro>::iterator i = macros.begin(), j = macros.end(); i != j;) {
    mouse_macro& m = *i;
    if (m.sb == sb) {
      cons << GREEN << "Removed mouse capture from " << m.hit << eol;
      i = macros.erase (i);
      j = macros.end ();
      if (macros.empty()) {
        do_nothing ();
        return;
      }
    } else ++i;
  }
}

void curve_editor::toggle_mouse_capture (vector<state_button*>& caps) {
  int p = 0, q = 0;
  for (vector<mouse_macro>::iterator i = macros.begin(), j = macros.end(); i != j; ++i) {
    mouse_macro& mm = *i;
    for (int i = 0, ncaps = caps.size (); i < ncaps; ++i) {
      state_button* sb = caps[i];
      if (sb->state && mm.sb == sb) {
        mm.paused = !mm.paused;
        if (mm.paused) ++p; else ++q;
      }
    }
  }
  cons << GREEN << "Played " << q << " and Paused " << p << " mouse captures" << eol;
}

void curve_editor::start_mouse_capture_from_menu () {
  start_mouse_capture ();
  todo = START_CAPTURE;
  cursor_mesg = " Capturing mouse. Click or ESC to finish!";
}

void curve_editor::set_limit (float f) {
  if (!pik()) pik = pick_cur_curve ();
  curve_info& ci = curveinfo [pik.crv_id];
  multi_curve* crv = ci.curve;
  crv->set_limit (f);
  crv->evaluate ();
  ci.lisner->edited (this, pik.crv_id);
}

void curve_editor::setup_tools_menu () {
  multi_curve* crv = get_curve (curcrv);
  MENU.lf_curve_name.set_text (crv->name);
  MENU.sp_curve_limit.set_value (crv->limit);
  MENU.picked (MENU.ol_mirror.option, 0);
  set_curve_style ();
}

void curve_editor::curve_picked () {
  setup_tools_menu ();
  if (next_todo == FOLD_ALL) fold_all_tangents (pik);
  else if (next_todo == UNFOLD_ALL) unfold_all_tangents (pik);
  else if (next_todo == MIRROR_ALL) {int whole_curve = 1; mirror (whole_curve);}
  else if (next_todo == COPY) copy_curve ();
  else if (next_todo == PASTE) paste (pik);
  else {
    todo = next_todo;
    cursor_mesg = next_cursor_mesg;
  }
}


void curve_editor::set_curve_style () {
  multi_curve* crv = get_curve (curcrv);
  string pre (" Curve is "); if (is_waveform_editor) pre = " Waveform is ";
  if (crv->shapeform) MENU.ol_curve_style.set_text(pre + "a shape"); else MENU.ol_curve_style.set_text (pre + "classical");
}

void curve_editor::toggle_curve_style () {
  multi_curve* crv = get_curve (curcrv);
  mix = *crv;
  crv->set_shapeform (!crv->shapeform);
  set_curve_style ();
  render_curve_samples ();
  if (crv->shapeform && is_waveform_editor && !samples_enabled) toggle_waveform_samples_display ();
  curveinfo[curcrv].lisner->edited (this, curcrv);
}

void curve_editor::set_rpm (float _rpm) {
  if (_rpm == 0) {
    rpm = hit_t ();
  } else {
    if (!pik()) pik = pick_cur_curve ();
    rpm = pik;
    float rps = _rpm / 60.0;
    float rpf = rps / FPS;
    angle = rpf * TWO_PI;
    if (last_rpm == 0) {
      undos.push_back (undo_t (rpm.crv_id, *rpm.crv, win));
      rpm.crv->calc_centroid ();
    }
    startt = ui_clk() - TIME_PER_FRAME;
  }
  last_rpm = _rpm;
}

void curve_editor::rotate () {
  if (rpm()) {
    float now = ui_clk();
    float elapsed = now - startt;
    if (elapsed >= TIME_PER_FRAME) {
      float mult = elapsed / TIME_PER_FRAME;
      rpm.crv->rotate (mult * angle);
      curveinfo[rpm.crv_id].lisner->edited (this, rpm.crv_id);
      startt = ui_clk();
    }
  }
}

void curve_editor::scale (float sx, float sy) {
  get_picked_curve ();
  undos.push_back (undo_t (pik.crv_id, *pik.crv, win));
  pik.crv->scale (sx, sy);
}

void curve_editor::hilite_item (int id) {
  hit_t& hit = hitlist[id];
  glLineWidth (2);
  point<float>& v = hit.crv->vertices [hit.id];
  point<float>& lt = hit.crv->left_tangents [hit.id];
  point<float>& rt = hit.crv->right_tangents [hit.id];
  glColor3f (0, 1, 0);
  draw_handle (v);
  switch (hit.what) {
    case hit_t::LEFT_TANGENT:
      draw_tangent (v, lt);
      draw_handle (lt);
      break;
    case hit_t::RIGHT_TANGENT:
      draw_tangent (v, rt);
      draw_handle (rt);
      break;
  }
  glLineWidth (1);
}

void curve_editor::apply_plugin (plugin* p) {
  multi_curve* crv = curveinfo [curcrv].curve;
  if (p->mix) mix = *crv;
  if (p->undo) undos.push_back (undo_t (curcrv, *crv, win));
  if (!p->apply (*crv) && p->undo) undos.pop_back ();
  else {
    if (p == &spiraler_ || p == &rosemilker)
      crv->centroid_style = multi_curve::SET;
    else
      crv->centroid_style = multi_curve::CALC;
    pomo.validate ();
    curveinfo[curcrv].lisner->edited (this, curcrv);
    if (last_rpm && rpm.crv == crv) rpm.crv->calc_centroid ();
  }
}

int curve_editor::stop_todo () {
  if (todo != curve_editor::NOTHING) {
    do_nothing ();
    return 1;
  }
  return 0;
}

#ifdef __SVG__
void curve_editor::write_samples (ofstream& svg) {
  float x = 0, y = 0;
  float ox, oy;
  obj2win (x, y, ox, oy);
  static float cx = cs.x[0];
  float dx, dy;
  for (int i = 0; i < cs.n; ++i) {
    obj2win (cs.x[i], cs.y[i], dx, dy);
    svg << "<polyline fill=\"none\" stroke=\"black\" points=\"";
    float cdx = cx + dx;
    svg << cdx << ',' << dy << ' ' << cdx << ',' << oy << endl;
    svg << "\"/>" << endl;
  }
  cx += dx;
}

void curve_editor::write_curve (multi_curve* crv, ofstream& svg, float w, float h, float t) {
  svg << "<polyline style=\"stroke-width:" << t << ";stroke-linecap:round;stroke-miterlimit:" << t << ";\" fill=\"none\" stroke=\"black\" points=\"";
  vector<crvpt> vpts; crv->get_profile_points (vpts);
  for (int p = 0, q = vpts.size (); p < q; ++p) {
    float dx, dy; obj2win (vpts[p].x, vpts[p].y, dx, dy);
    //dy = win.top - dy;
    svg << w * (dx - win.left) / win.width << "," << h - 1 - h * (dy - win.bottom) / win.height << SPC;
  }
  svg << "\"/>" << endl;
}

void curve_editor::write_svg (float h, float t, const string& fn) {
  multi_curve* crv = get_picked_curve ();
  string fname = user_data_dir + fn;
  ofstream svg (fname.c_str(), ios::out);
  float w = win.width / win.height * h;
  svg << "<?xml version=\"1.0\"?>" << endl;
  svg << "<svg xmlns=\"https://www.w3.org/2000/svg\" width=\"" << w /*win.width*/ << "\" height=\"" << h /*win.height*/ << "\" viewBox=\"" << "0 0 " << w << ' ' << h << "\">" << endl;
    write_curve (crv, svg, w, h, t);
  svg << "</svg>" << endl;
  cons << GREEN << "written curve to: " << fname << eol;
}

#endif

#ifdef __HPGL__

void curve_editor::write_curve (multi_curve* crv, ofstream& hpgl, float scale, float penmag) {
  vector<crvpt> vpts; crv->get_profile_points (vpts);
  float minx = -1.0f, miny = -1.0f, maxx = 1.0f, maxy = 1.0f, width = maxx - minx, height = maxy - miny;
  for (int i = 0, j = vpts.size (); i < j; ++i) {
    float x = vpts[i].x, y = vpts[i].y;
    float ox = (x - minx) * scale / width;
    float oy = (y - miny) * scale / height;
    vpts[i].x = ox;
    vpts[i].y = oy;
  }
  float lx = vpts[0].x, ly = vpts[0].y;
  float m = 0;
  rnd<int> pen (1, 8);
  static const char semicolon = ';';
  for (int p = 1, q = vpts.size (); p < q; ++p) {
    float px = vpts[p].x, py = vpts[p].y;
    float dx = (px - lx), dy = (py - ly);
    m += magnitude (dx, dy);
    if (m >= penmag) { // switch pen for fun
      m -= penmag;
      hpgl << "SP" << pen () << semicolon << eol;
    }
    hpgl << "PR" << dx << ", " << dy << semicolon << eol;
    lx = px;
    ly = py;
  }
}

void curve_editor::write_hpgl (float scale, float penmag) {
  multi_curve* crv = get_picked_curve ();
  string fname = user_data_dir + crv->name + ".hpgl";
  ofstream hpgl (fname.c_str(), ios::out);
  hpgl << "IN;" << eol;
  hpgl << "PD;" << eol;
  write_curve (crv, hpgl, scale, penmag);
  hpgl << "PU;" << eol;
  cons << GREEN << "written curve to: " << fname << eol;
}

#endif

int curve_editor::esc () {
  int ret = 0;
  if (stop_todo())
    ret = 1;
  else {
    if (!rmb) {
      load_instrument ();
      ret = 1;
    } else
      ret = 0;
  }
  if (sinemixer.sine_levels.editing) {
    sinemixer.sine_levels.stop_editing ();
    ret = 1;
  }
  return ret;
}