/*
* mondrian.cc
* inspired by the works of Piet Mondrian
* DIN Is Noise is released under GNU Public License 2.0
* DIN Is Noise is copyright (c) 2006-2022 Jagannathan Sampath
* For more information, please visit https://dinisnoise.org/
*/

#include <fstream>
#include <iostream>
#include <sstream>
#include "ui_list.h"
#include "main.h"
#include "font.h"
#include "console.h"
#include "mondrian.h"
#include "solver.h"
#include "random.h"
#include "utils.h"
#include "vector2d.h"
#include "font.h"
#include "audio.h"
#include "curve_library.h"
#include "container.h"
#include "rect.h"
#include "ball.h"
#include "slit.h"
#include "oscilloscope.h"

#include <algorithm>
#include <math.h>

using namespace std;

extern std::string user_data_dir;
extern viewport view;
extern font fnt;
extern mondrian mondrian0;
extern audio_out aout;
extern curve_library wav_lib, attack_lib, decay_lib;
extern gotog _gotomax;
extern int quit;
extern beat2value octave_shift;
extern float GOLDEN_RATIO;
extern const float PI_BY_180;
extern int IPS; 
extern std::map <string, float> INTERVALS;
extern oscilloscope scope;
extern char BUFFER [];
extern float MIN_TIME;
extern int line_height;

mondrian::mondrian () :
wave ("mondrian_waveform.crv"), waved ("mondrian_waveform.ed"), wavlis (wave_listener::MONDRIAN), 
attack ("mondrian_attack.crv"), decay ("mondrian_decay.crv"), attacked ("mondrian_attack.ed"), 
decayed ("mondrian_decay.ed"), fdr (slit::INITIAL_OPEN_CLOSE_TIME), _help ("mondrian.hlp") {
	
  pan = zoom = 1;
  root = 0;
  hit = 0;
  edge = edge::NONE;
  num_balls = 0;
  adding_balls = 0;
  moving_balls = 0;
  editing_edge = 0;
  editing_slit = 0;
  slitting = NOTHING;
  started_making_ball = 0;
  new_ball = 0;
  n_pts = 0;
  pts = 0;
	pts_d = 0;
  pts_clr = 0;
  n_mrk = 0;
  mrk = 0;
  cursor = 0;
  num_triggered_notes = 0;
  note_volume = 0;
  voices = 0;
	min_voices = 0;
	auto_adjust_voices = 0;
	delta_attack_time = delta_decay_time = MIN_TIME;
	lmb_clicked = 0;
	num_boxes = 8;
	label_notes = 1;
	label_hz_vol = 0;
	fill_boxes = 1;
	draw__boxes = 1;
	draw__notes = 1;
	draw_slit_cutter = 0;
	num_selected_slits = 0;
	num_selected_balls = 0;
	sel_tar = SELECT_BALLS;
  nleaves = 0;

  // box auto splitting
  splitting_rects = 0;
  auto_split_orient = split::BOTH;
  auto_split_at = split::NOTES;

  // both [auto]split, delete
	split_leaf = rect::BIGGEST;
	delete_leaf = rect::RANDOM;

  delete_all_rects = 0;

  inst = 1;

}

mondrian::~mondrian () {
  wave.save ("mondrian_waveform.crv");
  attack.save ("mondrian_attack.crv");
  decay.save ("mondrian_decay.crv");
  ofstream edf ((user_data_dir + "mondrian.ed").c_str (), ios::out); save_settings (edf);
  ofstream dataf ((user_data_dir + "mondrian.data").c_str(), ios::out);
  string R("R");
	save_boxes (dataf, root, R);
	save_balls (dataf);
	dataf << "poly radius " << poly.radius << " points " << poly.points << endl;
	save_slits (dataf);
  if (pts) delete[] pts;
	if (pts) delete[] pts_d;
  if (pts_clr) delete[] pts_clr;
  if (mrk) delete[] mrk;
  for (balls_iterator p = balls.begin (), q = balls.end (); p != q; ++p) delete *p;
  if (root) delete_children (root);
  if (new_ball) delete new_ball;
  dlog << "--- destroyed Mondrian --" << endl;
}

void mondrian::enter () {}

void mondrian::leave () {
	stop_doing_stuff ();
  ui::leave ();
}


void mondrian::launch_note (ball* _ball, float t, float t0, float dt, const pair<float, float>& invl) {
  if (quit != DONT || (lmb && _ball == new_ball)) return; 
  float mid = (invl.first + invl.second) / 2.0f;
	if (t < mid) t = invl.first; else t = invl.second;
  float interval = 1 + (t - t0) * dt;
  float frequency = get_tonic (this) * interval * _ball->pitch_mult;
  N.set_freq (frequency);
	++num_triggered_notes;
	if (auto_adjust_voices) 
		voices = max (min_voices, min_voices + num_triggered_notes);
	else 
		voices = min_voices;
  note_volume = 1.0f / voices * _ball->vol_mult;

  triggered_notes.push_back (triggered_note (N, note_volume, _ball->x, _ball->y, _ball->trig_what));
	triggering_balls.push_back (_ball);
	++_ball->num_notes;
	triggered_note& last_triggered_note = triggered_notes.back (); 
	last_triggered_note.setup (&wave, &attack, &decay);
  last_triggered_note.r *= _ball->pitch_mult;
  last_triggered_note.g *= _ball->pitch_mult;
  last_triggered_note.b *= _ball->pitch_mult;
	print_num_triggered_notes ();
}

void mondrian::print_num_triggered_notes () {
	stringstream ss; ss << "Voices: " << num_triggered_notes << "/" << voices;
	uis.l_mondrian_voices.set_text (ss.str());
}

void mondrian::randomise_box_color () {
	finding f; find (root, win.mousex, win.mousey, f);
  rect* found = f.found;
  if (found) found->make_random_color ();
}

list<ball*>& mondrian::get_box_balls () { // get balls in box or all balls
	finding f; find (root, win.mousex, win.mousey, f);
	rect* found = f.found;
	if (found) return found->balls; else return balls;
}

list<ball*>& mondrian::get_balls () { // get selected or balls in box or all balls
  if (num_selected_balls) return selected_balls; else return get_box_balls ();
}

list<ball*>& mondrian::get_balls (int) { // get selected or all balls
	if (num_selected_balls) return selected_balls; else return balls;
}

void mondrian::freeze_thaw_balls (list<ball*>& _balls) {
  for (balls_iterator p = _balls.begin (), q = _balls.end (); p != q; ++p) {
    ball* b = *p;
    b->frozen = !b->frozen;
		if (b->frozen == 0 && b->V == 0) cons << YELLOW << "Defrosted ball [" << (uintptr_t) b << "] cant move [0 speed]" << eol;
  }
}

void mondrian::freeze_balls (list<ball*>& _balls) {
  for (balls_iterator p = _balls.begin (), q = _balls.end (); p != q; ++p) {
    ball* b = *p;
    b->frozen = 1;
		if (b->V == 0) cons << YELLOW << "Ball [" << (uintptr_t) b << "] cant move already [0 speed]" << eol;
  }
}

void mondrian::thaw_balls (list<ball*>& _balls) {
  for (balls_iterator p = _balls.begin (), q = _balls.end (); p != q; ++p) {
    ball* b = *p;
    b->frozen = 0;
		if (b->V == 0) cons << YELLOW << "Defrosted ball [" << (uintptr_t) b << "] cant move [0 speed]" << eol;
  }
}

void mondrian::clear_modulations (list<ball*>& _balls) {
	if (num_selected_balls == 0) {
		cons << YELLOW << "Please select some balls!" << eol; 
		return;
	}
  for (balls_iterator p = _balls.begin (), q = _balls.end (); p != q; ++p) {
    ball* b = *p;
		b->pitch_mult = 1;
		b->mod = 0;
		b->color_using_modulation ();
  }
	cons << GREEN << "Cleared modulation on " << num_selected_balls << " balls" << eol;
}

void mondrian::update_parent (rect* C) {
  rect* P = C->parent;
  if (P) {
    rect* child1 = P->child1, *child2 = P->child2;
    box<float>& el = child1->extents;
    box<float>& er = child2->extents;
    box<float>& e = P->extents;
    box<float> e0 (e);
    if (P->split == split::VERTICAL) {
      if (C == child1) {
        er.left = el.right;
        er.bottom = el.bottom;
        er.top = el.top;
        er.calc ();
      } else {
        el.right = er.left;
        el.bottom = er.bottom;
        el.top = er.top;
        el.calc ();
      }
    } else {
      if (C == child1) {
        er.left = el.left;
        er.right = el.right;
        er.bottom = el.top;
        er.calc ();
      } else {
        el.left = er.left;
        el.right = er.right;
        el.top = er.bottom;
        el.calc ();
      }
    }
    e.left = el.left;
    e.right = er.right;
    e.bottom = el.bottom;
    e.top = er.top;
    e.calc ();
    if (e == e0) {
      update_children (P->child1);
      update_children (P->child2);
    } else update_parent (P);
  } else {
    update_children (C);
    calc_visual_params ();
  }
}

void mondrian::set_edge (rect* R, int e, float x, float y) {
  R->extents.set_edge (e, x, y);
  update_parent (R);
}

void mondrian::obj2win (const point<float>& v, float& wx, float& wy) {
  wx = win_per_obj.x * v.x;
  wy = win_per_obj.y * v.y;
}

void mondrian::obj2win (const float& ox, const float& oy, float& wx, float& wy) {
  wx = win_per_obj.x * ox;
  wy = win_per_obj.y * oy;
}

void mondrian::win2obj (const float& wx, const float& wy, float& ox, float& oy) {
  ox = obj_per_win.x * wx;
  oy = obj_per_win.y * wy;
}

void mondrian::load_settings () {
  ifstream file ((user_data_dir + "mondrian.ed").c_str (), ios::in);
  if (!file) return;
  load_settings (file);
}


