/*
* din.cc
* DIN Is Noise is copyright (c) 2006-2022 Jagannathan Sampath
* DIN Is Noise is released under GNU Public License 2.0
* For more information, please visit https://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 "scale_info.h"
#include "ui_list.h"
#include "vector2d.h"
#include "keyboard_keyboard.h"
#include "defvelaccel.h"
#include "log.h"
#include <sstream>
#include <algorithm>

#define ENDER -1
#define LAUNCHLATER 1

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; // microtonal keyboard extents
extern int HEIGHT; // default number of volumes on the microtonal keyboard
extern int WIDTH; // 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 int TRAILSIZE; // drone trail size (== number of trail points)
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 int line_height; // of text
extern keyboard_keyboard keybd2; // keyboard-keyboard
extern int IPS; // inputs per second
extern beat2value octave_shift; // octave shift over bpm

extern const float PI_BY_180;
extern const float PI;
extern mouse_slider mouse_slider0;
extern const char* s_drones;
extern float VOICE_VOLUME; 

typedef std::list<mesh>::iterator mesh_iterator;

// din::din () in eval.cc

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::scale_loaded () {
	int load_drones_too = 1, load_ranges_too = 1;
	load_scale (load_drones_too, load_ranges_too);
}

void din::scale_changed () {
	reset_all_ranges ();
}

void din::load_scale (int _load_drones_, int _load_ranges_) {
	setup_ranges (NUM_OCTAVES, _load_ranges_);
	if (_load_drones_) load_drones ();
	refresh_all_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, h;
  for (int i = 0; i < num_ranges; ++i) {
    range& R = ranges[i];
    file >> w >> h;
    r = l + w;
    R.extents (l, BOTTOM, r, BOTTOM + h);
    l = r;
		file >> R.mod.active >> 
						R.fixed >>
						R.mod.am.depth >> R.mod.am.bv.bpm >> R.mod.am.bv.now >> R.mod.am.bv.bps >> R.mod.am.initial >>
						R.mod.fm.depth >> R.mod.fm.bv.bpm >> R.mod.fm.bv.now >> R.mod.fm.bv.bps >> R.mod.fm.initial;
						R.notes[0].load (file) >> R.intervals[0];
						R.notes[1].load (file) >> R.intervals[1];
    R.fixed = range::CENTER;
  }

  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 << r.extents.width << spc << r.extents.height << spc << r.mod.active << spc 
						<< r.fixed << spc 
            << r.mod.am.depth << spc << r.mod.am.bv.bpm << spc << r.mod.am.bv.now << spc << r.mod.am.bv.bps << spc << r.mod.am.initial << spc
						<< r.mod.fm.depth << spc << r.mod.fm.bv.bpm << spc << r.mod.fm.bv.now << spc << r.mod.fm.bv.bps << spc << r.mod.fm.initial << spc;
						r.notes[0].save (file) << r.intervals[0] << spc;
						r.notes[1].save (file) << r.intervals[1] << spc;
    }
    dlog << "+++ saved ranges in " << fname << " +++" << endl;
  }

}

void din::create_ranges (int n) {
  ranges.resize (n);
  initranpar (n);
}

void din::setup_ranges (int last_num_octaves, int load) {

  if (load) {
		load_ranges ();
  } else { 
    int last_num_ranges = num_ranges;
		create_ranges (NUM_OCTAVES * scaleinfo.num_ranges);
		set_range_width (last_num_ranges, last_range, WIDTH);
		set_range_height (last_num_ranges, last_range, HEIGHT);
		init_range_mod (last_num_ranges, last_range);
		if (last_num_octaves < NUM_OCTAVES) calc_added_range_notes (last_num_octaves, last_num_ranges);
  }

  find_current_range ();

}

void din::reset_all_ranges () {
	create_ranges (NUM_OCTAVES * scaleinfo.num_ranges);
	set_range_width (0, last_range, WIDTH);
	set_range_height (0, last_range, HEIGHT);
	init_range_mod (0, last_range);
	calc_added_range_notes (0, 0);
	refresh_all_drones ();
}

void din::calc_added_range_notes (int p, int r) {
	int rn = r;
  for (; p < NUM_OCTAVES; ++p) {
    for (int i = 0, j = 1; i < scaleinfo.num_ranges; ++i, ++j) {
      range& R = ranges[r++];
			note& n0 = R.notes[0];
			note& n1 = R.notes[1];
			n0.scale_pos = i;
			n1.scale_pos = j;
			n0.octave = n1.octave = p;
			string& i0 = R.intervals[0];
			string& i1 = R.intervals[1];
      i0 = scaleinfo.notes[i];
      i1 = scaleinfo.notes[j];
			R.calc (scaleinfo);
			n0.set_name (i0, scaleinfo.western);
			n1.set_name (i1, scaleinfo.western);
    }
  }
	if (rn) {
		range	&R1 = ranges[rn-1], &R = ranges[rn];
		// last note of existing ranges should be = to first note of first new range
		if (!equals (R.notes[0].hz, R1.notes[1].hz)) { 
			R.notes[0]=R1.notes[1];
			R.intervals[0]=R1.intervals[1];
			R.delta_step = R.notes[1].step - R.notes[0].step;
		}
	}
}

void din::init_range_mod (int s, int t) {
	for (int i = s; i <= t; ++i) ranges[i].init_mod ();
}

void din::set_range_width (int ran, int sz) {
  range& R = ranges[ran];
  int delta = sz - R.extents.width;
  R.extents (R.extents.left, R.extents.bottom, R.extents.left + sz, R.extents.top);
	R.mod.fm.initial = R.extents.width;
	R.mod.fm.bv.now = 0;
  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);
  }
	refresh_drones (ran, last_range);
  find_visible_ranges ();
}

void din::set_range_width (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, R.extents.bottom, r, R.extents.top);
		R.mod.fm.initial = R.extents.width;
		R.mod.fm.bv.now = 0;
	}
	refresh_drones (s, t);
	find_visible_ranges ();
}

void din::set_range_height (int s, int t, int h) {
	for (int i = s; i <= t; ++i) {
		range& R = ranges[i];
		R.extents (R.extents.left, BOTTOM, R.extents.right, BOTTOM+h);
		R.mod.am.initial = h;
		R.mod.am.bv.now = 0;
	}
	refresh_drones (s, t);
}

void din::set_range_height (int r, int h) {
	range& R = ranges[r];
	R.extents (R.extents.left, BOTTOM, R.extents.right, BOTTOM + h);
	R.mod.am.initial = h;
	R.mod.am.bv.now = 0;
	refresh_drones (r);
}

void din::default_range_to_all (int h) {
	extern multi_curve ran_width_crv, ran_height_crv;
	if (h) {
		set_range_height (0, last_range, HEIGHT);
		ran_height_crv.load ("range-height.crv.default");

	}
  else {
		set_range_width (0, last_range, WIDTH);
		ran_width_crv.load ("range-width.crv.default");
	}
}

void din::selected_range_to_all (int h) {
	if (h)
		set_range_height (0, last_range, ranges[dinfo.sel_range].extents.height);
	else
		set_range_width (0, last_range, ranges[dinfo.sel_range].extents.width);
}

void din::default_range_to_selected (int h) {
	if (h)
		set_range_height (dinfo.sel_range, HEIGHT);
	else
  	set_range_width (dinfo.sel_range, WIDTH);
}

int din::range_left_changed (int r, int dx, int mr) {
	int valid = 0;
  range& R = ranges [r];
  int oldleft = R.extents.left;
  if (dx != 0) {
		int newleft = oldleft + dx;
		valid = newleft < R.extents.right ? 1 : 0;
		if (valid) {
      if (adjustranges.others) {
        R.extents (newleft, R.extents.bottom, R.extents.right, R.extents.top);
        for (int i = 0; i < r; ++i) {
          range& ir = ranges [i];
          ir.extents (ir.extents.left + dx, ir.extents.bottom, ir.extents.right + dx, ir.extents.top);
        }
      } else {
        int j = r - 1;
        if (j > -1) {
          range& rl = ranges[j];
          box<int>& rle = rl.extents;
          if (rle.left < newleft) {
            R.extents (newleft, R.extents.bottom, R.extents.right, R.extents.top);
            rle (rle.left, rle.bottom, R.extents.left, rle.top);
          }
        } else {
          R.extents (newleft, R.extents.bottom, R.extents.right, R.extents.top);
        }
      }
			if (mr) { // reset modulation
				R.mod.fm.initial = R.extents.width;
				R.mod.fm.bv.now = 0;
			}
      if (R.mod.active == 0) R.print_hz_per_pixel ();
		} 
		LEFT = ranges[0].extents.left;
  }
	return valid;
}

int din::range_right_changed (int r, int dx, int mr) {
	int valid = 0;
  range& R = ranges [r];
  int oldright = R.extents.right;
  if (dx != 0) {
		int newright = oldright + dx;
		valid = newright > R.extents.left ? 1 : 0;
		if (valid) {
      if (adjustranges.others) {
        R.extents (R.extents.left, R.extents.bottom, newright, R.extents.top);
        for (int i = r + 1; i < num_ranges; ++i) {
          range& ir = ranges [i];
          ir.extents (ir.extents.left + dx, ir.extents.bottom, ir.extents.right + dx, ir.extents.top);
        }
      } else {
        int j = r + 1;
        if (j < num_ranges) {
          range& rr = ranges[j];
          box<int>& rre = rr.extents;
          if (newright < rre.right) {
            R.extents (R.extents.left, R.extents.bottom, newright, R.extents.top);
            rre (newright, rre.bottom, rre.right, rre.top);
          }
        } else {
          R.extents (R.extents.left, R.extents.bottom, newright, R.extents.top);
        }
      }
			if (mr) { // reset modulation
				R.mod.fm.initial = R.extents.width;
				R.mod.fm.bv.now = 0;
			}
      if (R.mod.active == 0) R.print_hz_per_pixel ();
		} 
  }
	return valid;
}

void din::calc_all_range_notes () {
	int r = 0; 
	for (int i = 0; i < num_ranges; ++i) ranges[r++].calc (scaleinfo);
}

void din::tonic_changed () {
	all_notes.set_tonic (scaleinfo.tonic);
	calc_all_range_notes ();
	refresh_all_drones ();
  notate_all_ranges ();
}

void din::notate_all_ranges () {
  extern int NOTATION;
	int western = scaleinfo.western;

  switch (NOTATION) {

    case WESTERN:
      extern const char* WESTERN_FLAT [];
      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]);
      }
      break;

    case NUMERIC:
      for (int i = 0; i < num_ranges; ++i) {
        range& ri = ranges [i];
        ri.notes[0].set_name (ri.intervals[0]);
        ri.notes[1].set_name (ri.intervals[1]);
      }
      break;

    default:
      extern map<string, string> INT2IND;
      for (int i = 0; i < num_ranges; ++i) {
        range& ri = ranges [i];
        ri.notes[0].set_name (INT2IND[ri.intervals[0]]);
        ri.notes[1].set_name (INT2IND[ri.intervals[1]]);
      }
  }
}

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);
		MENU.screen_mousex = wx;
		MENU.screen_mousey = mousey;
	}
}

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

void din::tuning_changed () {
  scaleinfo.intervals = INTERVALS;
	calc_all_range_notes ();
	refresh_all_drones ();
}

void din::save () {
  save_ranges ();
  save_drones ();
  wave.save ("microtonal-keyboard-waveform.crv");
  scaleinfo.save_scale ();
  dinfo.save ();
}

din::~din () {
  if (dvap) delete[] dvap;
  if (dap) delete[] dap;
  if (dcol) delete[] dcol;
	if (con_pts) delete[] con_pts;
	if (con_clr) delete[] con_clr;
  for (drone_iterator i = drones.begin(), j = drones.end(); i != j; ++i) delete *i;
	for (int i = 0, j = drone_pendulums.size (); i < j; ++i) {
		group* grp = drone_pendulums[i];
		if (grp) delete grp;
	}
  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]->bpm);
  select_all_drones ();
  change_drone_bpm (modulator::FM, 0);
  change_drone_bpm (modulator::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; 
    print_num_drones ();
    file >> ignore >> drone::mastervolume;
    dlog << "<<< loading " << num_drones << " drones from: " << fdrone;

    int T = 0;
    for (int i = 0; i < num_drones; ++i) {
      drone* pdi = new drone;
      drone& di = *pdi;
			file >> ignore >> di.id;
			file >> ignore >> di.is;
      file >> ignore >> di.cx >> di.cy >> di.posafxvel.pt.x >> di.posafxvel.pt.y >> di.posafxvel.yes;
      file >> ignore >> di.player.x >> di.vol;
      file >> ignore >> di.r >> di.g >> di.b;
			//file >> ignore >> di.arrow.u >> di.arrow.v;
      file >> ignore >> di.arrow;
      file >> ignore >> di.mod.am.result >> di.mod.am.bv.now >> di.mod.am.bv.delta >> di.mod.am.depth >> di.mod.am.bv.bpm >> di.mod.am.bv.bps >> di.mod.fm.result >> di.mod.fm.bv.now >> di.mod.fm.bv.delta >> di.mod.fm.depth >> di.mod.fm.bv.bpm >> di.mod.fm.bv.bps >> di.mod.am.id >> di.autorot.v >> di.mod.fm.id >> di.autorot.a;
      di.mod.am.calcdir (di);
      di.mod.fm.calcdir (di);
      file >> ignore >> di.trail.total; if (T < di.trail.total) T = di.trail.total;
      file >> di.handle_size; 
			file >> ignore >> di.orbiting;
			file >> ignore >> di.V >> di.A >> di.vx >> di.vy >> di.v_mult >> di.ax >> di.ay;
      di.modv.val = di.V;

			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; file >> tt >> di.lpm >> di.dpl >> di.gen;
        di.launch_every.triggert = tt;
        di.launch_every.startt = ui_clk ();
        launchers.push_back (pdi);
      } else {
        extern void initlaunch (drone*);
        initlaunch (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) {
					uintptr_t pt; file >> pt;
					targets.push_back ((drone*) pt);
				}
			}

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

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

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

      file >> ignore >> di.reincarnate;
      file >> ignore >> di.birth;
      if (di.birth != -1) {
        float elapsed = di.birth;
        di.birth = ui_clk () - elapsed;
      }

			file >> ignore >> di.life;
			file >> ignore >> di.insert;
			file >> ignore >> di.snap;
			file >> ignore >> di.frozen;
			if (di.frozen) {
				di.frozen = 1;
				di.froze_at = ui_clk (); 
				di.set_pos (di.cx + di.mod.fm.result, di.cy + di.mod.am.result);
			} else {
				di.set_pos (di.cx, di.cy);
				di.froze_at = -1;
			}

      di.state = drone::RISING;
      di.fdr.set (0.0f, 1.0f, 1, MENU.riset());
      risers.push_back (pdi);
      ++rising;
      drones.push_back (pdi);

			float smp, spr;
			file >> ignore >> smp >> spr;
			di.nsr.set_samples (smp);
			di.nsr.set_spread (spr);

			file >> ignore;
			drone::proc_conn [pdi] = false;
			long long cd;
			double mag;
			while (file.eof () == 0) {
				file >> cd;
				if (cd == ENDER)
					break; 
				else {
					di.connections.push_back ((drone *)cd);
					file >> mag; di.mags.push_back (mag);
					++di.nconn;
					++totcon;
				}
			}

      {
        float start, end, amount;
        file >> ignore >> start >> end >> amount;
        di.gab.set (start, end);
        di.gab.amount = amount;
      }

      {
        file >> ignore >> di.chuck.yes;
        if (di.chuck.yes) file >> di.chuck;
      }

    } // load complete

    trail_t::alloc (T);

		_2totcon = 2 * totcon;
		alloc_conns ();

    // load the meshes
    //
    map<int, drone*> dmap;
    file >> ignore >> meshh.num;
    if (meshh.num) {
      for (int m = 0; m < meshh.num; ++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);
      }
    }
		file >> ignore >> meshh.draw;

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


		load_selected_drones (file);

		load_drone_pendulum_groups (file);

    // 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);
      di.tracked_drone = get_drone ((uintptr_t) di.tracked_drone);
    }

		// convert targets and connections
		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]);
			if (di.nconn) for (drone_iterator p = di.connections.begin (), q = di.connections.end (); p != q; ++p) *p = get_drone ((uintptr_t) *p);
      if (di.chuck.yes) {
        if (di.chuck.sun) di.chuck.sun = get_drone ((uintptr_t) di.chuck.sun);
        if (di.chuck.sat) di.chuck.sat = get_drone ((uintptr_t) di.chuck.sat);
      }
		}

		// 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);
  if (file) {
		file << "uid " << drone::UID << endl;
		file << "num_drones " << num_drones << endl;
    file << "master_volume " << drone::mastervolume << endl;
    for (drone_iterator i = drones.begin(), j = drones.end(); i != j; ++i) {
      drone& di = *(*i);
			file << "id " << di.id << endl;
			file << "is " << di.is << endl;
      file << "positon " << di.cx << spc << di.cy << spc << di.posafxvel.pt.x << spc << di.posafxvel.pt.y << spc << di.posafxvel.yes << endl;
      file << "wavepos " << di.player.x << spc << di.vol << endl;
      file << "color " << di.r << spc << di.g << spc << di.b << endl;
			file << "arrow " << di.arrow << endl;
      file << "modulation " << di.mod.am.result << spc << di.mod.am.bv.now << spc << di.mod.am.bv.delta << spc << di.mod.am.depth << spc << di.mod.am.bv.bpm << spc << di.mod.am.bv.bps << spc << di.mod.fm.result << spc << di.mod.fm.bv.now << spc << di.mod.fm.bv.delta << spc << di.mod.fm.depth << spc << di.mod.fm.bv.bpm << spc << di.mod.fm.bv.bps << spc << di.mod.am.id << spc << di.autorot.v << spc << di.mod.fm.id << spc << di.autorot.a << endl;
			file << "trail+handle " << di.trail.total << spc << di.handle_size << endl;
			file << "orbiting " << di.orbiting << endl;
			file << "vel+accel " << di.V << spc << di.A << spc << di.vx << spc << di.vy << spc << di.v_mult << spc << di.ax << spc << di.ay << endl;
			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 << endl;

      file << "launcher " << di.launcher;
      if (di.launcher) 
				file << spc << di.launch_every.triggert <<  spc << di.lpm << spc << di.dpl << spc << di.gen << endl;
			else 
				file << endl;

			file << "launcher_targets " << di.num_targets << endl;
			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 << endl;
			}

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

      file << "gravity " << di.gravity << endl;

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

      file << "reincarnate " << di.reincarnate << endl;
      if (di.birth != -1) 
        file << "birth " << (ui_clk() - di.birth) << endl;
      else 
        file << "birth -1" << endl;

			file << "life-time " << di.life << endl;
			file << "insert-time " << di.insert << endl;
			file << "snap " << di.snap << endl;
      file << "frozen " << di.frozen << endl;

			file << "noiser ";
			file << di.nsr << endl;

			file << "connections ";
			if (di.nconn) {
				list<double>::iterator mi = di.mags.begin ();
				for (drone_iterator p = di.connections.begin(), q = di.connections.end(); p != q; ++p, ++mi) {
					file << (*p)->id << spc << *mi << spc;
				}
			}
			file << ENDER << endl;

      file << "gab " << di.gab.start <<  spc << di.gab.end << spc << di.gab.amount << endl;

      file << "chuck " << di.chuck.yes << spc;
      if (di.chuck.yes) file << di.chuck << endl; else file << endl;

    }

    file << "num_meshes " << meshh.num << endl;
    if (meshh.num) {
      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 << endl;
        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 << endl;
        }
      }
    }
		file << "draw_meshes " << meshh.draw << endl;

    file << "drone_tracked_by_gravity ";
    if (dinfo.gravity.tracked_drone) {
      file << dinfo.gravity.tracked_drone->id << endl;
    } else file << '0' << endl;


		save_selected_drones (file);

		save_drone_pendulum_groups (file);
      
    dlog << "+++ saved " << num_drones << " drones in: " << drone_file << " +++" << endl;
  }

}

void din::save_drone_pendulum_groups (ofstream& file) {
	int ng = drone_pendulums.size ();
	file << "groups " << ng << spc;
	for (int i = 0; i < ng; ++i) {
		drone_pendulum_group& dpg = *drone_pendulums[i];
		file << dpg.n << spc << dpg.orient << spc << dpg.depth << spc;
		vector<drone*> dpgd = dpg.drones;
		for (int j = 0, k = dpg.n; j < k; ++j) {
			drone* dj = dpgd[j];
			file << dj->id << spc;
		}
	}
  file << endl;
}

void din::load_drone_pendulum_groups (ifstream& file) {
	string ignore;
	int ng; file >> ignore >> ng;
  if (ng) {
    drone_pendulums.resize (ng);
    for (int i = 0; i < ng; ++i) {
      drone_pendulum_group* pdpg = new drone_pendulum_group ();
      drone_pendulum_group& dpg = *pdpg;
      drone_pendulums[i] = pdpg;
      file >> dpg.n >> dpg.orient >> dpg.depth;
      vector<drone*>& dpgd = dpg.drones;
      dpgd.resize (dpg.n);
      int did;
      for (int j = 0, k = dpg.n; j < k; ++j) {
        file >> did;
        dpgd[j] = get_drone (did);
      }
    }
  }
}

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::refresh_all_drones () {
	for (drone_iterator i = drones.begin(), j = drones.end(); i != j; ++i) {
		drone& di = *(*i);
		di.set_pos (di.x, di.y);
	}
}

void din::refresh_drones (int r1, int r2) {
	for (drone_iterator i = drones.begin(), j = drones.end(); i != j; ++i) {
		drone& di = *(*i);
		if ((di.range >= r1) && (di.range <= r2)) di.set_pos (di.x, di.y);
	}
}

void din::refresh_drones (int r) {
	for (drone_iterator i = drones.begin(), j = drones.end(); i != j; ++i) {
		drone& di = *(*i);
		if (di.range == r) di.set_pos (di.x, di.y);
	}
}

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.range >= s) && (di.range <= t)) di.set_pos (di.x, di.y);
	}
}

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

void din::set_drone (drone& dd, float wx, float wy) {

		// create drone at position
		dd.cx = dd.posafxvel.pt.x = wx;
		dd.cy = dd.posafxvel.pt.y = wy;

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

		// prep to rise the drones
		dd.fdr.set (0.0f, 1.0f, 1, MENU.riset());
		dd.set_pos (dd.cx, dd.cy);
		dd.state = drone::RISING;
		risers.push_back (&dd);
		++rising;

		drone::proc_conn [&dd] = false;

    dd.setcolor ();

}

void din::movedrone (drone& dd) {
	float cx = dd.cx, cy = dd.cy;
	if (!SHIFT) cx += delta_mousex;
	if (!CTRL) cy -= delta_mousey;
	dd.set_center (cx, cy);
  dd.update_pv = drone::EMPLACE;
  if (dd.chuck.yes) RESETCHUCKTRAILS(dd)
	ec = &dd;
}

void din::color_selected_drones () {
	if (num_selected_drones) {
		int last = num_selected_drones - 1;
		float _1bylast = 1.0f / last;
		for (int i = 0; i < num_selected_drones; ++i) {
			drone& ds = *selected_drones[i];
			get_color::data.p = i * _1bylast;
      ds.setcolor ();
		}
	} else cons << RED_PSD << eol;
}

void din::mortalize_drones (int reincarnate) {
	if (num_selected_drones) {
		for (int i = 0; i < num_selected_drones; ++i) {
			drone& ds = *selected_drones[i];
      ds.birth = ui_clk ();
      ds.reincarnate = reincarnate;
    }
  } else cons << RED_PSD << eol;
}

void din::immortalize_drones () {
	if (num_selected_drones) {
		for (int i = 0; i < num_selected_drones; ++i) {
			drone& ds = *selected_drones[i];
      ds.birth = -1;
      ds.reincarnate = 0;
    }
  } else cons << RED_PSD << eol;
}

void din::delete_selected_drones () {
	if (num_selected_drones) {
		for (int i = 0; i < num_selected_drones; ++i) {
			drone& ds = *selected_drones[i];
      if (ds.reincarnate) ds.reincarnate = 0;
      if (ds.state == drone::FALLING) 
        ds.fdr.retime (MENU.fallt());
			else
        delete_drone (ds);
		}
		// clear_selected_drones ();
	} else cons << RED_PSD << eol;
}

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

void din::delete_drone (drone& ds) {
  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 (ds.fdr.alpha, 0.0f, 1, MENU.fallt());
  }
}

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

int din::select_launchers () {
	CLEAR_SELECTED_DRONES
	for (drone_iterator i = launchers.begin(), j = launchers.end(); i != j; ++i) {
		drone* pdi = *i;
		add_drone_to_selection (pdi);
	}
	print_selected_drones ();
	return 1;
}

void din::clear_selected_drones () {
  for (int i = 0; i < num_selected_drones; ++i) selected_drones[i]->sel = 0;
  selected_drones.clear ();
	num_selected_drones = 0;
}

void din::orbit_selected_drones () { // attach selected drones to attractor
	if (num_selected_drones > 1) {
		int last = num_selected_drones - 1;
    drone* p_att = selected_drones [last];  // last selected drone is attractor
    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];
			if (!pae->orbiting) {
				attractee ae (pae->id, pae);
				if (push_back (lae, ae)) {
					++att.attractor;
					pae->orbiting = 1;
				}
			} else {
				cons << RED << "Drone orbits already, ignored!" << eol;
			}
    }
	} else cons << RED_A2D << ". 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 () {
  if (num_selected_drones) {
    for (int i = 0; i < num_selected_drones; ++i) {
      drone* pdg = selected_drones[i];
      if (pdg->y < BOTTOM) pdg->gravity = -1; else pdg->gravity = 1;
      push_back (gravitated, pdg);
      pdg->birth = ui_clk ();
    }
  } else cons << RED_PSD << eol;
}

void din::move_drones_under_gravity () {

  float modv = 1.0f;
  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
      modv = di.modv ();
			di.set_pos (di.x + modv * di.vx, di.y + modv * 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
      di.bounces.max = MENU.sp_bounces ();
			if ((di.bounces.max != -1) && (di.target == 0) && ((di.gravity == 1 && di.y <= BOTTOM) || (di.gravity == -1 && di.y >= BOTTOM)) ) {
        if (++di.bounces.n > di.bounces.max) {
          delete_drone (di); 
				} else {
					float dx = di.x - di.xi;
					if (dx == 0.0f) { // slope is infinite
						di.set_pos (di.x, BOTTOM);
					} else { // slope is available
						float dy = di.y - di.yi;
						if (dy == 0.0f) 
							di.set_pos (di.x, BOTTOM);
						else {
							float m = dy * 1.0f / dx;
							di.set_pos (di.xi + (BOTTOM - di.yi) / m, BOTTOM);
						}
					}
					float reduction = MENU.sp_rebound() / 100.0;
          int style = dinfo.bounce.style;
          if (style == din_info::bouncet::RANDOM) style = get_rand_bit ();
					if (style == din_info::bouncet::BACK) di.vx *= -reduction;
					di.vy = reduction * -di.vy; 
        }
			}

			di.move_center ();

		}
	}
}

void din::set_targets () {

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

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

	d0->clear_targets ();

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

	for (int i = 1; i < num_selected_drones; ++i) { // make other drones in selection the targets
		drone* di = selected_drones[i];
		vector<drone*> targets = d0->targets;
		vector<drone*>::iterator te = targets.end (); 
    vector<drone*>::iterator f = find (targets.begin (), targets.end (), di);
		if (f == te) d0->targets.push_back (di);
	}
	d0->num_targets = d0->targets.size ();
	cons << GREEN << "First drone is launcher, it targets " << d0->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; i < num_selected_drones; ++i) {
		drone* pdi = selected_drones[i];
		if (pdi->num_targets) {
			pdi->clear_targets ();
			++n;
		}
	}
	if (n) cons << GREEN << "Cleared targets of " << n << s_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.frozen && (di.birth != -1) && (di.state != drone::FALLING)) {
			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, dt.x - di.x, 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_pos (newx, newy);
				di.move_center ();
				++i;
			}
		} else ++i;
	}
}

void din::toggle_launchers () {
  if (num_selected_drones == 0) {
    cons << RED_PSD << eol;
    return;
  }
	int nl = 0, nd = 0;
  for (int i = 0; i < num_selected_drones; ++i) {
    drone* pdi = selected_drones[i];
    drone& di = *pdi;
    di.launcher = !di.launcher;
    if (di.launcher) {
      di.launch_every.start (di.launch_every.triggert); // instant launch
      launchers.push_back (pdi); 
			++nl;
    } else {
			erase (launchers, pdi);
			++nd;
		}
  }
	cons << GREEN << "Launching from " << nl << " drones, Stopped Launching from " << nd << s_drones << eol;
}

void din::make_launcher (drone* pl, int l8r) {
  pl->launcher = 1;
  launchers.push_back (pl); 
  alarm_t& le = pl->launch_every;
  if (l8r) 
    le.start ();
  else
    le.start (le.triggert);
}

void din::make_launchers () {
  if (num_selected_drones == 0) {
    cons << RED_PSD << eol;
    return;
  }
  int nl = 0;
  for (int i = 0; i < num_selected_drones; ++i) {
    drone* pdi = selected_drones[i];
		if (pdi->launcher == 0) {
			make_launcher (pdi);
			++nl;
		}
  }
  cons << GREEN << "Made " << nl << " launchers" << eol; 
}

void din::destroy_launchers () {
	if (num_selected_drones == 0) {
		cons << RED_PSD << eol;
		return;
	}
  int nl = 0;
  for (int i = 0; i < num_selected_drones; ++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 << s_drones << eol; else cons << RED << "No drone launchers found!" << eol;
}


void din::seloncre (drone* d) {
  if (dinfo.seloncre) {
    d->sel = 1;
    selected_drones.push_back (d);
    print_selected_drones ();
  }
}

drone* din::add_drone (float wx, float wy, int fromlauncher) {

  drone* newdrone = new drone (wy);

  seloncre (newdrone);

  drones.push_back (newdrone);

  ++num_drones;
  print_num_drones ();

  set_drone (*newdrone, wx, wy);
  if (!fromlauncher && !drone::anchored) balloon (newdrone);

  return newdrone;

}

void din::balloon (drone* d) {
  d->reincarnate = 0;
  d->setlife (MENU.lifetime());
  if (d->y < BOTTOM) d->gravity = -1; else d->gravity = 1;
  gravitated.push_back (d);
  d->birth = ui_clk ();
}

void din::launch_drones () {

  list<drone*> l2;
  int n2 = 0;

  for (drone_iterator i = launchers.begin(), j = launchers.end(); i != j; ++i) { 

		drone* pdi = *i;
    drone& di = *pdi;

    if (!di.frozen && di.launch_every (ui_clk())) { // time to launch a drone 


      for (int p = 0, q = di.dpl; p < q; ++p) {

        #define CALLEDFROMLAUNCHER 1
        drone* newdronep = add_drone (di.x, di.y, CALLEDFROMLAUNCHER);
        drone& newdrone = *newdronep;

        newdrone.launched_by = pdi;

        if (newdrone.y < BOTTOM) 
          newdrone.gravity = -1; 
        else 
          newdrone.gravity = 1; // reverse gravity vector if launched below microtonal keyboard

        newdrone.is = di.is;

        newdrone.setcolor (); // random color

        // init velocity/speed
        if (drone::v0.mag.rndrd) newdrone.V = drone::v0.mag.rd () * di.V; else newdrone.V = di.V;
        newdrone.vx = di.vx;
        newdrone.vy = di.vy;
        if (drone::v0.rndrot) rotate_vector (newdrone.vx, newdrone.vy, drone::v0.rotrd()); 

        // init acceleration
        if (drone::a0.mag.rndrd) newdrone.A = drone::a0.mag.rd () * di.V; else newdrone.A = di.A;
        newdrone.ax = di.ax;
        newdrone.ay = di.ay;
        if (drone::a0.rndrot) rotate_vector (newdrone.ax, newdrone.ay, drone::a0.rotrd()); 

        newdrone.handle_size = di.handle_size;
        newdrone.trail.total = di.trail.total;

        newdrone.snap = di.snap;
        newdrone.gab = di.gab;

        newdrone.arrow = di.arrow;

        int num_targets = di.num_targets;
        if (num_targets) { // launch a satellite
          newdrone.insert = di.insert;
          int& cur_target = di.cur_target;
          newdrone.target = di.targets [cur_target];
          if (++cur_target >= num_targets) cur_target = 0;
          satellites.push_back (newdronep);
          newdrone.orbiting = 1;
        } else {
          gravitated.push_back (newdronep);
        }

        newdrone.reincarnate = 0;
        newdrone.setlife (getval<float, double> (MENU.lifetime, di.life));
        newdrone.birth = ui_clk();

        newdrone.gen.n = pdi->gen.n;
        if (++newdrone.gen.n < newdrone.gen.max) {
          newdrone.lpm = getval (MENU.dpm, di.lpm);
          newdrone.launch_every.triggert = ppm2t (newdrone.lpm);
          newdrone.dpl = getval (MENU.ddpl, di.dpl);
          l2.push_back (newdronep);
          ++n2;
        }
      }

    }
  }

  if (n2) for (drone_iterator i = l2.begin(), j = l2.end(); i != j; ++i) make_launcher (*i, LAUNCHLATER);

}


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.sx - 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 carry_satellites_to_orbit ()
			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;
				de.move_center ();
				de.set_pos (de.x, de.y);
			}
		}
	}
}

void din::add_drone_to_selection (drone* pd) {
	int& sel = pd->sel;
	if (CTRL) { 
		if (sel) {
			remove_drone_from_selection (pd);
			return;
		}
	} 
	if (sel) ; else {
		pd->sel = 1;
		selected_drones.push_back (pd);
	}
}

int din::get_selected_drone_id (drone* d) {
	for (int i = 0; i < num_selected_drones; ++i) {
		if (selected_drones[i] == d) return i;
	}
	return -1;
}

void din::remove_drone_from_selection (drone* pd) {
  pd->sel = 0;
	if (xforming) {
		int id = get_selected_drone_id (pd);
		if (id != -1) {
			if (xforming == SCALE) 
				erase_id (svec, id);
			else
				erase_id (rvec, id);
		}
	}
	if (erase (selected_drones, pd)) --num_selected_drones;
	if (erase (browsed_drones, pd)) {
		--num_browsed_drones;
		last_browseable_drone = num_browsed_drones - 1;
	}
}

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 make_arrow (float* A, int k, int cap, int& n, float x, float y, float ux, float uy, float vx, float vy, float u, float v, float t) {

  // make arrow
  //

  float ak2, ak3, ak2t, ak3t;

  // base to neck
  A[k]=x;
  A[k+1]=y;
  A[k+2] = ak2 = x + ux;
  A[k+3] = ak3 = y + uy;
  ak2t = x + t * ux;
  ak3t = y + t * uy;

  float arx = x + u * ux, ary = y + u * uy;
  float vvx = v * vx, vvy = v * vy;

  // flank1 to neck
  float ak4, ak5;
  ak4 = A[k+4] = arx + vvx; 
  ak5 = A[k+5] = ary + vvy;
  A[k+6] = ak2t; 
  A[k+7] = ak3t; 

  // flank2 to neck
  float ak8, ak9;
  ak8 = A[k+8]= arx - vvx;
  ak9 = A[k+9]= ary - vvy;
  A[k+10]= ak2t;
  A[k+11]= ak3t;

  if (cap) {
    A[k+12]=ak4;
    A[k+13]=ak5;
    A[k+14]=ak2;
    A[k+15]=ak3;
    A[k+16]=ak2;
    A[k+17]=ak3;
    A[k+18]=ak8;
    A[k+19]=ak9;
    n = 20;
  } else n = 12;

}

void din::draw_drones () {

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

	draw_connections ();

  drawchuck ();

  // draw drone mesh
  if (meshh.num && meshh.draw) 
    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.gab.amount, di.g * di.gab.amount, di.b * di.gab.amount, di.fdr.amount);
			di.trail.draw ();
		//}
  }

  // hilite browsed drone
  static int dhp [12] = {0};
  glVertexPointer (2, GL_INT, 0, dhp);
	if (num_selected_drones == 1 && num_browsed_drones) { 
		drone& ds = *selected_drones[0];
		glEnable (GL_LINE_STIPPLE);
		glLineStipple (1, 0xf0f0);
		glColor3f (ds.r, ds.g, ds.b);
		dhp[0]=ds.handle.midx;dhp[1]=win.top;
		dhp[2]=ds.handle.midx;dhp[3]=win.bottom;
		dhp[4]=win.left;dhp[5]=ds.handle.midy;
		dhp[6]=win.right;dhp[7]=ds.handle.midy;
		glDrawArrays (GL_LINES, 0, 4);
		glDisable (GL_LINE_STIPPLE);
	}

  // draw drone handles and pitch/volume info
	if (dinfo.show_pitch_volume.drones) tb_hz_vol.clear ();
  for (drone_iterator i = drones.begin(), j = drones.end(); i != j; ++i) {
    drone& di = *(*i);
    if (inbox<int> (win, di.x, di.y)) {
      glColor4f (di.r * di.gab.amount, di.g * di.gab.amount, di.b * di.gab.amount, di.fdr.amount);
			if (dinfo.show_pitch_volume.drones && di.sel) { 
				sprintf (BUFFER, " %0.3f @ %d%%", di.step * SAMPLE_RATE, int(di.vol*di.gab.amount*100.0+0.5)); // draw pitch/volume
				tb_hz_vol.add (text (BUFFER, di.handle.right, di.handle.bottom, di.r, di.g, di.b, text::temporary, 
														 text::normal, 
														 di.handle.right - win.left + fnt.spacing.word, 
														 di.handle.top - win.bottom - fnt.spacing.ch));
			}

      glRecti (di.handle.left, di.handle.bottom, di.handle.right, di.handle.top); // fill handle
      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); // draw handle outline with selection status

			if (di.attractor) { // mark + on 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) { // mark x on 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 float [4 * num_drones];
      n_dap = num_drones;
    }
    glVertexPointer (2, GL_FLOAT, 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) ; else {
      //if (di.range >= visl && di.range <= visr) {
        dap[ai++] = di.sx; dap[ai++] = di.y; dap[ai++] = di.sx; dap[ai++] = BOTTOM;
        ++ad;
      }
    }
    glColor3f (0.25, 0.25, 0.25);
    glDrawArrays (GL_LINES, 0, 2 * ad);
  }

  // draw velocity and acceleration vectors
  if (num_drones && (dinfo.vel || dinfo.accel)) {

	  static const int v_size = 4, a_size = 8 * v_size;

    int nn_dvap = 20 * num_drones;
    if (n_dvap < nn_dvap) {
      if (dvap) delete[] dvap;
      if (dcol) delete[] dcol;
      dvap = new float [nn_dvap];
      dcol = new float [2 * nn_dvap];
      n_dvap = nn_dvap;
    }
    int v = 0, nv = 0;
    if (dinfo.vel) {
      int ci = 0;
      for (drone_iterator i = drones.begin(), j = drones.end(); i != j; ++i) {
        drone& di = *(*i);
        if (di.range < visl || di.range > visr) ; else {
        // if (di.range >= visl && di.range <= visr) {
          float vl =  di.modv.val * v_size, vdx = vl * di.vx, vdy = vl * di.vy, pvdx = -vdy, pvdy = vdx;
          int dv = 12;
          make_arrow (dvap, v, di.arrow.cap, dv, di.sx, di.y, vdx, vdy, pvdx, pvdy, di.arrow.u, di.arrow.v, di.arrow.t);
          di.xv = di.sx + vdx; di.yv = di.y + vdy;
          v += dv;

          color dic (di.r * di.gab.amount, di.g * di.gab.amount, di.b * di.gab.amount);
          for (int s = 0, t = dv / 2; s < t; ++s) {
            dcol[ci++] = dic.r;
            dcol[ci++] = dic.g;
            dcol[ci++] = dic.b;
            dcol[ci++] = 1.0f;
          }
          ++nv;
        }
      }
      if (nv) {
        glEnableClientState (GL_COLOR_ARRAY);
        glColorPointer (4, GL_FLOAT, 0, dcol);
				glVertexPointer (2, GL_FLOAT, 0, dvap);
        glDrawArrays (GL_LINES, 0, v / 2);
        glDisableClientState (GL_COLOR_ARRAY);
      }
    }

    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) ; else {
        //if (di.range >= visl && di.range <= visr) {
          float al = di.A * a_size, adx = al * di.ax, ady = al * di.ay, padx = -ady, pady = adx;
          int da = 12;
          make_arrow (dvap, a, di.arrow.cap, da, di.sx, di.y, adx, ady, padx, pady, di.arrow.u, di.arrow.v, di.arrow.t);
          di.xa = di.sx + adx; di.ya = di.y + ady;
          a += da;
          ++na;
        }
      }
      if (na) {
        glColor4f (1, 0.25, 0.5, 1);
				glVertexPointer (2, GL_FLOAT, 0, dvap);
        glDrawArrays (GL_LINES, 0, a / 2);
      }
    }

  }

	if (dinfo.show_pitch_volume.drones) tb_hz_vol.draw ();

	glDisable (GL_BLEND);


}

void din::setdronemastervolume (float d) {
  drone::mastervolume = d;
	for (drone_iterator i = drones.begin(), j = drones.end(); i != j; ++i) {
		drone& di = *(*i);
		di.update_pv = drone::EMPLACE;
	}
	MENU.sp_drone_master_vol.set_value (d);
	sprintf (BUFFER, "Drone master volume = %0.3f", d);
	cons << YELLOW << BUFFER << eol;
}

void din::update_drone_solvers (multi_curve& crv) {
  static const char* dw = "drone-waveform";
  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, dw);
  }
}


void din::update_drone_modv_solvers () {
  for (drone_iterator i = gravitated.begin(), j = gravitated.end(); i != j; ++i) {
    drone& di = *(*i);
    di.modv.sol.update ();
  }
}

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 << spc;
  }
  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_pos (x, y); 
		pd->move_center ();
	}
}

void din::calc_win_mouse () {

  if (MENU.show == 0) {

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

		prev_mousex = mousex;
		prev_mousey = 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;

  }

}

int din::is_drone_hit (drone& di, const box<float>& rgn) {
  float x [] = {di.handle.midx, di.handle.left, di.handle.right};
  float 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<float> (rgn, x[i], y[j])) return 1;
  return 0;
}

void din::calc_selector_range (const box<float>& rgn, int& left, int& right) {
  float 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<float>& rgn) {
  // select drones that lie inside selected region
  // supports ctrl & shift keys
  int sell, selr; calc_selector_range (rgn, sell, selr);
	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)) 
			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 () {
  num_selected_drones = selected_drones.size ();
  if (num_selected_drones) {
		if (num_selected_drones != 1) {
			browsed_drones = selected_drones;
			num_browsed_drones = num_selected_drones;
			last_browseable_drone = num_browsed_drones - 1;
			browsed_drone = -1;
			MENU.sp_browse_drone.set_listener (MENUP.brwdl);
			MENU.sp_browse_drone.set_value (browsed_drone);
		} else {
			MENU.sp_browse_drone.set_listener (0);
			ec = selected_drones[0];
		}
    cons << GREEN;
    prep_modulate (MODULATE_DRONES);
		MENU.next_tab = MENUP.cb_mkb_drone_params;
		MENU.next_tab_instr = this;
		if (xforming) resize_xform_vectors ();
  } else {
    cons << RED;
		browsed_drones.clear ();
		num_browsed_drones = 0;
		browsed_drone = last_browseable_drone = -1;
		ec = 0;
  }
  cons << "Selected " << num_selected_drones << s_drones << eol;
}

int din::handle_input () {

	// if (butting) butt_drones ();

  static const double reptf = 1./7, repts = 1./64.;
	static const double first_repeat_time = 0.3, other_repeat_time = 0.05;
  static double start_time, repeat_time = first_repeat_time;
	static int lmb_clicked = 0, wheeled = 0;

  // mov
  gravlis* gravl = dinfo.gravity.pguil;
  if (keypressedd (SDLK_a, dinfo.scroll.rept, dinfo.scroll.rept, gravl)) scroll (-dinfo.scroll.dx, 0); else
  if (keypressedd (SDLK_d, dinfo.scroll.rept, dinfo.scroll.rept, gravl)) scroll (+dinfo.scroll.dx, 0); else
  if (keypressedd (SDLK_w, dinfo.scroll.rept, dinfo.scroll.rept, gravl)) scroll (0, +dinfo.scroll.dy); else
  if (keypressedd (SDLK_s, dinfo.scroll.rept, dinfo.scroll.rept, gravl)) scroll (0, -dinfo.scroll.dy);
  if (wheel && !MENU.show && !mouse_slider0.active) {
    if (SHIFT) scroll (0, wheel * dinfo.scroll.dy); else scroll (-wheel * dinfo.scroll.dx, 0);
    wheeled = 1;
  } else {
    if (wheeled) {
      if (gravl) gravl->released ();
      wheeled = 0;
    }
  }

  if (lmb) {

    if (lmb_clicked == 0) { 
      lmb_clicked = 1;
      if (adding) {
        add_drone (win_mousex, win_mousey); 
        start_time = ui_clk();
      }
    } else {
      if (adding) {
        // for spraying drones
        double delta_time = ui_clk() - start_time;
        if (delta_time >= repeat_time) { // click repeat
          lmb_clicked = 0;
          repeat_time = other_repeat_time;
        }
      }
    }

  } else {
    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)) {
    if (!mouse_slider0.active) {
      if (SHIFT) MENU.bsdl.clicked (MENU.b_scale_drones); else
      if (CTRL) MENU.brdl.clicked (MENU.b_rotate_drones); 
      else { // move drones
        if (moving_drones) set_moving_drones (0); // stop moving
        else if (mouse_slider0.deactivate()) ; // bcos scale or rotate
        else if (!MENU.show) start_moving_drones (); // start moving
      }
    }
  }

  else if (moving_drones) {
    if (num_selected_drones) {
      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];
          movedrone (di);
        }
      }     
      return 1;
    } else {
      cons << RED_PSD << eol;
    }
  }

  else if (keypressed (SDLK_f)) {
      if (SHIFT) {
        dinfo.sel_range = current_range;
        MENU.load_range (current_range);
        MENU.next_tab = MENUP.cb_mkb_ranges;
        MENU.next_tab_instr = this;
      } else if (CTRL) {
        MENU.cnol.picked (MENU.ol_change_note.option, 0);
        print_range_info (ranges[dinfo.sel_range]);
      } else {
        do_phrase_recording ();
      }
	}
	else if (keypressed (SDLK_v)) { 
      if (SHIFT) {
        MENU.rwl.clicked (MENU.b_adjust_range_both); // adjust range left and right
      } else if (CTRL) {
        MENU.cnb.clicked (MENU.b_change_note_both); // change both notes of range
      } else { // phrase play/pause
        if (phrasor0.state == phrasor::playing) {
          if (MENU.show == 0) {
            phrasor0.state = phrasor::paused;
            find_current_range ();
            cons << YELLOW << "phrasor has PAUSED." << eol;
          } else cons << RED << "Close menu!" << eol;
        } else {
          if (phrasor0.validate ()) {
            phrasor0.play ();
            if (phrasor0.state == phrasor::playing) cons << GREEN << "Phrasor is PLAYING" << eol;
          } else {
            pos_afx_vel (-1);
          }
        }
      }
	}
	else if (keypressed (SDLK_g)) { // phrase clear
      if (SHIFT) {
        MENU.rwl.clicked (MENU.b_adjust_range_left); // adjust range left
      } else if (CTRL) {
        MENU.cnl.clicked (MENU.b_change_note_left); // change range left note
      } else {
        clear_all_phrases ();
      }
	}
	else if (keypressed (SDLK_h)) {
		if (SHIFT) {
			MENU.rwl.clicked (MENU.b_adjust_range_right); // adjust range right
		} else
		if (CTRL) {
			MENU.cnr.clicked (MENU.b_change_note_right); // change range right note
		} else 
			toggle_launchers ();
	}
	else if (keypressedd (SDLK_n)) --MENU.sp_drones_per_min;
	else if (keypressedd (SDLK_m)) ++MENU.sp_drones_per_min;
	else if (keypressed (SDLK_b)) {
		if (SHIFT) {
			MENU.rhl.clicked (MENU.b_adjust_range_height);
		} else if (CTRL) {
			MENU.bhl.clicked (MENU.b_adjust_board_height);
		} else uis.cb_gater.toggle ();
	}

  // drones
  //
  else if (keypressedd (SDLK_q)) {
    if (!mouse_slider0.active) {
      if (SHIFT) {
        MENU.picked (MENU.ol_drone_is.option, 0);
      } else {
        if (dinfo.wand)  {
          if (wanding) 
            stopwanding (); 
          else 
            MENU.dcl.startwanding ();
        }
        else
          add_drone (win_mousex, win_mousey); 
      }
    }
	}
  else if (keypressedd (SDLK_c)) {
    if (SHIFT) 
      set_drones_under_gravity (); 
    else 
      delete_selected_drones ();
  }
	else if (keypressedd (SDLK_LEFTBRACKET, reptf, repts)) {
    if (SHIFT) --MENU.sp_rotate_drone_vel; else --MENU.sp_change_drone_vel;
  }
  else if (keypressedd (SDLK_RIGHTBRACKET, reptf, repts)) {
    if (SHIFT) ++MENU.sp_rotate_drone_vel; 
    else if (CTRL) 
      toggle_this (dinfo.vel, MENU.cb_show_vel);
    else
      ++MENU.sp_change_drone_vel;
  }
	else if (keypressed (SDLK_l)) {
		if (SHIFT) 
			select_launchers ();
		else 
			select_all_drones ();
	}
	else if (keypressed (SDLK_i)) {
		if (SHIFT) {
			dinfo.show_pitch_volume.board = !dinfo.show_pitch_volume.board; 
			dont_call_listener (uis.cb_show_pitch_volume_board, dinfo.show_pitch_volume.board);
		}
		else
			invert_selected_drones ();
	} 
	
	else if (keypressedd (SDLK_LEFT)) {
		if (SHIFT) browse_range (-1); else browse_drone (-1); 
	} else if (keypressedd (SDLK_RIGHT)) {
		if (SHIFT) browse_range (+1); else browse_drone (+1);
	}

	else if (keypressed (SDLK_j)) {
		if (SHIFT) {
			dinfo.show_pitch_volume.drones = !dinfo.show_pitch_volume.drones; 
			dont_call_listener (uis.cb_show_pitch_volume_drones, dinfo.show_pitch_volume.drones);
		} else 
			toggle_freeze_drones ();
	}
	else if (keypressed (SDLK_k)) {
		if (SHIFT) 
			snap_drones (1);
		else if (CTRL) 
			snap_drones (0);
		else snap_drones (-1); // toggle
	} 
  else if (keypressedd (SDLK_o, reptf, repts)) 
		--MENU.sp_change_drone_accel;
  else if (keypressedd (SDLK_p, reptf, repts)) {
    if (CTRL)
      toggle_this (dinfo.accel, MENU.cb_show_accel); 
    else 
			++MENU.sp_change_drone_accel;
  }
	/*else if (keypressed (SDLK_F3)) {
		butting = !butting;
	} 
	else if (keypressed (SDLK_F4)) {
		ring.x = win_mousex;
		ring.y = win_mousey;
	}*/

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

	else if (keypressedd (SDLK_COMMA, reptf, repts)) {
      if (SHIFT) 
        gab.set (this, 0.0f, "muting drones");
      else if (CTRL)
        noise2drone ();
      else
        setdronemastervolume (drone::mastervolume - float(MENU.sp_drone_master_vol.f_delta));
  }
	else if (keypressedd (SDLK_PERIOD, reptf, repts)) {
      if (SHIFT) 
        gab.set (this, 1.0f, "unmuting drones");
      else if (CTRL)
        drone2noise ();
      else
        setdronemastervolume (drone::mastervolume + float(MENU.sp_drone_master_vol.f_delta));
  }

  else if (keypressed (SDLK_F4)) switch_modulation ();
	else if (keypressed (SDLK_BACKSLASH)) set_key_to_pitch_at_cursor ();
	else if (keypressed (SDLK_SPACE)) {
    if (adjustranges.active) {
      adjustranges.others = !adjustranges.others;
      if (adjustranges.others) cons << GREEN << "Adjust other ranges too" << eol; else cons << YELLOW << "Adjusting this range only" << eol;
    } else 
      uis.cb_voice.toggle (); // toggle lead voice
  }
	else if (keypressed (SDLK_F1)) helptext();

  // bpms
  if (keypressedd (SDLK_F5)) { // decrease gater bpm upto limit
		if (SHIFT) 
			lower_delta (gater_delta.bpm, -1, "delta_gater_bpm = ");
		else if (CTRL) {
			gatr.min_bpm = gatr.bpm; 
			cons << YELLOW << "set minimum gater bpm to " << gatr.bpm << eol;
		}
		else 
    	change_bpm (gatr, -gater_delta.bpm); //-(float)MENU.sp_gater_bpm.f_delta);
  } else if (keypressedd (SDLK_F6)) { // increase gater bpm 
		if (SHIFT) 
			raise_delta (gater_delta.bpm, +1, "delta_gater_bpm = ");
		else if (CTRL) {
			gatr.min_bpm = 0;
			cons << YELLOW << "set minimum gater bpm to " << gatr.min_bpm << eol;
		}
		else
    	change_bpm (gatr, gater_delta.bpm); //MENU.sp_gater_bpm.f_delta);
  } else if (keypressedd (SDLK_F7)) { // decrease fm bpm
		if (SHIFT) 
			lower_delta (fm_delta.bpm, -1, "delta_fm_bpm = ");
		else
    	change__bpm (modulator::FM, fm, -fm_delta.bpm); //-(float)MENU.sp_fm_bpm.f_delta);
  } else if (keypressedd (SDLK_F8)) { // increase fm bpm
		if (SHIFT) 
			raise_delta (fm_delta.bpm, +1, "delta_fm_bpm = ");
		else
    	change__bpm (modulator::FM, fm, fm_delta.bpm); //MENU.sp_fm_bpm.f_delta);
  } else if (keypressedd (SDLK_F9)) { // decrease am bpm
		if (SHIFT)
			lower_delta (am_delta.bpm, -1, "delta_am_bpm = ");
		else
    	change__bpm (modulator::AM, am, -am_delta.bpm); //-(float)MENU.sp_am_bpm.f_delta);
  } else if (keypressedd (SDLK_F10)) { // increase am bpm
		if (SHIFT) 
			raise_delta (am_delta.bpm, 1, "delta_am_bpm = ");
		else
    	change__bpm (modulator::AM, am, am_delta.bpm); //MENU.sp_am_bpm.f_delta);
  } else if (keypressedd (SDLK_F11)) { // decrease octave shift bpm
		if (SHIFT)
			lower_delta (os_delta.bpm, -1, "delta_octave_shift_bpm = ");
		else
    	change_bpm (octave_shift, -os_delta.bpm); //-(float)MENU.sp_octave_shift_bpm.f_delta);
  } else if (keypressedd (SDLK_F12)) { // increase octave shift bpm
		if (SHIFT)
			raise_delta (os_delta.bpm, +1, "delta_octave_shift_bpm = ");
		else
    	change_bpm (octave_shift, os_delta.bpm); //MENU.sp_octave_shift_bpm.f_delta);
  }

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

  else if (keypressedd (SDLK_MINUS)) {
		--MENU.sp_change_drone_trail_length;
  } else if (keypressedd (SDLK_EQUALS)) {
		++MENU.sp_change_drone_trail_length;
  } else if (keypressedd (SDLK_9)) {
		if (!mouse_slider0.active) --MENU.sp_change_drone_handle_size;
  } else if (keypressedd (SDLK_0)) {
		if (!mouse_slider0.active) ++MENU.sp_change_drone_handle_size;
  }

	else if (keypressed (SDLK_INSERT)) {
		dinfo.dist.vol = !dinfo.dist.vol;
		MENU.cb_vol_dis.set_state (dinfo.dist.vol);
	} else if (keypressed (SDLK_DELETE)) {
		dinfo.dist.pitch = !dinfo.dist.pitch;
		MENU.cb_pitch_dis.set_state (dinfo.dist.pitch);
	}

	/*else if (keypressedd (SDLK_INSERT, reptf, repts)){
		if (SHIFT) ++inter_butt; 
		else if (CTRL) ring.r += 10;
		else ++butt;
	}
	else if (keypressedd (SDLK_DELETE, reptf, repts)) {
		if (SHIFT) --inter_butt; 
		else if (CTRL) ring.r -= 10;
		else --butt;
		if (inter_butt < 0) inter_butt = 0;
		if (butt < 0) butt = 0;
		if (ring.r < 0) ring.r = 0;
	}*/

  return 1;

}

