Subversion Repositories DIN Is Noise

Rev

Rev 2339 | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
* curve_editor.cc
* DIN Is Noise is copyright (c) 2006-2025 Jagannathan Sampath
* DIN Is Noise is released under GNU Public License 2.0
* 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, PI;
extern char BUFFER [];
extern instrument* get_current_instrument ();
extern int BEATER;
extern float BPM_MULT;

curve_editor::curve_editor (const std::string& settingsf) {

  todo = next_todo = NOTHING;

  curcrvchgd = 1;
  curves = curcrv = savedrots = \
  beater = waved = \
  fft_enabled = samples_enabled = \
  drawcurve = draweditables = marksegments = \
  lmb_move = lmb_clicked = n_pts = hlabel_only \
  = 0;

  gl_pts = 0;

  load  (settingsf);


}

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

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

  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);

  file >> ignore >> carry_tangents;

  file >> ignore >> mirror_tangents;

  tb.load (file);

  file >> ignore >> waved;
  if (waved) {
    file >> ignore >> fft_enabled;
    file >> ignore >> samples_enabled;
    file >> ignore >> hz >> ignore >> nperiods;
    cs.set (hz, nperiods);
  }

  file >> ignore >> drawcurve >> draweditables >> marksegments >> label_vertices;
  file >> ignore >> kbkb_attack_editor;
  file >> ignore >> overlay;
  file >> ignore >> axis;
  file >> ignore >> draw_plugin_output;

  file >> ignore >> savedrots;

  visibles = 0;
  if (savedrots) {
    curveinfo.resize (savedrots);
    float deg;
    for (int i = 0; i < savedrots; ++i) {
      curve_info& ci = curveinfo[i];
      float s, t;
      file >> ci.lastrpm >> ci.angle >> ci.totang >> ci.dir >> deg >> ci.autoflip >> ci.randoflip >> s >> t >> ci.visible;
      ci.rd.set (s, t);
      ci.every0 = deg;
      ci.every = ci.every0;
      ci.startt = 0;
      visibles += ci.visible;
    }
  }

  file >> ignore >> beater;

  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;

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

  file << "waved " << waved << endl;
  if (waved) {
    file << "fft_enabled " << fft_enabled << endl;
    file << "samples_enabled " << samples_enabled << endl;
    file << "hz " << hz << endl;
    file << "nperiods " << nperiods << endl;
  }
  file << "draw=curve+editables+marksegments+labelvertex " << drawcurve << spc << draweditables << spc << marksegments << spc << label_vertices << endl;
  file << "sustain_editing " << kbkb_attack_editor << endl;
  file << "overlay " << overlay << endl;
  file << "mirror_axis " << axis << endl;
  file << "draw_plugin_output " << draw_plugin_output << endl;

  file << "curvesinfo " << curves << endl;
  for (int i = 0; i < curves; ++i) {
    curve_info& ci = curveinfo[i];
    file << ci.lastrpm << spc << ci.angle << spc << ci.totang << spc << ci.dir \
    << spc << ci.every0.deg << spc << ci.autoflip << spc << ci.randoflip \
    << spc << ci.rd.min << spc << ci.rd.max << spc << ci.visible << spc;
  }
  file << endl << "beater " << beater << endl;

}