void mondrian::save_settings (ofstream& file) {
  file << "window " << win.left << spc << win.bottom << spc << win.right << spc << win.top << endl;
  file << "win_chunk " << win_chunk.x << spc << win_chunk.y << endl;
  file << "obj_chunk " << obj_chunk.x << spc << obj_chunk.y << endl;
  file << "win_resolution " << win_resolution << endl;
	file << "label_hz_vol " << label_hz_vol << endl;
	file << "min_voices " << min_voices << endl;
	file << "auto_delete_rect " << auto_del_rect.active << endl;
	file << "auto_delete_rect_time " << auto_del_rect.triggert << endl;
	file << "auto_split_rect " << auto_split_rect.active << endl;
	file << "auto_split_time " << auto_split_rect.triggert << endl;
	file << "auto_split_orient " << auto_split_orient << endl;
	file << "auto_split_at " << auto_split_at << endl;
	file << "pick_box_split " << split_leaf << endl;
	file << "pick_box_delete " << delete_leaf << endl;
	file << "min_split_size " << mondrian::min_split_size << endl;
	file << "draw_boxes " << draw__boxes << endl;
	file << "draw_ball_pos " << draw_ball.position << endl;
	file << "draw_ball_heading " << draw_ball.heading << endl;
	file << "draw_notes " << draw__notes << endl;
	file << "label_notes " << label_notes << endl;
	file << "fill_boxes " << fill_boxes << endl;
	file << "num_boxes " << num_boxes << endl;
	file << "added_ball_type " << added_ball_type << endl;
	file << "cursor " << cursor << endl;
	file << "auto_adjust_voices " << auto_adjust_voices << endl;
	file << "half_slit_size " << slit::HALF_SIZE << endl;
	file << "turn_sync " << MENU.cb_turn_sync.state << endl;
	file << "speed_sync " << MENU.cb_speed_sync.state << endl;
}

void mondrian::calc_win_mouse () {
  if (MENU.show == 0) win.update_mouse ();
}


void mondrian::toggle_triggered_sound () {
	list<ball*>& _balls = get_balls ();
	int n = 0;
	for (balls_iterator p = _balls.begin (), q = _balls.end (); p != q; ++p) {
		ball* b = *p;
		int& tw = b->trig_what;
		tw = !tw;
		cons << "Ball " << ++n;
		if (tw) cons << " triggers noise"; else cons << " triggers note";
		cons << eol;
	}
}

void mondrian::clear_selected_targets () {
	if (sel_tar == SELECT_BALLS) {
		browse.clear ();
		clear_selected<ball> (selected_balls, num_selected_balls);
  }
	else
		clear_selected<slit> (selected_slits, num_selected_slits);
}

void mondrian::select_all_targets () {
	if (sel_tar == SELECT_BALLS) {
		browse.clear ();
		select_all<ball> (balls, selected_balls, num_selected_balls);
  }
	else
		select_all<slit> (slits, selected_slits, num_selected_slits);
}

void mondrian::invert_selected_targets () {
  if (sel_tar == SELECT_BALLS) {
		browse.clear ();
    invert_selection<ball> (balls, selected_balls, num_selected_balls);
  }
  else
    invert_selection<slit> (slits, selected_slits, num_selected_slits);
}

void mondrian::select_box_targets () {
	if (sel_tar == SELECT_BALLS) {
		select_box_balls (); 
  }
	else 
		select_box_slits ();
}

void mondrian::browse_ball (int i) {
  if (browse.n) {
		clear_selected<ball> (selected_balls, num_selected_balls, 0);
    browse.which += i;
		wrap (0, browse.which, browse.last);
		ball* bb = browse.balls [browse.which];
		bb->select = 1;
		selected_balls.push_back (bb);
		++num_selected_balls;
    after_selection ();
  }
}

void mondrian::delete_selected_targets () {
	if (sel_tar == SELECT_BALLS) 
		delete_selected_balls (); 
	else 
		remove_selected_slits ();
}

void mondrian::delete_all_targets () {
	if (sel_tar == SELECT_BALLS) 
		delete_all_balls ();
	else
		remove_all_slits ();
}

void mondrian::update_attack () {
	float x, y; attack.get_vertex (attack.last_vertex, x, y); _gotomax.set (x); 
  for (note_iterator i = triggered_notes.begin (), j = triggered_notes.end (); i != j; ++i) (*i).attack.update ();
}

void mondrian::update_decay () {
  for (note_iterator i = triggered_notes.begin (), j = triggered_notes.end (); i != j; ++i) {
		triggered_note& ti = *i;
		solver& sol = ti.decay; 
		sol.update ();
		float y; sol.mcrv->get_vertex (sol.mcrv->last_vertex, ti.decay_lastx, y);
	}
}

void mondrian::update_waveform (multi_curve& crv) {
  static const string nam ("mondrian-waveform");
  for (note_iterator i = triggered_notes.begin (), j = triggered_notes.end (); i != j; ++i) 
    (*i).update_solver (crv, nam);
}

void mondrian::draw_rect (rect* what) {
	box<float>& extents = what->extents;

	if (fill_boxes) {
		glEnable (GL_BLEND);
		glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		glPolygonMode (GL_FRONT_AND_BACK, GL_FILL);
		glColor4f (what->r, what->g, what->b, 0.5);
		glRectf (extents.left, extents.bottom, extents.right, extents.top);
		glDisable (GL_BLEND);
	}

	// draw slits
	glColor3f (1, 1, 1);
	if (what->total_slits == 0) { // no slits
		glPolygonMode (GL_FRONT_AND_BACK, GL_LINE);
			glRectf (extents.left, extents.bottom, extents.right, extents.top); // outline rect
		glPolygonMode (GL_FRONT_AND_BACK, GL_FILL);
	} else {
		float startt [] = {extents.left, extents.bottom, extents.left, extents.bottom};
		float endd [] = {extents.right, extents.top, extents.right, extents.top};
		float levell [] = {extents.bottom, extents.right, extents.top, extents.left};
		int typp [] = {slit::HORIZONTAL, slit::VERTICAL, slit::HORIZONTAL, slit::VERTICAL};
		for (int i = 0; i < rect::nedges; ++i) {
			if (what->nslits[i]) {
				draw_slits (startt[i], endd[i], levell[i], typp[i], what->slits[i], slit_drawerr); 
			} else {
				float li = levell[i];
				float si = startt[i], ei = endd[i];
				if (typp[i] == slit::HORIZONTAL) {
					slit_drawerr.add (si, li);
					slit_drawerr.add (ei, li);
				} else {
					slit_drawerr.add (li, si);
					slit_drawerr.add (li, ei);
				}
			}
		}
		slit_drawerr.draw ();
	}
}

void mondrian::draw_leaves () {
	for (box_iterator i = leaves.begin (), j = leaves.end (); i != j; ++i) {
		rect* bi = *i;
		draw_rect (bi);
	}
}

void mondrian::draw  () {

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

	draw_notes ();

	if (label_notes) {
		tb.draw (); // note labels
		glColor3f (0.9, 0.9, 1);
		glVertexPointer (2, GL_FLOAT, 0, mrk);
		glDrawArrays (GL_LINES, 0, n_mrk); // note markers
	}

	// draw cursor
	box<float>& rex = root->extents;
	if (inbox (rex, win.mousex, win.mousey)) {
		static const float cc = 0.5f;
		glColor3f (cc, cc, cc);
		crsr[0]=rex.left; crsr[1]=win.mousey; 
		crsr[2]=rex.right; crsr[3]=win.mousey; 
		crsr[4]=win.mousex; crsr[5]=rex.bottom;
		crsr[6]=win.mousex;crsr[7]=rex.top;
		glVertexPointer (2, GL_FLOAT, 0, crsr);
		glDrawArrays (GL_LINES, 0, 4);
	}

	if (draw_slit_cutter) {
		glColor3f (1, 1, 0);
		glPolygonMode (GL_FRONT_AND_BACK, GL_LINE);
			glRectf (win.mousex - slit::HALF_SIZE, win.mousey - slit::HALF_SIZE, win.mousex + slit::HALF_SIZE, win.mousey + slit::HALF_SIZE);
		glPolygonMode (GL_FRONT_AND_BACK, GL_FILL);  
	}

	if (draw__boxes) draw_leaves ();
	draw_balls ();

	mon_selector.draw (rgn);

	//selector.draw ();

	mark_selected_slits ();

	/*glPointSize (5);
	glColor3f (0.0, 1.0, 1.0);
	for (int i = 0, j = marks.size (); i < j; ++i) {
		point<float>& pi = marks[i];
		glBegin (GL_POINTS);
		glVertex2f (pi.x, pi.y);
		glEnd ();
	}
	glPointSize (1);*/

}

void mondrian::calc_visual_params () {
  win.calc ();
  make_notes ();
  tb.refresh (this);
}

void mondrian::do_panx (int dir) {
  win.panx (dir); 
  calc_visual_params (); 
}

void mondrian::do_pany (int dir) {
  win.pany (dir);
  calc_visual_params ();
}

void mondrian::do_zoom (int dir) {
  win.zoom (dir);
  calc_visual_params ();
}

void mondrian::setup () {

	dlog << "*** setting up Mondrian ***" << endl;

  load_settings ();
	load_boxes_and_balls ();

	if (root == 0) {
  	root = new rect;
		const int xsize = 800;
  	root->extents (0, 0, xsize, xsize / GOLDEN_RATIO);
		root->calc_intervals ();
		poly.radius = root->extents.height / 4.0f;
		set_note_poly_points (24); cons.clear ();
    add_leaf (root);
	}

	poly.delta_radius = 1;

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

  // setup attack curve
  attacked.add (&attack, &attacklis);
  attacklis.inst = this;
  attacked.attach_library (&attack_lib);

  // setup decay curve
  decayed.add (&decay, &decaylis);
  decaylis.inst = this;
	decayed.attach_library (&decay_lib);

	scaleinfo.scl = this;

  dlog << "+++ Mondrian setup complete +++" << endl;

}

rect* mondrian::get_sibling (rect* R) {
  rect* P = R->parent;
  if (P != 0) {
    if (P->child1 == R) 
      return P->child2; 
    else 
      return P->child1;
  }
  return 0;
}