#ifdef __SVG__
void din::write_trail () {
  dlog << "<svg>" << endl;
  for (int i = 0; i < num_selected_drones; ++i) {
    drone& ds = *selected_drones[i];
    ds.trail.write ();
  }
  dlog << "</svg>" << endl;
}
#endif

void din::change_drone_lifetime (spinner<float>& s) {
	if (num_selected_drones) {
		for (int i = 0; i < num_selected_drones; ++i) {
			drone& ds = *selected_drones[i];
      ds.setlife (ds.life + s());
			cons << GREEN << "Drone: " << i << ", lifetime = " << ds.life << " secs" << eol;
		}
	} else {
		cons << RED_PSD << eol;
	}
}

void din::change_orbit_insertion_time (spinner<float>& s) {
	if (num_selected_drones) {
		for (int i = 0; i < num_selected_drones; ++i) {
			drone& ds = *selected_drones[i];
			if (ds.launcher) {
				ds.insert += s ();
				if (ds.insert < 0) ds.insert = 0;
				cons << "Drone: " << i << ", orbit insertion time = " << ds.insert << " secs" << eol;
			} 
		}
	} else {
		cons << RED_PSD << eol;
	}
}

void din::change_drone_trail_points (spinner<int>& s) {
	if (num_selected_drones) {
		for (int i = 0; i < num_selected_drones; ++i) {
			drone& ds = *selected_drones[i];
			ds.trail.change (s());
			cons << GREEN << "Drone: " << i << ", trail points = " << ds.trail.total << eol;
		}
	} else cons << RED_PSD << eol;
}