void curve_editor::prep_move () {
  undos.push_back (undo_t (pik.crv_id, *pik.crv, win));
  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) {

      lmb_clicked = 1;

      if (lmb_move == FINISH_ON_CLICK) {
        cons << RED << "Finished moving " << pik << eol;
        quick_nothing ();
        mouse_slider0.deactivate ();
        return 1;
      }

      if (todo == NOTHING) { // try to hit something to move
        hittest ();
        if (pik()) { // hit
          if (pomo.hit (pik))
            ; // point modulator is moving hit element so ignore
          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 PINUNPIN:
            hittest ();
            pinunpin ();
            return 1;
          case FOLD_SELECTED:
            hittest ();
            if (pik()) fold_tangents_of_vertex (pik);
            return 1;
          /*case UNFOLD_SELECTED:
            hittest ();
            if (pik()) unfold_tangents_of_vertex (pik);
            return 1;*/

          case MIRROR_VERTEX:
            hittest ();
            if (pik()) mirror ();
            return 1;
          case START_CAPTURE:
            abs_nothing ();
            return 1;
          case ASSIGN_CAPTURE:
            hittest ();
            assign_mouse_capture ();
            return 1;
          case PICK_CURVE:
          case PICK_TANGENT:
            hittest ();
            if (pik()) curve_picked ();
            return 1;
        }
      }
    }
  } else {

    lmb_clicked = 0;

    if (lmb_move && !MENU.show) {

      lmb_move = FINISH_ON_CLICK;
      if (todo == MOVE_PICKED)
        move ();
      else if (todo == MOVE_ALL)
        move (MOVE_ALL);

      if (keypressed (SDLK_f) && pik.what > hit_t::VERTEX && !mouse_slider0.active) {
        calc_hit_params (pik);
        start_sizing_tangent ();
      }

      if (keypressed (SDLK_p)) pinunpin ();

      return 1;

    } else if (todo == SIZE_TANGENT) start_sizing_tangent ();
  }
 
  if (library) {
    if (library->num_curves () && !mouse_slider0.active) {
      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 (keypressedd (SDLK_z)) { // undo & redo
    if (SHIFT) // l_shift + z - redo
      do_redo ();
    else
      do_undo ();
  }
  else if (keypressed (SDLK_u)) {
    MENU.viewtoo.toggle ();
  }
  else if (keypressedd (SDLK_r)) apply_plugin (uis.plugin__browser.get_cur ()); // apply current plugin
  else if (keypressed (SDLK_v)) {
    if (CTRL) { // ctrl + v ie paste
      paste_using_menu ();
    } else { // delete vertex or tanget
      todo = REMOVE_VERTEX;
      cursor = " Click on vertex to delete. ESC to stop.";
      hittest ();
      if (pik()) {
        remove ();
        quick_nothing ();
      }
    }
  }
  else if (keypressed (SDLK_i)) insert_using_menu ();
  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_SLASH)) {
    if (!mouse_slider0.active) toggle_waveform_samples_display ();
  }
  else if (keypressedd (SDLK_COMMA)) {
    if (!mouse_slider0.active) {
      drawcurve = !drawcurve;
      justset (MENU.cb_draw_curve, drawcurve);
    }
  }
  else if (keypressed (SDLK_PERIOD)) {
    draweditables = !draweditables;
    justset (MENU.draweditables, draweditables);
  }
  else if (keypressed (SDLK_SEMICOLON)) {
    if (!mouse_slider0.active) {
      marksegments = !marksegments;
      justset (MENU.cb_mark_segments, marksegments);
    }
  }
  else if (keypressed (SDLK_c)) {
    if (CTRL) { // l_ctrl + c - copy curve
      if (onecurve()) {
        pik = pick_cur_curve ();
        copy_curve ();
      } else {
        copy_using_menu ();
      }
    }  
  }
  else if (keypressed (SDLK_l)) {
    label_vertices = !label_vertices;
    justset (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 instrument/editor
    overlay = !overlay;
    if (overlay)
      cons << GREEN << "Overlaid the " << uis.over->name << eol;  
    else
      cons << GREEN << "Removed the " << uis.over->name << " from overlay" << eol;  
    MENU.cb_overlay.set_state (overlay, 0);
  }
  else if (keypressed (SDLK_F3)) do_powbeat (1.0f / BPM_MULT);
  else if (keypressed (SDLK_F4)) do_powbeat (BPM_MULT);
  else if (keypressed (SDLK_F5)) {
    if (beater) modulate_down ();
  }
  else if (keypressed (SDLK_F6)) {
    if (beater) modulate_up ();
  }
  else if (keypressed (SDLK_F1)) helptext();

  return 1;

}