void mondrian::find (rect* R, float x, float y, finding& fnd) {
  if (inbox (R->extents, x, y)) {
    if (R->child1 != 0) {
			find (R->child1, x, y, fnd);
      if (fnd.found == 0) find (R->child2, x, y, fnd);
    } else {
      fnd.found = R;
      fnd.sibling = get_sibling (R);
    }
  }
}

finding mondrian::make_finding (rect* R) {
  finding result;
  result.found = R;
  result.sibling = get_sibling (R);
  return result;
}

rect* mondrian::box_under_cursor () {
  finding result; find (root, win.mousex, win.mousey, result);
  rect* F = result.found;
  if (F == 0) {
		cons << YELLOW << "Sorry cant split because you are outside all boxes!" << eol;
    return 0;
  } else return F;
}


int mondrian::find_hit_slit (float x, float y) {
	rect* R = 0;
	for (box_iterator i = leaves.begin (), j = leaves.end (); i != j; ++i) { // slit can only be on leaves
		rect* li = *i;
		box<float> exts = li->extents;
		exts.resize (gutter, gutter);
		if (inbox(exts, x, y)) { // finding 1 box is enough
			R = li;
			break;
		}
	}
	if (R) {
		int e = R->extents.get_edge_hit (x, y, gutter2);
		if (e != edge::NONE) {
			float v[] = {x, y, x, y}; 
			slit* hs = slit_hit (R, e, v[e]); // hit slit
			if (hs) {
				push_back (selected_slits, hs);
				++num_selected_slits;
				return 1;
			}
		}
	} 
	return 0;
}

int mondrian::add_remove_slit (float x, float y, fader* fdr, float sz)  {


	int result = 0;
	int boxes_hit = 0;
	int k = 0;

	// slit can only be on boxes that are leaves
	for (box_iterator i = leaves.begin (), j = leaves.end (); i != j; ++i) {
		rect* li = *i;
		box<float> lie = li->extents;
		lie.resize (gutter, gutter);
		if (inbox(lie, x, y)) {
			if (++boxes_hit > 2) break; // too many!
			bxs[k++] = li;
		}
	}

	if (boxes_hit == 2) { // slit can only be on 2 leaves
		slit* hs = slit_hit (bxs, x, y); // did we hit a slit?
		if (hs == 0) { // no slit hit
			slit* s = new slit (bxs, x, y, sz, fdr); // so make slit
			rect** boxes = s->boxes;
			int* edges = s->edges;
			if (edge_on_root (root, boxes, edges) == 0) { // balls wont escape
				// add slit to edges of the 2 leaves
				int result0 = boxes[0]->add_slit (s, edges[0]);
				int result1 = boxes[1]->add_slit (s, edges[1]);
				if (result0 && result1) { // added successfully
					++num_slits;
					slits.push_back (s); // add to global slits 
				} else { 
					// not added so discard
					remove_slit (s);
				}
			} else { 
				// balls will escape playing area so discard
				remove_slit (s); 
			}
		} else { 
			remove_slit (hs);
		}
		result = 1;
	} 
	return result;
}

rect* mondrian::split_rect (int type, float t, rect* B) {

	if (B == 0) {
		B = box_under_cursor ();
		if (B == 0) return 0;
	}
	
  stop_editing_slit ();
	stop_editing_edge ();

  box<float>& extents = B->extents;
  B->split = type;
  rect* v1 = new rect; 
  rect* v2 = new rect; 
  box<float>& v1e = v1->extents;
  box<float>& v2e = v2->extents;
	if (type == split::VERTICAL) {
  	v1e (extents.left, extents.bottom, t, extents.top);
  	v2e (t, extents.bottom, extents.right, extents.top);
	} else {
		v1e (extents.left, extents.bottom, extents.right, t);
		v2e (extents.left, t, extents.right, extents.top);
	}
  v1->parent = B;
  v2->parent = B;
  B->child1 = v1;
  B->child2 = v2;
  v1->calc_intervals ();
  v2->calc_intervals ();
  split_balls (B, v1, v2);
	remove_leaf (B);
	add_leaf (v1);
	add_leaf (v2);
	recreate_slits (B);
	return B;
}


void mondrian::recreate_slits (rect* R) {
	box<float>& e = R->extents;
	float fixed_val [rect::nedges] = {e.bottom, e.right, e.top, e.left};
	list<slit_info> reslits;
	for (int i = 0; i < rect::nedges; ++i) {
		list<slit*>& slits = R->slits[i];
		int ns = R->nslits [i];
		if (ns) {
			slit_info sf;
			float* moving [rect::nedges] = {&sf.x, &sf.y, &sf.x, &sf.y};
			float* fixed [rect::nedges] = {&sf.y, &sf.x, &sf.y, &sf.x};
			float* movingi = moving [i];
			float* fixedi = fixed [i]; 
			*fixedi = fixed_val [i];
			for (slit_iterator si = slits.begin (), sj = slits.end (); si != sj;) {
				slit* s = *si;
				if (s->fdr) {
					s->start = s->anim_start;
					s->end = s->anim_end;
					s->calc_mid ();
					sf.anim = 1;
					sf.fdr.copy (s->fdr);
				} else sf.anim = 0;
				*movingi = s->mid;
				sf.half_size = s->mid - s->start;
				reslits.push_back (sf);
				remove_slit (s);
				si = slits.begin ();
				sj = slits.end ();
			}
		}
	}

	for (list<slit_info>::iterator i = reslits.begin (), j = reslits.end(); i != j; ++i) {
		slit_info& si = *i;
		if (si.anim) 
			add_remove_slit (si.x, si.y, &si.fdr, si.half_size);
		else
			add_remove_slit (si.x, si.y, 0, si.half_size);
	}
}


void mondrian::add_leaf (rect* L) {
	leaves.push_back (L);
  ++nleaves;
}


void mondrian::remove_leaf (rect* L) {
	erase (leaves, L);
  --nleaves;
}

rect* mondrian::earliest_leaf () {
  return *(leaves.begin());
}

rect* mondrian::latest_leaf () {
  return *(--leaves.end());
}

rect* mondrian::balled_leaf () {
  list<ball*>& balls = get_balls (0);
  int sz = balls.size ();
  if (sz) {
    rnd<float> rd (0, balls.size()-1);
    int r = rd() + 0.5;
    ball* b = get <list<ball*>, ball*> (balls, r);
    return b->R;
  } else return rnd_leaf();
}

rect* mondrian::rnd_leaf () {
  rnd<float> rd (0, nleaves-1);
  return  get< list<rect*>, rect*> (leaves, int(rd()+0.5));
}

rect* mondrian::biggest_leaf () {
  rect* mal = earliest_leaf ();
  float max_area = mal->get_area ();
	for (box_iterator i = ++leaves.begin (), j = leaves.end (); i != j; ++i) {
    rect* li = *i;
    float li_area = li->get_area ();
    if (li_area > max_area) {
      max_area = li_area;
      mal = li;
    }
  }
  return mal;
}

int mondrian::delete_rect (const finding& f) { // delete found box

	rect* found = f.found;
	rect* sibling = f.sibling;

  // turn sibling into parent, kill parent
  rect* parent = found->parent;
  rect* grand_parent = parent->parent;
  if (grand_parent) {
    if (grand_parent->child1 == parent) 
      grand_parent->child1 = sibling;
    else 
      grand_parent->child2 = sibling;
  } else root = sibling;

  sibling->extents = parent->extents;
  sibling->parent = grand_parent;
  update_children (sibling);

	finding fnd;
  for (balls_iterator p = found->balls.begin (), q = found->balls.end (); p != q; ++p) { // find new home for balls of found
    ball* b = *p;
		find (sibling, b->x, b->y, fnd);
		b->R = fnd.found;
    if (b->R == 0) b->R = sibling;
    b->R->balls.push_back (b);
		fnd.clear ();
  }

  delete parent;

  remove_leaf (found);
  recreate_slits (found);
  delete found;

  return 1;
}

void mondrian::delete_current_rect () { // delete box under cursor
  finding f; find (root, win.mousex, win.mousey, f);
  if (bad_rect (f.found)) return;
	delete_rect (f); 
}

void mondrian::delete_children (rect* _root) {
  rect* left = _root->child1;
  rect* right = _root->child2;
  if (left) {
    delete_children (left);
    delete_children (right);
  }
  delete _root;
}

void mondrian::update_children (rect* R) {
  box<float>& extents = R->extents;
  R->calc_intervals ();
  if (R->child1 && R->child2) { 
    box<float>& c1e = R->child1->extents;
    if (R->split == split::HORIZONTAL) {
      R->child1->extents (extents.left, extents.bottom, extents.right, c1e.top);
      R->child2->extents (extents.left, c1e.top, extents.right, extents.top);
    } else if (R->split == split::VERTICAL) {
      R->child1->extents (extents.left, extents.bottom, c1e.right, extents.top);
      R->child2->extents (c1e.right, extents.bottom, extents.right, extents.top);
    }
    update_children (R->child1);
    update_children (R->child2);
  } else {
		if (edge != edge::NONE) {
			if (edge == edge::LEFT || edge == edge::RIGHT) {
				R->update_slits (edge::BOTTOM, R->extents.left, R->extents.right);
				R->update_slits (edge::TOP, R->extents.left, R->extents.right);
			} else {
				R->update_slits (edge::LEFT, R->extents.bottom, R->extents.top);
				R->update_slits (edge::RIGHT, R->extents.bottom, R->extents.top);
			}
		}
	}
}

rect* mondrian::pick_leaf (int& what) {
  rect* asr = 0;
  if (what == rect::RANDOM) 
    asr = rnd_leaf (); 
  else if (what == rect::BIGGEST)  
    asr = biggest_leaf ();
  else if (what == rect::LATEST) 
    asr = latest_leaf ();
	else if (what == rect::EARLIEST)
		asr = earliest_leaf ();
	else {
		asr = balled_leaf ();
	}
  return asr;
}