void din::change_drone_handle_size (spinner<int>& s) {
	if (num_selected_drones) {
		for (int i = 0; i < num_selected_drones; ++i) {
			drone& ds = *selected_drones[i];
			ds.handle_size += s();
			if (ds.handle_size < 0) ds.handle_size = 0; 
      cons << GREEN << "Drone " << i << ", handle size = " << ds.handle_size << eol;
		}
		update_drone_anchors ();
	} else cons << RED_PSD << eol;
}


/*void din::change_drone_label_offset (int w, int sz) {
	rnd<float> rd (-1.0f, +1.0f);
	if (num_selected_drones) {
		for (int i = 0; i < num_selected_drones; ++i) {
			drone& ds = *selected_drones[i];
			float* xy [2] = {&ds.lbloff.x, &ds.lbloff.y};
			*xy[w] += (sz + rd());
		}
	} else cons << RED_PSD << eol;
}*/

void din::change_drone_arrow (spinner<float>& s, int w) {
  static const char* str [] = {"Shoulder position = ", "Shoulder width = ", "Neck = "}; 
	if (num_selected_drones) {
		for (int i = 0; i < num_selected_drones; ++i) {
			drone& ds = *selected_drones[i];
      drone::arrowt& ar = ds.arrow;
      float* wa [] = {&ar.u, &ar.v, &ar.t};
      float& ww = *wa[w];
      ww += s ();
      cons << GREEN << str[w] << ww << eol;
		}
	} else cons << RED_PSD << eol;
}


