Subversion Repositories DIN Is Noise

Rev

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

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


#include "main.h"
#include "din.h"
#include "console.h"
#include "solver.h"
#include "container.h"
#include "utils.h"
#include "input.h"
#include "color.h"
#include "random.h"
#include "command.h"
#include "delay.h"
#include "chrono.h"
#include "delay.h"
#include "tcl_interp.h"
#include "font.h"
#include "ansi_color_codes.h"
#include "scale_info.h"
#include "ui_list.h"
#include "vector2d.h"
#include "keyboard_keyboard.h"
#include "log.h"
#include <sstream>
#include <algorithm>

extern string user_data_dir; // user data directory
extern console cons; // console
extern viewport view; // viewport
extern int mousex, mousey, mouseyy; // mouse pos
extern int lmb, rmb, mmb; // mouse button state
extern int LEFT, BOTTOM, RIGHT, TOP; // din board extents
extern int HEIGHT; // TOP - BOTTOM; of the microtonal keyboard
extern float BOTTOM01, HEIGHT01; // bottom & height of microtonal keyboard (as proportion of window height)
extern float DELTA_VOLUME; // delta volume per unit key height
extern int LAST_VOLUME; // last volume in key height units
extern int NUM_VOLUMES; // number of available volumes
extern int NUM_MICROTONES; // default number of microtones in a range
extern int NUM_OCTAVES; // number of octaves the board spans
extern map<string, int> NOTE_POS; // interval name -> value, 1 - 1, 1# - 2, 2 - 3  etc
extern int SAMPLE_RATE; // sampling rate
extern map<string, float> INTERVALS; // interval name -> value
extern audio_out aout; // audio output
extern tcl_interp interpreter; // integrated tcl interpreter
extern scale_info scaleinfo; // scale information for microtonal and keyboard keyboard
extern int TRAIL_LENGTH; // drone trail length (== number of trail points)
extern int DRONE_HANDLE_SIZE;
extern float DELTA_DRONE_MASTER_VOLUME;
extern audio_clock clk; // audio clock
extern din din0; // microtonal-keyboard
extern float VOLUME; // volume of voice on microtonal-keyboard
extern curve_library wav_lib; // waveform library
extern float FRAME_TIME; // time per frame in seconds
extern int quit; // user wants to quit?

extern beat2value octave_shift;

extern const float PI_BY_180;
extern const int MILLION;

typedef std::list<drone*>::iterator drone_iterator;
typedef std::list<mesh>::iterator mesh_iterator;

extern oscilloscope scope;

extern int IPS; // inputs per second

extern mouse_slider mouse_slider0;

void make_arrow (int* A, int k, float x, float y, float ux, float uy, float vx, float vy, float u, float v) {
  // make arrow
  //
  A[k]=x+ux;
  A[k+1]=y+uy;
  A[k+2]=x;
  A[k+3]=y;
  float arx = x + u * ux, ary = y + u * uy;
  float vvx = v * vx, vvy = v*vy;
  A[k+4] = arx + vvx;
  A[k+5] = ary + vvy;
  A[k+6] = A[k];
  A[k+7] = A[k+1];
  A[k+8]= arx - vvx;
  A[k+9]= ary - vvy;
  A[k+10]= A[k];
  A[k+11]= A[k+1];

}

din::din (cmdlist& cl) :
wave ("waveform1.crv"),
waved ("waveform1.ed"),
wavlis (wave_listener::MICROTONAL_KEYBOARD),
win (0, 0, view.xmax, view.ymax),
drone_master_volume (0.05),
drone_wave ("drone.crv"),
droneed ("drone.ed"),
dronelis (wave_listener::DRONE),
fm ("fm", "fm.crv"),
am ("am", "am.crv"),
gatr ("gr", "gater.crv"),
gated ("gater.ed"),
gatlib ("gater-patterns.lib"),
moded ("modulation.ed"),
am_delta (0.01f, 1),
helptext ("din.hlp")

{
#ifdef __EVALUATION__
    name = "microtonal-keyboard [Evaluation Version]";
#else
    name = "microtonal-keyboard";
#endif

    prev_mousex = prev_mousey = delta_mousex = delta_mousey = 0;
    win_mousex = win_mousey = prev_win_mousex = prev_win_mousey = 0;
    tonex = toney = 0;

    current_range = resize_range = 0;

    adding_drones = 0;

    moving_drones = 0;

    show_pitch_volume = 0;

    rising = falling = 0;

    mark_current_range = 0;

    n_dvap = 0;
    dvap = 0;

    dap = 0;
    n_dap = 0;

    num_drones = 0;

    create_mesh = 0;
    num_meshes = 0;

    static const int cap = 1024;
    selected_drones.reserve (cap);

    scaleinfo.scl = this;

    fdr_gater_prev_amount = 0;

    p_am_delta = &am_delta;
    am_delta.depth = 0.01f;

    vel_effect = NO_CHANGE;
    rvec.reserve (cap);
    svec.reserve (cap);

    auto_select_launched = 0;

}

void din::setup () {
 
  droneed.add (&drone_wave, &dronelis);
  droneed.attach_library (&wav_lib);

  wavsol (&wave);
  wavplay.set_wave (&wavsol);

  waved.add (&wave, &wavlis);
  waved.attach_library (&wav_lib);

  gatr.setup ();
  gated.attach_library (&gatlib);
  gated.add (&gatr.crv, &gatrlis);
  gated.bv.push_back (&gatr);

  am_depth = 0;
  fm_depth = 0;

  fm.setup ();
  am.setup ();

  moded.add (&fm.crv, &fmlis);
  moded.add (&am.crv, &amlis);
  moded.bv.push_back (&fm);
  moded.bv.push_back (&am);

  fmlis.set (&fm);
  amlis.set (&am);
  gatrlis.set (&gatr);

  dinfo.gravity.calc ();

}

void din::load_scale (int _load_drones) {
  setup_ranges ();
  if (_load_drones) load_drones ();
  update_drone_tone ();
  update_drone_x (0, last_range);
}

void din::scale_loaded () {
  int load_drones_too = 1;
  load_scale (load_drones_too);
}

void din::scale_changed () {
  int dont_load_drones = 0;
  load_scale (dont_load_drones);
}

int din::load_ranges () {

  string fname = user_data_dir + scaleinfo.name + ".ranges";
  dlog << "<< loading ranges from: " << fname;
  ifstream file (fname.c_str(), ios::in);
  if (!file) {
    dlog << "!!! couldnt load range pos from " << fname << ", will use defaults +++" << endl;
    return 0;
  }

  string ignore;

  file >> ignore >> NUM_OCTAVES;
  int n; file >> ignore >> n;
  create_ranges (n);

  int l = LEFT, r, w;
  for (int i = 0; i < num_ranges; ++i) {
    range& R = ranges[i];
    file >> ignore >> w;
    r = l + w;
    R.extents (l, BOTTOM, r, TOP);
    l = r;
  }

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

}

void din::save_ranges () {

  string fname = user_data_dir + scaleinfo.name + ".ranges";
  ofstream file (fname.c_str(), ios::out);
  if (file) {
    file << "num_octaves " << NUM_OCTAVES << endl;
    file << "num_ranges " << num_ranges << endl;
    for (int i = 0; i < num_ranges; ++i) {
      range& r = ranges[i];
      file << i << ' ' << r.extents.width << endl;
    }
    dlog << "+++ saved ranges in " << fname << " +++" << endl;
  }

}

void din::create_ranges (int n) {

  if (n > 0) {
    num_ranges = n;
    ranges.resize (num_ranges);
    last_range = num_ranges - 1;
    firstr = &ranges [0];
    lastr = &ranges [last_range];
  }

}

void din::setup_ranges (int load) {

  if (load) {
    if (!load_ranges()) {
      create_ranges (NUM_OCTAVES * scaleinfo.num_ranges);
      set_range_size (0, last_range, NUM_MICROTONES);
    }
  } else { // number of octaves has changed
    int last_num_ranges = num_ranges;
    create_ranges (NUM_OCTAVES * scaleinfo.num_ranges);
    set_range_size (last_num_ranges, last_range, NUM_MICROTONES); // new ranges to default size, keep size of existing ranges
  }

  setup_range_notes ();
  update_range_notes ();
  calc_range_label ();
  notate_ranges ();
  find_current_range ();

}

void din::setup_range_notes () {

  for (int p = 0, r = 0; p < NUM_OCTAVES; ++p) {
    for (int i = 0, j = 1; i < scaleinfo.num_ranges; ++i, ++j) {
      range& R = ranges[r++];
      R.intervals[0] = scaleinfo.notes[i];
      R.intervals[1] = scaleinfo.notes[j];
    }
  }

}

void din::update_range_notes () {
  float octave_start = scaleinfo.lo_tonic;
  for (int p = 0, r = 0; p < NUM_OCTAVES; ++p) {
    for (int i = 0; i < scaleinfo.num_ranges; ++i) {
      ranges[r++].calc (p, octave_start, scaleinfo.intervals);
    }
    octave_start *= 2;
  }

}

void din::calc_range_label () {
  if (num_ranges) {
    range::char_height = get_line_height ();
    range::ybot1 = firstr->extents.bottom - range::char_height;
    range::ybot2 = range::ybot1 - range::char_height - range::spacer;
    range::ytop1 = firstr->extents.top + range::char_height;
    range::ytop2 = range::ytop1 + range::char_height + range::spacer;
  }
}

void din::set_range_size (int ran, int sz) {

  // set size of range ran
  range& R = ranges[ran];
  int delta = sz - R.extents.width;
  R.extents (R.extents.left, BOTTOM, R.extents.left + sz, TOP);
  for (int i = ran + 1; i < num_ranges; ++i) {
    range& Ri = ranges[i];
    Ri.extents (Ri.extents.left + delta, Ri.extents.bottom, Ri.extents.right + delta, Ri.extents.top);
  }

  update_drone_x (ran, last_range);
  find_visible_ranges ();

}

void din::set_range_size (int s, int t, int sz) {

    int r, l;
    if (s < 1) {
      r = LEFT;
    } else r = ranges[s-1].extents.right;

    for (int i = s; i <= t; ++i) {
      l = r;
      r = l + sz;
      range& R = ranges[i];
      R.extents (l, BOTTOM, r, TOP);
    }

    update_drone_x (s, t);
    find_visible_ranges ();

}

void din::range_left_changed (int r, int dx) {
  range& R = ranges [r];
  int old_left = R.extents.left;
  if (dx != 0) {
    R.extents (R.extents.left + dx, R.extents.bottom, R.extents.right, R.extents.top);
    int delta_left = R.extents.left - old_left;
    for (int i = 0; i < r; ++i) {
      range& ir = ranges [i];
      ir.extents (ir.extents.left + delta_left, ir.extents.bottom, ir.extents.right + delta_left, ir.extents.top);
    }
    update_drone_x (0, r);
    find_visible_ranges ();
  }
  LEFT = ranges[0].extents.left;
}

void din::range_right_changed (int r, int dx) {
  range& R = ranges [r];
  int old_right = R.extents.right;
  if (dx != 0) {
    R.extents (R.extents.left, R.extents.bottom, R.extents.right + dx, R.extents.top);
    int delta_right = R.extents.right - old_right;
    for (int i = r + 1; i < num_ranges; ++i) {
      range& ir = ranges [i];
      ir.extents (ir.extents.left + delta_right, ir.extents.bottom, ir.extents.right + delta_right, ir.extents.top);
    }
    update_drone_x (r, last_range);
    find_visible_ranges ();
  }
}

void din::notate_ranges () {

  extern string NOTATION;
  extern const char* WESTERN_FLAT [];
  int western = scaleinfo.western;

  if (NOTATION == "western") {
    for (int i = 0; i < num_ranges; ++i) {
      range& ri = ranges [i];
      string i0 = ri.intervals[0], i1 = ri.intervals[1];
      int ii0 = NOTE_POS[i0], ii1 = NOTE_POS[i1];
      int kii0 = (western + ii0) % 12;
      int kii1 = (western + ii1) % 12;
      ri.notes[0].set_name (WESTERN_FLAT[kii0]);
      ri.notes[1].set_name (WESTERN_FLAT[kii1]);
    }
  } else if (NOTATION == "numeric") {
    for (int i = 0; i < num_ranges; ++i) {
      range& ri = ranges [i];
      string i0 = ri.intervals[0], i1 = ri.intervals[1];
      ri.notes[0].set_name (i0);
      ri.notes[1].set_name (i1);
    }
  }

  // misc labels
  //dlog << num_ranges << ' ' << scaleinfo.num_ranges << endl;
  int m = num_ranges / scaleinfo.num_ranges;
  for (int i = 0; i < num_ranges; ++i) {
    range& ri = ranges [i];
    stringstream ss; ss << 1 + i / scaleinfo.num_ranges << '/' << m; ss >> ri.octave;
    if (ri.intervals[0] == "1") ri.key = range::LEFT; else ri.key = range::NONE; // highlight key note in green
  }
  ranges[last_range].key = range::RIGHT;

}

void din::mouse2tonic () {
  // set mouse at tonic
  range& r = ranges[scaleinfo.notes.size () - 1]; // range of middle tonic
  int wx = r.extents.left;
  //if (wx >= 0 && wx <= view.xmax) warp_mouse (wx, mousey);
  uis.main_menu.screen_mousex = wx;
  uis.main_menu.screen_mousey = mousey;
}

float din::get_note_value (const string& s) {
  return scaleinfo.intervals[s];
}