void curve_editor::pinunpin () {
  static const char* pup [] = {"Unpinned ", "Pinned "};
  if (pik (1)) {
    pointinfo<float>& p = pik.get ();
    p.pin = !p.pin;
    cons << pup[p.pin] << pik << eol;
  } else {
    int n = hitlist.size ();
    if (n && curve_picker.visible == 0) {
      for (int m = 0; m < n; ++m) {
        hit_t& h = hitlist[m];
        pointinfo<float>& p = h.get ();
        p.pin = !p.pin;
        cons << pup[p.pin] << h << eol;
      }
    }
  }
}

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::calc_hit_params (hit_t& hit) {

  if (!hit()) return;

  multi_curve* crv = get_curve (hit.crv_id);
  float vx0, vy0, vx, vy, lx, ly, rx, ry;
  int id = hit.id, lv = crv->last_vertex;

  crv->get_vertex (id, vx0, vy0);

  if (id == 0) {
    crv->get_vertex (lv, vx, vy);
    crv->get_left_tangent (lv, lx, ly);
  }
  else {
    vx = vx0; vy = vy0;
    crv->get_left_tangent (id, lx, ly);
  }

  hit.left_tangent_magnitude = unit_vector (hit.left_tangent.x, hit.left_tangent.y, vx, vy, lx, ly);

  if (id == lv) {
    crv->get_vertex (0, vx, vy);
    crv->get_right_tangent (0, rx, ry);
  }
  else {
    vx = vx0; vy = vy0;
    crv->get_right_tangent (id, rx, ry);
  }

  hit.right_tangent_magnitude = unit_vector (hit.right_tangent.x, hit.right_tangent.y, vx, vy, rx, ry);

  const point<float>& hp = hit.get ();
  obj2mouse (hp.x, hp.y);

  curcrv = hit.crv_id;
  curcrvchgd = 1;

}

void curve_editor::hittest () {

  hitlist.clear ();
  for (int i = 0, j = curves; i < j; ++i) {
    curve_info& ci = curveinfo[i];
    multi_curve* crv = ci.curve;
    if (ci.visible) {
      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 ();
  else {
    pik = hit_t ();
    return;
  }
  if (n == 1) {
    pik = hitlist [0];
    calc_hit_params (pik);
  } else {
    pik = hit_t ();
    if (n > 1 && SHIFT == 0) curve_picker.show ();
  }
}

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) ; else hitlist.push_back (hit_t (crv, crv_id, what, i));
  }
}

int curve_editor::filter_hitlist () {

  for (int i = 0, j = curves; 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 if (todo == PICK_TANGENT && hit.what == hit_t::VERTEX) {
      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);
  cons << GREEN << "Picked " << pik << eol;
}

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

  // move hit element to x,y

  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);
      break;

    case hit_t::RIGHT_TANGENT:
        need_eval = crv->set_right_tangent (hit.id, x, y);
        if (mirror_tangents && need_eval) {
          float vx, vy, rux, ruy;
          crv->get_vertex (hit.id, vx, vy);
          unit_vector (rux, ruy, vx, vy, x, y);
          if (hit.id == 0) { // first right tangent of curve, mirror with left tangent of last vertex
            int lv = crv->last_vertex;
            crv->get_vertex (lv, vx, vy);
            float m = crv->get_left_tangent_mag (lv);
            crv->set_left_tangent (lv, vx - m * rux, vy - m * ruy);
          } else
            crv->set_left_tangent (hit.id, vx - hit.left_tangent_magnitude * rux, vy - hit.left_tangent_magnitude * ruy);
        }
      break;

    case hit_t::LEFT_TANGENT:
        need_eval = crv->set_left_tangent (hit.id, x, y);
        if (mirror_tangents && need_eval) {
          float vx, vy, lux, luy;
          crv->get_vertex (hit.id, vx, vy);
          unit_vector (lux, luy, vx, vy, x, y);
          int lv = crv->last_vertex;
          if (hit.id == lv) {
            crv->get_vertex (0, vx, vy);
            float m = crv->get_right_tangent_mag (0);
            crv->set_right_tangent (0, vx - m * lux, vy - m * luy);
          } else
            crv->set_right_tangent (hit.id, vx - hit.right_tangent_magnitude * lux, vy - hit.right_tangent_magnitude * luy);
        }
      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 dx, dy;
  pointinfo<float>& p = pik.get ();

  dx = cx - p.x;
  dy = cy - p.y;

  crv.move (dx, dy);
  ci.lisner->edited (this, pik.crv_id);

  return 1;

}

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 << "Cannot delete curve: " << crv->name << eol;
    undos.pop_back ();
  }
  clear_hit (pik);
}

void curve_editor::swap () {
  if (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 = curveinfo[0], &ci1 = curveinfo[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);
      }
    } 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);
      }
    }
    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 ();
    if (draw_plugin_output) uis.plugin__browser.draw (this);
    draw_all ();
    if (show_scratch_curve) draw_scratch_curve ();
    if (curve_picker.visible) hilite_item (curve_picker.id);
  unproject ();
  if (overlay) ui::over->drawerlay ();
}