void din::capdronearrows (int c) {
	if (num_selected_drones) {
		for (int i = 0; i < num_selected_drones; ++i) {
			drone& ds = *selected_drones[i];
      ds.arrow.cap = c;
    }
  } else cons << RED_PSD << 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);
  }

  dinfo.gravity.ldwx = dinfo.gravity.ldwy = -1; // see din::evalgravity ()

}

void din::find_current_range () {
  // find the range where mouse is found
  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) { 
  // 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 (float x, int r) {
  while (1) {
    range& curr = ranges [r];
    float 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 () {

  // 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 = warp_pitch (deltax * curr->extents.width_1);
  step = curr->notes[0].step + delta * curr->delta_step; // step determines frequency see note.h

	// find VOLUME
	static const int if_uniq = 1;
	int dv = toney - BOTTOM;
	float iv = dv * 1.0f / ranges[current_range].extents.height;
	float fin_vol = 1.0f;
  if (dv < 0) { // below keyboard, silence voice
		fin_vol = 0.0f;
		wavplay.set_interpolated_pitch_volume (step, fin_vol, if_uniq);
		am_vol = 0;
		VOLUME = -warp_vol (-iv);
  } else {
		VOLUME = warp_vol (iv);
		float fdr_vol = uis.fdr_voice.amount * VOLUME;
		fin_vol = fdr_vol * VOICE_VOLUME;
		wavplay.set_interpolated_pitch_volume (step, fin_vol, if_uniq);
		am_vol = fdr_vol * am_depth;
  }

	if (dinfo.voice_is_voice == 0) {
		nsr.set_spread (fin_vol);
		nsr.set_samples (1.0f / step);
	}

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

	if (dinfo.show_pitch_volume.board) {
		sprintf (BUFFER, "%0.3f @ %03d%%", (step * SAMPLE_RATE), int(VOLUME * 100));
		pitch_volume_info = BUFFER;
	}
	
  return 1;

}

void din::draw () {

  glMatrixMode (GL_PROJECTION);
  glLoadIdentity ();
  glOrtho (win.left, win.right, win.bottom, win.top, -1, 1);

  glMatrixMode (GL_MODELVIEW);
  glLoadIdentity ();

  draw_drones (); 
  if (MENU.cb_show_gravity.state) dinfo.gravity.drawrrow ();

	if (UI_OFF == 0) {

	  if (dinfo.dist.vol) draw_vol_dist ();
	  if (dinfo.dist.pitch) draw_pitch_dist ();

		// mark selected range?
  	if (dinfo.mark_sel_range && !(dinfo.sel_range < visl || dinfo.sel_range > visr)) {
    	range& cr = ranges[dinfo.sel_range];
    	box<int>& cre = cr.extents;
    	glLineWidth (3);
			glEnable (GL_LINE_STIPPLE);
			glLineStipple (1, 0xf0f0);
    	glColor3f (0.5f, 0.75f, 1.0f);
    	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;
			glVertexPointer (2, GL_INT, 0, gl_pts);
    	glDrawArrays (GL_LINE_LOOP, 0, 4);
    	glLineWidth (1);
			glDisable (GL_LINE_STIPPLE);
  	}

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

  	// draw cursor info
  	int cursorx = tonex + 8, cursory = toney;
  	if (dinfo.show_pitch_volume.board && !basic_editor::hide_cursor) {
			glColor3f (0.9f, 0.9f, 1.0f);
    	draw_string (pitch_volume_info, cursorx, cursory);
      if (rising || falling) {
    	  cursory += line_height;
        draw_string (num_drones_info, cursorx, cursory);
      }
  	}

    // drones xform center
    dinfo.cen.draw ();

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

    // draw selector
		mkb_selector.draw (rgn);

	}


  
}

void din::enter () {
  if (phrasor0.state == phrasor::playing) 
		return; 
	else {
    ui::enter ();
		win_mousex = win.left + mousex;
		win_mousey = win.bottom + mouseyy;
  }
}

void din::window_resized (int w, int h) {
	clear_all_phrases ();
	win (win.left, win.bottom, win.left + w, win.bottom + h);
	warp_mouse (prev_mousex, prev_mousey);
	win_mousex = win.left + mousex;
	win_mousey = win.bottom + mouseyy;
	find_current_range ();
}

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

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

void din::change_bpm (beat2value& which, float amt) {
  float bpm = which.bpm + amt;
  bpm = which.set_bpm (bpm);
  cons << YELLOW << which.name << " bpm: " << bpm << eol;
  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::modulate_drones () {

  for (drone_iterator i = drones.begin(), j = drones.end(); i != j; ++i) {
    drone& di = *(*i);
		if (di.frozen == 0) {
			modulator& dm = di.mod;
			dm.calc ();
      di.autorot.calc (dm.dt);
      // AM along a direction vector, FM along a direction vector
      float x = di.cx + dm.fm.result * (*dm.fm.dirx) + dm.am.result * (*dm.am.dirx);
      float y = di.cy + dm.fm.result * (*dm.fm.diry) + dm.am.result * (*dm.am.diry);
      if (di.autorot.v.yes) rotate_vector (di.vx, di.vy, di.autorot.v.angle.theta);
      if (di.autorot.a.yes) rotate_vector (di.ax, di.ay, di.autorot.a.angle.theta); 
      di.set_pos (x, y);
      // di.set_pos (di.cx + dm.fm.result, di.cy + dm.am.result);
		}
	}
}

void din::setvelaccel (int w, int id, int neg) {
	if (num_selected_drones) {
    float xx[5], yy[5];
    int negs[2] = {1, -1};
    xx[0] = 0; xx[1] = 1;
    yy[0] = 1; yy[1] = 0;
    xx[4] = 0; yy[4] = 0;
		for (int i = 0; i < num_selected_drones; ++i) {
      drone& ds = *selected_drones[i];
      xx[2]=ds.vx;
      yy[2]=ds.vy;
      xx[3]=ds.ax;
      yy[3]=ds.ay;
      float* wx[2] = {&ds.vx, &ds.ax};
      float* wy[2] = {&ds.vy, &ds.ay};
      float* aft [2] = {&ds.autorot.v.autoflip.total, &ds.autorot.a.autoflip.total};
      alarm_t* art [2] = {&ds.autorot.v.tik, &ds.autorot.a.tik};
      int negg = negs[neg];
      float xxx = negg * xx[id];
      float yyy = negg * yy[id];
      if (w == 2) {
        ds.vx = ds.ax = xxx;
        ds.vy = ds.ay = yyy;
        ds.autorot.v.autoflip.total = ds.autorot.a.autoflip.total = 0.0f;
        ds.autorot.v.tik.start ();
        ds.autorot.a.tik.start ();
      } else {
        *wx[w] = xxx;
        *wy[w] = yyy;
        *aft[w] = 0.0f;
        art[w]->start ();
      }
    }
  } else cons << RED_PSD << eol;
}

void din::setmoddir (int w, int id) {
	if (num_selected_drones) {
    const char* dirs [] = {"Vertical", "Horizontal", "Velocity", "Acceleration"};
    const char* whats [] = {"AM", "FM"};
		for (int i = 0; i < num_selected_drones; ++i) {
      drone& ds = *selected_drones[i];
      mod_params* modp [] = {&ds.mod.am, &ds.mod.fm};
      mod_params* modpw = modp[w];
      modpw->id = id;
      modpw->calcdir (ds);
    }
    cons << "Set " << whats[w] << " direction of " << num_selected_drones << " drones to " << dirs[id] << eol; 
  } else cons << RED_PSD << eol;
}

void din::setautorot (int what, int state, int tog) {
  if (what == menu::autorott::BOTH) {
    for (int i = 0; i < num_selected_drones; ++i) {
      drone& ds = *selected_drones[i];
      int amst [] = {state, !ds.autorot.v.yes};
      int fmst [] = {state, !ds.autorot.a.yes};
      ds.autorot.v.yes = amst[tog];
      ds.autorot.a.yes = fmst[tog];
    }
  } else {
    for (int i = 0; i < num_selected_drones; ++i) {
      drone& ds = *selected_drones[i];
      int& yes = ds.autorot.arr[what]->yes;
      int states[] = {state, !yes};
      yes = states[tog];
    }
  }
  TOGGLEMENU
}

void din::setautoflip (int what, int state, int tog) {
  if (num_selected_drones) { 
    if (what == menu::autorott::BOTH) {
      for (int i = 0; i < num_selected_drones; ++i) {
        drone& ds = *selected_drones[i];
        int amst [] = {state, !ds.autorot.v.autoflip.yes};
        int fmst [] = {state, !ds.autorot.a.autoflip.yes};
        ds.autorot.v.autoflip.yes = amst[tog];
        ds.autorot.a.autoflip.yes = fmst[tog];
        ds.autorot.v.autoflip.total = 0.0f;
        ds.autorot.a.autoflip.total = 0.0f;

      }
    } else { 
      for (int i = 0; i < num_selected_drones; ++i) {
        drone& ds = *selected_drones[i];
        autoflipt& af = ds.autorot.arr[what]->autoflip;
        af.total = 0.0f;
        int& yes = af.yes;
        int states[] = {state, !yes};
        yes = states[tog];
      }
    }
    TOGGLEMENU
  } else
    cons << RED_PSD << eol;
}


void din::setautorotparam (int which, int what) {
  if (which == menu::autorott::BOTH) { 
    for (int i = 0; i < num_selected_drones; ++i) {
      drone& ds = *selected_drones[i];
      autorotator &var = ds.autorot.v, &aar = ds.autorot.a;
      var.yes = aar.yes = 1;
      if (what == menu::autorott::RPM) {
        var.mov = aar.mov = autorotator::SMOOTH;
        var.set_rpm (ds.autorot.v.rpm + MENU.autorotate.rpm());
        aar.set_rpm (ds.autorot.a.rpm + MENU.autorotate.rpm());
        cons << "Drone : " << i << " Velocity RPM = " << var.rpm << " Acceleration RPM = " << aar.rpm << eol;
      } else if (what == menu::autorott::DEG) {
        var.mov = aar.mov = autorotator::TICK;
        var.chgdeg (MENU.autorotate.deg());
        aar.chgdeg (MENU.autorotate.deg());
        cons << "Drone : " << i << " Velocity Degrees per Tick = " << var.deg << ", Acceleration Degrees per Tick = " << aar.deg << eol;
      } else if (what == menu::autorott::TPS) {
        var.mov = aar.mov = autorotator::TICK;
        var.chgtps (MENU.autorotate.tps());
        aar.chgtps (MENU.autorotate.tps());
        cons << "Drone : " << i << " Velocity Ticks per Second = " << var.tps << ", Acceleration Ticks per Second = " << aar.tps << eol;
      } else {
        var.mov = aar.mov = MENU.autorotate.mov.id;
        if (MENU.autorotate.mov.id == autorotator::SMOOTH) {
          var.set_rpm (var.rpm);
          aar.set_rpm (aar.rpm);
        } else {
          var.settps (var.tps);
          var.setdeg (var.deg);
          aar.settps (aar.tps);
          aar.setdeg (aar.deg);
          alarm_t& vart = var.tik;
          alarm_t& aart = aar.tik;
          vart.start (vart.triggert);
          aart.start (aart.triggert);
        }
      }
    }
  } else {
    const char* strs[2] = {" Velocity", " Acceleration"};
    for (int i = 0; i < num_selected_drones; ++i) {
      drone& ds = *selected_drones[i];
      autorotator& ar = *ds.autorot.arr[which];
      ar.yes = 1;
      if (what == menu::autorott::RPM) {
        ar.mov = autorotator::SMOOTH;
        ar.set_rpm (ar.rpm + MENU.autorotate.rpm());
        cons << "Drone : " << i << strs[which] << " RPM = " << ar.rpm << eol;
      } else if (what == menu::autorott::DEG) {
        ar.mov = autorotator::TICK;
        ar.chgdeg (MENU.autorotate.deg());
        cons << "Drone : " << i << strs[which] << " Degrees per Tick = " << ar.deg << spc << ", Ticks per Second = " << ar.tps << eol;
      } else if (what == menu::autorott::TPS) {
        ar.mov = autorotator::TICK;
        ar.chgtps (MENU.autorotate.tps());
        cons << "Drone : " << i << strs[which] << " Degrees per Tick = " << ar.deg << spc << ", Ticks per Second = " << ar.tps << eol;
      } else {
        ar.mov = MENU.autorotate.mov.id;
        if (MENU.autorotate.mov.id == autorotator::SMOOTH) {
          ar.set_rpm (ar.rpm);
        } else {
          ar.settps (ar.tps);
          ar.tik.start ();
          ar.setdeg (ar.deg);
        }
      }
    }
  }
}

void din::setautorotdir (int what, int dir) {
  if (num_selected_drones) { 
    if (what == menu::autorott::BOTH) { 
		  for (int i = 0; i < num_selected_drones; ++i) {
			  drone& ds = *selected_drones[i];
        ds.autorot.v.dir = dir;
        ds.autorot.a.dir = dir;
      }
    } else {
      for (int i = 0; i < num_selected_drones; ++i) {
        drone& ds = *selected_drones[i];
        ds.autorot.arr[what]->dir = dir;
      }
    }
    TOGGLEMENU
  } else
    cons << RED_PSD << eol;
}


void din::setautoflipangle (int what) {
  if (what == menu::autorott::BOTH) {
    for (int i = 0; i < num_selected_drones; ++i) {
      drone& ds = *selected_drones[i];
      autoflipt &amaf = ds.autorot.v.autoflip, &fmaf = ds.autorot.a.autoflip;
      amaf.total = fmaf.total = 0.0f;
      amaf.set (amaf.angle.deg + MENU.autorotate.autoflip.angle());
      fmaf.set (fmaf.angle.deg + MENU.autorotate.autoflip.angle());
      cons << "Drone : " << i << " Velocity Auto flip angle = " << amaf.angle.deg << " Acceleration Auto flip angle = " << fmaf.angle.deg << eol;
    }
  } else {
    const char* strs[2] = {" Velocity", " Acceleration"};
    for (int i = 0; i < num_selected_drones; ++i) {
      drone& ds = *selected_drones[i];
      autoflipt& af = ds.autorot.arr[what]->autoflip;
      af.total = 0.0f;
      af.set (af.angle.deg + MENU.autorotate.autoflip.angle());
      cons << "Drone : " << i << strs[what] << " Auto flip angle = " << af.angle.deg << eol;
    }
  }
}

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

	int ret = 0;

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

	find_tone_and_volume ();
	float *lout = out0, *rout = out1;

	if (dinfo.voice_is_voice) {
		wavplay.gen_wav_fm_am_mix (lout, aout.samples_per_channel);
		ret += wavplay.mixer.active;
	} else { // voice is noise 
		nsr (lout, rout, aout.samples_per_channel, 1.0f);
		ret = 1;
	}

  // gater on voice
  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

  // render drones
  for (drone_iterator i = drones.begin(), j = drones.end(); i != j; ++i) {
    drone& di = *(*i);
    float* lout = out0, *rout = out1;
    if (di.update_pv) di.update_pitch_volume ();
		if (di.is == drone::DRONE) {
			play& dp = di.player;
			dp.master (lout, rout, aout.result, aout.samples_per_channel, dp.pvol);
			ret += dp.mixer.active;
		} else {
			di.nsr (lout, rout, aout.samples_per_channel, drone::mastervolume * di.fdr.amount * di.gab.amount);
		}
  }

	return ret;

}

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

void din::fall_drones () {
  if (falling) {
    for (drone_iterator i = fallers.begin(), j = fallers.end (); i != j;) {
      drone* pdi = *i;
      drone& di = *pdi;
      if (di.frozen == 0) {
        di.fdr.eval ();
        if (di.fdr.reached) {

          i = fallers.erase (i);
          j = fallers.end ();
          --falling;

          if (pdi->reincarnate) {
            di.setlife (MENU.lifetime());
            di.state = drone::RISING;
            risers.push_back (pdi);
            ++rising;
            di.fdr.set (0.0f, 1.0f, 1, MENU.riset());
            di.birth = ui_clk ();
          } else {
            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);
              list<attractee>& lae = di.attractees;
              for (list<attractee>::iterator iter = lae.begin (), jter = lae.end(); iter != jter; ++iter) {
                attractee& ae = *iter;
                drone& de = *ae.d;
                de.orbiting = 0;
              }
            }
            if (di.gravity) {
              erase (gravitated, pdi);
              erase (satellites, pdi);
            }
            remove_drone_from_targets (pdi);
            remove_drone_from_selection (pdi);
            remove_drone_from_pre_mesh (pdi);
            remove_drone_from_mesh (pdi);

            remove_connections (pdi);

            remove_from_groups (pdi);

            if (ec == pdi) ec = 0;

            gab.erase (pdi);

            --num_drones;
            print_num_drones ();

            if (num_drones == 0) prep_modulate (MODULATE_VOICE);

            if (pdi->chuck.yes) pdi->chuck.de ();

            erase (drones, pdi);
            delete pdi;

          }

        } else {
          ++i; 
          di.update_pv = drone::INTERPOLATE;
        }
      } 
      else 
        ++i;
    }
  }
}