void mondrian::bg () {

	remove_finished_notes ();

	if (quit) return;

  if (splitting_rects) {
    for (split_iterator m = lsd.begin (), n = lsd.end (); m != n;) {
      if (multi_split_rect (*m)) {
        m = lsd.erase (m); 
        n = lsd.end ();
        splitting_rects = !(m == n);
      } else ++m;
    }
  } else {

		if (delete_all_rects) {
			if (nleaves > 1) {
				rect* pl = pick_leaf (delete_leaf);
				int good_rect = !bad_rect (pl);
				if (good_rect) delete_rect ( make_finding (pl) );
			} else {
				delete_all_rects = 0;
			}
		} else {
			if (auto_del_rect.active ) {
				if (auto_del_rect (ui_clk())) {
					if (!editing_edge && !editing_slit) {
						rect* adr = pick_leaf (delete_leaf);
						if (!bad_rect (adr)) delete_rect (make_finding (adr));
					}
				}
			}
		}

    if (auto_split_rect.active) { // auto split box?

      if (auto_split_rect (ui_clk())) { // reached time to auto split

        // pick box to split
        rect* asr = pick_leaf (split_leaf);

        int split_orient;
        if (auto_split_orient == split::BOTH) { 
          rnd<float> rd (split::HORIZONTAL, split::VERTICAL); // horizontal or vertical at random
          split_orient = rd () + 0.5; 
        } else 
          split_orient = auto_split_orient;

        if (auto_split_at == split::NOTES) { // split at notes of the scale
          pair<int, int> note_ids; 
          if (get_note_ids (note_ids, asr, split_orient)) {
            rnd<float> rd (note_ids.first, note_ids.second - 1);
            int i = rd() + 0.5;
            split_data sd (i, i + 1, auto_split_at, split_orient, asr);
            lsd.push_back (sd); // split in bg!
            splitting_rects = 1;
          }
        } else { // split at any microtone
          float delta;
          float low, high;
          if (split_orient == split::HORIZONTAL) {
            low = asr->extents.bottom;
            high = asr->extents.top;
            delta = asr->extents.height;
          } else {
            low = asr->extents.left;
            high = asr->extents.right;
            delta = asr->extents.width;
          }
          float _2_min_split_size = 2 * min_split_size;
          if (delta > _2_min_split_size) {
            rnd<float> rd (min_split_size, max (min_split_size, delta - min_split_size));
            float sz = rd ();
            int cl = 0;
            float pos = low + sz;
            cl = clamp (low, pos, high);
            if (!cl) {
              split_data sd (0, 1, auto_split_at, split_orient, asr);
              sd.sz = sz;
              lsd.push_back (sd); // split in bg!
              splitting_rects = 1;
            }
          }
        }
      }
    }

  }

	for (slit_iterator i = slits.begin (), j = slits.end (); i != j; ++i) (*i)->eval_anim ();

  for (balls_iterator p = balls.begin (), q = balls.end (); p != q; ++p) {
		ball* b = *p;
		b->eval_ops ();
		b->update ();
	}

}

int mondrian::render_audio (float* L, float* R) {
	int ret = 0;
	balls_iterator bter = triggering_balls.begin ();
  for (note_iterator i = triggered_notes.begin (), j = triggered_notes.end (); i != j; ++i, ++bter) {
    triggered_note& ti = *i;
		ball* bi = *bter;
    if (ti.state != triggered_note::FINISHED) {
      ti.eval (L, R, aout.result, aout.vol, aout.samples_per_channel, bi->attack_time, bi->decay_time, _gotomax);
      ti.eval (bi->attack_time);
			ret += ti.player.mixer.active;
    }
  }
	return ret;
}

void mondrian::remove_finished_notes () {
	balls_iterator bter = triggering_balls.begin ();
  for (note_iterator i = triggered_notes.begin (); i != triggered_notes.end();) {
    triggered_note& ti = *i;
    if (ti.state == triggered_note::FINISHED) {
      i = triggered_notes.erase (i);
			ball* b = *bter; if (--b->num_notes <= 0 && b->del) delete b;
			bter = triggering_balls.erase (bter);
      --num_triggered_notes;
			print_num_triggered_notes ();
    } else {
			++i;
			++bter;
		}
  }
}

ball* mondrian::get_one_selected_ball () {
	if (num_selected_balls == 1) return *selected_balls.begin (); else return 0;
}

int mondrian::change_param (float& param, float value, float& recent) {
	static const float minval = 0.0f;
	int atminval = (param == minval);
	param += value; 
	int ret = 1;
	if (param < minval) {
		param = minval;
		if (atminval) ret = 0;
	} 
	recent = param;
	return ret;
}

void mondrian::change_attack_time_kb (spinner<float>& s) { 
	int n = 0;
	list<ball*>& _balls = get_balls ();
	for (balls_iterator p = _balls.begin (), q = _balls.end (); p != q; ++p) {
		ball* b = *p;
		change_param (b->attack_time, s(), ball::recent_attack_time);
		sprintf (BUFFER, "Ball %d, attack time = %0.3f secs", ++n, b->attack_time);
		cons << BUFFER << eol;
	}

}

void mondrian::change_decay_time_kb (spinner<float>& s) {
	list<ball*>& _balls = get_balls ();
	int n = 0;
	for (balls_iterator p = _balls.begin (), q = _balls.end (); p != q; ++p) {
		ball* b = *p;
		change_param (b->decay_time, s(), ball::recent_decay_time);
		sprintf (BUFFER, "Ball %d, decay time = %0.3f secs", ++n, b->decay_time);
		cons << BUFFER << eol;
	}
}

void mondrian::draw_balls () {

	int num_balls_ = num_balls;
	if (new_ball) ++num_balls_;
  if (num_balls_ == 0) return;
  if (num_balls_ > n_pts) {
    if (pts) delete[] pts;
		if (pts_d) delete[] pts_d;
    if (pts_clr) delete[] pts_clr;
    pts = new float [2 * num_balls_];
		pts_d = new float [4 * num_balls_];
    pts_clr = new float [3 * num_balls_];
    n_pts = num_balls_;
  }

  int i = 0, j = 0, k = 0;
  float r, g, cb;

	static const int S = 25;
  for (balls_iterator p = balls.begin (), q = balls.end (); p != q; ++p) {
    ball* b = *p;
    if (b->select) {r = 0; g = 1; cb = 0;} else {r = b->r; g = b->g; cb = b->b;}
		if (draw_ball.trails) {
			glColor3f (r, g, cb); 
			b->trail.draw ();
		}

		// position
    pts[i++] = b->x; 
    pts[i++] = b->y;

		// color
    pts_clr[j++]=r;
    pts_clr[j++]=g;
    pts_clr[j++]=cb;

		// direction
		pts_d[k++]=b->x+S*b->vx;
		pts_d[k++]=b->y+S*b->vy;
		pts_d[k++]=b->x;
		pts_d[k++]=b->y;

  }

	if (new_ball) {
		pts[i++]=new_ball->x;
		pts[i++]=new_ball->y;
		pts_clr[j++]=new_ball->r;
		pts_clr[j++]=new_ball->g;
		pts_clr[j++]=new_ball->b;
	}

	if (draw_ball.heading) {
		glColor3f (1, 1, 1);
		glVertexPointer (2, GL_FLOAT, 0, pts_d);
		glDrawArrays (GL_LINES, 0, 2 * num_balls);
	}

	if (draw_ball.position) {
		glPointSize (5);
		glEnableClientState (GL_COLOR_ARRAY);
		glColorPointer (3, GL_FLOAT, 0, pts_clr);
		glVertexPointer (2, GL_FLOAT, 0, pts);
		glDrawArrays (GL_POINTS, 0, num_balls_);
		glDisableClientState (GL_COLOR_ARRAY);
		glPointSize (1);
	}

}

void mondrian::draw_notes () {

	int n = poly.points;
	float xy [2*n];

	if (label_hz_vol) tb_hz_vol.clear ();

  for (note_iterator i = triggered_notes.begin (), j = triggered_notes.end (); i != j; ++i) {
		triggered_note& ti = *i;
		float r = poly.radius * ti.volume.now / ti.volume.max;
		if (draw__notes) {
			for (int i = 0, j = 0; i < n; ++i) {
				xy[j++]=ti.x+r*poly.coss[i];
				xy[j++]=ti.y+r*poly.sinn[i];
			}
			if (scope.limit) glColor3f (1.0, 0.1, 0.1); else glColor3f (ti.r, ti.g, ti.b);
			glVertexPointer (2, GL_FLOAT, 0, xy);
			glDrawArrays (GL_LINE_LOOP, 0, n);
		} 

		if (label_hz_vol) {
			xy[0]=ti.x+r*poly.coss[0];
			xy[1]=ti.y+r*poly.sinn[0];
			float wtx = xy[0] + gutter, wty = xy[1] + gutter;
			float clr = ti.volume.now * 1.0 / ti.volume.max;
			sprintf (BUFFER, "%0.3f @ %03d%%", ti.start_hz, int(clr * 100.0 + 0.5));
			tb_hz_vol.add (text (BUFFER, wtx, wty, ti.r, ti.g, ti.b));
		}

	}

	if (label_hz_vol) {
		tb_hz_vol.refresh (this);
		tb_hz_vol.draw ();
	}
}

void mondrian::split_balls (rect* P, rect* C1, rect* C2) {
  list<ball*>& P_balls = P->balls;
  for (balls_iterator p = P_balls.begin (); p != P_balls.end ();) {
    ball* b = *p;
    if (inbox (C1->extents, b->x, b->y)) {
      C1->balls.push_back (b); 
      b->R = C1;
    } else {
      C2->balls.push_back (b);
      b->R = C2;
    }
    p = P_balls.erase (p);
  }
}

void mondrian::change_speed (spinner<float>& s, float d) {
  list<ball*>& _balls = get_balls ();
	int n = 0;
  for (balls_iterator p = _balls.begin (), q = _balls.end (); p != q; ++p) {
    ball* b = *p;

		float& bv = b->V;
		bv += s(d);

    if (bv <= 0) bv = 0.0f;

		/*float& maxx = b->op_speed.max;
		float omaxx = maxx;
		maxx = max (maxx, bv);
		if ((num_selected_balls == 1) && (maxx != omaxx)) MENU.sp_max_speed.set_value (maxx);*/

    sprintf (BUFFER, "Ball %d, speed = %0.3f", ++n, bv);

    cons << YELLOW << BUFFER << eol;

  }
}

