Subversion Repositories DIN Is Noise

Rev

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

/*
* 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-2025 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,
                    _ball->binaural, _ball->just, _ball->sep
                   )
    );
  triggering_balls.push_back (_ball);
  ++_ball->num_notes;
  triggered_note& last_triggered_note = triggered_notes.back ();
  last_triggered_note.setup (&wave, &attack, &decay);
  print_num_triggered_notes ();
  rnd<unsigned char> rd (0, 255);
}

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

void mondrian::fillpatbuf () {
  memset (patbuf, 0, 1024);
  if (patlen) {
    for (int i = 0, j = 0; i < 1024; i += patstep) {
      if (j >= patlen) j = 0;
      patbuf[i]=patstr[j++];
    }
  }
}

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;
  file << "texture " << MENU.texture.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);
    int texture = MENU.texture.state;
    if (texture) {
      glEnable (GL_POLYGON_STIPPLE);
      glPolygonStipple (patbuf);
    }
    glColor4f (what->r, what->g, what->b, 0.5);
    glRectf (extents.left, extents.bottom, extents.right, extents.top);
    glDisable (GL_BLEND);
    if (texture) glDisable (GL_POLYGON_STIPPLE);
  }

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

void mondrian::typing (field& f) {
  patstr = f.text;
  patlen = patstr.length ();
  fillpatbuf ();
}