void din::height_changed (int r, int dh) {
	if (r == -1) {
  	for (int i = 0; i < num_ranges; ++i) ranges[i].change_height (dh);
		refresh_all_drones ();
	} else {
		ranges[r].change_height (dh);
		refresh_drones (r);
	}
}

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];
	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 (modulator::AM, d, 0, d);
}

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

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

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

void din::change_drone_depth (int what, float delta) {
	if (num_selected_drones) {
		for (int i = 0; i < num_selected_drones; ++i) {
			drone& ds = *selected_drones[i];
			ds.change_depth (i, what, delta);
		}
	} else cons << RED_PSD << eol;
}

void din::change_drone_bpm (int what, float delta) {
	if (num_selected_drones) {
		for (int i = 0; i < num_selected_drones; ++i) {
			drone& ds = *selected_drones[i];
			ds.change_bpm (i, what, delta);
		}
	} else cons << RED_PSD << eol;
}

void din::change_drone_depth (int what, spinner<float>& s) {
	if (num_selected_drones) {
		for (int i = 0; i < num_selected_drones; ++i) {
			drone& ds = *selected_drones[i];
			float dv = s ();
			ds.change_depth (i, what, dv);
		}
	} else cons << RED_PSD << eol;
}

void din::change_drone_bpm (int what, spinner<float>& s) {
	if (num_selected_drones) {
		for (int i = 0; i < num_selected_drones; ++i) {
			drone& ds = *selected_drones[i];
			float dv = s ();
			ds.change_bpm (i, what, dv);
		}
	} else cons << RED_PSD << eol;
}

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

void din::start_moving_drones () {
  if (num_selected_drones) set_moving_drones (1); else cons << RED_PSD << 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 << YELLOW << "@ " << name << eol;
}

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 () {
	if (phrasor0.size != 0) {
		phrasor0.clear ();
		if (MENU.show == 0) find_current_range ();
		MENU.s_phrase_position.set_val (0);
    wanding = 0;
		cons << RED << "Phrase cleared!" << eol;
	}
}


void din::set_key_to_pitch_at_cursor () {
	float hz = step * SAMPLE_RATE;
	set_tonic (this, hz);
}

void din::change_drone_accel (spinner<float>& s) {
	if (num_selected_drones) {
		cons << YELLOW;
    int gt0 = MENU.accelgt0.state;
		for (int i = 0; i < num_selected_drones; ++i) {
			drone& di = *selected_drones[i];
			di.A += s ();
      if (gt0) {if (di.A < 0.0f) di.A = 0.0f;}
			cons << "Drone: " << i << ", Acceleration = " << di.A << eol;
		}
	} else cons << RED_PSD << eol;
}