void mondrian::toggle_slit_anim () {
	int hs = 0;
	if (num_selected_slits == 0) hs = find_hit_slit (win.mousex, win.mousey);
  for (slit_iterator s = selected_slits.begin (), t = selected_slits.end (); s != t; ++s) {
		slit* si = *s;
		si->toggle_anim ();
	}
	if (hs) clear_selected<slit> (selected_slits, num_selected_slits, 0);
}

void mondrian::select_balls (const box<float>& rgn) {
	browse.clear ();
  if (SHIFT || CTRL) ; else clear_selected<ball> (selected_balls, num_selected_balls);
  for (balls_iterator p = balls.begin (), q = balls.end (); p != q; ++p) {
    ball* b = *p;
    if (inbox (rgn, b->x, b->y)) select_using_modifiers<ball> (b, CTRL, selected_balls, num_selected_balls);
  }
	after_selection ();
}

void mondrian::after_selection () {
	if (sel_tar == SELECT_BALLS) {
		if (num_selected_balls) {
      cons << GREEN << "Selected " << num_selected_balls << " balls" << eol;
			if (num_selected_balls == 1) {
				ball* bsel = *selected_balls.begin();
        bsel->print ();
				MENU.set_ball_ops (bsel);
				if (browse.n) {
					sprintf (BUFFER, " Ball %d of %d", browse.which+1, browse.n);
					MENU.ol_browse_balls.set_text (BUFFER);
				}
			} else {
				if (browse.n == 0) {
					browse.balls.resize (num_selected_balls);
					copy (selected_balls.begin (), selected_balls.end (), browse.balls.begin ());
					browse.n = browse.balls.size ();
					browse.last = browse.n - 1;
					browse.which = -1;
				}
				MENU.ol_browse_balls.set_text (" Click arrow to load ball");
			}
		} else {
			cons << RED << "No balls selected" << eol;
			MENU.clear_ball_ops ();
		}
	} else {
		if (num_selected_slits) 
			cons << GREEN << "Selected " << num_selected_slits << " slits" << eol;
		else 
			cons << RED << "No slits selected" << eol;
	}
}


void mondrian::select_box_balls () {
  finding f; find (root, win.mousex, win.mousey, f);
	rect* R = f.found;
  if (R) {
    list<ball*>& b_balls = R->balls;
		browse.clear ();
    if (SHIFT || CTRL); else clear_selected<ball> (selected_balls, num_selected_balls);
    for (balls_iterator p = b_balls.begin (), q = b_balls.end (); p != q; ++p) select_using_modifiers<ball> (*p, CTRL, selected_balls, num_selected_balls);
		after_selection ();
  }
}

void mondrian::select_box_slits () {
	finding f; find (root, win.mousex, win.mousey, f);
	rect* R = f.found;
  if (R) {
		if (SHIFT || CTRL); else clear_selected<slit> (selected_slits, num_selected_slits);
		for (int i = 0; i < 4; ++i) {
			list<slit*>& slits = R->slits[i];
			for (slit_iterator s = slits.begin (), t = slits.end (); s != t; ++s) select_using_modifiers<slit> (*s, CTRL, selected_slits, num_selected_slits);
		}
		after_selection ();
	}
}

void mondrian::delete_ball (ball* b) {
	if (::erase (balls, b)) --num_balls;
	if (::erase (selected_balls, b)) --num_selected_balls;
	if (::erase (browse.balls, b)) {
		--browse.n;
		if (browse.n) {
			browse.last = browse.n - 1;
			MENU.ol_browse_balls.set_text (" Click arrow to load ball");
		} else {
			browse.last = 0;
		}
	}

	b->R->erase (b);
	if (b->num_notes == 0) delete b; else b->del = 1;
}

void mondrian::delete_selected_balls () {
  int nb = num_balls;
	while (num_selected_balls) delete_ball (*selected_balls.begin());
  cons << YELLOW << "Deleted " << (nb - num_balls) << " balls" << eol;
}


void mondrian::locate_ball (ball* b) {
	if (!inbox (b->R->extents, b->x, b->y)) {
		locator:
		finding fnd; find (root, b->x, b->y, fnd);
		rect* nbr = fnd.found;
		if (nbr) {
			b->R->erase (b);
			b->R = nbr;
			b->R->balls.push_back (b);
		} else {
			clamp<float>(root->extents.left, b->x, root->extents.right);
			clamp<float>(root->extents.bottom, b->y, root->extents.top);
			goto locator;
		}
	}
}

void mondrian::move_balls (float dx, float dy) {
  for (balls_iterator p = selected_balls.begin (), q = selected_balls.end (); p != q; ++p) {
    ball* b = *p;
    b->x += dx;
    b->y += dy;
		locate_ball (b);
  }
}

void mondrian::toggle_flag (int& flag, const string& poz, const string& neg) {
  flag = !flag;
  if (flag) 
		cons.set_cmd_line (poz, GREEN);
  else {
		cons.set_cmd_line ("");
		cons << YELLOW << neg << eol;
	}
}

void mondrian::do_add_balls (int type) {
	adding_balls = 1; 
	added_ball_type = type;
	if (new_ball) new_ball->set_type (added_ball_type);
	string bt (ball::types_str[added_ball_type]); 
	stringstream ss; ss << "Click, drag and release to launch " << bt << ". ESC to stop.";
	cons.set_cmd_line (ss.str(), GREEN);
	MENU.ol_ball_types.set_text (bt);
}

void mondrian::do_move_balls () {
	if (stop_moving_balls()) return;
  if (num_selected_balls) {
    stop_adding_balls ();
		toggle_flag (moving_balls, "Just move your mouse to move the balls. ESC to stop.");
  }
  else 
    cons << YELLOW << "Please select some balls!" << eol;
}

void mondrian::delete_all_balls () {
	select_all<ball> (balls, selected_balls, num_selected_balls);
  delete_selected_balls ();
}

void mondrian::make_notes () {
  vector<string> notes;
	extern int NOTATION;
  extern int NUM_INTERVALS;
  extern const char* WESTERN_FLAT [];
  extern vector<string> INTERVAL_NAMES;
  extern vector<float> INTERVAL_VALUES;
  extern map<string,string> INT2IND;
	int western = scaleinfo.western;
  if (NOTATION == WESTERN) {
    for (int i = 0; i < NUM_INTERVALS; ++i) notes.push_back (WESTERN_FLAT[(western + i) % 12]);
  } else if (NOTATION == NUMERIC) {
    for (int i = 0; i < NUM_INTERVALS; ++i) notes.push_back (INTERVAL_NAMES[i]);
  } else {
    for (int i = 0; i < NUM_INTERVALS; ++i) notes.push_back (INT2IND[INTERVAL_NAMES[i]]);
  }
  int lh = line_height, lh1 = 2 * lh, mcw = fnt.charwidth.max; 
  box<float>& br = root->extents;
  float x = br.left, y1 = br.bottom - lh1, y2 = br.top + lh;
  int ks = 10, ksx = ks + 7;
  float y = br.bottom, x1 = br.left - ksx - 2 * mcw, x2 = br.right + ks + mcw;

	static const int nsides = 4, verts_per_mark = 2;
  int _n_mrk = NUM_INTERVALS * nsides * verts_per_mark; 
  if (_n_mrk > n_mrk) {
    if (mrk) delete[] mrk;
    n_mrk = _n_mrk;
    mrk = new float [2 * n_mrk];
  }
  int k = 0;
  tb.clear ();
  for (int i = 0; i < NUM_INTERVALS; ++i) {
    float iv = INTERVAL_VALUES[i] - 1;
    float xn = x + iv * br.width;
    float yn = y + iv * br.height;
    const string& ni = notes[i];
    mrk[k++]=xn;
    mrk[k++]=br.bottom;
    mrk[k++]=xn;
    mrk[k++]=br.bottom-ks;
    mrk[k++]=br.right;
    mrk[k++]=yn;
    mrk[k++]=br.right+ks;
    mrk[k++]=yn;
    mrk[k++]=xn;
    mrk[k++]=br.top;
    mrk[k++]=xn;
    mrk[k++]=br.top+ks;
    mrk[k++]=br.left;
    mrk[k++]=yn;
    mrk[k++]=br.left-ks;
    mrk[k++]=yn;
    tb.add (text (ni, xn, y1));
    yn -= fnt.lift;
    tb.add (text (ni, x1, yn));
    tb.add (text (ni, x2, yn));
    tb.add (text (ni, xn, y1));
    tb.add (text (ni, xn, y2));
  }
}


void mondrian::save_boxes (ofstream& f, rect* R, string& id) {
	R->id = id;
	rect* child1 = R->child1;
	rect* child2 = R->child2;
	box<float>& e = R->extents;
	rect* P = R->parent;
	string c1 ("-"), c2 ("-");
	if (child1 != 0) {
		string L (id + 'L'), R(id + 'R');
		save_boxes (f, child1, L);
		save_boxes (f, child2, R);
		c1 = child1->id;
		c2 = child2->id;
	}
	f << "box ";
	if (P) f << P->id; else f << "0";
	f << spc << R->id << spc << e.left << spc << e.bottom << spc << e.right << spc << e.top << spc << R->split << spc << R->r << spc << R->g << spc << R->b << spc << c1 << spc << c2 << spc;
	int leaf = R->is_leaf (); 
	f << " leaf " << leaf << spc; 
	if (leaf) { // only leaf can have slits
		int total_slits = R->total_slits;
		f << "slits " << total_slits << spc;
		// box can be leaf and not have any slits
		if (total_slits) { // this leaf has slits
			for (int i = 0; i < rect::nedges; ++i) {
				int& nse = R->nslits[i];
				f << nse << spc;
				// not all of the leaf's edges need have slits
				if (nse) { // this edge has slits
					list<slit*>& si = R->slits[i];
					for (slit_iterator p = si.begin (), q = si.end (); p != q; ++p) {
						f << get_index_of_slit (*p) << spc;
					}
				}
			}
		}
	}
	f << endl;
}