void din::retune_note (const string& nn, float v) {
  float b4 = scaleinfo.intervals[nn];
  scaleinfo.intervals[nn] = v;
  update_range_notes ();
  update_drone_tone ();
  cons << GREEN << "retuned note: " << nn << " from: " << b4 << " to " << v << eol;
}

void din::retune_note () {

  // find nearest note
  range& r = ranges[current_range];
  int left = r.extents.left, right = r.extents.right;
  int delta_left = win_mousex - left, delta_right = right - win_mousex;
  int i = 0; if (delta_left > delta_right) i = 1;
  string label(r.intervals[i]);

  float sas [] = {scaleinfo.lo_tonic, scaleinfo.tonic, scaleinfo.hi_tonic};
  float sa = sas [(int) octave_position];
  float freq = step * SAMPLE_RATE;
  float interval = freq / sa;
  label = r.intervals[i];
  float b4 = scaleinfo.intervals[label];
  scaleinfo.intervals [label] = interval;
  update_range_notes ();
  update_drone_tone ();

  cons << GREEN << "retuned " << label << " from " << b4 << " to " << interval << eol;

}

void din::restore_note () {

  // find nearest note
  range& r = ranges[current_range];
  int left = r.extents.left, right = r.extents.right;
  int delta_left = win_mousex - left, delta_right = right - win_mousex;
  int i = 0; if (delta_left > delta_right) i = 1;
  string n (r.intervals[i]);
  if (n != "S") {
    n = r.intervals[i];
    cons << GREEN << "restored " << n << " from " << scaleinfo.intervals[n] << " to " << INTERVALS[n] << eol;
    scaleinfo.intervals [n] = INTERVALS [n];
    update_range_notes ();
    update_drone_tone ();
  }
}

void din::restore_all_notes () {
  scaleinfo.intervals = INTERVALS;
  update_range_notes ();
  update_drone_tone ();
}

void din::save_scale () {
  save_ranges ();
  save_drones ();
  wave.save ("waveform1.crv");
  scaleinfo.save_scale ();
  scaleinfo.save_custom_tuning ();
}

din::~din () {
  if (dvap) delete[] dvap;
  if (dap) delete[] dap;
  for (drone_iterator i = drones.begin(), j = drones.end(); i != j; ++i) delete *i;
  dlog << "--- destroyed microtonal-keyboard ---" << endl;
}

void din::sample_rate_changed () {

  for (int i = 0; i < num_ranges; ++i) ranges[i].sample_rate_changed ();

  beat2value* bv [] = {&fm, &am, &gatr, &octave_shift};
  for (int i = 0; i < 4; ++i) bv[i]->set_bpm (bv[i]->get_bpm());

  select_all_drones ();
  change_drone_bpm (drone_modulation::FM, 0);
  change_drone_bpm (drone_modulation::AM, 0);
  update_drone_tone ();

}

void din::samples_per_channel_changed () {
  wavplay.realloc ();
  for (drone_iterator i = drones.begin(), j = drones.end(); i != j; ++i) {
    drone& di = *(*i);
    di.player.realloc ();
    di.update_pv = drone::EMPLACE;
  }
}