void din::change_drone_vel (spinner<float>& s) {
	if (num_selected_drones) {
		cons << YELLOW;
    int gt0 = MENU.velgt0.state;
		for (int i = 0; i < num_selected_drones; ++i) {
			drone& di = *selected_drones[i];
      di.V += s ();
      di.modv.val = di.V;
      if (gt0) {if (di.V < 0.0f) di.V = 0.0f;}
			cons << "Drone: " << i << ", Velocity = " << di.V << eol;
		}
	} else cons << RED_PSD << eol;
}

void din::rotate_drone_vel (spinner<float>& s) {
	if (num_selected_drones) {
		cons << YELLOW;
		for (int i = 0; i < num_selected_drones; ++i) {
			drone& di = *selected_drones[i];
			float deg = -s(), rad = deg * PI_BY_180;
			rotate_vector (di.vx, di.vy, rad);
			cons << "Drone: " << i << ", Rotated Velocity by " << deg << " degrees." << eol;
		}
	} else cons << RED_PSD << eol;
}

void din::calc_xform_vectors (vector<point <float> >& V, int n) {
	V.resize (n);
	for (int i = 0; i < n; ++i) {
		drone& di = *selected_drones [i];
		point<float>& vv = V[i];
    if (di.chuck.yes && di.chuck.sun) {
		  direction<float> (vv.x, vv.y, di.chuck.sun->cx, di.chuck.sun->cy, di.cx, di.cy);
    } else
		  direction<float> (vv.x, vv.y, dinfo.cen.x, dinfo.cen.y, di.cx, di.cy);
	}
}

void din::calc_xform_vectors () {
  if (xforming == SCALE) calc_xform_vectors (svec, num_selected_drones); else
  if (xforming == ROTATE) calc_xform_vectors (rvec, num_selected_drones);
}

void din::resize_xform_vectors () {
	if (xforming == SCALE) svec.resize (num_selected_drones); else rvec.resize (num_selected_drones);
}

void din::calc_drones_centroid () {
  startagain:
  if (num_selected_drones) {
    dinfo.cen.x = dinfo.cen.y = 0.0f;
    for (int i = 0; i < num_selected_drones; ++i) {
      drone& di = *selected_drones [i];
      dinfo.cen.x += di.cx; dinfo.cen.y += di.cy;
    }
    dinfo.cen.x /= num_selected_drones;
    dinfo.cen.y /= num_selected_drones;
  } else {
    select_all_drones ();
    if (num_selected_drones) goto startagain; else cons << RED_PSD << eol;
  }
}

int din::prep_scale_drones () {
	int n = num_selected_drones;
	if (n) {
		calc_xform_vectors (svec, n);
		scl = 1.0f;
		xforming = SCALE;
		return n;
	}
	return 0;
}

int din::prep_rotate_drones () {
	int n = num_selected_drones;
	if (n) {
		calc_xform_vectors (rvec, n);
		angle = 0.0f;
		xforming = ROTATE;
		return n;
	}
	return 0;
}

void din::rotate_drones () {
	float dx, dy, cx, cy;
	for (int i = 0; i < num_selected_drones; ++i) {
		drone& di = *selected_drones[i];
		point<float> rv = rvec[i];
    rotate_vector (rv.x, rv.y, angle);
    if (!di.chuck.yes) {
      cx = dinfo.cen.x;
      cy = dinfo.cen.y;
      dx = cx + rv.x;
      dy = cy + rv.y;
      di.set_center (dx, dy);
    } else cons << RED << "Wont rotate chucked drone!" << eol;
    /*if (di.chuck.yes && di.chuck.sun) {
      cx = di.chuck.sun->cx;
      cy = di.chuck.sun->cy;
      di.chuck.len = unit_vector (di.chuck.ux, di.chuck.uy, rv.x, rv.y);
      dx = cx + rv.x;
      dy = cy + rv.y;
      di.set_center (dx, dy);
      if (di.chuck.sat) di.chuck.sat->chuck.re (*di.chuck.sat);
    } else {
      cx = dinfo.cen.x;
      cy = dinfo.cen.y;
      dx = cx + rv.x;
      dy = cy + rv.y;
      di.set_center (dx, dy);
    }*/
	}
}

void din::scale_drones () {
  list<drone*> rm;
  int nrm = 0;
	for (int i = 0; i < num_selected_drones; ++i) {
    drone* pdi = selected_drones[i];
		drone& di = *pdi;
    if (!di.chuck.yes) {
      point<float> sv = svec[i];
      sv *= scl;
      di.set_center (dinfo.cen.x + sv.x, dinfo.cen.y + sv.y, 0);
      if (di.nconn) {
        rm.push_back (pdi);
        for (drone_iterator p = di.connections.begin (), q = di.connections.end (); p != q; ++p) rm.push_back (*p);
        nrm = nrm + di.nconn + 1;
      }
    } else cons << RED << "Wont scale chucked drone!" << eol;
	}
  if (nrm) for (drone_iterator p = rm.begin (), q = rm.end (); p != q; ++p) (*p)->remagconns ();
}

void din::scale_drones (float ds) {
	if (CTRL) scl.x += ds;
	else if (SHIFT) scl.y += ds;
	else scl += ds;
}

void din::change_drones_per_min (spinner<float>& s) {
	if (num_selected_drones) {
		cons << YELLOW;
		float dpm;
		for (int i = 0; i < num_selected_drones; ++i) {
			drone* pds = selected_drones[i];
			drone& ds = *pds;
			dpm = ds.lpm + s ();
			if (dpm > 0.0f) {
				ds.lpm = dpm;
				ds.launch_every.triggert = 60.0 / ds.lpm;
			} else {
        ds.lpm = 0.0f;
        ds.launch_every.triggert = MILLION;
      }
			cons << "Drone: " << i << ", launches per minute = " << ds.lpm << eol;
		}
	} else cons << RED_PSD << eol;
}


void din::change_drones_per_launch (spinner<int>& s) {
	if (num_selected_drones) {
		cons << YELLOW;
    int dpl;
    for (int i = 0; i < num_selected_drones; ++i) {
      drone* pds = selected_drones[i];
      drone& ds = *pds;
      dpl = ds.dpl + s();
      if (dpl < 0) dpl = 0;
      ds.dpl = dpl;
      cons << "Drone: " << i << ", drones per launch = " << ds.dpl << eol;
    }
  } else cons << RED_PSD << eol;
}

void din::select_attractees () { // select the attractees of the selected drones or all drones

	if (num_selected_drones == 0) {
    if (num_drones) {
      select_all_drones ();
    } else {
      cons << RED << "No drones, so no attractees" << eol;
      return;
    }
	}

	vector<drone*> new_selected_drones;
	for (int i = 0; i < num_selected_drones; ++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
	if (num_selected_drones) {
		vector<drone*> selv (selected_drones);
		int q = num_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 (list<attractee>::iterator iter = lae.begin (), jter = lae.end(); iter != jter; ++iter) { 
				attractee& ae = *iter;
				for (int p = 0; p < q; ++p) {
					drone* sd = selv [p];
					if (sd == ae.d) {
						add_drone_to_selection (pdi);
						goto next_attractor;
					}
				}
			}
			next_attractor:
				;
		}
	} else {
		for (drone_iterator i = attractors.begin(), j = attractors.end(); i != j; ++i) {
			drone* pdi = *i;
			pdi->sel = 1;
			selected_drones.push_back (pdi);
		}
	}
	print_selected_drones ();
}

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

void din::toggle_create_this () {
	if (dinfo.create_this) {
		toggle_create_drone_pendulum ();
	} else {
		toggle_create_mesh ();
	}
}

void din::toggle_create_drone_pendulum () {
	if (create_drone_pend) ; 		
	else {
		cons << GREEN << "Click and drag a box to make a drone pendulum, ESC to cancel" << eol;
		create_drone_pend = 1;
	}
}

void din::toggle_create_mesh () {

	if (amd.active) {
		cons << RED << "Already making a mesh, please wait for it to finish :)" << eol;
		return;
	}

  if (meshh.create) {
		create_drone_mesh (); 
		meshh.create = 0;
	} else {
		cons << GREEN << "Click and drag a box to preview drone mesh, ESC to cancel" << eol;
  	meshh.create = 1;
	}

}

void din::stop_creating_mesh () {
	meshh.create = 0;
	mkb_selector.mesh = 0;
	cons << RED << "Stopped creating mesh" << eol;
}

void din::stop_creating_drone_pendulum () {
	create_drone_pend = 0;
	cons << RED << "Stopped making drone pendulum" << eol;
}

void din::bg () {

	drone::proc_conn.clear ();
	if (ec && !xforming) ec = ec->eval_conns ();

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

  if (wanding) {
    if ((wand.x != tonex) || (wand.y != toney)) {
      if (magnitude2 (wand.x, wand.y, tonex, toney) >= drone::wand.dist2) {
        wand.x = tonex;
        wand.y = toney;
        if (SHIFT || CTRL) 
          ; 
        else 
          add_drone (wand.x, wand.y);
      }
    }
  }

	if (amd.active && !amd.drop && amd (ui_clk())) {
		create_drone:
		if (amd.i < mkb_selector.rowcol) {
				amd.p = mkb_selector.order [amd.i];
				int p = 2 * amd.p;
				int x = mkb_selector.meshp [p];
				int y = mkb_selector.meshp [p + 1];
				drone* d = add_drone (x, y);
				mkb_selector.meshd[amd.p] = d;
				if (dinfo.mesh_vars.apply_to.active) {
					float a = ((amd.i % dinfo.mesh_vars.dpp) + 1) * 1. / dinfo.mesh_vars.dpp;
					float bpm = a * dinfo.drone_pend.bpm;
					d->mod.active = 1;
					if (dinfo.mesh_vars.apply_to.am) d->mod.am.bv.set_bpm (bpm);
					if (dinfo.mesh_vars.apply_to.fm) d->mod.fm.bv.set_bpm (bpm);
				}
        if (!dinfo.seloncre) {
          d->sel = 1;
          selected_drones.push_back (d);
        }
				amd.i++;
				if (amd.triggert == 0.0) goto create_drone;
		} else {
			// assign drones to polygons of the mesh
			mesh a_mesh;
      a_mesh.r = rndr (); 
      a_mesh.g = rndg (); 
      a_mesh.b = rndb ();
			for (int i = 0, j = mkb_selector.rows - 1; i < j; ++i) {
				int ic = i * mkb_selector.cols;
				for (int k = 0, l = mkb_selector.cols - 1; k < l; ++k) {
					int d0i = ic + k, d1i = d0i + 1;
					int d2i = d0i + mkb_selector.cols, d3i = d2i + 1;
					drone* d0 = mkb_selector.get_mesh_drone(d0i), *d1 = mkb_selector.get_mesh_drone(d1i);
					drone* d2 = mkb_selector.get_mesh_drone(d2i), *d3 = mkb_selector.get_mesh_drone(d3i);
					a_mesh.add_poly (d0, d1, d3, d2); // each poly has 4 drones
				}
			}

			meshes.push_back (a_mesh);
			++meshh.num;
      if (!dinfo.seloncre) print_selected_drones ();
			cons << GREEN << "Created a " << dinfo.rows << " x " << dinfo.cols << " drone mesh with " << mkb_selector.rowcol << " drones" << eol;
			amd.reset ();
			mkb_selector.clear ();
		}
	} else {
		if (amd.drop) {
			cons << RED << "Aborted drone mesh" << eol;
			amd.reset ();
		}
	}

  fall_drones ();
	rise_drones ();
	if (quit == DONT) launch_drones ();
	carry_satellites_to_orbit ();
	attract_drones ();
  track_drones (); 
  evalchuck ();
  modulate_drones ();
  evalgravity ();
  move_drones_under_gravity ();
	trail_drones ();
	kill_old_drones ();
	modulate_ranges ();
  gab.eval ();

}

void din::drawchuck () {
  if (MENU.choutline.state) {
    for (drone_iterator i = drones.begin(), j = drones.end(); i != j; ++i) {
      drone& di = *(*i);
      drone* sat = di.chuck.sat;
      if (di.chuck.yes && sat) {
        glBegin (GL_LINES);
          glColor3f (di.r, di.g, di.b);
          glVertex2f (di.sx, di.y);
          glColor3f (sat->r, sat->g, sat->b);
          glVertex2f (sat->sx, sat->y);
        glEnd ();
      }
    }
  }
}

void din::evalchuck () {

  for (drone_iterator i = drones.begin(), j = drones.end(); i != j; ++i) {
    drone& di = *(*i);
    if (di.chuck.yes) {
      di.chuck.cx = di.cx;
      di.chuck.cy = di.cy;
    }
  }

  for (drone_iterator i = drones.begin(), j = drones.end(); i != j; ++i) {
    drone& di = *(*i);
    if (di.chuck.yes && di.chuck.sun) {
      if (di.frozen) {
        di.chuck.len = unit_vector (di.chuck.ux, di.chuck.uy, di.chuck.sun->chuck.cx, di.chuck.sun->chuck.cy, di.cx, di.cy);
      } else {
        rotate_vector (di.chuck.ux, di.chuck.uy, di.chuck.dir * di.chuck.speed * drone::chuckt::apt.rad);
        di.set_center (di.chuck.sun->chuck.cx + di.chuck.len * di.chuck.ux, di.chuck.sun->chuck.cy + di.chuck.len * di.chuck.uy);
      }
    }
  }

}

void din::changechuckspeed (spinner<float>& sp) {
	if (num_selected_drones) {
    for (int i = 0; i < num_selected_drones; ++i) {
      drone& di = *selected_drones[i];
      if (di.chuck.yes) {
        float& speed = di.chuck.speed;
        speed += sp ();
        if (speed < 0.0f) speed = 0.0f;
        cons << YELLOW << "Drone " << i << ", speed = " << speed << eol;
        RESETCHUCKTRAILS(di)
      }
    }
  } else {
    cons << RED_PSD << eol;
  }
}

void din::changechucklength (spinner<float>& sp) {
	if (num_selected_drones) {
    for (int i = 0; i < num_selected_drones; ++i) {
      drone& di = *selected_drones[i];
      if (di.chuck.yes) {
        float& len = di.chuck.len;
        len += sp ();
        if (len < 0.0f) len = 0.0f;
        RESETCHUCKTRAILS(di)
        cons << YELLOW << "Drone " << i << ", length = " << len << eol;
      }
    }
  } else {
    cons << RED_PSD << eol;
  }
}

void din::flipchuckspeed () {
	if (num_selected_drones) {
    for (int i = 0; i < num_selected_drones; ++i) {
      drone& di = *selected_drones[i];
      if (di.chuck.yes) di.chuck.dir = -di.chuck.dir;
      RESETCHUCKTRAILS(di)
    }
  } else {
    cons << RED_PSD << eol;
  }
}

void din::togchuckspeed () {
	if (num_selected_drones) {
    for (int i = 0; i < num_selected_drones; ++i) {
      drone& di = *selected_drones[i];
      if (di.chuck.yes) swap (di.chuck.speed, di.chuck.speed0);
      RESETCHUCKTRAILS(di)
    }
  } else {
    cons << RED_PSD << eol;
  }
}

void din::resetchucktrails (drone& d) {
  d.trail.reset ();
  if (d.chuck.sat) resetchucktrails (*d.chuck.sat);
}

void din::resetallchucktrails () {
  for (drone_iterator i = drones.begin(), j = drones.end(); i != j; ++i) {
    drone& di = *(*i);
    if (di.chuck.yes) di.trail.reset ();
  }
}

void din::update_drone_pendulum (drone_pendulum_group& g) {
	float a = 0.0f, da = 1.0 / (g.n - 1);
	for (int i = 0, j = g.n; i < j; ++i) {
		drone* pd = g.drones[i];
		drone& d = *pd;
		mod_params* mods [] = {&d.mod.am, &d.mod.fm};
		mod_params& mod = *mods[g.orient];
		mod.depth = warp_depth (a) * float (uis.dpeu.depth.f_value);
		mod.bv.set_bpm (warp_bpm (a)* float (uis.dpeu.bpm.f_value));
		a += da;
	}
}