box_from_disk_t mondrian::get_box_from_disk (list<box_from_disk_t>& boxes, const string& id) {
	for (box_from_disk_iterator p = boxes.begin (), q = boxes.end (); p != q; ++p) {
		box_from_disk_t& rp = *p;
		if (rp.R->id == id) {
			return rp;
		}
	}
	return box_from_disk_t ();
}

void mondrian::load_boxes_and_balls () {
	list<box_from_disk_t> boxes;
	box_from_disk_t box_from_disk;
	string fname ("mondrian.data");
  ifstream f ((user_data_dir + fname).c_str (), ios::in);
	if (!f) {
		dlog << "!!! Failed loading boxes & balls from file: " << fname << " !!!" << endl;
		return;
	}
	while (!f.eof()) {
		string what, ignore;
		f >> what;
		if (what == "box") { // load box
			rect *R = new rect;
			box_from_disk.R = R;
			f >> box_from_disk.parent;
			f >> R->id;
			float l, b, r, t; 
			f >> l >> b >> r >> t;
			R->extents (l, b, r, t);
			f >> R->split;
			f >> R->r >> R->g >> R->b;
			f >> box_from_disk.child1 >> box_from_disk.child2;
			int is_leaf; f >> ignore >> is_leaf;
			if (is_leaf) {
        add_leaf (R);
				f >> ignore >> R->total_slits; 
				if (R->total_slits) { // can have slits
					for (int i = 0; i < rect::nedges; ++i) {
						list<slit*>& ls = R->slits [i]; // slits on edge
						int& nse = R->nslits [i]; // number of slits on edge
						f >> nse;
						if (nse) {
							for (int p = 0; p < nse; ++p) {
								int ix; f >> ix; // get slit index
								ls.push_back ((slit *) ix); // will resolve to slit* after slits are loaded
							}
						}
					}
				}
			}
			boxes.push_back (box_from_disk);
			if (box_from_disk.parent == "0") root = R;
		} else if (what == "ball") {

			ball* b = new ball;
			f >> b->x >> b->y >> b->V >> b->vx >> b->vy; b->set_velocity (b->V * b->vx, b->V * b->vy);
			string rid; f >> rid >> b->frozen >> b->pitch_mult >> b->mod >> b->r >> b->g >> b->b >> b->attack_time >> b->decay_time;
			int t; f >> t; b->trail.total = t; trail_t::alloc (t);
			f >> b->auto_rotate >> b->dtheta >> b->type >> b->vol_mult >> b->trig_what;

			float minn, maxx;
			f >> b->op_turn.alarm.active >> b->op_turn.alarm.triggert;
			f >> minn >> maxx; b->op_turn.rd.set (minn, maxx);

			f >> b->op_turn.vx >> b->op_turn.vy >> b->op_turn.angle;
			float turn_alpha; f >> turn_alpha;

			f >> b->op_speed.alarm.active >> b->op_speed.alarm.triggert; 
			f >> minn >> maxx; b->op_speed.rd.set (minn, maxx);
			f >> b->op_speed.max;
			f >> b->op_speed.start >> b->op_speed.delta;
			float speed_alpha; f >> speed_alpha;


			f >> b->op_teleport.alarm.active >> b->op_teleport.alarm.triggert;
			f >> b->op_teleport.radius;

			f >> b->op_clone.alarm.active >> b->op_clone.alarm.triggert;
			f >> b->op_clone.n >> b->op_clone.max >> b->op_clone.offset >> b->op_clone.clone_can_clone;

			f >> b->op_transform.alarm.active >> b->op_transform.alarm.triggert;

			ball_op* bops [] = {&b->op_turn, &b->op_speed, &b->op_teleport, &b->op_clone, &b->op_transform};
			for (int i = 0; i < ball_op::NUM_OPS; ++i) {
				ball_op* bopi = bops[i];
				if (bopi->alarm.active) bopi->alarm.start ();
			}

			b->op_turn.alarm.startt += (turn_alpha * b->op_turn.alarm.triggert);
			b->op_speed.alarm.startt += (speed_alpha * b->op_speed.alarm.triggert);

			rect* R = get_box_from_disk (boxes, rid).R;
			b->R = R;
			R->balls.push_back (b);
			balls.push_back (b);
			++num_balls;
		} else if (what == "rules") {
			for (int i = 0; i < 3; ++i) f >> Transform::rules[i];
		} else if (what == "poly") {
			f >> ignore >> poly.radius >> ignore >> poly.points;
			set_note_poly_points (poly.points); cons.clear ();
		} else if (what == "slits") {
			load_slits (f, boxes);
		} else if (what == "max_balls") {
			f >> Clone::max_balls;
		} else break;
	}

	for (box_from_disk_iterator i = boxes.begin (), j = boxes.end (); i != j; ++i) {
		box_from_disk_t& ri = *i;
		rect* R = ri.R;
		rect* P = get_box_from_disk (boxes, ri.parent).R;
		rect* C1 = get_box_from_disk (boxes, ri.child1).R;
		rect* C2 = get_box_from_disk (boxes, ri.child2).R;
		R->parent = P;
		R->child1 = C1;
		R->child2 = C2;
		R->calc_intervals ();
	}

	for (box_iterator i = leaves.begin (), j = leaves.end (); i != j; ++i) {
		rect* R = *i;
		if (R->total_slits) {
			for (int p = 0; p < rect::nedges; ++p) {
				list<slit*>& ls = R->slits[p];
				for (slit_iterator r = ls.begin (), s = ls.end (); r != s; ++r) {
					int ix = (uintptr_t) *r;
					*r = get_slit_from_index (ix);
				}
			}
		}
	}

}

void mondrian::save_balls (ofstream& f) {
	f << "max_balls " << Clone::max_balls << endl;
	for (balls_iterator i = balls.begin (), j = balls.end (); i != j; ++i) {
		ball* pb = *i;
		ball& b = *pb;
		f << "ball " << b.x << spc << b.y << spc << b.V << spc << b.vx << spc << b.vy << spc << b.R->id << spc << b.frozen << spc << b.pitch_mult << spc << b.mod << spc << b.r << spc << b.g << spc << b.b << spc << b.attack_time << spc << b.decay_time << spc << b.trail.total <<  spc << b.auto_rotate << spc << b.dtheta << spc << b.type << spc << b.vol_mult << spc << b.trig_what << spc << b.op_turn.alarm.active << spc << b.op_turn.alarm.triggert << spc << b.op_turn.rd.min << spc << b.op_turn.rd.max << spc << b.op_turn.vx << spc << b.op_turn.vy << spc << b.op_turn.angle << spc << b.op_turn.alarm () << spc << b.op_speed.alarm.active << spc << b.op_speed.alarm.triggert << spc << b.op_speed.rd.min << spc << b.op_speed.rd.max << spc << b.op_speed.max << spc << b.op_speed.start << spc << b.op_speed.delta << spc << b.op_speed.alarm () << spc << b.op_teleport.alarm.active << spc << b.op_teleport.alarm.triggert << spc << b.op_teleport.radius << spc << b.op_clone.alarm.active << spc << b.op_clone.alarm.triggert << spc << b.op_clone.n << spc << b.op_clone.max << spc << b.op_clone.offset << spc << b.op_clone.clone_can_clone << spc << b.op_transform.alarm.active << spc << b.op_transform.alarm.triggert << endl;
	}

	f << "rules ";
	for (int i = 0; i < 3; ++i) f << Transform::rules[i] << spc;
	f << endl;

}

void mondrian::save_slits (ofstream& f) {
	f << "slits " << num_slits << endl;
	for (slit_iterator i = slits.begin (), j = slits.end (); i != j; ++i) {
		slit* si = *i;
		for (int i = 0; i < 2; ++i) {
			rect* ri = si->boxes[i];
			int ei = si->edges[i];
			f << ri->id << spc << ei << spc; // nb: call after save_boxes for proper rect id
		}
		int _fdr = 0;
		if (si->fdr) {
			si->start = si->anim_start;
			si->end = si->anim_end;
			_fdr = 1;
		}
		f << si->type << spc << si->start << spc << si->end << spc << si->mid << spc;

		if (_fdr) {
			f << _fdr << spc;
			si->fdr->save (f);
		} else {
			f << _fdr;
		}
		f << endl;
	}
	for (slit_iterator i = slits.begin (), j = slits.end (); i != j; ++i) delete *i; // free mem of all slits
}

void mondrian::load_slits (ifstream& f, list<box_from_disk_t>& boxes) {
	f >> num_slits;
	string r0, r1;
	for (int i = 0; i < num_slits; ++i) {
		slit* s = new slit;
		f >> r0 >> s->edges[0] >> r1 >> s->edges[1];
		s->boxes[0] = get_box_from_disk (boxes, r0).R;
		s->boxes[1] = get_box_from_disk (boxes, r1).R;
		f >> s->type >> s->start >> s->end >> s->mid;
		int fdr_exists; f >> fdr_exists;
		if (fdr_exists) {
			s->toggle_anim ();
			s->fdr->load (f);
		}
		slits.push_back (s);
	}
}

void mondrian::toggle_balls_type (int T) {
	static const char* names [] = {" bouncers ", " wreckers ", " healers "};
	if (num_selected_balls) {
  	list<ball*>& _balls = get_balls ();
		int t = 0, b = 0;
		int Tt;
  	for (balls_iterator p = _balls.begin (), q = _balls.end (); p != q; ++p) {
			ball* pb = *p;
			if (pb->type == T) {
				Tt = ball::BOUNCER;
				++b;
			}
			else {
				Tt = T;
				++t;
			}
			pb->set_type (Tt);
		}
		cons << GREEN << "Made " << t << names[T] << "and " << b << names[0] << eol; 
	} else cons << YELLOW << "Please select some balls!" << eol;
}