void din::load_drones () {

  string fdrone = user_data_dir + scaleinfo.name + ".drone";
  ifstream file (fdrone.c_str(), ios::in);
  drones.clear ();

  rising = falling = 0;

  if (!file) return; else {
    string ignore;
    num_drones = 0;
    file >> ignore >> drone::UID;
    file >> ignore >> num_drones;
    file >> ignore >> drone_master_volume;
    dlog << "<<< loading " << num_drones << " drones from: " << fdrone;
    for (int i = 0; i < num_drones; ++i) {
      drone* pdi = new drone;
      drone& di = *pdi;
      file >> ignore >> di.id;
      file >> ignore >> di.cx >> di.cy;
      di.set_xy (di.cx, di.cy);
      file >> ignore >> di.player.x >> di.vol;
      file >> ignore >> di.r >> di.g >> di.b;
      file >> ignore >> di.mod.active >> di.mod.am.b >> di.mod.am.db >> di.mod.am.depth >> di.mod.am.bpm >> di.mod.fm.b >> di.mod.fm.db >> di.mod.fm.depth >> di.mod.fm.bpm;
      file >> di.trail.max_points >> di.handle_size;
      file >> ignore >> di.V >> di.A >> di.vx >> di.vy >> di.v_mult >> di.ax >> di.ay;
      file >> ignore >> di.attractor;
      if (di.attractor) {
        int n = di.attractor;
        for (int i = 0; i < n; ++i) {
          attractee ae;
          file >> ae.id;
          di.attractees.push_back (ae);
        }
        attractors.push_back (pdi);
      }
      file >> ignore >> di.launcher;
      if (di.launcher) {
        float tt, dt; file >> tt >> dt >> di.dpm;
        di.launch_every.triggert = tt;
        di.launch_every.startt = ui_clk () - dt;
        launchers.push_back (pdi);
      }

      file >> ignore >> di.num_targets;
      if (di.num_targets) {
        file >> ignore >> di.cur_target;
        vector<drone*>& targets = di.targets;
        targets.clear ();
        for (int i = 0; i < di.num_targets; ++i) {
          int pt; file >> pt;
          targets.push_back ((drone*) pt);
        }
      }

      file >> ignore >> di.tracking;
      if (di.tracking) {
        int id; file >> id;
        di.tracked_drone = (drone *) id;
        trackers.push_back (pdi);
      }

      file >> ignore >> di.gravity;
      if (di.gravity) gravitated.push_back (pdi);

      int pt; file >> ignore >> pt;
      di.target = (drone *) pt;
      if (di.target) satellites.push_back (pdi);

      double elapsed; file >> ignore >> elapsed;
      if (elapsed >= 0) di.birth = ui_clk () - elapsed; else di.birth = -1;

      file >> ignore >> di.life;
      file >> ignore >> di.insert;

      /*file >> ignore >> di.frozen;
      if (di.frozen) di.froze_at = ui_clk ();*/


      di.state = drone::RISING;
      di.fdr.set (0, 1);
      risers.push_back (pdi);
      ++rising;
      drones.push_back (pdi);

    }

    // load the meshes
    //
    map<int, drone*> dmap;
    file >> ignore >> num_meshes;
    if (num_meshes) {
      for (int m = 0; m < num_meshes; ++m) {
        mesh a_mesh;
        file >> ignore >> a_mesh.r >> a_mesh.g >> a_mesh.b;
        int num_polys;
        file >> ignore >> num_polys;
        for (int i = 0; i < num_polys; ++i) {
          drone* drones[4] = {0}; // 4 drones to a poly
          file >> ignore;
          for (int p = 0; p < 4; ++p) {
            int id; file >> id;
            drone* did = dmap [id];
            if (did == 0) {
              did = get_drone (id);
              dmap[id] = did;
            }
            drones[p] = did;
          }
          a_mesh.add_poly (drones[0], drones[1], drones[2], drones[3]);
        }
        meshes.push_back (a_mesh);
      }
    }

    // load drone tracked by gravity
    int tid;
    file >> ignore >> tid;
    if (tid) dinfo.gravity.tracked_drone = get_drone (tid);

    // convert attractees
    for (drone_iterator i = attractors.begin (), j = attractors.end(); i != j; ++i) {
      drone& di = *(*i);
      for (list<attractee>::iterator iter = di.attractees.begin (), jter = di.attractees.end (); iter != jter; ++iter) {
        attractee& ae = *iter;
        ae.d = get_drone (ae.id);
      }
    }

    // convert tracked drone
    for (drone_iterator i = trackers.begin (), j = trackers.end(); i != j; ++i) {
      drone& di = *(*i);
      int id = (uintptr_t) di.tracked_drone;
      di.tracked_drone = get_drone (id);
    }

    // convert targets
    for (drone_iterator i = drones.begin (), j = drones.end (); i != j; ++i) {
      drone& di = *(*i);
      if (di.num_targets) for (int i = 0; i < di.num_targets; ++i) di.targets[i] = get_drone ((uintptr_t) di.targets[i]);
    }

    // convert satellites
    for (drone_iterator i = satellites.begin (), j = satellites.end (); i != j; ++i) {
      drone& di = *(*i);
      di.target = get_drone ((uintptr_t) di.target);
    }

    update_drone_players ();

    if (num_drones)
      prep_modulate (MODULATE_DRONES);
    else
      prep_modulate (MODULATE_VOICE);

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

}

void din::save_drones () {
  drone_wave.save ("drone.crv");
  string drone_file = user_data_dir + scaleinfo.name + ".drone";
  ofstream file (drone_file.c_str(), ios::out);
  const char spc = ' ';
  if (file) {
    file << "uid " << drone::UID << eol;
    file << "num_drones " << num_drones << eol;
    file << "master_volume " << drone_master_volume << eol;
    for (drone_iterator i = drones.begin(), j = drones.end(); i != j; ++i) {
      drone& di = *(*i);
      file << "id " << di.id << eol;
      file << "positon " << di.cx << spc << di.cy << eol;
      file << "wave_pos " << di.player.x << spc << di.vol << eol;
      file << "color " << di.r << spc << di.g << spc << di.b << eol;
      file << "modulation " << di.mod.active << spc << di.mod.am.b << spc << di.mod.am.db << spc << di.mod.am.depth << spc << di.mod.am.bpm << spc << di.mod.fm.b << spc << di.mod.fm.db << spc << di.mod.fm.depth << spc << di.mod.fm.bpm << spc << di.trail.max_points << spc << di.handle_size << eol;
      file << "vel+accel " << di.V << spc << di.A << spc << di.vx << spc << di.vy << spc << di.v_mult << spc << di.ax << spc << di.ay << eol;
      file << "attractor " << di.attractor;
      if (di.attractor) { // save attractees
        for (list<attractee>::iterator iter = di.attractees.begin (), jter = di.attractees.end (); iter != jter; ++iter) {
          attractee& ae = *iter;
          file << spc << ae.id; // only save unique id, rebuild on load
        }
      }
      file << eol;

      file << "launcher " << di.launcher;
      if (di.launcher)
        file << spc << di.launch_every.triggert << spc << (ui_clk()-di.launch_every.startt) << spc << di.dpm << spc << eol;
      else
        file << eol;

      file << "launcher_targets " << di.num_targets << eol;
      if (di.num_targets) {
        file << "cur_target " << di.cur_target;
        for (int t = 0; t < di.num_targets; ++t) {
          drone* pdt = di.targets[t];
          file << spc << pdt->id;
        }
        file << eol;
      }

      file << "tracking " << di.tracking;
      if (di.tracking) file << spc << di.tracked_drone->id << eol; else file << eol;

      file << "gravity " << di.gravity << eol;

      if (di.target) {
        file << "satellite_target " << di.target->id << eol;
      } else file << "satellite_target 0" << eol;

      if (di.birth != -1) {
        double elapsed = ui_clk () - di.birth;
        file << "birth " << elapsed << eol;
      } else file << "birth -1" << eol;
      file << "life_time " << di.life << eol;
      file << "insert_time " << di.insert << eol;
      //file << "frozen " << di.frozen << eol;

    }

    // save drone meshes
    file << "num_meshes " << num_meshes << eol;
    if (num_meshes) {
      for (mesh_iterator m = meshes.begin (), n = meshes.end(); m != n; ++m) { // save meshes
        mesh& mi = *m;
        file << "color " << mi.r << spc << mi.g << spc << mi.b << endl;
        file << "num_polys " << mi.num_polys << eol;
        for (poly_iterator p = mi.polys.begin (), q = mi.polys.end (); p != q; ++p) { // save polys
          poly& pp = *p;
          file << "poly";
          for (int r = 0; r < 4; ++r) file << spc << pp.drones[r]->id; // save drone id, on reload we will point to right drone
          file << eol;
        }
      }
    }

    // save gravity tracking
    file << "drone_tracked_by_gravity ";
    if (dinfo.gravity.tracked_drone) {
      file << dinfo.gravity.tracked_drone->id << endl;
    } else file << '0' << endl;
     
    dlog << "+++ saved " << num_drones << " drones in: " << drone_file << " +++" << endl;
  }

}

void din::update_drone_tone () {
  for (drone_iterator i = drones.begin(), j = drones.end(); i != j; ++i) {
    drone& di = *(*i);
    range& r = ranges[di.range];
    di.step = (1 - di.pos) * r.notes[0].step + di.pos * r.notes[1].step;
    di.update_pv = drone::EMPLACE;
  }
}

void din::update_drone_x (int s, int t) {
  for (drone_iterator i = drones.begin(), j = drones.end(); i != j; ++i) {
    drone& di = *(*i);
    if (di.mod.active == 0) {
      if ((di.range >= s) && (di.range <= t)) {
        range& r = ranges[di.range];
        di.x = di.cx = (int)((1 - di.pos) * r.extents.left + di.pos * r.extents.right);
        di.calc_handle ();
        //di.update_pv = drone::INTERPOLATE;
      }
    }
  }
}

void din::update_drone_anchors () {
  for (drone_iterator i = drones.begin(), j = drones.end(); i != j; ++i) {
    drone& di = *(*i);
    di.calc_handle ();
  }
}

void din::update_drone_ranges () {
  for (drone_iterator i = drones.begin(), j = drones.end(); i != j; ++i) {
    drone& di = *(*i);
    if (di.range > last_range) {
      di.range = last_range;
      range& rd = ranges[di.range];
      di.pos = (di.x - rd.extents.left) * 1.0f / rd.extents.width;
    } else if (di.pos > 1) {
      int r = find_range (di.x, di.range);
      range& rr = ranges[r];
      di.range = r;
      di.pos = (di.x - rr.extents.left) * 1.0f / rr.extents.width;
    }
    //di.update_pv = drone::INTERPOLATE;
  }
}

drone* din::add_drone (int wx, int wy) {
  drone* new_drone = new drone (wy);
  drones.push_back (new_drone);
  ++num_drones;
  int init = 1; set_drone (*new_drone, wx, wy, init);
  return new_drone;
}

void din::set_drone (drone& dd, int wx, int wy, int init, int shift, int ctrl) {

    find_volume ();

    if (init) {

      // random color
      dd.r = get_rand_01 ();
      dd.g = get_rand_01 ();
      dd.b = get_rand_01 ();

      // create drone at position
      dd.cx = wx;
      dd.cy = wy;
      dd.dy = wy - BOTTOM;

      // install waveform, pitch and volume
      dd.sol (&drone_wave);
      dd.player.set_wave (&dd.sol);

      // prep to rise the drones
      dd.fdr.set (0, 1);
      dd.set_xy (dd.cx, dd.cy);
      dd.state = drone::RISING;
      risers.push_back (&dd);
      ++rising;

    } else { // editing the drone
      // update drone position
      if (!shift) dd.cx += delta_mousex;
      if (!ctrl) dd.cy -= delta_mousey;
      if (dd.mod.active == 0) dd.set_xy (dd.cx, dd.cy);
    }


}

void din::delete_drone (drone& ds, float dt) {
  drone* pds = &ds;
  if (ds.state == drone::RISING) if (erase (risers, pds)) --rising;
  if (push_back (fallers, pds)) {
    ++falling;
    ds.state = drone::FALLING;
    ds.fdr.set (1, 0, 1, dt);
  }
}

void din::delete_selected_drones () {
  if (selected_drones.size () == 0) pick_drone ();
  for (int i = 0, j = selected_drones.size (); i < j; ++i) {
    drone& ds = *selected_drones[i];
    delete_drone (ds);
  }
  clear_selected_drones ();
}

int din::select_all_drones () {
  clear_selected_drones ();
  for (drone_iterator i = drones.begin(), j = drones.end(); i != j; ++i) {
    drone* pdi = *i;
    drone& di = *pdi;
    di.sel = 1;
    selected_drones.push_back (pdi);
  }
  print_selected_drones ();
  return 1;
}

void din::select_launched () {
  vector<drone*> launchers (selected_drones);
  int n = selected_drones.size ();
  clear_selected_drones ();
  for (drone_iterator i = drones.begin(), j = drones.end(); i != j; ++i) {
    drone* pdi = *i;
    drone& di = *pdi;
    for (int i = 0; i < n; ++i)
      if (launchers[i] == pdi->launched_by) {
        di.sel = 1;
        selected_drones.push_back (pdi);
      }
  }
}

int din::select_launchers () {
  clear_selected_drones ();
  for (drone_iterator i = launchers.begin(), j = launchers.end(); i != j; ++i) {
    drone* pdi = *i;
    pdi->sel = 1;
    selected_drones.push_back (pdi);
  }
  print_selected_drones ();
  return 1;
}

int din::delete_all_drones () {
  select_all_drones ();
  delete_selected_drones ();
  return 1;
}

void din::pick_drone () { // pick 1 drone under cursor
  clear_selected_drones ();
  for (drone_iterator i = drones.begin(), j = drones.end(); i != j; ++i) {
    drone* pdi = *i;
    drone& di = *pdi;
    if (inbox (di.handle, win_mousex, win_mousey)) {
      di.sel = 1;
      selected_drones.push_back (pdi);
      break;
    }
  }
}

void din::clear_selected_drones () {
  for (int i = 0, j = selected_drones.size(); i < j; ++i) selected_drones[i]->sel = 0;
  selected_drones.clear ();
  if (moving_drones) set_moving_drones (0);
}

void din::orbit_selected_drones () { // attach selected drones to attractor
  int n = selected_drones.size ();
  if (n > 1) {
    int last = n - 1;
    drone* p_att = selected_drones [last];
    push_back (attractors, p_att);
    drone& att = *p_att;
    list<attractee>& lae = att.attractees;
    for (int i = 0; i < last; ++i) { // other drones are attractees
      drone* pae = selected_drones[i];
      attractee ae (pae->id, pae);
      list<attractee>::iterator end = lae.end (), found = find (lae.begin (), end, ae);
      if (found == end) {
        lae.push_back (ae); // add if not already added
        ++att.attractor;
      }
    }
  } else cons << RED << "Please select at least 2 drones, drones will orbit around the last drone!" << eol;
}

void din::remove_attractee (drone* d) {
  for (drone_iterator i = attractors.begin(); i != attractors.end();) { // run thru list of attractors
    drone* p_att = *i;
    drone& att = *p_att;
    list<attractee>& lae = att.attractees;
    int erased = 0;
    for (list<attractee>::iterator iter = lae.begin (); iter != lae.end();) { // run thru list of attractees
      attractee& ae = *iter;
      if (ae.d != d)
        ++iter;
      else { // remove attractee
        lae.erase (iter);
        if (--att.attractor == 0) {
          i = attractors.erase (i);
          erased = 1;
        }
        break;
      }
    }
    if (!erased) ++i;
  }
}

void din::set_drones_under_gravity () {

  int n = selected_drones.size ();
  if (n == 0) {
    cons << RED << "Please select a drone to move under gravity!" << eol;
    return;
  }
  for (int i = 0; i < n; ++i) {
    drone* pdg = selected_drones[i];
    if (pdg->y < BOTTOM) pdg->gravity = -1; else pdg->gravity = 1;
    push_back (gravitated, pdg);
  }
}

void din::move_drones_under_gravity () {

  for (drone_iterator i = gravitated.begin(), j = gravitated.end(); i != j; ++i) { // run thru list of drones driven by gravity

    drone* pdi = *i;
    drone& di = *pdi; // get the ith drone

    if (di.frozen == 0) {

      // current position
      di.xi = di.x;
      di.yi = di.y;

      // calculate new position along its velocity
      di.set_xy (di.x + di.V * di.vx, di.y + di.V * di.vy);

      // acceleration is due to gravity!
      di.ax = dinfo.gravity.gx;
      di.ay = di.gravity * dinfo.gravity.gy; // reverse gravity effect if drone launched below 0 volume line

      // update velocity ie we accelerate
      di.vx += di.ax;
      di.vy += di.ay;

      // bounce when reached bottom line of microtonal keyboard
      if (((di.gravity == 1 && di.y <= BOTTOM) || (di.gravity == -1 && di.y >= BOTTOM)) && (di.target == 0)) {
        if (di.bounces++ >= dinfo.bounces) {
          delete_drone (di);
        } else {
          float dx = di.x - di.xi;
          if (dx) { // slope is available
            float dy = di.y - di.yi;
            float m = dy / dx;
            di.set_xy (di.xi + (BOTTOM - di.yi) / m, BOTTOM);
          } else // slope is infinite
            di.set_xy (di.x, BOTTOM);
          float reduction = dinfo.rebound / 100.0;
          di.vy = reduction * -di.vy; // reduce? and flip velocity
        }
      }
      di.move_center ();
    }
  }
}

void din::set_targets () {

  int n = selected_drones.size ();

  if (n == 0) {
    cons << RED << "Select a launcher and drones to target" << eol;
    return;
  }

  drone* pd0 = selected_drones[0];
  if (pd0->launcher == 0) make_launcher (pd0); // first drone is launcher
  pd0->clear_targets ();

  if (n == 1) {
    pd0->targets.push_back (pd0);
    pd0->num_targets = pd0->targets.size ();
    cons << GREEN << "Selected drone is a launcher and also the target" << eol;
    return;
  }

  for (int i = 1; i < n; ++i) { // make other drones in selection the targets
    drone* pdi = selected_drones[i];
    vector<drone*> targets = pd0->targets;
    vector<drone*>::iterator te = targets.end (), f = find (targets.begin (), targets.end (), pdi);
    if (f == te) pd0->targets.push_back (pdi);
  }
  pd0->num_targets = pd0->targets.size ();
  cons << GREEN << "First drone is launcher, it targets " << pd0->num_targets << " other drones" << eol;

}

void din::remove_drone_from_targets (drone* T) {

  for (drone_iterator i = satellites.begin(), j = satellites.end(); i != j;) { // remove satellites going towards T
    drone* pdi = *i;
    drone& di = *pdi;
    if (di.target == T) {
      di.target = 0;
      i = satellites.erase (i);
      j = satellites.end ();
    } else ++i;
  }

  for (drone_iterator i = launchers.begin(), j = launchers.end (); i != j; ++i) { // remove target from launcher
    drone* pdi = *i;
    vector<drone*>& targets = pdi->targets;
    vector<drone*>::iterator te = targets.end (), f = find (targets.begin (), te,  T);
    if (f != te) {
      targets.erase (f);
      pdi->num_targets = targets.size ();
      clamp (0, pdi->cur_target, pdi->num_targets - 1);
    }
  }
}

void din::clear_targets () {
  int n = 0;
  for (int i = 0, j = selected_drones.size(); i < j; ++i) {
    drone* pdi = selected_drones[i];
    if (pdi->num_targets) {
      pdi->clear_targets ();
      ++n;
    }
  }
  if (n) cons << GREEN << "Cleared targets of " << n << " drones" << eol; else cons << RED << "No targets found!" << eol;
}

void din::kill_old_drones () {
  for (drone_iterator i = drones.begin(), j = drones.end(); i != j; ++i) {
    drone& di = *(*i);
    if ((di.birth != -1) && (di.frozen == 0)) {
      double elapsed = ui_clk() - di.birth;
      if (elapsed >= di.life) delete_drone (di);
    }
  }
}

void din::carry_satellites_to_orbit () { // satellites is a bunch of drones to be inserted into orbit around another drone
  for (drone_iterator i = satellites.begin(), j = satellites.end(); i != j;) { // run thru satellites to be inserted into circular orbit
    drone* pdi = *i;
    drone& di = *pdi;
    if (di.frozen == 0) {
      drone& dt = *di.target; // the target we want the satellite to orbit
      unit_vector (di.ax, di.ay, float (dt.x - di.x), float (dt.y - di.y)); // centripetal acceleration ie unit vector joining satellite & target
      float pvx = -di.ay, pvy = di.ax; // velocity to insert into orbit (just perpendicular to centripetal acceleration so its centrifugal velocity)
      double now = ui_clk(), delta = now - di.birth;
      float alpha = delta / di.insert; // alpha is how far we are b4 we must insert satellite into orbit; 0 => at the start, 1 => orbit now!
      if (alpha >= 1.0f) { // insert drone into orbit now!
        list<attractee>& lae = dt.attractees;
        lae.push_back (attractee (pdi->id, pdi));
        push_back (attractors, di.target);
        di.target = 0; // inserted into orbit, so clear
        ++dt.attractor;
        i = satellites.erase (i); j = satellites.end (); // no longer a satellite we need to insert
      } else { // continue carrying satellites into orbit
        float dot = di.vx * pvx + di.vy * pvy; // dot product current velocity and insertion velocity to see if they are facing the same direction
        if (dot < 0) { // no so flip insertion velocity so it faces the same direction as current velocity
          pvx = -pvx;
          pvy = -pvy;
          di.v_mult = -1; // see attract_drones ()
        } else di.v_mult = 1;
        // set interpolated velocity as current satellite velocity
        float ivx, ivy;
        unit_vector (ivx, ivy, di.vx + alpha * (pvx - di.vx), di.vy + alpha * (pvy - di.vy)); // interpolate current velocity and insertion velocity
        di.vx = ivx; di.vy = ivy;
        float newx = di.x + di.V * di.vx + di.A * di.ax, newy = di.y + di.V * di.vy + di.A * di.ay; // update drone position
        di.xi = di.x;
        di.yi = di.y;
        di.set_xy (newx, newy);
        di.move_center ();
        ++i;
      }
    } else ++i;
  }
}

void din::toggle_launchers () {
  int n = selected_drones.size ();
  if (n == 0) {
    cons << RED << "Please select a drone!" << eol;
    return;
  }
  double startt = ui_clk();
  for (int i = 0; i < n; ++i) {
    drone* pdi = selected_drones[i];
    drone& di = *pdi;
    di.launcher = !di.launcher;
    if (di.launcher) {
      di.launch_every.startt = startt - di.launch_every.triggert;
      launchers.push_back (pdi);
    } else erase (launchers, pdi);
  }
}

void din::make_launcher (drone* pl) {
  double startt = ui_clk();
  pl->launcher = 1;
  pl->launch_every.startt = startt - pl->launch_every.triggert;
  launchers.push_back (pl);
}

void din::make_launchers () {
  int j = selected_drones.size ();
  if (j == 0) {
    cons << RED << "Please select a drone!" << eol;
    return;
  }
  int nl = 0;
  for (int i = 0; i < j; ++i) {
    drone* pdi = selected_drones[i];
    if (pdi->launcher == 0) {
      make_launcher (pdi);
      ++nl;
    }
  }
  if (nl) cons << GREEN << "Made " << nl << " launchers" << eol; else cons << RED << "All selected drones are launchers!" << eol;
}

void din::destroy_launchers () {
  int j = selected_drones.size ();
  if (j == 0) {
    cons << RED << "Please select a drone!" << eol;
    return;
  }
  int nl = 0;
  for (int i = 0; i < j; ++i) {
    drone* pdi = selected_drones[i];
    drone& di = *pdi;
    if (di.launcher) {
      di.launcher = 0;
      erase (launchers, pdi);
      if (di.tracking) {
        di.tracking = 0;
        di.tracked_drone = 0;
        erase (trackers, pdi);
      }
      ++nl;
    }
  }
  if (nl) cons << GREEN << "Stopped launching from " << nl << " drones" << eol; else cons << RED << "No drone launchers found!" << eol;
}

void din::launch_drones () {
  // launch drones from drone launchers
  //
  for (drone_iterator i = launchers.begin(); i != launchers.end(); ++i) { // run thru the launchers
    drone* pdi = *i;
    drone& di = *pdi;
    if (di.frozen == 0 && di.launch_every (ui_clk())) { // time has come to launch a drone
      drone* p_new_drone = add_drone (di.x, di.y); // make the drone
      drone& new_drone = *p_new_drone;
      if (new_drone.y < BOTTOM) new_drone.gravity = -1; else new_drone.gravity = 1; // reverse gravity vector if launched below microtonal keyboard
      new_drone.V = di.V;
      new_drone.vx = di.vx;
      new_drone.vy = di.vy;
      new_drone.A = di.A;
      new_drone.ax = di.ax;
      new_drone.ay = di.ay;
      new_drone.handle_size = di.handle_size;
      new_drone.trail.max_points = di.trail.max_points;
      new_drone.birth = ui_clk();
      new_drone.life = di.life;
      new_drone.launched_by = pdi;
      int num_targets = di.num_targets;
      if (di.sel && auto_select_launched) {
        new_drone.sel = 1;
        selected_drones.push_back (p_new_drone);
      }
      if (num_targets) { // launch a satellite
        new_drone.insert = di.insert;
        int& cur_target = di.cur_target;
        new_drone.target = di.targets [cur_target];
        if (++cur_target >= num_targets) cur_target = 0;
        satellites.push_back (p_new_drone);
      } else gravitated.push_back (p_new_drone); // add to list of drones driven by gravity
    }
  }
}

void din::attract_drones () {
  // attract drones that orbit other drones
  //
  for (drone_iterator i = attractors.begin(), j = attractors.end(); i != j; ++i) {
    drone* pda = *i;
    drone& da = *pda;
    list<attractee>& lae = da.attractees;
    for (list<attractee>::iterator iter = lae.begin (), jter = lae.end(); iter != jter; ++iter) { // run thru list of attractees
      attractee& ae = *iter;
      drone& de = *ae.d;
      unit_vector (de.ax, de.ay, (float)(da.x - de.x), (float)(da.y - de.y)); // centripetal acceleration
      de.vx = -de.ay; de.vy = de.ax; // centrifugal velocity is just perpendacular to centripetal acceleration
      de.vx *= de.v_mult; de.vy *= de.v_mult; // flip if necessary - see move_satellites ()
      if (de.frozen == 0) {
        // calculate position of the drones
        de.xi = de.x;
        de.yi = de.y;
        de.x = de.xi + de.V * de.vx + de.A * de.ax;
        de.y = de.yi + de.V * de.vy + de.A * de.ay;
      }
    }
  }

  // now update all drone positions
  for (drone_iterator i = attractors.begin(), j = attractors.end(); i != j; ++i) {
    drone* pda = *i;
    drone& da = *pda;
    list<attractee>& lae = da.attractees;
    for (list<attractee>::iterator iter = lae.begin (), jter = lae.end(); iter != jter; ++iter) {
      attractee& ae = *iter;
      drone& de = *ae.d;
      if (de.frozen == 0) {
        // move drone centre so drones can modulate while attraction takes place
        de.move_center ();
        de.set_xy (de.x, de.y);
      }
    }
  }
}

void din::add_drone_to_selection (drone* pd) {
  pd->sel = 1;
  push_back (selected_drones, pd);
}

void din::remove_drone_from_selection (drone* pd) {
  pd->sel = 0;
  if (erase (selected_drones, pd)) print_selected_drones ();
}

void din::update_drone_players () {
  for (drone_iterator i = drones.begin(), j = drones.end(); i != j; ++i) {
    drone& di = *(*i);
    di.sol (&drone_wave);
    di.player.set_wave (&di.sol);
  }
}

void din::draw_drones () {

  glEnable (GL_BLEND);
  glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

  // draw drone mesh
  if (num_meshes) {
    for (mesh_iterator i = meshes.begin (), j = meshes.end(); i != j; ++i) (*i).draw ();
  }

  // draw drone trails
  for (drone_iterator i = drones.begin(), j = drones.end(); i != j; ++i) {
    drone& di = *(*i);
    if (di.range >= visl && di.range <= visr) {
      glColor4f (di.r, di.g, di.b, di.fdr.amount);
      di.trail.draw ();
    }
  }
 
  // draw drone handles
  int dhp [12] = {0};
  glVertexPointer (2, GL_INT, 0, dhp);
  for (drone_iterator i = drones.begin(), j = drones.end(); i != j; ++i) {
    drone& di = *(*i);
    if (di.range >= visl && di.range <= visr) {
      glColor4f (di.r, di.g, di.b, di.fdr.amount);
      glRecti (di.handle.left, di.handle.bottom, di.handle.right, di.handle.top);
      if (di.sel) glColor4f (0, 1, 0, di.fdr.amount); else glColor4f (1, 1, 1, di.fdr.amount);
      dhp[0]=di.handle.left; dhp[1] = di.handle.bottom; dhp[2]=di.handle.right; dhp[3]=di.handle.bottom;
      dhp[4]=di.handle.right; dhp[5]=di.handle.top; dhp[6]=di.handle.left; dhp[7]=di.handle.top;
      glDrawArrays (GL_LINE_LOOP, 0, 4);
      if (di.attractor) { // mark as attractor
        dhp[0]=di.handle.midx; dhp[1] = di.handle.top; dhp[2]=di.handle.midx; dhp[3]=di.handle.bottom;
        dhp[4]=di.handle.left; dhp[5]=di.handle.midy; dhp[6]=di.handle.right; dhp[7]=di.handle.midy;
        glDrawArrays (GL_LINES, 0, 4);
      }
      if (di.launcher) {
        dhp[0]=di.handle.left; dhp[1] = di.handle.top; dhp[2]=di.handle.right; dhp[3]=di.handle.bottom;
        dhp[4]=di.handle.left; dhp[5]=di.handle.bottom; dhp[6]=di.handle.right; dhp[7]=di.handle.top;
        glDrawArrays (GL_LINES, 0, 4);
      }
    }
  }

  if (dinfo.anchor) { // draw drone anchors
    if (n_dap < num_drones) {
      if (dap) delete[] dap;
      dap = new int [4 * num_drones];
      n_dap = num_drones;
    }
    glVertexPointer (2, GL_INT, 0, dap);
    int ai = 0, ad = 0;
    for (drone_iterator i = drones.begin(), j = drones.end(); i != j; ++i) {
      drone& di = *(*i);
      if (di.range >= visl && di.range <= visr) {
        dap[ai++] = di.x; dap[ai++] = di.y; dap[ai++] = di.x; dap[ai++] = BOTTOM;
        ++ad;
      }
    }
    glColor3f (0.25, 0.25, 0.25);
    glDrawArrays (GL_LINES, 0, ad << 1);
  }

  // draw velocity and acceleration vectors
  if (num_drones && (dinfo.vel || dinfo.accel)) {
    static const float arrow_u = 0.4, arrow_v = 0.2;
    static const int v_size = 5, a_size = 10 * v_size;

    int nn_dvap = 12 * num_drones;
    if (n_dvap < nn_dvap) {
      if (dvap) delete[] dvap;
      dvap = new int [nn_dvap];
      n_dvap = nn_dvap;
    }
    glVertexPointer (2, GL_INT, 0, dvap);
    int v = 0, nv = 0;
    if (dinfo.vel) {
      for (drone_iterator i = drones.begin(), j = drones.end(); i != j; ++i) {
        drone& di = *(*i);
        if (di.range >= visl && di.range <= visr) {
          int vl = di.V * v_size, vdx = vl * di.vx + 0.5f, vdy = vl * di.vy + 0.5f, pvdx = -vdy, pvdy = vdx;
          make_arrow (dvap, v, di.x, di.y, vdx, vdy, pvdx, pvdy, arrow_u, arrow_v);
          di.xv = di.x + vdx; di.yv = di.y + vdy;
          v += 12;
          ++nv;
        }
      }
      if (nv) {
        glColor4f (0.5, 1, 0.5, 1);
        glDrawArrays (GL_LINES, 0, 6 * nv);
      }
    }

    if (dinfo.accel) {
      int a = 0, na = 0;
      for (drone_iterator i = drones.begin(), j = drones.end(); i != j; ++i) {
        drone& di = *(*i);
        if (di.range >= visl && di.range <= visr) {
          int al = di.A * a_size, adx = al * di.ax, ady = al * di.ay, padx = -ady, pady = adx;
          make_arrow (dvap, a, di.x, di.y, adx, ady, padx, pady, arrow_u, arrow_v);
          di.xa = di.x + adx; di.ya = di.y + ady;
          a += 12;
          ++na;
        }
      }
      if (na) {
        glColor4f (1, 0.5, 0.5, 1);
        glDrawArrays (GL_LINES, 0, 6 * na);
      }
    }
  }

  glDisable (GL_BLEND);

  /*glPointSize (10);
  glBegin (GL_POINTS);
    glVertex2f (cenx, ceny);
  glEnd ();
  glPointSize (1);*/



}

void din::update_drone_master_volume (float d) {

  if (num_drones == 0) return;

  float nd = d / num_drones;
  drone_master_volume += nd;

  for (drone_iterator i = drones.begin(), j = drones.end(); i != j; ++i) {
    drone& di = *(*i);
    di.update_pv = drone::EMPLACE;
  }

  cons.rollup (1);
  uis.main_menu.set_drone_master_volume ();
  cons << "Drone master volume = " << drone_master_volume << eol;

}

void din::update_drone_solvers (multi_curve& crv) {
  for (drone_iterator i = drones.begin(), j = drones.end(); i != j; ++i) {
    drone& di = *(*i);
    di.sol.update ();
    if (crv.num_vertices) di.player.set_mix (crv);
  }
}

string din::get_selected_drones () {
  stringstream ss;
  for (drone_iterator i = drones.begin(), j = drones.end(); i != j; ++i) {
    drone& di = *(*i);
    if (di.sel) ss << di.id << ' ';
  }
  return ss.str();
}

void din::set_drone_volume (int i, float v) {
  drone* pd = get_drone (i);
  if (pd) {
    pd->xi = pd->x; pd->yi = pd->y;
    int x = pd->x, y = (int) (BOTTOM + v *  HEIGHT + 0.5f);
    pd->set_xy (x, y);
    pd->move_center ();
  }
}

void din::calc_win_mouse () {

  if (uis.main_menu.show == 0) {

    delta_mousex = mousex - prev_mousex;
    delta_mousey = mousey - prev_mousey;

    prev_win_mousex = win_mousex;
    prev_win_mousey = win_mousey;

    win_mousex += delta_mousex;
    win_mousey -= delta_mousey;

    tonex = win_mousex;
    toney = win_mousey;

    prev_mousex = mousex;
    prev_mousey = mousey;

  }

}

int din::is_drone_hit (drone& di, const box<int>& rgn) {
  int x [] = {di.handle.midx, di.handle.left, di.handle.right};
  int y [] = {di.handle.midy, di.handle.bottom, di.handle.top};
  for (int i = 0; i < 3; ++i)
    for (int j = 0; j < 3; ++j)
      if (inbox (rgn, x[i], y[j])) return 1;
  return 0;
}

void din::calc_selector_range (const box<int>& rgn, int& left, int& right) {
  int xl = rgn.left, xr = rgn.right;
  left = right = 0;
  for (int i = 0; i < num_ranges; ++i) {
    range& ri = ranges[i];
    if (xl >= ri.extents.left) left = max (0, i - 1);
    if (xr >= ri.extents.right) right = min (last_range, i + 1);
  }
}

void din::find_selected_drones (const box<int>& rgn) {
  // select drones that lie inside selected region
  // supports modifiers
  int sell, selr; calc_selector_range (rgn, sell, selr);
  int shift = shift_down ();
  int ctrl = ctrl_down ();
  if (shift || ctrl); else clear_selected_drones ();
  for (drone_iterator i = drones.begin(), j = drones.end(); i != j; ++i) {
    drone* pdi = *i;
    drone& di = *pdi;
    if ((di.state > drone::DEAD) && (di.range >= sell) && (di.range <= selr) && is_drone_hit (di, rgn)) {
      if (ctrl) {
        if (di.sel)
          remove_drone_from_selection (pdi);
        else
          add_drone_to_selection (pdi);
      } else
        add_drone_to_selection (pdi);
    }
  }
  print_selected_drones ();
}

void din::invert_selected_drones () {
  selected_drones.clear ();
  for (drone_iterator i = drones.begin(), j = drones.end(); i != j; ++i) {
    drone* pdi = *i;
    drone& di = *pdi;
    if (di.sel) di.sel = 0; else {
      di.sel = 1;
      selected_drones.push_back (pdi);
    }
  }
  print_selected_drones ();
}

void din::print_selected_drones () {
  int j = selected_drones.size ();
  if (j) {
    cons << GREEN;
    prep_modulate (MODULATE_DRONES);
    update_xform_params = 1;
    update_scale_vectors = 1;
    uis.main_menu.after_drone_selection ();
  } else {
    cons << YELLOW;
    if (moving_drones) set_moving_drones (0);
  }
  cons << "Selected " << j << " drones" << eol;
}

int din::handle_input () {

  static const double reptf = 1./7, repts = 1./64., r1_by_ips = 1./IPS;
  static const double first_repeat_time = 0.33, other_repeat_time = 0.05;
  static double start_time;
  static double repeat_time = first_repeat_time;
  static int lmb_clicked = 0;
 
  // movement
  if (keypressedd (SDLK_a, dinfo.scroll.rept, dinfo.scroll.rept)) scroll (-dinfo.scroll.dx, 0); else
  if (keypressedd (SDLK_d, dinfo.scroll.rept, dinfo.scroll.rept)) scroll (+dinfo.scroll.dx, 0); else
  if (keypressedd (SDLK_w, dinfo.scroll.rept, dinfo.scroll.rept)) scroll (0, +dinfo.scroll.dy); else
  if (keypressedd (SDLK_s, dinfo.scroll.rept, dinfo.scroll.rept)) scroll (0, -dinfo.scroll.dy);

  check_range_resized (); // need to do b4 selector

  if (lmb) {
    if (lmb_clicked == 0) {
      if (adding_drones) add_drone (win_mousex, win_mousey);
      lmb_clicked = 1;
      start_time = ui_clk();
    }

    // for spraying drones when adding them
    double delta_time = ui_clk() - start_time;
    if (delta_time >= repeat_time) { // click repeat
      lmb_clicked = 0;
      repeat_time = other_repeat_time;
    }

  } else {
    if (lmb_clicked) {
      if (adding_drones) mkb_selector.abort ();
      if (moving_drones) {
        set_moving_drones (0); // finish moving drones
        mkb_selector.abort ();
      }
      else if (phrasor0.state == phrasor::recording) finish_phrase_recording ();
    }
    lmb_clicked = 0;
    repeat_time = first_repeat_time;
  }

  if (phrasor0.state == phrasor::recording) { // record mouse pos for playback l8r
    static point<int> pt;
    pt.x = win_mousex; pt.y = win_mousey;
    phrasor0.add (pt);
    ++phrasor0.size;
  }

  // octave shift
  if (keypressed (SDLK_z)) modulate_down ();
  else if (keypressed (SDLK_x)) modulate_up ();
  else if (keypressed (SDLK_e)) { // move selected drones
    if (moving_drones) set_moving_drones (0);
    else if (uis.main_menu.show == 0) start_moving_drones ();
  }
  else if (moving_drones) {
    int num_selected_drones = selected_drones.size ();
    if (prev_win_mousex != win_mousex || prev_win_mousey != win_mousey) {
      for (int i = 0; i < num_selected_drones; ++i) {
        drone& di = *selected_drones[i];
        //if (di.frozen == 0) {
          int not_init = 0;
          set_drone (di, win_mousex, win_mousey, not_init, shift_down (), ctrl_down ());
          di.update_pv = drone::EMPLACE;
        //}
      }
    }    
    return 1;
  }
  else if (keypressed (SDLK_b)) uis.cb_gater.toggle ();
  else if (keypressed (SDLK_f)) do_phrase_recording ();
  else if (keypressed (SDLK_v)) { // phrase play/pause
    if (phrasor0.state == phrasor::playing) {
      if (uis.main_menu.show == 0) {
        phrasor0.state = phrasor::paused;
        find_current_range ();
        cons << YELLOW << "phrasor has PAUSED." << eol;
      } else cons << RED << "Close menu!" << eol;
    } else {
      phrasor0.validate ();
      phrasor0.play ();
      if (phrasor0.state == phrasor::playing) cons << GREEN << "Phrasor is PLAYING" << eol;
    }
  }
  else if (keypressed (SDLK_g)) { // phrase clear
    clear_all_phrases ();
  }

  // drones
  //
  else if (keypressedd (SDLK_q)) add_drone (win_mousex, win_mousey); // add drone
  else if (keypressedd (SDLK_c)) {
    if (shift_down()) set_drones_under_gravity (); else delete_selected_drones ();
  }
  else if (keypressedd (SDLK_LEFTBRACKET, reptf, repts)) {
    if (shift_down()) rotate_drone_vel ((float) uis.main_menu.sp_rotate_drone_vel.f_delta); // rotate drone velocity
    else change_drone_vel (-1 * (float)uis.main_menu.sp_change_drone_vel.f_delta); // decrease drone velocity
  }
  else if (keypressedd (SDLK_RIGHTBRACKET, reptf, repts)) {
    if (shift_down()) rotate_drone_vel (-(float) uis.main_menu.sp_rotate_drone_vel.f_delta); else
    if (ctrl_down ()) toggle_this (dinfo.vel, uis.main_menu.cb_show_vel); // toggle velocity vector display
    else
      change_drone_vel (+1 * (float)uis.main_menu.sp_change_drone_vel.f_delta);
  }
  else if (keypressed (SDLK_l)) select_all_drones ();
  else if (keypressed (SDLK_i)) {
    if (shift_down()) {
      show_pitch_volume = !show_pitch_volume; // label mouse cursor, notes, pitches
      dont_call_listener (uis.cb_pitch_volume_info, show_pitch_volume);
    } else invert_selected_drones ();
  }
  else if (keypressed (SDLK_j)) toggle_freeze_drones ();
  else if (keypressed (SDLK_k)) {
    drone::SNAP_TO_NOTES = !drone::SNAP_TO_NOTES;
    dont_call_listener (uis.main_menu.cb_snap_drones, drone::SNAP_TO_NOTES);
  }
  else if (keypressedd (SDLK_o, reptf, repts)) change_drone_accel (-1 * (float)uis.main_menu.sp_change_drone_accel.f_delta);
  else if (keypressedd (SDLK_p, reptf, repts)) {
    if (ctrl_down())
      toggle_this (dinfo.accel, uis.main_menu.cb_show_accel);
    else
      change_drone_accel (+1 * (float)uis.main_menu.sp_change_drone_accel.f_delta);
  }

  else if (keypressed (SDLK_h)) {
    if (shift_down()) {
      auto_select_launched = !auto_select_launched;
      uis.main_menu.cb_select_launched.set_state (auto_select_launched);
      if (auto_select_launched)
        cons << GREEN << "Will auto select launched drones" << eol;
      else
        cons << RED << "Will not auto select launched drones" << eol;
    } else toggle_launchers ();
  }
  else if (keypressedd (SDLK_n)) change_drones_per_min (-(int)uis.main_menu.sp_drones_per_min.f_delta);
  else if (keypressedd (SDLK_m)) change_drones_per_min (+(int)uis.main_menu.sp_drones_per_min.f_delta);
  else if (keypressed (SDLK_F3)) orbit_selected_drones ();

  else if (keypressed (SDLK_SEMICOLON)) select_attractors ();
  else if (keypressed (SDLK_QUOTE)) select_attractees ();

  else if (keypressedd (SDLK_COMMA, reptf, repts)) update_drone_master_volume (-DELTA_DRONE_MASTER_VOLUME);
  else if (keypressedd (SDLK_PERIOD, reptf, repts)) update_drone_master_volume (DELTA_DRONE_MASTER_VOLUME);

  else if (keypressedd (SDLK_KP1, r1_by_ips, r1_by_ips)) rotate_selected_drones (PI_BY_180);
  else if (keypressedd (SDLK_KP2, r1_by_ips, r1_by_ips)) rotate_selected_drones (-PI_BY_180);
  else if (keypressedd (SDLK_KP4, r1_by_ips, r1_by_ips)) scale_selected_drones (-0.01);
  else if (keypressedd (SDLK_KP5, r1_by_ips, r1_by_ips)) scale_selected_drones (+0.01);

  else if (keypressed (SDLK_SLASH)) mute_drones ();
  else if (keypressed (SDLK_F4)) switch_modulation ();
  else if (keypressed (SDLK_BACKSLASH)) set_key_to_pitch_at_cursor ();
  else if (keypressed (SDLK_SPACE)) uis.cb_voice.toggle (); // toggle lead voice
  else if (keypressed (SDLK_F1)) helptext();

  /*else if (keypressedd (SDLK_UP)) change_drone_lifetime (-(float)uis.main_menu.sp_drone_lifetime.f_delta);
  else if (keypressedd (SDLK_DOWN)) change_drone_lifetime (+(float)uis.main_menu.sp_drone_lifetime.f_delta);
  else if (keypressedd (SDLK_LEFT)) change_orbit_insertion_time (+(float)uis.main_menu.sp_orbit_insertion_time.f_delta);
  else if (keypressedd (SDLK_RIGHT)) change_orbit_insertion_time (-(float)uis.main_menu.sp_orbit_insertion_time.f_delta);*/


  // bpms
  if (keypressedd (SDLK_F5)) { // decrease gater bpm upto limit
    if (shift_down ())
      lower_delta (gater_delta.bpm, -1, "delta_gater_bpm = ");
    else if (ctrl_down()) {
      gatr.min_bpm = gatr.bpm;
      cons << YELLOW << "set minimum gater bpm to " << gatr.bpm << eol;
    }
    else
      change_bpm (gatr, -gater_delta.bpm); //-(float)uis.main_menu.sp_gater_bpm.f_delta);
  } else if (keypressedd (SDLK_F6)) { // increase gater bpm
    if (shift_down())
      raise_delta (gater_delta.bpm, +1, "delta_gater_bpm = ");
    else if (ctrl_down()) {
      gatr.min_bpm = 0;
      cons << YELLOW << "set minimum gater bpm to " << gatr.min_bpm << eol;
    }
    else
      change_bpm (gatr, gater_delta.bpm); //uis.main_menu.sp_gater_bpm.f_delta);
  } else if (keypressedd (SDLK_F7)) { // decrease fm bpm
    if (shift_down())
      lower_delta (fm_delta.bpm, -1, "delta_fm_bpm = ");
    else if (ctrl_down()) { // no decrease below this
      fm.min_bpm = fm.bpm;
      drone_modulation::fm_crv.min_bpm = drone_modulation::fm_crv.bpm;
      cons << YELLOW << "set minimum FM bpm to " << fm.bpm << "[voice], " << drone_modulation::fm_crv.bpm << " [drones]" << eol ;
    }
    else
      change__bpm (drone_modulation::FM, fm, -fm_delta.bpm); //-(float)uis.main_menu.sp_fm_bpm.f_delta);
  } else if (keypressedd (SDLK_F8)) { // increase fm bpm
    if (shift_down())
      raise_delta (fm_delta.bpm, +1, "delta_fm_bpm = ");
    else if (ctrl_down()) {
      fm.min_bpm = 0;
      drone_modulation::fm_crv.min_bpm = 0;
      cons << YELLOW << "set minimum FM bpm to " << fm.min_bpm << "[voice], " << drone_modulation::fm_crv.min_bpm << " [drones]" << eol;
    }
    else
      change__bpm (drone_modulation::FM, fm, fm_delta.bpm); //uis.main_menu.sp_fm_bpm.f_delta);
  } else if (keypressedd (SDLK_F9)) { // decrease am bpm
    if (shift_down())
      lower_delta (am_delta.bpm, -1, "delta_am_bpm = ");
    else if (ctrl_down()) {
      am.min_bpm = am.bpm;
      drone_modulation::am_crv.min_bpm = drone_modulation::am_crv.bpm;
      cons << YELLOW << "set minimum AM bpm to " << am.bpm << "[voice], " << drone_modulation::am_crv.bpm << " [drones]" << eol;
    }
    else
      change__bpm (drone_modulation::AM, am, -am_delta.bpm); //-(float)uis.main_menu.sp_am_bpm.f_delta);
  } else if (keypressedd (SDLK_F10)) { // increase am bpm
    if (shift_down())
      raise_delta (am_delta.bpm, 1, "delta_am_bpm = ");
    else if (ctrl_down()) {
      am.min_bpm = 0;
      drone_modulation::am_crv.min_bpm = 0;
      cons << YELLOW << "set minimum AM bpm to " << am.min_bpm << "[voice], " << drone_modulation::am_crv.min_bpm << " [drones]" << eol;
    }
    else
      change__bpm (drone_modulation::AM, am, am_delta.bpm); //uis.main_menu.sp_am_bpm.f_delta);
  } else if (keypressedd (SDLK_F11)) { // decrease octave shift bpm
    if (shift_down())
      lower_delta (os_delta.bpm, -1, "delta_octave_shift_bpm = ");
    else if (ctrl_down()) {
      octave_shift.min_bpm = octave_shift.bpm;
      cons << YELLOW << "set minimum octave_shift bpm to " << octave_shift.bpm << eol;
    }
    else
      change_bpm (octave_shift, -os_delta.bpm); //-(float)uis.main_menu.sp_octave_shift_bpm.f_delta);
  } else if (keypressedd (SDLK_F12)) { // increase octave shift bpm
    if (shift_down())
      raise_delta (os_delta.bpm, +1, "delta_octave_shift_bpm = ");
    else if (ctrl_down()) {
      octave_shift.min_bpm = 0;
      cons << YELLOW << "set minimum octave_shift bpm to " << octave_shift.min_bpm << eol;
    }
    else
      change_bpm (octave_shift, os_delta.bpm); //uis.main_menu.sp_octave_shift_bpm.f_delta);
  }

  // depths
  else if (keypressedd (SDLK_r)) { // decrease am depth
    if (shift_down())
      lower_delta (p_am_delta->depth, -float(uis.main_menu.sp_am_depth.f_delta), "delta_am_depth = ", 0.0f);
    else
      change__depth (drone_modulation::AM, -dam_delta.depth, 0, -am_delta.depth);
  } else if (keypressedd (SDLK_t)) { // increase am depth
    if (shift_down())
      raise_delta (p_am_delta->depth, float(uis.main_menu.sp_am_depth.f_delta), "delta_am_depth = ");
    else
      change__depth (drone_modulation::AM, dam_delta.depth, 0, am_delta.depth);
  } else if (keypressedd (SDLK_y)) { // decrease fm depth
    if (shift_down())
      lower_delta (fm_delta.depth, -float (uis.main_menu.sp_fm_depth.f_delta), "delta_fm_depth = ");
    else
      change__depth (drone_modulation::FM, -fm_delta.depth, 1, -fm_delta.depth);
  } else if (keypressedd (SDLK_u)) { // increase fm depth
    if (shift_down())
      raise_delta (fm_delta.depth, float (uis.main_menu.sp_fm_depth.f_delta), "delta_fm_depth = ");
    else
    change__depth (drone_modulation::FM, fm_delta.depth, 1, fm_delta.depth);
  }

  else if (keypressedd (SDLK_MINUS)) {
    change_drone_trail_points (-1);
  } else if (keypressedd (SDLK_EQUALS)) {
    change_drone_trail_points (+1);
  } else if (keypressedd (SDLK_9)) {
    change_drone_handle_size (-1);
  } else if (keypressedd (SDLK_0)) {
    change_drone_handle_size (+1);
  }

  return 1;

}

void din::change_drone_trail_points (int delta) {
  int n = selected_drones.size ();
  if (n) {
    for (int i = 0, j = selected_drones.size (); i < j; ++i) {
      drone& ds = *selected_drones[i];
      ds.trail.change_size (delta);
      cons << "Drone: " << i << ", trail size = " << ds.trail.max_points << eol;
    }
  } else cons << RED << "Please select a drone!" << eol;
}

void din::change_drone_handle_size (int delta) {
  int n = selected_drones.size ();
  if (n) {
    for (int i = 0, j = selected_drones.size (); i < j; ++i) {
      drone& ds = *selected_drones[i];
      ds.handle_size += delta;
      if (ds.handle_size < 0) ds.handle_size = 0; else cons << "Drone " << i << ", handle size = " << ds.handle_size << eol;
    }
    update_drone_anchors ();
  } else cons << RED << "Please select a drone!" << eol;
}


void din::change_drone_lifetime (float d) {
  int n = selected_drones.size ();
  if (n == 0) {
    cons << RED << "Please select a drone!" << eol;
    return;
  }
  for (int i = 0; i < n; ++i) {
    drone& ds = *selected_drones[i];
    if ((ds.birth == -1) && !ds.launcher) ds.birth = ui_clk();
    ds.life += d;
    if (ds.life < 0) ds.life = 0;
    cons << "Drone: " << i << ", lifetime = " << ds.life << " secs" << eol;
  }
}

void din::change_orbit_insertion_time (float d) {
  int n = selected_drones.size ();
  if (n == 0) {
    cons << RED << "Please select a drone!" << eol;
    return;
  }
  for (int i = 0; i < n; ++i) {
    drone& ds = *selected_drones[i];
    if (ds.launcher) {
      ds.insert += d;
      if (ds.insert < 0) ds.insert = 0;
      cons << "Drone: " << i << ", orbit insertion time = " << ds.insert << " secs" << eol;
    }
  }
}

void din::scroll (int dx, int dy, int warp_mouse) {

  mousex -= dx;
  prev_mousex -= dx;
  mousey += dy;
  prev_mousey += dy;

  win (win.left + dx, win.bottom + dy, win.right + dx, win.top + dy);
  find_visible_ranges (dx);

  if (warp_mouse) {
    if ((mousex > 0 && mousex < view.width) && (mousey > 0 && mousey < view.height)) SDL_WarpMouse (mousex, mousey);
  }

}

void din::find_current_range () {

  // find the range where mouse is found

  win_mousex = win.left + mousex;
  win_mousey = win.bottom + mouseyy;

  if (win_mousex <= ranges[0].extents.left) current_range = 0; else
  if (win_mousex >= ranges[last_range].extents.right) current_range = last_range; else
  for (int i = 0; i < num_ranges; ++i) {
    range& curr = ranges[i];
    box<int>& ext = curr.extents;
    if ( (win_mousex >= ext.left) && (win_mousex <= ext.right)) {
      current_range = i;
      break;
    }
  }

  find_visible_ranges ();

}

void din::find_visible_ranges (int dir) { // bcos we only draw visible ranges

  if (dir > 0) {
    while ((visr < last_range) && (ranges[visr].extents.right < win.right)) ++visr;
    while ((visl < last_range) && (ranges[visl].extents.right < win.left)) ++visl;
  } else if (dir < 0) {
    while ((visl > 0) && (ranges[visl].extents.left > win.left)) --visl;
    while ((visr > 0) && (ranges[visr].extents.left > win.right)) --visr;
  } else {
    visl = current_range;
    visr = current_range;
    while ( (visl > 0) && (win.left < ranges[visl].extents.left) ) --visl;
    while ( (visr < last_range) && (ranges[visr].extents.right < win.right) ) ++visr;
  }

}

int din::find_range (int x, int r) {
  while (1) {
    range& curr = ranges [r];
    int deltax = x - curr.extents.left;
    if (deltax > curr.extents.width) {
      if (++r < num_ranges); else {
        r = last_range;
        break; // drone in last range
      }
    }
    else if (deltax < 0) {
      if (--r < 0) {
        r = 0; // drone in first range
        break;
      }
    }
    else
      break; // drone in current range
  }
  return r;
}

int din::find_tone_and_volume () {

  find_volume ();

  // locate current tone
  range* curr = &ranges [current_range];
  int deltax = tonex - curr->extents.left;
  if (deltax >= curr->extents.width) { // tone in range to the right
    ++current_range;
    if (current_range == num_ranges) { // snap to last range
      current_range = last_range;
      curr = lastr;
    } else {
      curr = &ranges [current_range];
    }
  } else if (deltax < 0) { // tone in range to the left
    --current_range;
    if (current_range < 0) { // snap to first range
      curr = firstr;
      current_range = 0;
    } else {
      curr = &ranges [current_range];
    }
  }

  // located tone so find frequency
  //
  deltax = tonex - curr->extents.left;

  delta = deltax * curr->extents.width_1;
  step = curr->notes[0].step + delta * curr->delta_step; // step determines frequency see note.h

  // octave position of tone among all octaves
  octave_position = curr->notes[0].octave_position + delta * curr->delta_octave_position;

  if (show_pitch_volume) { // display frequency & volume at mouse cursor
    stringstream ss;
    ss.clear (); ss << (step * SAMPLE_RATE) << " / " << VOLUME; pitch_volume_info = ss.str();
  }

  static const int if_uniq = 1;
  if (dv < 0) { // below keyboard, silence
    wavplay.set_interpolated_pitch_volume (step, 0, if_uniq);
    am_vol = 0;
  } else {
    extern float VOICE_VOLUME;
    float fdr_vol = uis.fdr_voice.amount * VOLUME;
    wavplay.set_interpolated_pitch_volume (step, fdr_vol * VOICE_VOLUME, if_uniq);
    am_vol = fdr_vol * am_depth;
  }

  Tcl_UpdateLinkedVar (interpreter.interp, "volume"); // VOLUME is accessible in Tcl interpreter as variable volume
 
  return 1;

}

void din::draw () {
 
  glMatrixMode (GL_PROJECTION);
  glLoadIdentity ();
  glOrtho (win.left, win.right, win.bottom, win.top, -1, 1);

  glMatrixMode (GL_MODELVIEW);
  glLoadIdentity ();

  extern int TURN_OFF_UI;
  if (TURN_OFF_UI == 0) {

    // label visible ranges
    for (int i = visl; i < visr; ++i) ranges[i].draw_labels (range::LEFT, show_pitch_volume);
    ranges[visr].draw_labels (range::BOTH, show_pitch_volume);


    // mark range bottom & top boundary
    int rl = ranges[visl].extents.left, rr = ranges[visr].extents.right;
    float tr = 1; glColor3f (tr, tr, tr);

    glVertexPointer (2, GL_INT, 0, gl_pts);
    gl_pts[0]=rl; gl_pts[1]=BOTTOM;
    gl_pts[2]=rr; gl_pts[3]=BOTTOM;
    gl_pts[4]=rl; gl_pts[5]=TOP;
    gl_pts[6]=rr; gl_pts[7]=TOP;
    glDrawArrays (GL_LINES, 0, 4);

    // mark current range?
    if (mark_current_range && (current_range >= visl && current_range <= visr)) {
      range& cr = ranges[current_range];
      box<int>& cre = cr.extents;
      glLineWidth (3);
      glColor3f (0, 1, 0.5);
      gl_pts[0]=cre.left; gl_pts[1]=cre.bottom;
      gl_pts[2]=cre.right; gl_pts[3]=cre.bottom;
      gl_pts[4]=cre.right; gl_pts[5]=cre.top;
      gl_pts[6]=cre.left; gl_pts[7]=cre.top;
      glDrawArrays (GL_LINE_LOOP, 0, 4);
      glLineWidth (1);
    }

    // phrasor markers
    phrasor0.draw ();

    // draw cursor info
    int cursorx = tonex + 3, cursory = toney, lh = get_line_height ();
    if (show_pitch_volume) {
      glColor3f (1, 0.25f, 0.15f);
      draw_string (pitch_volume_info, cursorx, cursory, 0);
      cursory -= lh;
    }

    // draw guide for positioning drones
    if (dinfo.voice == 0) {
      glColor3f (0.25, 0.25, 0.25);
      gl_pts[0]=tonex;gl_pts[1]=toney;
      gl_pts[2]=tonex;gl_pts[3]=BOTTOM;
      glVertexPointer (2, GL_INT, 0, gl_pts);
      glDrawArrays (GL_LINES, 0, 2);
    }

    mkb_selector.draw (rgn);

  }

  draw_drones (); // draw drones

 
}

void din::enter () {
  if (phrasor0.state != phrasor::playing) {
    ui::enter ();
    find_current_range ();
  }
}

void din::change_depth (int i, float d) {

  cons.rollup(1);
  if (i == 1) {
    fm_depth += d;
    hz2step (fm_depth, fm_step);
    cons << YELLOW << "Voice FM depth = " << fm_depth << eol;
    uis.main_menu.sp_fm_depth.set_value (fm_depth);
  } else {
    am_depth += d;
    cons << YELLOW << "Voice AM depth = " << am_depth << eol;
    uis.main_menu.sp_am_depth.set_value (am_depth);
  }
}

void din::change_bpm (beat2value& which, float amt) {
  cons.rollup (1);
  float bpm = which.get_bpm () + amt;
  bpm = which.set_bpm (bpm);
  cons << YELLOW << which.name << " bpm: " << bpm << eol;
  uis.main_menu.update_bpm (which.name, bpm);
}

int din::calc_am_fm_gater () {
  int ret = 0;
  memcpy (aout.bufL, wavplay.pvol, aout.samples_channel_size);
  multiply (aout.bufL, aout.samples_per_channel, am_depth);
  ret += am.modulate_and_mix (aout.ams, aout.mix, aout.mixa, aout.samples_per_channel, aout.bufL);
  ret += fm.modulate_and_mix (aout.fms, aout.mix, aout.mixa, aout.samples_per_channel, fm_step);
  ret += gatr.gen_and_mix (aout.gatr, aout.mix, aout.mixa, aout.samples_per_channel);
  return ret;
}

void din::bg () { // always runs even when din board is not visible

  if (phrasor0.state == phrasor::playing) {
    phrasor0.get (tonex, toney);
    phrasor0.next ();
  }

}

void din::modulate_drones () {
  for (drone_iterator i = drones.begin(), j = drones.end(); i != j; ++i) {
    drone& di = *(*i);
    drone_modulation& dm = di.mod;
    if (di.frozen == 0) dm.calc ();
    if (dm.active) {
      int x = di.cx + dm.fm.result, y = di.cy + dm.am.result;
      di.set_xy (x, y);
    }
  }
}

int din::render_audio (float* out0, float* out1) {

  int ret = 0;

  ret = calc_am_fm_gater (); // compute voice AM & FM & gater over bpm

  // render voice
  find_tone_and_volume ();
  float *lout = out0, *rout = out1;
  wavplay.gen_wav_fm_am_mix (lout, aout.samples_per_channel);
  ret += wavplay.mixer.active;

  // apply gater
  lout = out0;
  rout = out1;
  if (uis.fdr_gater.on) {
    memcpy (aout.result, lout, aout.samples_channel_size); // voice
    multiply (lout, aout.gatr, aout.samples_per_channel); // voice * gater
    fill (aout.bufR, fdr_gater_prev_amount, uis.fdr_gater.amount, aout.samples_per_channel);
    fdr_gater_prev_amount = uis.fdr_gater.amount;
    tween (lout, aout.result, aout.samples_per_channel, aout.bufR); // voice > voice*gater
  } else {
    if (dinfo.gater) multiply (lout, aout.gatr, aout.samples_per_channel); // voice * gater
  }
  memcpy (rout, lout, aout.samples_channel_size); // copy left -> right

  // evaluate drones
  fall_drones ();
  rise_drones ();
  move_drones_under_gravity ();
  if (quit == DONT) launch_drones ();
  track_drones ();
  carry_satellites_to_orbit ();
  attract_drones ();
  modulate_drones ();
  trail_drones ();
  kill_old_drones ();
 
  // render drones
  for (drone_iterator i = drones.begin(), j = drones.end(); i != j; ++i) {
    drone& di = *(*i);
    float* lout = out0, *rout = out1;
    play& dp = di.player;
    if (di.update_pv) di.update_pitch_volume (drone_master_volume);
    dp.master (lout, rout, aout.result, aout.samples_per_channel, dp.pvol);
    ret += dp.mixer.active;
  }

  return ret;

}

void din::rise_drones () {
  if (rising) {
    for (drone_iterator i = risers.begin(); i != risers.end();) {
      drone* pdi = *i;
      drone& di = *pdi;
      di.fdr.eval ();
      if (di.fdr.on == 0) {
        di.state = drone::ACTIVE;
        --rising;
        i = risers.erase (i);
        di.update_pv = drone::EMPLACE;
      } else {
        ++i;
        di.update_pv = drone::INTERPOLATE;
      }
    }
  }
}

void din::fall_drones () {
  if (falling) {
    for (drone_iterator i = fallers.begin(); i != fallers.end();) {
      drone* pdi = *i;
      drone& di = *pdi;
      di.fdr.eval ();
      if (di.fdr.on == 0) {
        i = fallers.erase (i);
        remove_attractee (pdi);
        remove_tracker (pdi);
        if (dinfo.gravity.tracked_drone == pdi) dinfo.gravity.tracked_drone = 0;
        if (di.launcher) erase (launchers, pdi);
        if (di.attractor) erase (attractors, pdi);
        if (di.gravity) {
          erase (gravitated, pdi);
          erase (satellites, pdi);
        }
        remove_drone_from_targets (pdi);
        remove_drone_from_selection (pdi);
        remove_drone_from_mesh (pdi);
        --falling;
        --num_drones;
        if (num_drones == 0) prep_modulate (MODULATE_VOICE);
        erase (drones, pdi);
        delete pdi;
      } else {
        ++i;
        di.update_pv = drone::INTERPOLATE;
      }
    }
  }
}

din_info::din_info () {
  ifstream file ((user_data_dir + "din_info").c_str(), ios::in);
  string ignore;
  if (file) {
    file >> ignore >> BOTTOM01 >> BOTTOM >> HEIGHT01 >> HEIGHT >> LEFT;
    TOP = BOTTOM + HEIGHT;
    calc_volume_vars (HEIGHT);
    file >> ignore >> delay;
    file >> ignore >> gater;
    file >> ignore >> compress;
    file >> ignore >> voice;
    file >> ignore >> anchor;
    file >> ignore >> vel;
    file >> ignore >> accel;
    file >> ignore >> fader::TIME;
    file >> ignore >> rows >> cols;
    gravity.load (file);
    file >> ignore >> bounces;
    file >> ignore >> rebound;
    file >> n >> start_depth >> depth_inc >> start_bpm;
  }
  cons.last ();
}

void din_info::save () {
  ofstream file ((user_data_dir + "din_info").c_str(), ios::out);
  file << "board " << BOTTOM01 << ' ' << BOTTOM << ' ' << HEIGHT01 << ' ' << HEIGHT << ' ' << LEFT << endl;
  file << "delay " << delay << endl;
  file << "gater " << gater << endl;
  file << "compress " << compress << endl;
  file << "voice " << voice << endl;
  file << "anchor " << anchor << endl;
  file << "vel " << vel << endl;
  file << "accel " << accel << endl;
  file << "fade_time " << fader::TIME << endl;
  file << "drone_mesh " << rows << ' ' << cols << endl;
  gravity.save (file);
  file << "bounces " << bounces << endl;
  file << "rebound " << rebound << endl;
  file << n << ' ' << start_depth << ' ' << depth_inc << ' ' << start_bpm << endl;
  dlog << "+++ saved din_info +++" << endl;
}

void din::board_height_changed () {

  HEIGHT01 = HEIGHT * 1.0 / win.height;
  TOP = BOTTOM + HEIGHT;
  if (TOP <= BOTTOM) { // set sane values
    HEIGHT = 2;
    TOP = BOTTOM + HEIGHT;
  }

  // update volume internals
  calc_volume_vars (HEIGHT);

  // update ranges
  for (int i = 0; i < num_ranges; ++i) {
    range& ri = ranges[i];
    ri.extents.bottom = BOTTOM;
    ri.extents.top = TOP;
    ri.extents.height = HEIGHT;
    ri.extents.calc ();
  }
  calc_range_label ();

  // update drones
  for (drone_iterator i = drones.begin(), j = drones.end(); i != j; ++i) {
    drone& di = *(*i);
    if (di.mod.active == 0) {
      di.dy = (int) (di.vol * HEIGHT);
      di.cy = BOTTOM + di.dy;
      di.calc_handle ();
    }
  }

}

void din::toggle_this (int& what, checkbutton& cb) {
  what = !what;
  cb.set_state (what);
}

void din::switch_modulation () { // switch modulation target
  static const char* swhat [] = {"Modulating drones", "Modulating voice"};
  modulate_what = !modulate_what;
  prep_modulate (modulate_what);
  cons << YELLOW << swhat[modulate_what] << eol;
}

void din::prep_modulate (int op) {
  modulate_what = op;
  delta_t* pdt [] = {&dam_delta, &am_delta};
  p_am_delta = pdt [modulate_what];
  uis.main_menu.init_modulation ();
}

void din::change__bpm (int type, beat2value& bv2, float amount) {
  if (modulate_what == MODULATE_DRONES)
    change_drone_bpm (type, amount);
  else
    change_bpm (bv2, amount);
}

void din::change__depth (int drone_arg1, float amount1, int voice_arg2, float amount2) {
  if (modulate_what == MODULATE_DRONES)
    change_drone_depth (drone_arg1, amount1);
  else
    change_depth (voice_arg2, amount2);
}

void din::change_am_depth (float d) {
  change__depth (drone_modulation::AM, d, 0, d);
}

void din::change_fm_depth (float d) {
  change__depth (drone_modulation::FM, d, 1, d);
}

void din::change_am_bpm (float d) {
  change__bpm (drone_modulation::AM, am, d);
}

void din::change_fm_bpm (float d) {
  change__bpm (drone_modulation::FM, fm, d);
}

void din::change_drone_depth (int what, float delta) {
  int n = selected_drones.size ();
  if (n) {
    cons.rollup (1);
    for (int i = 0; i < n; ++i) {
      drone& ds = *selected_drones[i];
      ds.change_depth (i, what, delta);
    }
  } else cons << RED << "Please select a drone!" << eol;
}

void din::change_drone_bpm (int what, float delta) {
  int n = selected_drones.size ();
  if (n) {
    cons.rollup (1);
    for (int i = 0; i < n; ++i) {
      drone& ds = *selected_drones[i];
      ds.change_bpm (i, what, delta);
    }
  } else cons << RED << "Please select a drone!" << eol;
}

void din::toggle_adding_drones () {
  adding_drones = !adding_drones;
  if (adding_drones) {
    cons << GREEN << "Click to add drones. ESC to stop" << eol;
  } else {
    cons << RED << "Stopped adding drones!" << eol;
  }
}

void din::start_moving_drones () {
  if (selected_drones.size () == 0) pick_drone ();
  if (selected_drones.size ()) set_moving_drones (1); else cons << RED << "Please select some drones!" << eol;
}

void din::toggle_moving_drones () {
  if (moving_drones == 0) {
    start_moving_drones ();
  } else set_moving_drones (0);
}

void din::set_moving_drones (int md) {
  moving_drones = md;
  if (moving_drones) {
    cons << GREEN << "Just move mouse to move drones, ESC or Click to stop!" << eol;
  } else {
    cons << "@ " << name << eol;
    update_xform_params = 1;
  }
}

int din::finish_phrase_recording () {
  if (phrasor0.state == phrasor::recording) {
    if (phrasor0.validate ()) {
      phrasor0.play ();
      cons << GREEN << "Phrasor has stopped recording and started playing!" << eol;
      return 1;
    }
  }
  return 0;
}

void din::do_phrase_recording () {
  if (!finish_phrase_recording()) {
    phrasor0.clear ();
    phrasor0.state = phrasor::recording;
    cons << GREEN << "Phrasor is recording. Click or press f to finish!" << eol;
  }
}

void din::clear_all_phrases (int quiet) {
  phrasor0.clear ();
  if (uis.main_menu.show == 0) find_current_range ();
  uis.main_menu.s_phrase_position.set_val (0);
  if (quiet == 0) cons << RED << "Phrase cleared!" << eol;
}

int din::check_range_resized () {

  extern int IPS;
  static const float reptf = 1./IPS, reptn = 1./IPS;

  if (shift_down()) {
    if (keypressedd (SDLK_LEFT, reptf, reptn))
      range_left_changed (current_range, -1);
    else if (keypressedd (SDLK_RIGHT, reptf, reptn))
      range_right_changed (current_range, +1);
  } else if (ctrl_down()) {
    if (keypressedd (SDLK_LEFT, reptf, reptn))
      range_right_changed (current_range, -1);
    else if (keypressedd (SDLK_RIGHT, reptf, reptn))
      range_left_changed (current_range, +1);
  } else if (alt_down()) {
    if (delta_mousey != 0) {
      HEIGHT -= delta_mousey;
      board_height_changed ();
      return 1;
    }
  }

  return 0;

  /*static int resizing = -1;
  if (lmb) {

    if (resizing != -1) { // resizing range

      if (delta_mousex != 0) {
        if (resizing < 2) range_left_changed (resize_range, delta_mousex); else range_right_changed (resize_range, delta_mousex);
      }
   
      if (delta_mousey != 0) {
        if (resizing == 0 || resizing == 2) { // resize bottom of board
          BOTTOM -= delta_mousey;
          HEIGHT += delta_mousey;
        } else HEIGHT -= delta_mousey; // resize top of board
        board_height_changed ();
      }

    } else { // hittest
      resize_range = current_range;
      range& R = ranges [resize_range];
      box<int>& extents = R.extents;
      int lh = get_line_height ();
      int delta_left = win_mousex - extents.left;
      int delta_right = extents.right - win_mousex;
      int tops = TOP + lh, tope = tops + lh;
      int bots = BOTTOM - lh;
      if (delta_left < delta_right) { // check left and bottom edge of range
        int ell = extents.left, elr = extents.left + R.notes[0].len * fnt.max_char_width;
        box<int> lb (ell, bots, elr, BOTTOM); // left bottom
        box<int> lt (ell, tops, elr, tope); // left top
        if (inbox (lb, win_mousex, win_mousey)) resizing = 0; else if (inbox (lt, win_mousex, win_mousey)) resizing = 1; else resizing = -1;
      } else { // check right and top edge of range
        int erl = extents.right - R.notes[1].len * fnt.max_char_width, err = extents.right;
        box<int> rb (erl, bots, err, BOTTOM); // right bottom
        box<int> rt (erl, tops, err, tope); // right top
        if (inbox (rb, win_mousex, win_mousey)) resizing = 2; else if (inbox (rt, win_mousex, win_mousey)) resizing = 3; else resizing = -1;
      }
    }

  } else
    resizing = -1;

  if (resizing < 0) return 0; else return 1;
  */

 
}

void din::default_range_to_all () {
  set_range_size (0, last_range, NUM_MICROTONES);
}

void din::default_range_to_current () {
  set_range_size (current_range, NUM_MICROTONES);
}

void din::current_range_to_all () {
  set_range_size (0, last_range, ranges[current_range].extents.width);
}

void din::set_key_to_pitch_at_cursor () {
  if (phrasor0.state != phrasor::playing) {
      float hz = step * SAMPLE_RATE;
      set_tonic (this, hz);
      mouse2tonic ();
  } else cons << RED << "Phrasor is playing. Please turn it off!" << eol;
}

void din::change_drone_accel (float amount) {
  //rnd<float> variance (0, 0.5);
  int n = selected_drones.size ();
  if (n) {
    cons << YELLOW;
    for (int i = 0, j = selected_drones.size (); i < j; ++i) {
      drone& di = *selected_drones[i];
      di.A += amount; // ((1 + variance()) * amount);
      if (di.A < 0) di.A = 0;
      cons << "Drone: " << i << ", Acceleration = " << di.A << eol;
    }
  } else cons << RED << "Please select a drone!" << eol;
}

void din::change_drone_vel (float amount) {
  //rnd<float> variance (0, 0.5);
  int n = selected_drones.size ();
  if (n) {
    cons << YELLOW;
    for (int i = 0, j = selected_drones.size (); i < j; ++i) {
      drone& di = *selected_drones[i];
      di.V += amount; // ((1 + variance()) * amount);
      if (di.V < 0) di.V = 0;
      cons << "Drone: " << i << ", Velocity = " << di.V << eol;
    }
  } else cons << RED << "Please select a drone!" << eol;
}

void din::rotate_drone_vel (float angle) {
  int n = selected_drones.size ();
  if (n) {
    cons << YELLOW;
    float angle_r = angle * PI_BY_180;
    for (int i = 0; i < n; ++i) {
      drone& di = *selected_drones[i];
      rotate_vector (di.vx, di.vy, angle_r);
      cons << "Drone: " << i << ", Rotated Velocity to " << di.vx << ' ' << di.vy << eol;
    }
  } else cons << RED << "Please select a drone!" << eol;
}

void din::calc_xform_vectors (vector<point <float> >& V, int n, int& flag) {
  V.clear ();
  for (int i = 0; i < n; ++i) {
    drone& di = *selected_drones [i];
    point<float> vv;
    direction<float> (vv.x, vv.y, cenx, ceny, di.cx, di.cy);
    V.push_back (vv);
  }
  flag = 0;
}

void din::calc_xform_params (int n) {

  cenx = ceny = 0.0f;
  for (int i = 0; i < n; ++i) {
    drone& di = *selected_drones [i];
    cenx += di.cx; ceny += di.cy;
  }
  cenx /= n;
  ceny /= n;

  calc_xform_vectors (rvec, n, update_rotation_vectors);
  calc_xform_vectors (svec, n, update_scale_vectors);

  angle = 0.0f;
  scl = 1.0f;

  update_xform_params = 0;

}

void din::rotate_selected_drones (float da) {
  int n = selected_drones.size ();
  if (n == 0) {
    cons << RED << "Please select some drones to rotate" << eol;
    return;
  }
  if (update_xform_params)
    calc_xform_params (n);
  else if (update_rotation_vectors) {
    calc_xform_vectors (rvec, n, update_rotation_vectors);
    angle = 0.0f;
  }
  angle += da;
  float dx, dy;
  for (int i = 0; i < n; ++i) {
    drone& di = *selected_drones[i];
    point<float> rv = rvec[i];
    rotate_vector (rv.x, rv.y, angle);
    dx = cenx + rv.x;
    dy = ceny + rv.y;
    di.set_center (dx, dy);
    if (vel_effect != NO_CHANGE) {
      unit_vector (di.vx, di.vy, rv.x, rv.y);
      if (vel_effect != ALIGN) {
        int dir = 1;
        float vx, vy;
        perpendicular (vx, vy, di.vx, di.vy);
        if (vel_effect == PERP2) dir = -1;
        di.vx = dir * vx;
        di.vy = dir * vy;
      }
    }
  }
  update_scale_vectors = 1;
}

void din::scale_selected_drones (float ds) {
  int n = selected_drones.size ();
  if (n == 0) {
    cons << RED << "Please select some drones to scale" << eol;
    return;
  }
  if (update_xform_params)
    calc_xform_params (n);
  else
  if (update_scale_vectors) {
    calc_xform_vectors (svec, n, update_scale_vectors);
    scl = 1.0f;
  }
  scl += ds;
  for (int i = 0; i < n; ++i) {
    drone& di = *selected_drones [i];
    point<float> sv = svec[i];
    sv.x *= scl;
    sv.y *= scl;
    di.set_center (cenx + sv.x, ceny + sv.y);
  }
  update_rotation_vectors = 1;
}

void din::change_drones_per_min (int d) {
  int n = selected_drones.size ();
  if (n) {
    cons << YELLOW;
    for (int i = 0; i < n; ++i) {
      drone* pds = selected_drones[i];
      drone& ds = *pds;
      ds.dpm += d;
      if (ds.dpm < 1) ds.dpm = 1;
      ds.launch_every.triggert = 60.0 / ds.dpm;
      cons << "Drone: " << i << ", drones per minute = " << ds.dpm << eol;
    }
  } else cons << RED << "Please select a drone!" << eol;
}

void din::select_attractees () { // select the attractees of the selected drones or all drones
  int num_selected = selected_drones.size ();
  if (num_selected == 0) {
    if (num_drones) {
      select_all_drones ();
      num_selected = selected_drones.size ();
    } else {
      cons << RED << "No drones, so no attractees" << eol;
      return;
    }
  }

  vector<drone*> new_selected_drones;
  for (int i = 0, j = selected_drones.size (); i < j; ++i) {
    drone& di = *selected_drones[i];
    if (di.attractor) {
      list<attractee>& lae = di.attractees;
      for (list<attractee>::iterator iter = lae.begin (), jter = lae.end(); iter != jter; ++iter) {
        attractee& ae = *iter;
        new_selected_drones.push_back (ae.d);
      }
    }
  }

  int ns = new_selected_drones.size ();
  if (ns) {
    clear_selected_drones ();
    for (int i = 0; i < ns; ++i) {
      drone* pd = new_selected_drones[i];
      add_drone_to_selection (pd);
    }
    print_selected_drones ();
  } else {
    cons << RED << "Sorry, no attractees found!" << eol;
  }

}

void din::select_attractors () { // select the attractors of selected drones
  int n = selected_drones.size ();
  if (n) {
    vector<drone*> selv (selected_drones);
    clear_selected_drones ();
    for (drone_iterator i = attractors.begin(), j = attractors.end(); i != j; ++i) {
      drone* pdi = *i;
      drone& di = *pdi;
      list<attractee>& lae = di.attractees;
      for (int p = 0, q = selv.size (); p < q; ++p) {
        drone* sd = selv[p];
        for (list<attractee>::iterator iter = lae.begin (), jter = lae.end(); iter != jter; ++iter) {
          attractee& ae = *iter;
          if (sd == ae.d) add_drone_to_selection (pdi);
        }
      }
    }
  } else {
    for (drone_iterator i = attractors.begin(), j = attractors.end(); i != j; ++i) {
      drone* pdi = *i;
      add_drone_to_selection (pdi);
    }
  }
  n = selected_drones.size ();
  cons << GREEN << "Selected " << n << " drones" << eol;
}

void din::trail_drones () {
  for (drone_iterator i = drones.begin(), j = drones.end(); i != j; ++i) {
    drone& di = *(*i);
    di.trail.add (di.x, di.y);
  }
}

void din::mute_drones () {
  din0.drone_master_volume = 0;
  din0.update_drone_tone ();
  uis.main_menu.set_drone_master_volume (); // on menu
}

void din::toggle_create_mesh () {
  if (create_mesh) create_drone_mesh (); else cons << GREEN << "Click and drag a box to preview drone mesh, ESC to cancel" << eol;
  create_mesh = !create_mesh;
  mkb_selector.set_mesh (create_mesh, dinfo.rows, dinfo.cols);
}

void din::stop_creating_mesh () {
  if (create_mesh) {
    create_mesh = 0;
    cons << RED << "Stopped creating mesh" << eol;
  }
}

void din::create_drone_mesh () {

  vector<drone*> mesh_drones;

  // create the drones of the mesh on the mkb_selector box
  for (int i = 0, p = 0; i < mkb_selector.rowcol; ++i) {
    int x = mkb_selector.meshp[p++];
    int y = mkb_selector.meshp[p++];
    mesh_drones.push_back (add_drone (x, y));
  }

  // assign drones to polygons of the mesh
  mesh a_mesh;
  a_mesh.r = get_rand_01 (); a_mesh.g = get_rand_01 (); a_mesh.b = get_rand_01 (); // random color
  for (int i = 0, j = mkb_selector.rows - 1, m = 0, r = 0; i < j; ++i, r += mkb_selector.cols) {
    m = r;
    for (int k = 0, l = mkb_selector.cols - 1; k < l; ++k, ++m) {
      int n = m + 1;
      drone *d0 = mesh_drones[m], *d1 = mesh_drones[n];
      drone *d2 = mesh_drones [n + mkb_selector.cols], *d3 = mesh_drones [m + mkb_selector.cols];
      a_mesh.add_poly (d0, d1, d2, d3); // each poly has 4 drones
    }
  }

  meshes.push_back (a_mesh);
  ++num_meshes;

  cons << GREEN << "Created a " << dinfo.rows << " X " << dinfo.cols << " drone mesh with " << mkb_selector.rowcol << " drones!" << eol;

}

void din::remove_drone_from_mesh (drone* pd) {
  if (num_meshes == 0) return;
  for (mesh_iterator i = meshes.begin (); i != meshes.end ();) {
    mesh& mi = *i;
    mi.remove_poly (pd);
    if (mi.num_polys == 0) {
      i = meshes.erase (i);
      --num_meshes;
    } else ++i;
  }
}

drone* din::get_drone (int id) { // get drone given its unique id
  for (drone_iterator i = drones.begin(), j = drones.end(); i != j; ++i) {
    drone* pd = *i;
    if (pd->id == id) return pd;
  }
  return 0;
}

void din::make_trackers () {
  int n = selected_drones.size ();
  if (n < 1) {
    cons << RED << "Please select at least 2 drones." << eol;
    return;
  } else if (n == 1) { // toggle tracker
    drone* pd = selected_drones[0];
    if (pd->tracking) {
      remove_tracker (pd);
      cons << GREEN << "Selected drone no longer tracks another drone" << eol;
      return;
    }
    cons << RED << "Drone is not tracking any other drone!" << eol;
    return;
  }
  int last = n - 1;
  drone* p_tracked_drone = selected_drones [last];
  int nl = 0;
  for (int i = 0; i < last; ++i) {
    drone* pdi = selected_drones [i];
    push_back (trackers, pdi);
    pdi->tracking = 1;
    pdi->tracked_drone = p_tracked_drone;
    ++nl;
  }
  if (nl) cons << GREEN << "Number of drones tracking another drone = " << nl << eol;
}

void din::track_drones () { // set velocity vector of drones to face tracked drones
  for (drone_iterator i = trackers.begin (), j = trackers.end (); i != j; ++i) {
    drone* pdi = *i;
    drone& di = *pdi;
    drone& td = *di.tracked_drone;
    float dx = td.x - di.x, dy = td.y - di.y; // vector joining launcher with tracked drone
    float ux, uy;
    double mag = unit_vector (ux, uy, dx, dy);
    if (mag > 0) {
      di.vx = ux;
      di.vy = uy;
    }
  }

  if (dinfo.gravity.tracked_drone) { // gravity is tracking some drone
    static int ldwx = -1, ldwy = -1;
    int dwx = dinfo.gravity.tracked_drone->x, dwy = dinfo.gravity.tracked_drone->y; // in window space
    if (dwx != ldwx || dwy != ldwy) {
      double mag = dinfo.gravity.mag;
      float xr = (dwx - win.left) * win.width_1;
      float yr = (dwy - win.bottom) * win.height_1;
      int dvx = (int) (xr * view.xmax);
      int dvy = (int) (yr * view.ymax); // in view space
      point<int>& base = dinfo.gravity.base;
      float ubtx, ubty; unit_vector (ubtx, ubty, base.x, base.y, dvx, dvy); // normalise
      dvx = base.x + mag * ubtx; dvy = base.y + mag * ubty; // use magnitude of gravity
      int calc_mag = 0; dinfo.gravity.set (dinfo.gravity.tip, dvx, dvy, calc_mag); // set new tip
      ldwx = dwx;
      ldwy = dwy;
    }
  }
}

void din::select_tracked_drones () {
  clear_selected_drones ();
  int ns = 0;
  for (drone_iterator i = trackers.begin (), j = trackers.end (); i != j; ++i) {
    drone* pdi = *i;
    drone& di = *pdi;
    if (push_back (selected_drones, di.tracked_drone)) ++ns;
  }
  if (ns) cons << GREEN << "Selected " << ns << " tracked drones" << eol;
}

void din::remove_tracker (drone* ptd) {
  for (drone_iterator i = trackers.begin (), j = trackers.end (); i != j;) {
    drone* pdi = *i;
    drone& di = *pdi;
    if (pdi == ptd || di.tracked_drone == ptd) {
      di.tracking = 0;
      di.tracked_drone = 0;
      i = trackers.erase (i);
      j = trackers.end ();
    } else ++i;
  }
}

void din::set_gravity_to_track_drone () {
  if (selected_drones.size () < 1) {
    if (dinfo.gravity.tracked_drone) {
      dinfo.gravity.tracked_drone = 0;
      cons << GREEN << "Gravity no longer tracks a drone" << eol;
      return;
    } else {
      cons << RED << "Please select a drone gravity can track!" << eol;
      return;
    }
  }
  dinfo.gravity.tracked_drone = selected_drones[0];
  cons << GREEN << "Gravity is tracking selected drone" << eol;
}

void din::tonic_changed () {
  update_range_notes ();
  update_drone_tone ();
  notate_ranges ();
}

void din::sync_drones () {
  for (int i = 0, j = selected_drones.size (); i < j; ++i) {
    drone& ds = *selected_drones[i];
    ds.player.x = ds.mod.am.b = ds.mod.fm.b = 0;
  }
}

void din::toggle_freeze_drones () {
  int n = selected_drones.size ();
  if (n == 0) {
    cons << RED << "Please select some drones to toggle freeze/thaw" << eol;
    return;
  }
  for (int i = 0; i < n; ++i) {
    drone& ds = *selected_drones[i];
    if (ds.frozen == 0) {
      ds.frozen = 1;
      ds.froze_at = ui_clk ();
      ds.update_pv = drone::EMPLACE;
    } else {
      ds.frozen = 0;
      ds.handle_time_pass ();
    }
  }
}


void din::freeze_drones () {
  int n = selected_drones.size ();
  if (n == 0) {
    cons << RED << "Please select some drones to freeze" << eol;
    return;
  }
  for (int i = 0; i < n; ++i) {
    drone& ds = *selected_drones[i];
    if (ds.frozen == 0) {
      ds.frozen = 1;
      ds.froze_at = ui_clk ();
      ds.update_pv = drone::EMPLACE;
    }
  }
}

void din::thaw_drones () {
  int n = selected_drones.size ();
  if (n == 0) {
    cons << RED << "Please select some drones to thaw" << eol;
    return;
  }
  for (int i = 0; i < n; ++i) {
    drone& ds = *selected_drones[i];
    if (ds.frozen) {
      ds.frozen = 0;
      ds.handle_time_pass ();
    }
  }
}

void din::lower_delta (float& d, float v, const string& mesg, float minn) {
  d += v;
  if (d < minn) d = minn;
  cons << YELLOW << mesg << d << eol;
}

void din::raise_delta (float& d, float v, const string& mesg) {
  d += v;
  cons << YELLOW << mesg << d << eol;
}

delta_t::delta_t (float _depth, float _bpm) {
  depth = _depth;
  bpm = _bpm;
  min_depth = 0.0f;
}

void din::region_begin () {
  rgn.left = rgn.right = win_mousex;
  rgn.bottom = rgn.top = win_mousey;
}

const box<int>& din::region_update () {
  rgn.right = win_mousex;
  rgn.top = win_mousey;
  return rgn;
}

void din::region_end () {
  rgn.calc ();
  if (create_mesh)
    toggle_create_mesh ();
  else
    find_selected_drones (rgn);
}