void curve_editor::draw_scratch_curve () {
  int spn = win_scratch_points.size ();
  if (spn == 0) return;
  int n = spn + 1;
  alloc_gl_pts (n);
  glColor3f (1, 1, 0.7f);
  int k = 0;
  for (int i = 0; i < spn; ++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 (marksegments) mark_segments ();

  // draw curve profile, vertices, tangents and handles
  int j = curves;
  for (int i = 0; i < j; ++i) {
    curve_info& ci = curveinfo [i];
    if (ci.visible) {
      multi_curve* crv = ci.curve;
      if (draweditables) {
        glColor3f (crv->rt, crv->gt, crv->bt);
        draw_tangents (crv);
        glColor3f (crv->r, crv->g, crv->b);
        draw_vertices (crv);
      }
      if (drawcurve) {
        glColor3f (crv->r, crv->g, crv->b);
        draw_curve (crv);
      }
    }
  }

  // mark beat/value
  //
  int n = bv.size ();
  if (n) {
    float nowx, nowy;
    float x, y;
    static const int marker_size = 250;
    for (int i = 0; i < n; ++i) {
      curve_info& ci = curveinfo[i];
      if (ci.visible) {
        beat2value* bvi = bv[i];
        multi_curve& crv = *bvi->crv;
        if (crv.shapeform) crv.get_xy (bvi->now, nowx, nowy);
        else {
          /*if (bvi->swing) {
            nowx = bvi->swingsol (bvi->now);
          } else */

            nowx = bvi->now;
          nowy = bvi->sol (nowx);
        }
        obj2win (nowx, nowy, x, y);

        static const float cmul = 2;
        glColor3f (cmul * crv.r, cmul * crv.g, cmul * 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);
          /*glBegin (GL_LINES);
            glVertex2f (x, mkr[3]);
            glVertex2f (x, mkr[7]);
            glVertex2f (mkr[0], y);
            glVertex2f (mkr[4], y);
          glEnd ();*/

          glLineWidth (1);
      }
    }
  }

  // label vertices
  if (label_vertices) {
    for (int i = 0, j = curves; 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 && curves) 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);
    glBegin (GL_LINES);
      glVertex2i (susx, susbox.bottom);
      glVertex2i (susx, win.bottom);
    glEnd ();
    /*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_segments () {
  // drop vertical lines from current curve's profile points
  curve_info& ci = curveinfo [curcrv];
  if (ci.visible) {
    multi_curve* crv = ci.curve;
    if (!crv->shapeform) {
      const float cc = 0.5f;
      float cr = cc, cg = cr, cb = cr;
      glEnable (GL_BLEND);
      glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
      vector<curve>& curv = crv->curv;
      int m = 0;
      for (int i = 0, j = curv.size(); i < j; ++i) m += curv[i].vpts.size ();
      int n = 2 * m;
      alloc_gl_pts (n);
      for (int i = 0, j = curv.size(), k = 0, l = 0; i < j; ++i) {
        vector<crvpt>& vpts = curv[i].vpts;
        float pox = -MILLION;
        for (int p = 0, q = vpts.size (); p < q; ++p) {
          float ox = vpts[p].x, oy = vpts[p].y;
          cr = cg = cb = cc;
          if (ox < pox) {cr = 1.0f; cg = cb = 0.0f;}
          pox = ox;
          if (waved && ( oy < -1 || oy > 1 )) {cr = 1.0f; cg = cb = 0.0f;}
          float x, y; obj2win (ox, oy, x, y);
          gl_pts [k++]=x;
          gl_pts [k++]=y;
          gl_pts [k++]=x;
          gl_pts [k++]=0;
          gl_clr [l++]=cr;
          gl_clr [l++]=cg;
          gl_clr [l++]=cb;
          gl_clr [l++]=0.5f;
          gl_clr [l++]=cr;
          gl_clr [l++]=cg;
          gl_clr [l++]=cb;
          gl_clr [l++]=0.5f;
        }
      }
      glEnableClientState (GL_COLOR_ARRAY);
      glColorPointer (4, GL_FLOAT, 0, gl_clr);
      glVertexPointer (2, GL_FLOAT, 0, gl_pts);
      glDrawArrays (GL_LINES, 0, n);
      glDisable (GL_BLEND);
      glDisableClientState (GL_COLOR_ARRAY);
    }
  }
}

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);*/

  glBegin (GL_LINES);
    glVertex2f (x, y);
    glVertex2f (tx, ty);
  glEnd ();
}

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 < 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);
      if (MENU.show) setup_tools_menu (); else curcrvchgd = 1;
      pomo.validate ();
      ci.lisner->edited (this, last.i);
      if (undo_redo_win) 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) {
  if (curves < savedrots) {
    curve_info& cc = curveinfo [curves];
    cc.curve = crv;
    cc.lisner = lsnr;
  } else {
    curveinfo.push_back (curve_info (crv, lsnr));
  }
  if (++curves == 1) {
    render_curve_samples ();
    hlabel_only = crv->shapeform;
  }
}

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