int mondrian::modulate_balls (int w) {
	if (num_selected_balls) {
  	list<ball*>& _balls = get_balls ();
		int b = 0;
  	for (balls_iterator p = _balls.begin (), q = _balls.end (); p != q; ++p) {
			ball* pb = *p;
			pb->pitch_mult *= pow (octave_shift.sol.lasty, w);
      ball::recent_pitch_mult = pb->pitch_mult;
      sprintf (BUFFER, "Ball %d, pitch_multiplier = %0.3f", ++b, pb->pitch_mult);
      cons << BUFFER << eol;
			pb->mod += w;
			pb->color_using_modulation ();
		}
	}
	return num_selected_balls;
}

/*void mondrian::rotate_velocity (spinner<int>& s) {
	list<ball*>& _balls = get_balls ();
	for (balls_iterator p = _balls.begin (), q = _balls.end (); p != q; ++p) {
		ball* pb = *p;
		pb->rotate_velocity (dir);
	}
}*/

void mondrian::rotate_velocity (int dir) {
 	list<ball*>& _balls = get_balls ();
 	for (balls_iterator p = _balls.begin (), q = _balls.end (); p != q; ++p) {
		ball* pb = *p;
		pb->rotate_velocity (dir);
	}
}

void mondrian::change_ball_dtheta (int dir) {
	int n = 0;
	list<ball*>& _balls = get_balls ();
	for (balls_iterator p = _balls.begin (), q = _balls.end (); p != q; ++p) {
		ball* pb = *p;
		pb->dtheta += (dir * PI_BY_180);
		if (pb->dtheta <= 0) pb->dtheta = 0;
    sprintf (BUFFER, "Ball %d, delta rotate velocity = %0.3f", ++n, pb->dtheta);
    cons << BUFFER << eol;
	}
}

void mondrian::change_trail_size (spinner<int>& s) {
	int n = 0;
 	list<ball*>& _balls = get_balls ();
 	for (balls_iterator p = _balls.begin (), q = _balls.end (); p != q; ++p) {
		ball* pb = *p;
		pb->trail.change (s());
		cons << YELLOW << "Ball " << ++n << ", trail points = " << pb->trail.total << eol;
	}
}

int mondrian::set_note_poly_points (int p) {
	int ret = 1;
	poly.points = p;
	if (poly.points < 2) {
		poly.points = 2;
		ret = 0;
	}
	poly.coss.resize (poly.points);
	poly.sinn.resize (poly.points);
	int n = poly.points;
	extern const float TWO_PI;
	float dtheta = TWO_PI / n;
	float theta = TWO_PI / 4.0f;
	for (int i = 0; i < n; ++i) {
		poly.coss[i] = cos(theta);
		poly.sinn[i] = sin(theta);
		theta += dtheta;
	}
	cons << YELLOW << "Note Polygon points = " << poly.points << eol;
	return ret;
}

int mondrian::set_note_poly_radius (float r) {
	int ret = 1;
	poly.radius = r;
	if (poly.radius < 0) {
		poly.radius = 0;
		ret = 0;
	}
  sprintf (BUFFER, "Note polygon radius = %0.3f", poly.radius);
  cons << YELLOW << BUFFER << eol;
	return ret;
}

void mondrian::flip_velocity () {
 	list<ball*>& _balls = get_balls ();
 	for (balls_iterator p = _balls.begin (), q = _balls.end (); p != q; ++p) {
		ball* pb = *p;
		pb->vx = -pb->vx;
		pb->vy = -pb->vy;
		pb->calc_velocity_slope ();
		if (pb->auto_rotate) {
			pb->auto_rotate = -pb->auto_rotate;
			rotate_velocity (pb->auto_rotate);
		}
	}
}

void mondrian::set_auto_rotate (int ar) {
 	list<ball*>& _balls = get_balls ();
 	for (balls_iterator p = _balls.begin (), q = _balls.end (); p != q; ++p) (*p)->auto_rotate = ar;
}

void mondrian::toggle_auto_rotate (int ar) {
 	list<ball*>& _balls = get_balls ();
 	for (balls_iterator p = _balls.begin (), q = _balls.end (); p != q; ++p) {
		ball* pb = *p;
		if (pb->auto_rotate) pb->auto_rotate = 0; else pb->auto_rotate = ar;
	}
}

void mondrian::tonic_changed () {calc_visual_params ();}
void mondrian::scale_loaded () {}
void mondrian::scale_changed () {}

void mondrian::remove_slit (slit* s) {

	for (int i = 0; i < 2; ++i) { // slit is in 2 boxes
		rect* ri = s->boxes[i]; // box
		int ei = s->edges[i]; // edge
		int& nse = ri->nslits[ei]; // num slits on edge
		list<slit*>& si = ri->slits[ei]; // slits of edge
		if (erase (si, s)) { // erase slit
			--nse;
			--ri->total_slits;
		}
	}

	if (s == slit_lip.slitt) {
		if (editing_slit) toggle_flag (editing_slit, "", "Stopped editing deleted slit");
		slit_lip.clear ();
	}

	if (erase (slits, s)) --num_slits; // erase from global slits list
	if (erase (selected_slits, s)) --num_selected_slits; // erase from selected slits list

	delete s; // free mem
	s = 0;
}

int mondrian::try_slitting () {
	if (slitting == NOTHING) return 0; 
  else {
		add_remove_slit (win.mousex, win.mousey);
		return mon_selector.abort ();
	}
	return 0;
}

int mondrian::stop_adding_balls () {
  if (adding_balls) {
		stringstream ss; ss << "Stopped adding " << ball::types_str [added_ball_type];
		toggle_flag (adding_balls, "", ss.str());
		started_making_ball = 0;
		if (new_ball) delete new_ball;
		new_ball = 0;
		return 1;
	}
	return 0;
}

int mondrian::stop_moving_balls () {
  if (moving_balls) {
		toggle_flag (moving_balls, "", "Stopped moving balls");
		mon_selector.abort ();
		return 1;
	}
	return 0;
}


int mondrian::stop_slitting () {
	int ret = draw_slit_cutter;
	if (slitting) {
		toggle_flag (slitting, "", "Stopped slitting"); 
		ret = 1;
	}
	draw_slit_cutter = 0;
	return ret;
}

int mondrian::stop_editing_slit () {
	if (editing_slit) {
		toggle_flag (editing_slit, "", "Stopped editing slit.");
		if (slit_lip.slitt->is_too_small()) {
			cons << YELLOW << "Closed slit because it was too small" << eol;
			remove_slit (slit_lip.slitt);
		}
		mon_selector.abort();
		return 1;
	}
	return 0;
}

int mondrian::stop_editing_edge () {
	if (editing_edge) {
		hit = 0;
		edge = edge::NONE;
		toggle_flag (editing_edge, "", "Stopped editing edge.");
		mon_selector.abort();
		return 1;
	}
	return 0;
}

int mondrian::stop_doing_stuff () {
	int result = 0;
  result |= stop_adding_balls ();
	result |= stop_moving_balls ();
	result |= stop_slitting ();
	result |= stop_editing_slit ();
	result |= stop_editing_edge ();
	return result;
}

void mondrian::select_type (int t) {
	browse.clear ();
	clear_selected<ball> (selected_balls, num_selected_balls);
	for (balls_iterator p = balls.begin (), q = balls.end(); p != q; ++p) {
		ball* b = *p;
		if (b->type == t) {
			b->select = 1;
			selected_balls.push_back (b);
			++num_selected_balls;
		}
	}
	after_selection ();
}

void mondrian::switch_balls_type () {
	// all balls: wreckers < > healers
	for (balls_iterator p = balls.begin (), q = balls.end(); p != q; ++p) {
		ball* b = *p;
		if (b->type != ball::BOUNCER) {
			if (b->type == ball::WRECKER) 
				b->set_type (ball::HEALER);
			else 
				b->set_type (ball::WRECKER);
		}
	}
}

void mondrian::start_slitting () {
	if (slitting) {
		stop_slitting ();
		return;
	}
	cons.set_cmd_line ("Click on edge of a box to add or remove slit. ESC to stop.", GREEN); 
	draw_slit_cutter = slitting = JUST_SLIT;
}

void mondrian::change_slit_size (spinner<float>& s) {
	slit::HALF_SIZE += s();
	if (slit::HALF_SIZE < slit::MIN_HALF_SIZE) slit::HALF_SIZE = slit::MIN_HALF_SIZE;
	draw_slit_cutter = 1;
	cons << GREEN << "Default slit size = " << slit::HALF_SIZE << eol;
}

void mondrian::change_slit_anim_time (spinner<float>& sp) {
	int hs = 0;
	if (num_selected_slits == 0) hs = find_hit_slit (win.mousex, win.mousey);
	int i = 0;
	for (slit_iterator s = selected_slits.begin (), t = selected_slits.end (); s != t; ++s) {
		slit* S = *s;
		if (S->fdr) {
			double& dt = S->fdr->delta_time;
			dt += sp(); 
			if (dt < 0) dt = 0;
			S->animt = dt;
      sprintf (BUFFER, "Slit %d, open|close time = %0.3f", ++i, dt);
      cons << GREEN << BUFFER << eol;
		} else S->toggle_anim ();
	}
	if (hs) clear_selected<slit> (selected_slits, num_selected_slits, 0);
}

void mondrian::remove_slits (rect* R) {
	if (R->total_slits) {
		for (int e = 0; e < rect::nedges; ++e) remove_slits_on_edge (R, e);
	}
}

void mondrian::remove_slits_on_edge (rect* R, int e) {
	int nse = R->nslits [e];
	if (nse) {
		list<slit*>& slits = R->slits[e];
		while (slits.begin () != slits.end ()) {
			slit* sf = slits.front ();
			remove_slit (sf);
		}
	}
}

void mondrian::remove_slits_on_current_edge () {
	rect* hit = 0;
	for (box_iterator i = leaves.begin (), j = leaves.end (); i != j; ++i) {
		rect* li = *i;
		if (li->total_slits && inbox (li->extents, win.mousex, win.mousey)) { 
			hit = li;
			box<float>& bf = hit->extents;
			edge = bf.get_edge_hit (win.mousex, win.mousey, gutter2); // find edge hit
			if (edge != edge::NONE) {
				remove_slits_on_edge (hit, edge); // remove slits
				return;
			} else {
				cons << YELLOW << "Please get on an edge of this box to remove its slits" << eol;
				return;
			}
		}
	}
	cons << YELLOW << "Please get on an edge of a box that has slits" << eol;
}