void din::update_drone_pendulums () {
	map<group*, bool> updated;
	for (int i = 0, j = drone_pendulums.size (); i < j; ++i) {
		drone_pendulum_group* g = drone_pendulums[i];
		drone_pendulum_group& gr = *g;
		for (int m = 0, n = num_selected_drones; m < n; ++m) {
			drone* ds = selected_drones[m];
			if ((updated[g] == false) && gr.member (ds)) {
				updated[g] = true;
				update_drone_pendulum (gr);
			}
		}
	}
}

void din::remove_from_groups (drone* d) {
	for (vector<drone_pendulum_group*>::iterator i = drone_pendulums.begin (), j = drone_pendulums.end (); i < j;) {
		drone_pendulum_group& gi = *(*i);
		if (gi.remove (d)) {
			if (gi.n == 0) {
				i = drone_pendulums.erase (i);
				j = drone_pendulums.end ();
			} else ++i;
		} else ++i;
	}
}

void din::remove_drone_from_mesh (drone* pd) {
  if (meshh.num == 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); 
      --meshh.num;
    } else ++i;
  }
}

void din::remove_drone_from_pre_mesh (drone* d) {
	if (erase (mkb_selector.meshd, d)) amd.drop = 1;
}

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::load_selected_drones (ifstream& f) {
	string ignore;
	int n;
	f >> ignore >> n;
	if (n) {
		int did;
		selected_drones.resize (n);
		for (int m = 0; m < n; ++m) {
			f >> did;
			drone* sd = get_drone (did);
      sd->sel = 1;
      selected_drones[m] = sd;
		}
		print_selected_drones ();
	}
	num_selected_drones = n;
}

void din::save_selected_drones (ofstream& f) {
	f << "selected_drones " << num_selected_drones << spc;
	if (num_selected_drones) {
		for (int i = 0; i < num_selected_drones; ++i) {
			drone* pd = selected_drones[i];
			f << pd->id << spc;
		}
	}
	f << endl;
}