multi_curve* curve_editor::get_curve (int i) {
  curve_info& ci = curveinfo [i];
  return ci.curve;
}

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::enter () {

  calc_visual_params ();

  MENU.populatecurvestab (this);
  MENU.set_mirror_tangents (mirror_tangents);

  MENU.crvwav.hz.set_value (hz);
  MENU.crvwav.periods.set_value (nperiods);
  MENU.crvwav.time.set_value (curve_samples::nsec);
  curcrvchgd = 1;

  justset (MENU.cb_show_waveform_samples, samples_enabled);
  justset (MENU.cb_label_vertices, label_vertices);
  justset (MENU.cb_draw_curve, drawcurve);
  justset (MENU.draweditables, draweditables);
  justset (MENU.cb_mark_segments, marksegments);
  justset (MENU.cb_label_vertices, label_vertices);
  justset (MENU.cb_overlay, overlay);
  justset (MENU.drawsnaps, draww.snaps);
  justset (MENU.drawcursor, draww.guide);
  justset (MENU.viewtoo, undo_redo_win);

  if (uis.is_widget_on_screen (&uis.plugin__browser, this)) {
    uis.plugin__browser.set_ed (this);
    uis.plugin__browser.set_fold (!draw_plugin_output);
  }

  ui::enter ();

}

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 ();
}

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;
  if (MENU.show) setup_tools_menu (); else curcrvchgd = 1;
  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);
          if (SHIFT || MENU.ol_paste.id) paste_append (*crv, copy); else *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;
    quick_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 ();
}

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 PICK_TANGENT:
      curve_picked ();
      break;
    case REMOVE_VERTEX:
      remove ();
      break;
    case FOLD_SELECTED:
      fold_tangents_of_vertex (pik);
      break;
    /*case UNFOLD_SELECTED:
      unfold_tangents_of_vertex (pik);
      break;*/

    case PINUNPIN:
      pinunpin ();
      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 = " Click on vertex to delete. ESC to stop.";
}

void curve_editor::modulate_point () {
  todo = MODULATE_POINT;
  cursor = " 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 (onecurve()) {
    todo = INSERT_VERTEX;
    cursor = " Click to insert vertex. ESC to stop.";
  } else {
    todo = PICK_CURVE;
    cursor = " Pick curve to insert vertex. ESC to abort!";
    next_todo = INSERT_VERTEX;
    cursor += " 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_SELECTED;
    cursor = " Click on a tangent or vertex to fold its tangents. ESC to stop.";
  } else {
    if (onecurve()) {
      pik = pick_cur_curve ();
      fold_all_tangents (pik);
      quick_nothing ();
    } else {
      next_todo = FOLD_ALL;
      cursor += "";
      todo = PICK_CURVE;
      cursor = " Pick curve to fold tangents. ESC to stop.";
    }
  }
}

void curve_editor::pinunpin_using_menu () {
  todo = PINUNPIN;
  cursor = " Click on vertex or tangent to Pin / Unpin. ESC to stop.";
}


/*void curve_editor::unfold_tangents_using_menu () {
  int unfold_vertex = MENU.cb_selection_only.state;
  if (unfold_vertex) {
    todo = UNFOLD_SELECTED;
    cursor = " Click on vertex to unfold tangents. ESC to stop.";
  } else {
    if (onecurve()) {
      pik = pick_cur_curve ();
      unfold_all_tangents (pik);
      quick_nothing ();
    } else {
      todo = PICK_CURVE;
      cursor = " Pick curve to unfold tangents. ESC to stop.";
      next_todo = UNFOLD_ALL;
      cursor += "";
    }
  }
}*/


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