void mondrian::remove_slits_on_current_box () { 
	for (box_iterator i = leaves.begin (), j = leaves.end (); i != j; ++i) {
		rect* li = *i;
		if (li->total_slits && inbox (li->extents, win.mousex, win.mousey)) { 
			remove_slits (li);
			return;
		}
	}
}

void mondrian::remove_slits_on_boxes_with_balls () {
	list<ball*>& balls = get_balls (0);
	map<rect*, int> m;
	for (balls_iterator p = balls.begin (), q = balls.end (); p != q; ++p) {
		ball* b = *p;
		m[b->R] = 1;
	}
	for (map<rect*, int>::iterator i = m.begin (), j = m.end (); i != j; ++i) remove_slits ((*i).first);
}

void mondrian::remove_all_slits () { // in all boxes
	for (box_iterator i = leaves.begin (), j = leaves.end (); i != j; ++i) {
		rect* li = *i;
		if (li->total_slits) remove_slits (li);
	}
}

void mondrian::remove_selected_slits () {
	while (1) {
		slit_iterator i = selected_slits.begin ();
		if (i != selected_slits.end ()) remove_slit (*i); else break;
	}
}

int mondrian::get_index_of_slit (slit* s) {
	int k = 0;
	for (slit_iterator i = slits.begin (), j = slits.end (); i != j; ++i, ++k) {
		if (*i == s) return k;
	}
	return -1;
}

slit* mondrian::get_slit_from_index (int q) {
	int p = 0;
	for (slit_iterator i = slits.begin (), j = slits.end (); i != j; ++i, ++p) {
		if (p == q) return *i;
	}
	return 0;
}

int mondrian::get_note_ids (pair<int, int>& nids, rect* R, int type) {

  pair<float, float> intervals;

  if (type == split::VERTICAL) 
		R->get_vertical_interval (intervals, root); 
  else 
		R->get_horizontal_interval (intervals, root);

  get_note_ids_from_intervals (nids, intervals);

	return ((nids.first != -1) && (nids.second != -1));

}

void mondrian::get_note_ids_from_intervals (pair<int, int>& note_ids, const pair<float, float>& intervals) {

	note_ids.first = note_ids.second = -1;
	int n = scaleinfo.num_notes;

	static const float E = 0.0001;

	for (int i = 0; i < n; ++i) {
		const string& name = scaleinfo.notes[i];
		float value = INTERVALS [name];
		float delta = value - intervals.first;
		if (delta > E) {
			note_ids.first = i;
			break;
		}
	}

	if (note_ids.first != -1) {
		for (int j = note_ids.first; j < n; ++j) {
			const string& name = scaleinfo.notes [j];
			float value = INTERVALS [name];
			float delta = intervals.second - value;
			if (delta > E) { 
				note_ids.second = j + 1;
			} else break;
		}
	}

}

int mondrian::multi_split_rect (split_data& sd) {
	if (sd.start < sd.end) {
    float pos;
    if (sd.split_at == split::NOTES) {
      int i = sd.start;
      const string& name = scaleinfo.notes[i];
      float value = INTERVALS [name];
      float delta = value - 1.0f;
      if (sd.split_type == split::VERTICAL) pos = root->extents.left + delta * root->extents.width;
      else pos = root->extents.bottom + delta * root->extents.height;
    } else {
      if (sd.split_type == split::VERTICAL) 
        pos = sd.R->extents.left + sd.sz; 
      else 
        pos = sd.R->extents.bottom + sd.sz;
    }
		split_rect (sd.split_type, pos, sd.R);
		if (sd.lr) sd.lr->push_back (sd.R->child1);
		sd.R = sd.R->child2;
		++sd.start;
    ++sd.nsplits;
		return 0;
	} else {
    if (sd.lr && sd.R && sd.nsplits) sd.lr->push_back (sd.R);
		if (sd.lis) sd.lis->split_over (sd);
		return 1;
	}
}

void mondrian::multi_split_rect (int n, int type, rect* B, list<rect*> *lr, split_listener* sl) { // split into n boxes
	if (B == 0) {
		B = box_under_cursor ();
		if (B == 0) {
			cons << YELLOW << "Cant split as you are outside all boxes :(" << eol;
			return;
		}
	}
	float amt;
	if (type == split::VERTICAL) amt = B->extents.width; else amt = B->extents.height;
	float sz = amt / n;
	if (sz < min_split_size) {
		cons << YELLOW << "Cannot split as the boxes will be too small [" << " < " << min_split_size << " ] :(" << eol;
    return;
  }

  // 1 split every frame
  split_data sd (0, n - 1, split::ANYWHERE, type, B, lr, sl, sz, n);
  lsd.push_back (sd);
  splitting_rects = 1;
}

void mondrian::multi_split_rect (int type, rect* B, list<rect*> *lr, split_listener* sl) { // split at notes
	if (B == 0) B = box_under_cursor ();
	if (B) {
    pair<int, int> note_ids;
    if (get_note_ids (note_ids, B, type)) {
			// 1 split every frame
			split_data sd (note_ids.first, note_ids.second, split::NOTES, type, B, lr, sl);
			lsd.push_back (sd);
			splitting_rects = 1;
		}
	} else {
		cons << YELLOW << "Cant split as you are outside all boxes :(" << eol;
	}
}

void mondrian::make_note_grid () {
	if (root->child1 == 0 && root->child2 == 0) { 
		rect* B = root;
    columns.clear ();
		multi_split_rect (split::VERTICAL, B, &columns, this); // split on notes along horizontal axis
	} else {
		cons << YELLOW << "Please delete all boxes, cannot create note grid :(" << eol;
	}
}

void mondrian::make_nxn_grid () {
	if (root->child1 == 0 && root->child2 == 0) {
		rect* B = root;
    columns.clear ();
		multi_split_rect (num_boxes, split::VERTICAL, B, &columns, this);
	} else {
		cons << YELLOW << "Please delete all boxes, cannot create box grid :(" << eol;
	}
}

void mondrian::split_over (split_data& sd) {
  if (sd.split_at == split::NOTES) {
    for (box_iterator s = sd.lr->begin (), t = sd.lr->end (); s != t; ++s) {
      multi_split_rect (split::HORIZONTAL, *s); // at notes
    }
  } else {
    for (box_iterator s = sd.lr->begin (), t = sd.lr->end (); s != t; ++s) {
      multi_split_rect (sd.n, split::HORIZONTAL, *s); // n x n
    }
  }
}

void mondrian::set_ball_param (int what, float v) {
	list<ball*>& _balls = get_balls ();
  for (balls_iterator p = _balls.begin (), q = _balls.end (); p != q; ++p) {
		ball* b = *p;
		switch (what) {
			case ball::SET_DECAY_TIME:
				b->decay_time = v;
				break;
			case ball::SET_ATTACK_TIME:
				b->attack_time = v;
				break;
			case ball::SET_VOL_MULT:
				b->vol_mult = v;
		}
	}
}


void mondrian::change_ball_vol_mult (spinner<float>& s) {
	int n = 0;
	list<ball*>& _balls = get_balls ();
  for (balls_iterator p = _balls.begin (), q = _balls.end (); p != q; ++p) {
		ball* b = *p;
		b->vol_mult += s();
		// if (b->vol_mult < 0) b->vol_mult = 0.0f;
    sprintf (BUFFER, "Ball %d, volume multiplier = %0.3f", ++n, b->vol_mult);
    cons << GREEN << BUFFER << eol;
	}
}

void mondrian::change_min_voices (int d) {
	min_voices += d;
	if (min_voices < 1) min_voices = 1;
	cons << YELLOW << "Min Voices = " << min_voices << eol;
	MENU.sp_mondrian_min_voices.set_value (min_voices);
}

int edge_on_root (rect* root, rect** boxes, int* edges) {
	float edge_vals_root [rect::nedges] = {root->extents.bottom, root->extents.right, root->extents.top, root->extents.left};
	for (int i = 0; i < 2; ++i) {
		rect* ri = boxes[i];
		int ei = edges[i];
		float edge_vals_ri [rect::nedges] = {ri->extents.bottom, ri->extents.right, ri->extents.top, ri->extents.left};
		if (edge_vals_ri [ei] == edge_vals_root [ei]) return 1;
	}
	return 0;
}

void mondrian::mark_selected_slits () {
	if (num_selected_slits) {
		glEnable (GL_LINE_STIPPLE);
		glLineStipple (1, 0xF0F0);
		glColor3f (0, 1, 0);
		for (slit_iterator si = selected_slits.begin (), sj = selected_slits.end (); si != sj; ++si) {
			slit* s = *si;
			rect* r = s->boxes[0];
			int e = s->edges[0];
			box<float>& x = r->extents;
			float l [] = {x.bottom, x.right, x.top, x.left};
			int t [] =  {slit::HORIZONTAL, slit::VERTICAL, slit::HORIZONTAL, slit::VERTICAL};
			glBegin (GL_LINES);
			float le = l[e];
			if (t[e] == slit::HORIZONTAL) {
				glVertex2f (s->start, le);
				glVertex2f (s->end, le);
			} else {
				glVertex2f (le, s->start);
				glVertex2f (le, s->end);
			}
			glEnd ();
		}
	}
	glDisable (GL_LINE_STIPPLE);
}

void mondrian::region_begin () {
	rgn.left = rgn.right = win.mousex;
	rgn.bottom = rgn.top = win.mousey;
}

void mondrian::region_end () {
	rgn.calc ();
	select_balls (rgn);
}

const box<float>& mondrian::region_update () {
	rgn.right = win.mousex;
	rgn.top = win.mousey;
	return rgn;
}

void draw_slit_cutter (int yesno) {
	mondrian0.draw_slit_cutter = yesno;
}


mondrian::browse_t::browse_t () {
	n = which = last = 0;
}

void mondrian::browse_t::clear () {
	n = which = last = 0;
	balls.clear ();
	MENU.ol_browse_balls.set_text ("");
}