void din::make_trackers () {
  if (num_selected_drones < 1) {
    cons << RED_A2D << eol;

    return;
  } else if (num_selected_drones == 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 = num_selected_drones - 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 () {

  for (drone_iterator i = trackers.begin (), j = trackers.end (); i != j; ++i) {
    drone* pdi = *i;
    drone& di = *pdi;
    drone& td = *di.tracked_drone;
		if (di.tracking == drone::POINT) {
			unit_vector (di.vx, di.vy, di.x, di.y, td.x, td.y);
      di.ax = di.vy;
      di.ay = -di.vx;
		} else { // USE
			di.vx = td.vx;
			di.vy = td.vy;
      di.ax = td.ax;
      di.ay = td.ay;
		}
  }


}

void din::evalgravity () {

  gravity_t& grav = dinfo.gravity;
  if (grav.mouse.state) {
    if (delta_mousex || delta_mousey) grav.track (win_mousex, win_mousey);
  } else {
    if (grav.dron.state) { 
      if (grav.tracked_drone) {
        int dwx = grav.tracked_drone->sx, dwy = grav.tracked_drone->y; // in window space
        if (dwx != grav.ldwx || dwy != grav.ldwy) {
          grav.track (dwx, dwy);
          grav.ldwx = dwx;
          grav.ldwy = dwy;
        }
      } else {
        if (num_selected_drones) {
          grav.tracked_drone = selected_drones[0];
        }
      }
    } else {
      grav.tracked_drone = 0;
    }
  }
  grav.modulate ();
}

void din::select_tracked_drones () {
	CLEAR_SELECTED_DRONES
  for (drone_iterator i = trackers.begin (), j = trackers.end (); i != j; ++i) {
    drone* pdi = *i;
		drone* ptd = pdi->tracked_drone;
		if (ptd->sel == 0) {
			ptd->sel = 1;
			selected_drones.push_back (ptd);
		}
  }
	print_selected_drones ();
}

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::sync_drones () {
  for (int i = 0; i < num_selected_drones; ++i) {
    drone& ds = *selected_drones[i];
		ds.player.x = ds.mod.am.bv.now = ds.mod.fm.bv.now = 0.0f;
  }
}

void din::toggle_freeze_drones () {
	if (num_selected_drones) {
		int nf = 0, nt = 0;
		for (int i = 0; i < num_selected_drones; ++i) {
			drone& ds = *selected_drones[i];
			if (ds.frozen) nt += ds.thaw (); else nf += ds.freeze ();
		}
		cons << GREEN << "Froze " << nf << " drones, Thawed " << nt << s_drones << eol;
	} else 
    cons << RED_PSD << eol;
}

int din::freeze_drones () {
	if (num_selected_drones) {
		int nf = 0;
		for (int i = 0; i < num_selected_drones; ++i) {
			drone& ds = *selected_drones[i];
      if (ds.frozen == 0) nf += ds.freeze ();
		}
		cons << GREEN << "Froze " << nf << s_drones << eol;
		return nf;
	} else cons << RED_PSD << eol;
	return 0;
}

int din::thaw_drones () {
	if (num_selected_drones) {
		int nt = 0;
		for (int i = 0; i < num_selected_drones; ++i) {
			drone& ds = *selected_drones[i];
      if (ds.frozen) nt += ds.thaw ();
		}
		cons << GREEN << "Thawed " << nt << s_drones << eol;
		return nt;
	} else cons << RED_PSD << eol;
	return 0;
}

int din::freeze_orbiters () {
	if (num_selected_drones) {
		int nf = 0;
		for (int i = 0; i < num_selected_drones; ++i) {
			drone& ds = *selected_drones[i];
			if (ds.orbiting && (ds.frozen == 0)) nf += ds.freeze ();
		}
		return nf;
	}
	return 0;
}

int din::thaw_orbiters () {
	xforming = NONE;
	if (num_selected_drones) {
		int nt = 0;
		for (int i = 0; i < num_selected_drones; ++i) {
			drone& ds = *selected_drones[i];
			if (ds.orbiting && ds.frozen) nt += ds.thaw ();
		}
		return nt;
	}
	return 0;
}

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

din::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;
	if (meshh.create) mkb_selector.set_mesh (meshh.create, dinfo.rows, dinfo.cols);
}

const box<float>& din::region_update () {
	rgn.right = win_mousex;
	rgn.top = win_mousey;
	if (mkb_selector.mesh) {
		int s = SHIFT, c = CTRL, sc = s|c;
		if (sc) { // equalise width/height
			float w = rgn.right - rgn.left, h = rgn.top - rgn.bottom;
			float aw = abs(w), ah = abs(h);
			if (s) { // equalise to smaller of width, height
				if (aw > ah) {
					float dw = aw - ah;
					int _ve = w < 0? 1:-1;
					rgn.right += (_ve * dw);
				} else {
					float dh = ah - aw;
					int _ve = h < 0? 1:-1;
					rgn.top += (_ve * dh);
				}
			} else { // equalise to larger of width, height
				if (aw > ah) {
					float dw = aw - ah;
					int _ve = h < 0? -1:1;
					rgn.top += (_ve * dw);
				} else {
					float dh = ah - aw;
					int _ve = w < 0? -1:1;
					rgn.right += (_ve * dh);
				}
			}
		}
		mkb_selector.gen_mesh_pts (rgn);
	}
	return rgn;
}

void din::region_end () {
	int swp = rgn.calc ();
	if (meshh.create) {
		if (swp) mkb_selector.gen_mesh_pts (rgn);
		create_drone_mesh ();
		meshh.create = 0;
	} else if (create_drone_pend) {
		create_drone_pendulum ();
		create_drone_pend = 0;
	}
	else
		find_selected_drones (rgn);
}

void din::region_abort () {
	if (meshh.create) stop_creating_mesh ();
	if (create_drone_pend) stop_creating_drone_pendulum ();
}

void din::modulate_ranges () {
	int nw, nw_2, center, nl, nr, dl, dr, z;
	nw = nw_2 = center = nl = nr = dl = dr = z = 0;
	for (int i = 0; i < num_ranges; ++i) {
		range& ri = ranges[i];
		if (ri.mod.active > 0) {
			ri.mod.calc ();
			// width modulation
			nw = ri.mod.fm.initial + ri.mod.fm.result;
			switch (ri.fixed) {
				case range::LEFT:
					dr = nw - ri.extents.width;
					range_right_changed (i, dr, 0);
					break;
				case range::RIGHT:
					dl = ri.extents.width - nw;
					range_left_changed (i, dl, 0);
					break;
				default:
					nw_2 = nw / 2;
					center = (ri.extents.left + ri.extents.right) / 2;
					nl = center - nw_2, nr = center + nw_2;
					dl = nl - ri.extents.left;
					dr = nr - ri.extents.right;
					range_left_changed (i, dl, 0);
					range_right_changed (i, dr, 0);
					break;
			}
			// height modulation
			int nh = ri.mod.am.initial + ri.mod.am.result;
			if (nh < 1) nh = 1;
			ri.extents.top = BOTTOM + nh;
			ri.extents.calc ();

			z = 1;
		}
	}
	if (z) {
		refresh_all_drones ();
		find_visible_ranges ();
	}
}

void din::set_ran_mod (int w) {
	for (int i = 0; i < num_ranges; ++i) {
		range& ri = ranges[i];
		ri.mod.active = w;
	}
	MENU.cb_mod_ran.set_state (ranges[dinfo.sel_range].mod.active, 0);
}

void din::pause_resume_ran_mod () {
	for (int i = 0; i < num_ranges; ++i) {
		range& ri = ranges[i];
		ri.mod.active = -ri.mod.active;
	}
}

void din::toggle_ran_mod () {
	for (int i = 0; i < num_ranges; ++i) {
		range& ri = ranges[i];
		ri.mod.active = !ri.mod.active;
	}
	MENU.cb_mod_ran.set_state (ranges[dinfo.sel_range].mod.active, 0);
}

void din::update_drone_mod_solvers (int w, multi_curve& mx) {
	if (w == modulator::AM) {
		for (drone_iterator i = drones.begin(), j = drones.end(); i != j; ++i) {
			modulator& dm = (*i)->mod;
			dm.am.bv.sol.update (); 
		}
	} else {
		for (drone_iterator i = drones.begin(), j = drones.end(); i != j; ++i) {
			modulator& dm = (*i)->mod;
			dm.fm.bv.sol.update ();
		}
	}
}

void din::update_range_mod_solvers (int w, multi_curve& mx) {
	if (w == modulator::AM) {
		for (int i = 0; i < num_ranges; ++i) {
			modulator& rm = ranges[i].mod;
			rm.am.bv.sol.update (); // am for width
		}
	} else {
		for (int i = 0; i < num_ranges; ++i) {
			modulator& rm = ranges[i].mod;
			rm.fm.bv.sol.update (); // fm for height
		}
	}
}

void din::snap_drones (int v) {
	if (num_selected_drones) {
		int nt[2] = {0};
		static const char* what_str1 [] = {"Unsnapped ", "Snapped "};
		if (v == -1) { // toggle
			for (int i = 0; i < num_selected_drones; ++i) {
				drone* pdi = selected_drones[i];
				pdi->snap = !pdi->snap;
				++nt[pdi->snap];
			}
			cons << "Snapped " << nt[1] << " drones, Unsnapped " << nt[0] << s_drones << eol;

		} else { // set
			for (int i = 0; i < num_selected_drones; ++i) {
				drone* pdi = selected_drones[i];
				pdi->snap = v;
			}
			cons << GREEN << what_str1[v] << num_selected_drones << " drones" << eol;
		}

	} else {
		cons << RED_PSD << eol;
	}
}

void din::pos_afx_vel (int v) {
	int nm [2] = {0};
	if (num_selected_drones) {
		if (v == -1) { // toggle
			for (int i = 0; i < num_selected_drones; ++i) {
				drone* pdi = selected_drones[i];
				pdi->posafxvel.yes = !pdi->posafxvel.yes;
				++nm[pdi->posafxvel.yes];
			}
			cons << GREEN << "Position affects velocity for " << nm[1] << " drones, Unset for " << nm[0] << s_drones << eol;
		} else { // set
			for (int i = 0; i < num_selected_drones; ++i) {
				drone* pdi = selected_drones[i];
				pdi->posafxvel.yes = v;
			}
			static const char* strs[] = {"Unset", "Set"};
			cons << GREEN << strs[v] << " Position affects velocity for " << num_selected_drones << s_drones << eol;

		}
	} else {
		cons << RED_PSD << eol;
	}
}

void din::select_all_browsed_drones (int bd) {
	selected_drones.resize (num_browsed_drones);
	for (int i = 0; i < num_browsed_drones; ++i) {
		drone* pdb = browsed_drones[i];
		pdb->sel = 1;
		selected_drones[i] = pdb;
	}
	print_selected_drones ();
	browsed_drone = bd;
	MENU.sp_browse_drone.set_value (browsed_drone);
}

void din::browse_drone (int db) {
	if (num_browsed_drones) {
		clear_selected_drones ();
		browsed_drone += db;
		if (browsed_drone > last_browseable_drone) { // select all browsed drones
			select_all_browsed_drones (-1);
			return;
		} else if (browsed_drone < 0) {
			select_all_browsed_drones (num_browsed_drones);
			return;
		}
		drone* pdb = browsed_drones [browsed_drone];
		pdb->sel = 1;
		num_selected_drones = 1;
		selected_drones.resize (num_selected_drones);
		selected_drones[0] = pdb;
    sprintf (BUFFER, "Browsed drone %d of %d", browsed_drone+1, num_browsed_drones);
    cons << GREEN << BUFFER;
      drone::chuckt& chuck = pdb->chuck;
      if (chuck.yes) chuck.print (); 
    cons << eol;
    MENU.sp_browse_drone.set_value (browsed_drone);
	} else {
		cons << RED << "No drones to browse, make a new selection" << eol;
	}
}

void din::browse_range (int dr) {
	dinfo.sel_range += dr;
	clamp (0, dinfo.sel_range, last_range);
	MENU.load_range (dinfo.sel_range);
}

void din::all_ranges_width_changed () {
	float a = 0.0f, da = 1.0f / last_range;
	extern solver sol_ran_width;
	int w = 0;
	float s = 0.0f;
	for (int i = 0; i < num_ranges; ++i) {
		s = sol_ran_width (a);
		w = s * WIDTH;
		if (w < 1) w = 1;
		set_range_width (i, w);
		a += da;
	}
}

void din::all_ranges_height_changed () {
	float a = 0.0f, da = 1.0f / last_range;
	extern solver sol_ran_height;
	int h = 0;
	float s = 0.0f;
	for (int i = 0; i < num_ranges; ++i) {
		s = sol_ran_height (a);
		h = s * HEIGHT;
		if (h < 1) h = 1;
		set_range_height (i, h);
		a += da;
	}
}

void din::draw_vol_dist () {

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

	const float c = 1.0f, b = 0.9f;
	float yb, yt;
	int xl, xr;
	float a, da;
	float v, vc;
  for (int i = visl, j = visr + 1; i < j; ++i) {
    range& R = ranges[i];
		const int dy = min (dinfo.dist.pix, R.extents.height);
		yb = R.extents.bottom; yt = R.extents.top;
		xl = R.extents.left; xr = R.extents.right;
		a = 0.0f; da = dy * R.extents.height_1;
		v = vc = 0.0f;
		glBegin (GL_QUAD_STRIP);
			while (yb < yt) {
				v = warp_vol (a);
				vc = v * c;
				glColor4f (vc, 0, vc, b);
				glVertex2f (xl, yb);
				glVertex2f (xr, yb);
				yb += dy;
				a += da;
			}
			a = 1.0f;
			v = warp_vol (a);
			vc = v * c;
			glColor4f (vc, 0, vc, b);
			glVertex2f (xl, yt);
			glVertex2f (xr, yt);
		glEnd ();
	}
  glDisable (GL_BLEND);
}

void din::draw_pitch_dist () {

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

	const float c = 1.0f, b = 0.5f;
	float yb, yt;
	int xl, xr;
	float a, da;
	float p, pc;
  for (int i = visl, j = visr + 1; i < j; ++i) {
    range& R = ranges[i];
		const int dx = min (dinfo.dist.pix, R.extents.width);
		yb = R.extents.bottom; yt = R.extents.top;
		xl = R.extents.left; xr = R.extents.right;
		a = 0.0f; da = dx * R.extents.width_1;
		p = pc = 0.0f;
		glBegin (GL_QUAD_STRIP);
			while (xl < xr) {
				p = warp_pitch (a);
				pc = p * c;
				glColor4f (0, pc, pc, b);
				glVertex2f (xl, yb);
				glVertex2f (xl, yt);
				xl += dx;
				a += da;
			}
			a = 1.0f;
			p = warp_pitch (a);
			pc = p * c;
			glColor4f (0, pc, pc, b);
			glVertex2f (xr, yb);
			glVertex2f (xr, yt);
		glEnd ();
	}

  glDisable (GL_BLEND);
}

void din::noise_interpolator_changed () {
  for (drone_iterator i = drones.begin(), j = drones.end(); i != j; ++i) {
    drone& di = *(*i);
		di.nsr.warp (&noiser::interp);
	}
}

int din::can_connect (drone* d1, drone* d2) {
	if (d1 == d2) return 0; // no self connection
	if (d2->nconn) 
		for (drone_iterator p = d2->connections.begin (), q = d2->connections.end(); p != q; ++p) 
			if (d1 == *p) return 0; // already connected
	return 1;
}

void din::calc_stepz (const string& fld) {
	nstepz = 0;
	stepz.clear ();
	int p;
	map<int, bool> exists;
	string str;
	stringstream ss1 (fld);
	while (ss1.eof() == 0) {
		ss1 >> str;
		stringstream ss2 (str);
		ss2 >> p;
		if (p > 0 && (exists[p] == false)) {
			stepz.push_back (p);
			++nstepz;
			exists[p] = true;
		} 
	}
}

void din::alloc_conns () {
	if (totcon > con_size) {
		if (con_pts) delete[] con_pts;
		if (con_clr) delete[] con_clr;
		con_pts = new float [totcon * 2 * 2];
		con_clr = new float [totcon * 2 * 3];
		con_size = totcon;
	}
}

int din::disconnect_drones () {
	if (num_selected_drones) {
    if (MENU.trackcon.state == 0) {
		  for (int i = 0; i < num_selected_drones; ++i) {
        drone* pd = selected_drones[i];
        remove_connections (pd);
        if (pd->tracking) {
          erase (trackers, pd);
          pd->tracking = 0;
          pd->tracked_drone = 0;
        }
      }

    } else {
		  for (int i = 0; i < num_selected_drones; ++i) remove_connections (selected_drones[i]);
    }
		return 1;
	} else cons << RED_PSD << eol;
	return 0;
}

void din::remove_connections (drone* pd) {

	// remove connections to pd
	for (drone_iterator i = drones.begin (), j = drones.end (); i != j; ++i) {
		drone* pdi = *i;
		drone& di = *pdi;
		if (di.nconn && pdi != pd) { 
			list<double>::iterator mi = di.mags.begin ();
			for (drone_iterator p = di.connections.begin (), q = di.connections.end(); p != q;) {
				drone* pdp = *p;
				if (pdp == pd) {
					p = di.connections.erase (p);
					q = di.connections.end ();
					mi = di.mags.erase (mi);
					--di.nconn;
					--totcon;
				} else {
					++p;
					++mi;
				}
			}
		}
	}
	
	// remove connections from pd
	if (pd->nconn) {
		totcon -= pd->nconn;
		pd->nconn = 0;
		pd->connections.clear ();
		pd->mags.clear ();
	}

	_2totcon = 2 * totcon;

	/*for (vector<connect>::iterator i = conns.begin (), j = conns.end (); i != j;) {
		connect& ci = *i;
		if (ci.d1 == pd || ci.d2 == pd) {
			i = conns.erase (i);
			j = conns.end ();
		} else 
		++i;
	}*/

}

/*void din::dirty_connection (drone* d) {
	for (int i = 0, j = conns.size (); i < j; ++i) {
		connect& ci = conns[i];
		if (ci.d1 == d || ci.d2 == d) ci.dirty = 1;
	}
}

void din::dirty_connections () {
	for (int i = 0; i < num_selected_drones; ++i) {
		drone* pdi = selected_drones[i];
		dirty_connection (pdi);
	}
}*/

void din::draw_connections () {

	if (totcon == 0) return;

	int p = 0, q = 0;

	map<drone*, bool> drawn;
	for (drone_iterator i = drones.begin (), j = drones.end (); i != j; ++i) {
		drone& di = *(*i);
		if (di.nconn) {
			for (drone_iterator s = di.connections.begin (), t = di.connections.end(); s != t; ++s) {
				con_clr[q++] = di.r;
				con_clr[q++] = di.g;
				con_clr[q++] = di.b;
				drone& dj = *(*s);
				con_clr[q++] = dj.r;
				con_clr[q++] = dj.g;
				con_clr[q++] = dj.b;
				con_pts[p++]=di.sx;
				con_pts[p++]=di.y;
				con_pts[p++]=dj.sx;
				con_pts[p++]=dj.y;
			}
		}
	}

	glEnableClientState (GL_COLOR_ARRAY);
	glColorPointer (3, GL_FLOAT, 0, con_clr);
	glVertexPointer (2, GL_FLOAT, 0, con_pts);
	glDrawArrays (GL_LINES, 0, _2totcon);
	glDisableClientState (GL_COLOR_ARRAY);

	//for (int i = 0, j = conns.size (); i < j; ++i) conns[i].draw ();

}

void din::reset_drone_arrows () {
	if (num_selected_drones) {
		for (int i = 0; i < num_selected_drones; ++i) {
			drone& ds = *selected_drones[i];
			ds.arrow.reset ();
		}
	} else cons << RED_PSD << eol;
}

void din::gabber::abort () {
  for (drone_iterator i = drones.begin (), j = drones.end (); i != j; ++i) {
    drone& di = *(*i);
    di.tovol = di.gab.amount;
  }
  drones.clear ();
  n = 0;
  cons << RED << "Aborted " << what << eol;
}

void din::gabber::set (din* dd, float t, const string& w, float tdiv, int fid) {
  int ns = dd->num_selected_drones;
	if (ns) {
    if (n) abort ();
		for (int i = 0; i < ns; ++i) {
      drone* pds = dd->selected_drones[i];
			drone& ds = *pds;
      if (!equals (ds.gab.amount, t)) {
        ds.gab.set (ds.gab.amount, t * ds.tovol, 1, max (0.0f, MENU.gabt()) / tdiv); 
        ds.finl = drone::fins[fid];
        drones.push_back (pds);
        ++n;
      }
    }
    if (n) {
      what = w;
      cons << YELLOW << "Started " << what << spc << n << TOGO << eol;
    } 
  } else {
    cons << RED_PSD << eol;
  }
}

void din::gabber::eval () {
  if (n) {
    for (drone_iterator i = drones.begin (), j = drones.end (); i != j;) {
      drone* pdi = *i;
      drone& di = *pdi;
      di.update_pv = drone::INTERPOLATE;
      if (di.gab.eval () == 0) {
        di.tovol = di.gab.start;
        i = drones.erase (i);
        j = drones.end ();
        if (di.finl) di.finl->finished (din0, pdi);
        if (--n == 0) cons << GREEN << "Finished " << what << eol; else cons << YELLOW << what << spc << n << TOGO << eol;
      } else {
        ++i;
      }
    }
  }
}

void din::gabber::erase (drone* d) {
  if (::erase (drones, d)) --n;
}

void din::gabber::setgabt () {
  if (n) 
    for (drone_iterator i = drones.begin (), j = drones.end (); i != j; ++i)
      (*i)->gab.delta_time = MENU.gabt(); 
}

void din::setdronevol (spinner<float>& s) {
  if (num_selected_drones) {
    for (int i = 0; i < num_selected_drones; ++i) {
      drone& ds = *selected_drones[i];
      ds.gab.amount += s();
      ds.tovol = ds.gab.amount;
      ds.update_pv = drone::EMPLACE;
      cons << "Drone " << i << " volume = " << ds.gab.amount << eol;
    }
  }
}

void din::drone2noise () {
  gab.set (this, 0.0f, "Drone > Noise", 2.0f, 1);
}
 
void din::noise2drone () {
  gab.set (this, 0.0f, "Noise > Drone", 2.0f, 2);
}

void drone::drone2noise::finished (din& mkb, drone* pdi) {
  drone& di = *pdi;
  di.is = drone::NOISE;
  di.setnoise ();
  di.r = di.g = di.b = 1.0f;
  di.gab.set (di.gab.amount, 1.0f * di.tovol, 1, MENU.gabt() / 2.0f);
  di.finl = 0;
  mkb.gab.drones.push_back (pdi);
  ++mkb.gab.n;
}

void drone::noise2drone::finished (din& mkb, drone* pdi) {
  drone& di = *pdi;
  di.is = drone::DRONE;
  di.setcolor ();
  di.gab.set (di.gab.amount, 1.0f * di.tovol, 1, MENU.gabt() / 2.0f); 
  di.finl = 0;
  mkb.gab.drones.push_back (pdi);
  ++mkb.gab.n;
}


void din::set_random_color () {
  rndr.set (MENU.s_red_min (), MENU.s_red_max());
  rndg.set (MENU.s_green_min (), MENU.s_green_max());
  rndb.set (MENU.s_blue_min (), MENU.s_blue_max());
}

void din::gotpoint () {
  dinfo.cen.x = win_mousex;
  dinfo.cen.y = win_mousey;
}

void din::ranchkdro () {
  for (drone_iterator i = drones.begin (), j = drones.end (); i != j; ++i) {
    drone& di = *(*i);
    clamp (0, di.range, last_range);
  }
}

void din::delselran () {
  // delete selected range
  if (num_ranges == 1) {
    cons << RED << "Will not delete the only range!" << eol;
    return;
  }

  int& sr = dinfo.sel_range;
  vector<range>::iterator rb = ranges.begin();
  if (sr == 0 || sr == last_range) 
    ;
  else {
    int p = sr - 1, q = sr + 1;
    range& rp = ranges[p];
    range& rq = ranges[q];
    range& rs = ranges[sr];
    rp.notes[1] = rq.notes[0];
    rp.intervals[1] = rq.intervals[0];
    for (int i = sr + 1; i < num_ranges; ++i) {
      range& ri = ranges[i];
      ri.extents.move (-rs.extents.width, 0);
    }
  }

  ranges.erase (rb + sr);

  initranpar (num_ranges - 1);
  ranchkdro ();
  refresh_all_drones ();
  find_current_range ();

}

void din::initranpar (int n) {
	num_ranges = n;
	last_range = num_ranges - 1;
	firstr = &ranges [0];
	lastr = &ranges [last_range];
	clamp<int> (0, dinfo.sel_range, last_range);
	MENU.sp_range.set_limits (0, last_range);
}


mkb_selector_t::mkb_selector_t () : of_prox_far (proximity_orderer::FARTHEST) {
	orderers[0]=&of_asc;
	orderers[1]=&of_desc;
	orderers[2]=&of_asc_cols;
	orderers[3]=&of_desc_cols;
	orderers[4]=&of_rnd;
	orderers[5]=&of_prox_near;
	orderers[6]=&of_prox_far;
}

void mkb_selector_t::draw (const box<float>& region) {
  if (din0.dinfo.cen.op) din0.dinfo.cen.draw ();
  else {
    if (mesh) {
      glPointSize (4);
      glVertexPointer (2, GL_FLOAT, 0, meshp);
      glColor3f (1, 1, 1);
      glDrawArrays (GL_POINTS, 0, rowcol);
      glPointSize (1);
    }
    cross = din0.create_drone_pend | din0.meshh.create;
    box_selector::draw (region);
  }
}

int mkb_selector_t::handle_input () {
  if (lmb) {
    if (lmb_clicked == 0) {
      if (din0.wanding || din0.moving_drones || din0.phrasor0.state) {
        lmb_clicked = 1;
        return 1;
      }
    } else 
      return 1;
  } else {
    if (lmb_clicked) {
      lmb_clicked = 0;
      if (din0.moving_drones) din0.set_moving_drones (0);
      din0.stopwanding ();
      if (din0.phrasor0.state == phrasor::recording) din0.finish_phrase_recording ();
      return 1;
    }
  }
  int r = din0.dinfo.cen.handle_input();
  if (!r) r = din0.dinfo.gravity.handle_input2 ();
  if (!r) r = box_selector::handle_input (); 
  return r;
}

void drone::setcolor () {
  if (is == drone::NOISE) 
    r = g = b = 1;
  else {
    color& gc = MENU.colorer ();
    r = gc.r;
    g = gc.g;
    b = gc.b;
  }
}

int din::stopwanding () {
  if (wanding) {// && (phrasor0.state != phrasor::playing)) {
    wanding = 0;
    cons << RED << "Stopped wanding drones!" << eol;
    return 1;
  } 
  return 0;
}

/*void din::eval_conns () {
	for (drone_iterator i = drones.begin (), j = drones.end (); i != j; ++i) {
		drone& di = *(*i);
		if (di.conn) {
			list<double>::iterator mi = di.mags.begin ();
			for (drone_iterator s = di.connections.begin (), t = di.connections.end(); s != t; ++s) {
				drone& dc = *(*s);
				double now = magnitude (dc.cx, dc.cy, di.cx, di.cy);
				double org = *mi;
				if (equals (now, org)) 
					;
				else if (now > org) {
					double lead = 0.01 * (now - org);
					float ux, uy; unit_vector<float, int> (ux, uy, dc.cx, dc.cy, di.cx, di.cy);
					dc.set_center (dc.cx + float (lead * ux), dc.cy + float(lead * uy), &di);
				}
			}
		}
	}
}*/

/*void din::butt_drones () {
	if ((win_mousex == prev_win_mousex) && (win_mousey == prev_win_mousey)) return;
	double mag;
	float ux, uy; 
	for (int i = 0; i < num_selected_drones; ++i) {
		drone& di = *selected_drones[i];
		mag = unit_vector<float, int> (ux, uy, win_mousex, win_mousey, di.cx, di.cy);
		if (mag < butt) {
			double lead = drone::STIFFNESS * (butt - mag);
			float nx = di.cx + lead * ux, ny = di.cy + lead * uy;
			if (magnitude (nx, ny, ring.x, ring.y) < ring.r) {
				di.set_center (nx, ny);
				for (int j = 0; j < num_selected_drones; ++j) {
					if (i != j) {
						drone& dj = *selected_drones[j];
						mag = unit_vector<float, int> (ux, uy, dj.cx, dj.cy, di.cx, di.cy);
						if (mag < inter_butt) {
							double lead = drone::STIFFNESS * (inter_butt - mag);
							float nx = di.cx + lead * ux, ny = di.cy + lead * uy;
							if (magnitude (nx, ny, ring.x, ring.y) < ring.r) {
								di.set_center (nx, ny);
							}
						}
					}
				}
			}
		}
	}
}*/


/*void din::orbit_reciprocal () { // attach selected drones to attractor
  int n = selected_drones.size ();
	if (n) {
		for (int i = 0, j = 1; i < n; ++i) {
			drone* di = selected_drones[i];
			drone* dj = selected_drones[j];
			list<attractee>& lae = dj->attractees;
			if (!di->orbiting) {
				push_back (attractors, dj);
				attractee ae (di->id, di);
				if (push_back (lae, ae)) {
					(*dj).attractor++;
					di->orbiting = 1;
				}
			}
			if (++j == n) j = 0;
		}
	} else cons << RED << "Please select at least 2 drones!" << eol;
}*/


	/*if (butting) {
		float r [] = {ring.r, butt, inter_butt};
		float cx [] = {ring.x, win_mousex, win_mousex};
		float cy [] = {ring.y, win_mousey, win_mousey};

		glColor3f (0.25f, 0.25f, 0.25f);
		extern const float TWO_PI;
		int npts = 33;
		for (int p = 0; p < 3; ++p) {
			float R = r[p];
			float a = 0, da = TWO_PI / (npts - 1);
			glBegin (GL_LINE_LOOP);
			for (int i = 0; i < npts; ++i, a += da) {
				glVertex2f (cx[p] + R * cos(a), cy[p] + R * sin (a));
			}
			glEnd ();
		}
	}*/