void curve_editor::copy_using_menu () {
  if (onecurve()) {
    copy = *curveinfo[curcrv].curve;
    cons << GREEN << "Copied " << copy.name << eol;
  } else {
    todo = PICK_CURVE;
    cursor = " Pick the curve to copy.  ESC to stop.";
    next_todo = COPY;
    cursor += "";
  }
}

void curve_editor::paste_using_menu () {
  if (onecurve()) {
    pik = pick_cur_curve ();
    paste (pik);
  } else {
    todo = PICK_CURVE;
    cursor = " Pick the curve to paste.  ESC to stop.";
    next_todo = PASTE;
    cursor += "";
  }
}

multi_curve& curve_editor::set_mix_undo (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));
  return crv;
}

void curve_editor::fold_all_tangents (hit_t& hit) {
  multi_curve& crv = set_mix_undo (hit);
  convert2_polyline (crv);
  curveinfo[hit.crv_id].lisner->edited (this, hit.crv_id);
}

/*void curve_editor::unfold_all_tangents (hit_t& hit) {
  multi_curve& crv = set_mix_undo (hit);
  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
  curveinfo[hit.crv_id].lisner->edited (this, hit.crv_id);
}*/


void curve_editor::fold_tangents_of_vertex (hit_t& hit) {

  multi_curve& crv = set_mix_undo (hit);

  float vx, vy;
  crv.get_vertex (hit.id, vx, vy);

  if (hit.what == hit_t::VERTEX) {
    crv.set_left_tangent (hit.id, vx, vy);
    crv.set_right_tangent (hit.id, vx, vy);
  } else if (hit.what == hit_t::LEFT_TANGENT) {
    crv.set_left_tangent (hit.id, vx, vy);
  } else {
    crv.set_right_tangent (hit.id, vx, vy);
  }

  crv.evaluate ();
  curveinfo[hit.crv_id].lisner->edited (this, hit.crv_id);

}

/*void curve_editor::unfold_tangents_of_vertex (hit_t& hit) {

    multi_curve& crv = set_mix_undo (hit);

    float vx, vy; crv.get_vertex (hit.id, vx, vy);
    const point<float>& obj = get_obj_chunk ();

    if (hit.what == hit_t::VERTEX) {
      crv.set_left_tangent (hit.id, vx - obj.x, vy);
      crv.set_right_tangent (hit.id, vx + obj.x, vy);
    } else if (hit.what == hit_t::LEFT_TANGENT) {
      crv.set_left_tangent (hit.id, vx - obj.x, vy);
    } else {
      crv.set_right_tangent (hit.id, vx + obj.x, vy);
    }

    crv.evaluate ();
    curveinfo[hit.crv_id].lisner->edited (this, hit.crv_id);

}*/


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

void curve_editor::toggle_waveform_samples_display () {
  if (waved) {
    samples_enabled = !samples_enabled;
    render_curve_samples ();
    justset (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::abs_nothing () {
  stop_mouse_capture ();
  if (todo == ADD_VERTEX) {
    if (rmb)
      replace ();
    else
      clear_scratch_curve ();
  }
  quick_nothing ();
}

void curve_editor::quick_nothing (int lm) {
  todo = next_todo = NOTHING;
  cursor = "";
  cursor += "";
  lmb_move = lm;
}

void curve_editor::copy_curve () {
  if (pik()) {
    copy = *get_curve(pik.crv_id);
    cons << GREEN << "Copied "<< copy.name << "." << eol;
    quick_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);
  if (onecurve()) {
    pik = pick_cur_curve ();
    cons << "Picked " << pik.crv->name << eol;
    quick_nothing ();
    return;
  } else {
    todo = PICK_CURVE;
    cursor = " Click to pick curve, ESC to abort!";
    next_todo = NOTHING;
    cursor += "";
  }
}

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

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


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

void curve_editor::draw_replacement_curve_using_menu () {
  if (onecurve()) {
    pik = pick_cur_curve ();
    show_scratch_curve = 1;
    todo = ADD_VERTEX;
    cursor = " Click to add vertex. ESC to abort!";
  } else {
    todo = PICK_CURVE;
    cursor = " Pick curve to replace. ESC to abort!";
    next_todo = ADD_VERTEX;
    cursor += " 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 () > 1) cursor = "Click to add vertex. Right click 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;
    quick_nothing ();
  }
}

void curve_editor::assign_mouse_capture_from_menu () {
  if (mocap0()) {
    todo = ASSIGN_CAPTURE;
    next_todo = NOTHING;
    cursor = " 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()) {
        quick_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 = " Capturing mouse. Click or ESC to finish!";
}

void curve_editor::set_picked_curve_name (const string& name) {
  if (!pik()) pik = pick_cur_curve ();
  cons << "Curve " << pik.crv->name;  
  pik.crv->set_name (name);
  cons << " changed to " << name << eol;
}

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);
  cons << "Curve roughness = " << f << eol;
}

void curve_editor::set_curve_style (multi_curve* crv) {
  const char* pre [] = {" Curve is", " Waveform is"};
  int i = waved;
  int s = crv->shapeform;
  const char* awhat [] = {"classical", "a shape" };
  sprintf (BUFFER, "%s %s", pre[i], awhat[s]);
  MENU.ol_curve_style.set_text(BUFFER);
  float cx [] = {0.0f, 0.5f};
  crv->cx = cx[s];
  crv->cy = 0.0f;
}

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

void curve_editor::curve_picked () {
  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 = cursor.next_mesg;
    cons << GREEN << "Picked " << pik.crv->name << eol;
  }
}

void curve_editor::try_sizing_tangent () {
  todo = PICK_TANGENT;
  cursor = "Pick a tangent on a curve";
  next_todo = SIZE_TANGENT;
  cursor += "";
}

void curve_editor::start_sizing_tangent () {
  mouse_slider0.add (MENUP.col);
  activate_mouse_slider (-2);
  quick_nothing (lmb_move);
}

void curve_editor::size_tangent (float d) {
  curve_info& ci = curveinfo [pik.crv_id];
  multi_curve& crv = *ci.curve;
  float vx, vy; crv.get_vertex (pik.id, vx, vy);
  float m = 0.0f, x = 0.0f, y = 0.0f;
  switch (pik.what) {
    case hit_t::RIGHT_TANGENT:
      pik.right_tangent_magnitude += d;
      m = pik.right_tangent_magnitude;
      x = vx + m * pik.right_tangent.x;
      y = vy + m * pik.right_tangent.y;
      break;
    case hit_t::LEFT_TANGENT:
      pik.left_tangent_magnitude += d;
      m = pik.left_tangent_magnitude;
      x = vx + m * pik.left_tangent.x;
      y = vy + m * pik.left_tangent.y;
      break;
  }
  move (pik, x, y);
  cursor.set (x, y, this);
}

void curve_editor::set_rpm (float r) {
  cons << "RPM = " << r << eol;
  curve_info& ci = curveinfo [curcrv];
  if (r != 0) {
    float rps = r / 60.0;
    float rpf = rps / FPS;
    ci.angle = rpf * TWO_PI;
    if (ci.lastrpm == 0) {
      undos.push_back (undo_t (curcrv, *ci.curve, win));
      ci.totang = 0.0f;
    }
    ci.startt = ui_clk() - TIME_PER_FRAME;
  }
  ci.lastrpm = r;
}

void curve_editor::rotate () {
  for (int i = 0; i < curves; ++i) {
    curve_info& ci = curveinfo[i];
    if (ci.lastrpm) {
      float now = ui_clk();
      float elapsed = now - ci.startt;
      if (elapsed >= TIME_PER_FRAME) {
        ci.startt = ui_clk ();
        float mult = elapsed / TIME_PER_FRAME;
        float byangle = mult * ci.angle;
        float sum = ci.totang + byangle;
        if (ci.autoflip && sum >= ci.every.rad) {
          float finrot = ci.every.rad - ci.totang;
          ci.curve->rotate (ci.dir * finrot);
          ci.totang = 0.0f;
          ci.dir *= -1;
          if (ci.randoflip) ci.randeg ();
        } else {
          ci.totang = sum;
          ci.curve->rotate (ci.dir * byangle);
        }
        ci.lisner->edited (this, i);
      }
    }
  }
}

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];
  multi_curve& crv = *hit.crv;
  point<float>& v = crv.vertices [hit.id];
  point<float>& lt = crv.left_tangents [hit.id];
  point<float>& rt = crv.right_tangents [hit.id];
  static const float scl = 2.0f;

  glLineWidth (3);

  glColor3f (scl * crv.r, scl * crv.g, scl * crv.b);
  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) {
  int r = 0;
  curve_info& ci = curveinfo [curcrv];
  multi_curve* crv = ci.curve;
  if (p->mix) mix = *crv;
  if (p->undo) {
    undos.push_back (undo_t (curcrv, *crv, win));
    r = p->apply (*crv);
    if (r == 0) {
      undos.pop_back ();
      return;
    }
  } else {
    r = p->apply (*crv);
  }

  if (r) {
    pomo.validate ();
    ci.lisner->edited (this, curcrv);
  }

  ci.totang = 0.0f;

}

int curve_editor::stop_todo () {
  if (todo != curve_editor::NOTHING) {
    abs_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, float left, float top, box<float>& bbox) {
  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 = vpts[p].x, dy = vpts[p].y;
    svg << left + w * (dx - bbox.left) / bbox.width << "," << top + h * (dy - bbox.bottom) / bbox.height << spc;
  }
  svg << "\"/>" << endl;
}

void curve_editor::write_svg (float h, float t, const string& fn, float left, float top) {
  multi_curve* crv = get_picked_curve ();
  box<float> bbox; crv->calc_bbox (bbox);
  string fname = user_data_dir + fn;
  ofstream svg (fname.c_str(), ios::out);
  float w = bbox.width / bbox.height * h;
  cons << "w = " << w << " h = " << h << "bbox: " << bbox << eol;
  svg << "<?xml version=\"1.0\"?>" << endl;
  svg << "<svg xmlns=\"https://www.w3.org/2000/svg\" width=\"" << w << "\" height=\"" << h << "\" viewBox=\"" << left << spc << top << spc << w << spc << h << "\">" << endl;
    write_curve (crv, svg, w, h, t, left, top, bbox);
  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.harms.editing) {
    sinemixer.harms.stop_editing ();
    ret = 1;
  }
  return ret;
}

void curve_editor::drawerlay () {
  // called from plugin sin/cos/radius editors
  project ();
    if (over) uis.plugin__browser.plugins[uis.plugin__browser.cur]->draw (dynamic_cast<curve_editor*>(over));
  unproject ();
}

int curve_editor::setcrvvis (int i, int v) {

  int& vi = curveinfo[i].visible;
  vi = v;

  static const int d [] = {-1, 1};
  visibles += d[v];

  if (visibles == 0) {
    visibles = 1;
    vi = 1;
    curcrv = i;
    pik = pick_cur_curve ();
    setup_tools_menu ();
    return 0;
  } else if (visibles == 1) {
    curcrv = -1;
    for (int i = 0; i < curves; ++i) {
      curve_info& ci = curveinfo[i];
      if (ci.visible) {
        curcrv = i;
        break;
      }
    }
    pik = pick_cur_curve ();
    setup_tools_menu ();
  }

  return 1;

}

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

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

void curve_info::init () {
  picked = 0;
  visible = 1;
  angle = 0;
  lastrpm = 0;
  dir = 1;
  autoflip = 0;
  randoflip = 0;
  rd.set (0.0f, 1.0f);
  totang = 0.0f;
  every0 = 180.0f;
  every = every0;
  startt = 0.0;
}

void curve_editor::paste_append (multi_curve& to, multi_curve& from) {
  int n = from.num_vertices;
  int lv = to.last_vertex;
  point<float>& v0 = from.vertices[0];
  point<float>& vl = to.vertices[lv];
  point<float> delta; direction (delta, v0, vl);
  point<float>& rtl = to.right_tangents[lv];
  point<float>& rt0 = from.right_tangents[0];
  rtl.x = rt0.x + delta.x;
  rtl.y = rt0.y + delta.y;
  for (int i = 1; i < n; ++i) {
    point<float>& vi = from.vertices[i];
    point<float>& lt = from.left_tangents[i];
    point<float>& rt = from.right_tangents[i];
    to.add_vertex (vi.x + delta.x, vi.y + delta.y);
    to.add_left_tangent (lt.x + delta.x, lt.y + delta.y);
    to.add_right_tangent (rt.x + delta.x, rt.y + delta.y);
  }
  to.evaluate ();
}

void curve_editor::do_powbeat (float m) {
  if (beater) {
    if (!pik()) pik = pick_cur_curve ();
    powbeat (pik.crv->name, *bv[pik.crv_id], m);
  }
}