Subversion Repositories DIN Is Noise

Rev

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

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



#include <fstream>
#include <iostream>
#include <sstream>
#include "main.h"
#include "input.h"
#include "font.h"
#include "console.h"
#include "mondrian.h"
#include "solver.h"
#include "widget.h"
#include "random.h"
#include "utils.h"
#include "vector2d.h"
#include "font.h"
#include "audio.h"
#include "ui_list.h"
#include "curve_library.h"
#include "container.h"
#include <algorithm>
#include <math.h>

using namespace std;


extern ofstream dlog;
extern console cons;
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 float PI_BY_180;
extern int IPS;
static const char spc = ' ';
extern std::map <string, float> INTERVALS;

mondrian::mondrian () :
_help ("mondrian.hlp"), 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")
  {
  name = "Mondrian";
  pan = zoom = 1;
  root = 0;
  hit = 0;
  edge = edge::NONE;
  num_balls = 0;
  num_selected = 0;
  adding_balls = 0;
  started_making_ball = 0;
  new_ball = 0;
  moving_balls = 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;
  delta_attack_time = delta_decay_time = 0.01;
  lmb_clicked = 0;
  editing_edge = 0;
  min_box_size = 2;
  num_boxes = 8;
  label_notes = 1;
  fill_rects = 1;
  draw__boxes = 1;
  draw__notes = 1;
  draw__balls = 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);
  dlog << "--- destroyed Mondrian --" << endl;
}

void mondrian::enter () {}

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

ball::ball (float _x, float _y, rect* _R) : x(_x), y(_y), R(_R) {init ();}

ball::ball (int _type) : trail (1, 10) { init (_type); }

void ball::init (int _type) {
  V = 0;
  vx = vy = 0;
  select = 0;
  frozen = 1;
  pitch_mult = 1;
  mod = 0;
  r = g = b = 0.75f;
  attack_time = recent_attack_time;
  decay_time = recent_decay_time;
  num_notes = 0;
  del = 0;
  auto_rotate = 0;
  dtheta = PI_BY_180;
  set_ball_type (this, _type);
  ++ref;
}

void ball::set_velocity (float dx, float dy) {
  V = unit_vector (vx, vy, dx, dy);
  if (V == 0) {
    vx = 1;
    vy = 1;
    V = unit_vector (vx, vy, vx, vy);
  }
  calc_velocity_slope ();
}

void ball::rotate_velocity (int dir) {
  rotate_vector (vx, vy, dir * dtheta);
  calc_velocity_slope ();
}

void ball::calc_velocity_slope () {
  if (vx != 0) {
    vm = vy * 1.0f / vx;
    vm_1 = 1.0f / vm;
    vm_inf = 0;
  } else {
    vm = 0.0f;
    vm_1 = 0.0f;
    vm_inf = 1;
  }
}

void ball::update () {

  int edge_hit = 0;

  if (!frozen) {

    trail.add (x, y);

    float px = x, py = y; // prev position

    // move the ball by a constant speed we found when user made the ball
    x += (V * vx);
    y += (V * vy);
    box<float>& e = R->extents; // get the box the ball is bouncing in
    box<float>& eroot = mondrian0.root->extents; // get the root box

    // check if the ball is still inside this box after move
    if (inbox (e, x, y)) {
      // ball is still inside the box so theres nothing to do
    } else { // ball has hit a wall or ceiling of box
      float xy, t0, dt;
      pair<float, float> invl;
      int clx = clamp<float> (e.left, x, e.right);
      if (clx) { // hit wall
        edge_hit = 1;
        if (vm_inf == 0) y = py + vm * (x - px); else y = py;
        clamp (e.bottom, y, e.top);
        int walle [] = {edge::LEFT, 0, edge::RIGHT};
        slit* S = 0;
        rect* RO = get_other_rect_of_slit (R, walle[clx+1], y, &S);
        if (RO) { // ball in the slit
          if (type == HEALER) {
            mondrian0.remove_slit (S); // close slit
            //mondrian0.marks.push_back (point<float>(x, y));
          }
          R->erase (this);
          R = RO; // ball is now in other box
          R->balls.push_back (this);
          edge_hit = 0;
        } else { // ball not in slit
          if (type == WRECKER) mondrian0.add_remove_slit (x, y); // make slit
          vx = -vx; // flip horizontal component of velocity to rebound
          calc_velocity_slope ();
          xy = x;
          t0 = eroot.left;
          dt = eroot.width_1;
          mondrian0.launch_note (this, xy, t0, dt, R->vint);
        }
      } else {
        int cly = clamp<float> (e.bottom, y, e.top);
        if (cly) { // hit ceiling
          edge_hit = 1;
          if (vm_inf == 0) x = px + vm_1 * (y - py); else x = px;
          clamp (e.left, x, e.right);
          int walle [] = {edge::BOTTOM, 0, edge::TOP};
          slit* S = 0;
          rect* RO = get_other_rect_of_slit (R, walle[cly+1], x, &S);
          if (RO) { // ball in the slit
            if (type == HEALER) {
              mondrian0.remove_slit (S);
              //mondrian0.marks.push_back (point<float>(x,y));
            }
            R->erase (this);
            R = RO;
            R->balls.push_back (this); // ball is now in other box
            edge_hit = 0;
          } else {
            if (type == WRECKER) mondrian0.add_remove_slit (x, y); // make slit
            vy = -vy; // flip vertical component of velocity to rebound
            calc_velocity_slope ();
            xy = y;
            t0 = eroot.bottom;
            dt = eroot.height_1;
            mondrian0.launch_note (this, xy, t0, dt, R->hint);
          }
        }
      }
    }
  }
  if (auto_rotate && !edge_hit) rotate_velocity (auto_rotate); // auto_rotate = -1 is clockwise; auto_rotate = 1 is anti-clockwise
}

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_frequency (frequency);
  note_volume = 1.0f / voices;
  triggered_notes.push_back (triggered_note (N, 0, note_volume, _ball->x, _ball->y, 0, 0, 0, 0));
  triggering_balls.push_back (_ball);
  ++_ball->num_notes;
  triggered_note& last_triggered_note = triggered_notes.back ();
  last_triggered_note.setup (&wave, &attack, &decay);
  ++num_triggered_notes;
  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 () {
  rect* found = find (root, win.mousex, win.mousey).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);
  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) return selected; else return get_box_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 << console::yellow << "Defrosted ball [" << (unsigned int) 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 << console::yellow << "Ball [" << (unsigned int) 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 << console::yellow << "Defrosted ball [" << (unsigned int) b << "] cant move [0 speed]" << eol;
  }
}

void mondrian::clear_modulations (list<ball*>& _balls) {
  if (!num_selected) {
    cons << console::red << "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->r = b->g = b->b = 1;
  }
  cons << console::green << "Cleared modulation on " << num_selected << " 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::load_settings  (ifstream& file) {

  std::string ignore;

  file >> ignore >> name;

  float l, b, r,  t;
  file >> ignore >> l >> b >> r >> t;
  win.set (l, b, r, t);

  file >> ignore >> win_chunk.x >> win_chunk.y;
  file >> ignore >> obj_chunk.x >> obj_chunk.y;

  win_per_obj (win_chunk.x / obj_chunk.x, win_chunk.y / obj_chunk.y);
  obj_per_win (obj_chunk.x / win_chunk.x , obj_chunk.y / win_chunk.y);

  file >> ignore >> win_resolution;
  float dummy1 = 0, dummy2 = 0;
  win2obj (win_resolution, dummy1, obj_resolution, dummy2);

}

void mondrian::save_settings (ofstream& file) {
  file << "name " << name << endl;
  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;
}

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

int mondrian::handle_input () {

  if (moving_balls) move_balls (win.mousex - win.mousex_prev, win.mousey - win.mousey_prev);
  if (editing_slit) slit_lip.edit (); else
  if (editing_edge) set_edge (hit, edge, win.mousex, win.mousey);

  if (lmb) {
    if (lmb_clicked == 0) {
      if (stop_moving_balls ());
      else if (stop_editing_slit ());
      else if (stop_editing_edge ());
      else if (try_slitting ());
      else if (!uis.main_menu.show && selector.exists) {
        selector.handle_input (win.mousex, win.mousey);
        select_balls (selector.region);
      }
      else {
        hit = find (root, win.mousex, win.mousey).found;
        if (hit) { // box hit
          box<float>& bf = hit->extents;
          edge = bf.get_edge_hit (win.mousex, win.mousey, gutter2);
          if (edge != edge::NONE) { // edge hit
            // check if slit hit
            slit_lip.slitt = 0;
            float* curs [rect::nedges] = {&win.mousex, &win.mousey, &win.mousex, &win.mousey};
            slit_lip.cur = curs[edge];
            if (get_slit_lip (slit_lip, hit, edge, *slit_lip.cur)) { // slit hit
              float* prevs [rect::nedges] = {&win.mousex_prev, &win.mousey_prev, &win.mousex_prev, &win.mousey_prev};
              slit_lip.prev = prevs[edge];
              toggle_flag (editing_slit, "Just move mouse to edit slit. ESC to stop."); // edit this slit
            } else toggle_flag (editing_edge, "Just move mouse to move edge. ESC or click to stop."); // edit this edge
          } else {
            if (!adding_balls) selector.handle_input (win.mousex, win.mousey);
          }
        }
      }
    }

    if (adding_balls) { // user is adding balls
      if (started_making_ball == 0) {
#ifdef __EVALUATION__
          if (num_balls == 1) {
            stop_adding_balls ();
            cons << console::red << "You can launch more balls in the Licensed Version of DIN Is Noise" << eol;
            return 1;
          }
#endif
        started_making_ball = 1;
        new_ball = new ball (added_ball_type);
      } else {
        float dx, dy;
        win.diff_mouse (dx, dy);
        new_ball->set_velocity (dx, dy);
        new_ball->x = win.mousex;
        new_ball->y = win.mousey;
      }
    }    

    lmb_clicked = 1;

  } else {
    lmb_clicked = 0;
    if (new_ball) {
      new_ball->frozen = 0;
      finding f = find (root, new_ball->x, new_ball->y);
      if (f.found) {
        new_ball->select = 1;
        new_ball->R = f.found;
        f.found->balls.push_back (new_ball);
        balls.push_back (new_ball);
        selected.push_back (new_ball);
        ++num_selected;
        ++num_balls;
      } else {
        delete new_ball;
      }
      started_making_ball = 0;
      new_ball = 0;
    }
    if (selector.exists) selector.handle_input (win.mousex, win.mousey);
  }

  static const double reptf = 1./7, repts = 1./48.;
  static double repts1 = 1./IPS;

  // octave shift
  if (keypressed (SDLK_z)) {if (!modulate_balls (-1)) modulate_down ();}
  else if (keypressed (SDLK_x)) {if (!modulate_balls (1)) modulate_up ();}

  // split box
  if (keypressed (SDLK_r)) { // vertically
    if (shift_down())
      make_splits (split::VERTICAL); // vertically at notes
    else if (ctrl_down())
      make_splits (num_boxes, split::VERTICAL); // vertically make num_boxes
    else
      split_rect (split::VERTICAL, win.mousex); // into 2 new boxes
  }
  else if (keypressed (SDLK_f)) { // horizontally
    if (shift_down ())
      make_splits (split::HORIZONTAL); // horizontally at notes
    else if (ctrl_down ())
      make_splits (num_boxes, split::HORIZONTAL); // horizontally make num_boxes
    else
      split_rect (split::HORIZONTAL, win.mousey); // into 2 new boxes
  }
  else if (keypressed (SDLK_t)) {
    if (shift_down () || ctrl_down ())
      make_grid (); // make grid of num_boxes x num_boxes
    else
      make_note_grid (); // make note grid
  }

  // change ball speed
  if (keypressedd (SDLK_LEFTBRACKET)) change_vel (-(float)uis.main_menu.sp_mondrian_change_vel.f_delta);
  else if (keypressedd (SDLK_RIGHTBRACKET)) change_vel (+(float)uis.main_menu.sp_mondrian_change_vel.f_delta);

  // change ball attack & decay time
  static const float ddelta = 0.01;
  if (keypressedd (SDLK_SEMICOLON, reptf, repts)) {
    if (!shift_down()) change_attack_time_kb (-delta_attack_time); else {
      delta_attack_time -= ddelta;
      if (delta_attack_time < ddelta) delta_attack_time = ddelta;
      cons << "delta attack time = " << delta_attack_time << eol;
    }
  }
  else if (keypressedd (SDLK_QUOTE, reptf, repts)) {
    if (!shift_down()) change_attack_time_kb (delta_attack_time); else {
      delta_attack_time += ddelta;
      cons << "delta attack time = " << delta_attack_time << eol;
    }
  }
  else if (keypressedd (SDLK_COMMA, reptf, repts)) {
   if (!shift_down()) change_decay_time_kb (-delta_decay_time); else {
     delta_decay_time -= ddelta;
     if (delta_decay_time < ddelta) delta_decay_time = ddelta;
     cons << "delta decay time = " << delta_decay_time << eol;
   }
  }
  else if (keypressedd (SDLK_PERIOD, reptf, repts)) {
    if (!shift_down()) change_decay_time_kb (delta_decay_time); else {
      delta_decay_time += ddelta;
      cons << "delta decay time = " << delta_decay_time << eol;
    }
  }

  // change ball course
  if (keypressedd (SDLK_o, reptf, repts1)) {
    if (ctrl_down())
      toggle_auto_rotate (1); // change constantly anti-clockwise
    else  if (shift_down()) {
      change_ball_dtheta (-1);
    }
    else
      rotate_velocity (+1);
  } else if (keypressedd (SDLK_p, reptf, repts1)) {
    if (ctrl_down())
      toggle_auto_rotate (-1); // change constantly clockwise
    else if (shift_down()) {
      change_ball_dtheta (+1);
    }
    else
      rotate_velocity (-1); // rotate velocity vector clockwise
  }

  // ball selection
  if (keypressed (SDLK_i)) invert_selected_balls ();
  else if (keypressed (SDLK_k)) select_box_balls ();
  else if (keypressed (SDLK_l)) select_all_balls ();
  else if (keypressed (SDLK_n)) {
    /*if (shift_down()) marks.clear ();
    else*/
clear_selected_balls ();
  }
 
  // freeze/thaw balls
  if (keypressed (SDLK_SPACE)) {
    list<ball*>& balls = get_balls ();
    if (shift_down()) freeze_balls (balls);
    else if (ctrl_down()) thaw_balls (balls);
    else freeze_thaw_balls (balls);
  }
 
  if (keypressed (SDLK_j)) flip_velocity (); // flip ball velocity ie flips ball direction
 
  if (keypressedd (SDLK_KP_PLUS, reptf, repts)) change_slit_size (+1);
  else if (keypressedd (SDLK_KP_MINUS, reptf, repts)) change_slit_size (-1);

  if (keypressedd (SDLK_MINUS, reptf, repts)) change_trail_size (-1);
  else if (keypressedd (SDLK_EQUALS, reptf, repts)) change_trail_size (+1);

  if (keypressed (SDLK_b)) {
    if (shift_down()) do_add_balls (ball::WRECKER);
    else if (ctrl_down ()) do_add_balls (ball::HEALER);
    else do_add_balls (); // add bouncers
  }

  if (keypressed (SDLK_m)) do_move_balls (); // move balls
  if (keypressed (SDLK_c)) delete_selected_balls (); // delete balls

  if (keypressedd (SDLK_y)) {
    if (--voices <= 0) voices = 1;
    cons << console::yellow << "Voices = " << voices << eol;
    uis.main_menu.sp_mondrian_voices.set_value (voices);
  }
  else if (keypressedd (SDLK_u)) {
    ++voices;
    cons << console::yellow << "Voices = " << voices << eol;
    uis.main_menu.sp_mondrian_voices.set_value (voices);
  }
 
  if (keypressed (SDLK_F9)) remove_slits_on_current_edge ();
  else if (keypressed (SDLK_F10)) remove_slits_on_current_box ();
  else if (keypressed (SDLK_F11)) remove_all_slits ();

  if (keypressed (SDLK_F3)) toggle_balls_type (ball::WRECKER);
  else if (keypressed (SDLK_F4)) toggle_balls_type (ball::HEALER);
  else if (keypressed (SDLK_F5)) toggle_balls_type (ball::BOUNCER);
  else if (keypressed (SDLK_F6)) switch_balls_type ();
  else if (keypressed (SDLK_F7)) select_type (ball::WRECKER);
  else if (keypressed (SDLK_F8)) select_type (ball::HEALER);

  if (keypressed (SDLK_v)) {
    if (shift_down()) {
      delete_all_rects ();
      cons << console::green << "Deleted all boxes (except root :)" << eol;
    } else {
      delete_rect (); // delete current box
    }
  }

  if (keypressed (SDLK_h)) randomise_box_color ();

  if (keypressedd (SDLK_9)) {
    if (shift_down()) change_poly_points (-1); else change_poly_radius (-1);
  } else if (keypressedd (SDLK_0)) {
    if (shift_down()) change_poly_points (1); else change_poly_radius (1);
  }

  if (keypressed (SDLK_SLASH)) start_slitting ();

  if (keypressed (SDLK_F1)) _help (); // show help

  // movement
  double pan_rept = window::PAN_REPEAT, zoom_rept = window::ZOOM_REPEAT;
  if  (keypressedd (SDLK_a, pan_rept, pan_rept)) do_panx (-pan);
  else if (keypressedd (SDLK_d, pan_rept, pan_rept)) do_panx (+pan);
  else if (keypressedd (SDLK_w, pan_rept, pan_rept)) do_pany (+pan);
  else if (keypressedd (SDLK_s, pan_rept, pan_rept)) do_pany (-pan);

  // zoom
  else if (keypressedd (SDLK_q, zoom_rept, zoom_rept)) do_zoom (+zoom);
  else if (keypressedd (SDLK_e, zoom_rept, zoom_rept)) do_zoom (-zoom);


  return 1;
}

void mondrian::update_attack () {
  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->num_vertices () - 1, ti.decay_lastx, y);
  }
}

void mondrian::update_waveform (multi_curve& crv) {
  for (note_iterator i = triggered_notes.begin (), j = triggered_notes.end (); i != j; ++i) (*i).update_solver (crv);
}

void mondrian::draw_rect (rect* what) {
  //if (what->child1 == 0 && what->child2 == 0) {
    box<float>& extents = what->extents;
    if (fill_rects) {
      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);
    }
    glColor3f (1, 1, 1);
    if (what->total_slits == 0) {
      glPolygonMode (GL_FRONT_AND_BACK, GL_LINE);
        glRectf (extents.left, extents.bottom, extents.right, extents.top);
      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]);
        } else {
          if (typp[i] == slit::HORIZONTAL) {
            glBegin (GL_LINES);
              glVertex2f (startt[i], levell[i]);
              glVertex2f (endd[i], levell[i]);
            glEnd ();
          } else {
            glBegin (GL_LINES);
              glVertex2f (levell[i], startt[i]);
              glVertex2f (levell[i], endd[i]);
            glEnd ();
          }
        }
      }
    }
    /*return;
  }
  draw_rect (what->child1);
  draw_rect (what->child2);*/

}

void draw_slits (float start, float end, float level, int type, list<slit*>& slits) {
  float last_end = start;
  if (type == slit::VERTICAL) {
    for (slit_iterator i = slits.begin (), j = slits.end (); i != j; ++i) {
      slit* si = *i;
      glBegin (GL_LINES);
        glVertex2f (level, last_end);
        glVertex2f (level, si->start);
        /*glVertex2f (level - 5, si->start);
        glVertex2f (level + 5, si->start);
        glVertex2f (level - 5, si->end);
        glVertex2f (level + 5, si->end);*/

      glEnd ();
      last_end = si->end;
    }
    glBegin (GL_LINES);
      glVertex2f (level, last_end);
      glVertex2f (level, end);
    glEnd ();
  } else {
    for (slit_iterator i = slits.begin (), j = slits.end (); i != j; ++i) {
      slit* si = *i;
      glBegin (GL_LINES);
        glVertex2f (last_end, level);
        glVertex2f (si->start, level);
        /*glVertex2f (si->start, level - 5);
        glVertex2f (si->start, level + 5);
        glVertex2f (si->end, level - 5);
        glVertex2f (si->end, level + 5);*/

      glEnd ();
      last_end = si->end;
    }
    glBegin (GL_LINES);
      glVertex2f (last_end, level);
      glVertex2f (end, level);
    glEnd ();
  }

}

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 ();
  if (draw__notes) 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 (slitting) { // draw slit glyph
      glColor3f (1, 1, 0);
      glLineWidth (2);
      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);  
      glLineWidth (1);
    }
  }

  if (draw__boxes) draw_leaves ();
  if (draw__balls) draw_balls ();
  selector.draw ();

  /*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 (root);
    poly.radius = root->extents.height / 4.0f;
    change_poly_points (+24);
    leaves.push_back (root);
  }
  poly.delta_radius = 0.1 * poly.radius;
  if (poly.delta_radius == 0) poly.delta_radius = 100;

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

  slitting = 0;

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

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

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

int mondrian::add_remove_slit (float x, float y, float sz, int verbose) {
  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> exts = li->extents;
    exts.resize (gutter, gutter);
    if (inbox(exts, 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); // 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
          if (verbose) cons << console::green << "Created slit" << eol;
        } else { // not added so delete
          remove_slit (s);
          if (verbose) cons << console::red << "Removed slit because its too small." << eol;
        }
      } else { // balls will escape playing area
        remove_slit (s);
        if (verbose) cons << console::red << "Removed slit because balls will escape!" << eol;
      }
    } else { // remove hit slit
      remove_slit (hs);
      if (verbose) cons << console::red << "Removed hit slit" << eol;
    }
    result = 1;
  }
  return result;
}


rect* mondrian::split_rect (int type, float t, rect* B) {
  if (B == 0) {
    B = find_box_to_split ();
    if (B == 0) return 0;
  }
  stop_adding_balls ();
  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 (root);
  v2->calc_intervals (root);
  split_balls (B, v1, v2);
  remove_leaf (B);
  add_leaf (v1);
  add_leaf (v2);
  recreate_slits (B);
  return B;
}


template <class C, class V> int erase (C& c, V& v) {
  typename C::iterator ce = c.end (), ci = std::find (c.begin (), ce, v);
  if (ci != ce) {
    c.erase (ci);
    return 1;
  }
  return 0;
}

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

struct slit_info {
  float x, y, half_size;
};

void mondrian::recreate_slits (rect* R) {
  slit_info sf;
  box<float>& e = R->extents;
  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 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) {
      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;
        *movingi = s->mid;
        sf.half_size = s->mid - s->start;
        reslits.push_back (sf);
        remove_slit (s);
        si = slits.begin ();
        sj = slits.end ();
      }
    }
  }

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


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


int mondrian::delete_rect () { // delete box under cursor
  finding result = find (root, win.mousex, win.mousey);
  rect* found = result.found;
  rect* sibling = result.sibling;
  if (found != 0) {
    if (found == root) return 0;
    // 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);
    for (balls_iterator p = found->balls.begin (), q = found->balls.end (); p != q; ++p) { // find new home for balls of found
      ball* b = *p;
      b->R = find (sibling, b->x, b->y).found;
      if (b->R == 0) b->R = sibling;
      b->R->balls.push_back (b);
    }
    delete parent;
    remove_leaf (found);
    recreate_slits (found);
    delete found;
    return 1;
  } else {
    cons << console::red << "Cant delete. You are outside all boxes." << eol;
    return 0;
  }
}

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

void mondrian::bg () {
  for (balls_iterator p = balls.begin (), q = balls.end (); p != q; ++p) (*p)->update ();
  remove_finished_notes ();
}

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 == 1) return *selected.begin (); else return 0;
}

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

int mondrian::change_attack_time (float value) { // one selected ball only
  ball* bsel = get_one_selected_ball ();
  if (bsel) {
    int ret = change_param (bsel->attack_time, value, ball::recent_attack_time);
    cons << console::green << "Ball's attack time = " << bsel->attack_time << " secs." << eol;
    return ret;
  } else {
    cons << console::red << "Please select 1 ball at a time to use this menu item" << eol;
    return 0;
  }
}

int mondrian::change_decay_time (float value) { // one selected ball only
  ball* bsel = get_one_selected_ball ();
  if (bsel) {
    int ret = change_param (bsel->decay_time, value, ball::recent_decay_time);
    cons << console::green << "Ball's decay time = " << bsel->decay_time << " secs." << eol;
    return ret;
  } else {
    cons << console::red << "Please select 1 ball at a time to use this menu item" << eol;
    return 0;
  }
}

void mondrian::change_attack_time_kb (float value) { // selected or box or all balls, using keyboard short cut
  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->attack_time, value, ball::recent_attack_time);
    cons << console::green << "Ball " << ++n << ", attack time = " << b->attack_time << " secs." << eol;
  }

}

void mondrian::change_decay_time_kb (float value) { // selected or box or all balls, using keyboard short cut
  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, value, ball::recent_decay_time);
    cons << console::green << "Ball " << ++n << ", decay time = " << b->decay_time << " secs." << 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;}
    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;
  }

  glColor3f (1, 1, 1);
  glVertexPointer (2, GL_FLOAT, 0, pts_d);
  glDrawArrays (GL_LINES, 0, 2 * num_balls);

  glPointSize (6);
  glEnableClientState (GL_COLOR_ARRAY);
  glVertexPointer (2, GL_FLOAT, 0, pts);
  glColorPointer (3, GL_FLOAT, 0, pts_clr);
  glDrawArrays (GL_POINTS, 0, num_balls_);
  glDisableClientState (GL_COLOR_ARRAY);
  glPointSize (1);

}

void mondrian::draw_notes () {
  int n = poly.points;
  float xy [2*n];
  int tn = 1;
  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;
    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 (tn++ > voices)  glColor3f (1.0, 0.25, 0.25); else glColor3f (0.9, 0.9, 1);
    glVertexPointer (2, GL_FLOAT, 0, xy);
    glDrawArrays (GL_LINE_LOOP, 0, n);
  }
}

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_vel (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;
    b->V += d;
    if (b->V <= 0) {
      b->V = 0;
    }
    cons << console::yellow << "Ball " << ++n << ", speed = " << b->V << eol;
  }
}

void mondrian::clear_selected_balls () {
  for (balls_iterator p = selected.begin (), q = selected.end (); p != q; ++p) {
    ball* b = *p;
    b->select = 0;
  }
  selected.clear ();
  num_selected = 0;
}

void mondrian::select_using_modifiers (ball* b, int ctrl) {
  balls_iterator se = selected.end (), f = ::find (selected.begin (), se, b);
  if (ctrl) {
    if (b->select) {
      if (f != se) {
        b->select = 0;
        selected.erase (f);
        --num_selected;
      }
    } else {
      if (f == se) {
        b->select = 1;
        selected.push_back (b);
        ++num_selected;
      }
    }
  } else {
    if (f == se) {
      b->select = 1;
      selected.push_back (b);
      ++num_selected;
    }
  }
}

void mondrian::select_balls (box<int>& rgn) {
  int shift = shift_down ();
  int ctrl = ctrl_down ();
  if (shift || ctrl); else clear_selected_balls ();
  for (balls_iterator p = balls.begin (), q = balls.end (); p != q; ++p) {
    ball* b = *p;
    if (inbox<int> (rgn, b->x, b->y)) select_using_modifiers (b, ctrl);
  }
  after_selection ();
}

void mondrian::after_selection () {
  if (num_selected) {
    stop_adding_balls ();
    cons << console::cyan << "Selected " << num_selected << " balls";
    if (num_selected == 1) {
      ball* bsel = *selected.begin();
      cons << ", attack time = " << bsel->attack_time << ", decay time = " << bsel->decay_time << " secs.";
    }
    cons << eol;
  } else {
    cons << console::red << "No balls selected." << eol;
  }
}

void mondrian::select_all_balls () {
  clear_selected_balls ();
  for (balls_iterator p = balls.begin (), q = balls.end (); p != q; ++p) {
    ball* b = *p;
    b->select = 1;
    selected.push_back (b);
    ++num_selected;
  }
  after_selection ();
}

void mondrian::select_box_balls () {
  rect* R = find (root, win.mousex, win.mousey).found;
  if (R) {
    list<ball*>& b_balls = R->balls;
    int shift = shift_down ();
    int ctrl = ctrl_down ();
    if (shift || ctrl); else clear_selected_balls ();
    for (balls_iterator p = b_balls.begin (), q = b_balls.end (); p != q; ++p) select_using_modifiers (*p, ctrl);
    after_selection ();
  }
}

void mondrian::invert_selected_balls () {
  for (balls_iterator p = balls.begin (), q = balls.end (); p != q; ++p) {
    ball* b = *p;
    b->select = !b->select;
  }
  selected.clear ();
  num_selected = 0;
  for (balls_iterator p = balls.begin (), q = balls.end (); p != q; ++p) {
    ball* b = *p;
    if (b->select) {
      selected.push_back (b);
      ++num_selected;
    }
  }
  after_selection ();
}

void mondrian::delete_selected_balls () {
  int nb = num_balls;
  for (balls_iterator p = selected.begin (), q = selected.end(); p != q; ++p) {
    ball* b = *p;
    b->R->erase (b);
    if (::erase (balls, b)) {
    /*balls_iterator be = balls.end (), bf = ::find (balls.begin (), be, b);
    if (bf != be) {
      balls.erase (bf);*/

      --num_balls;
    }
    if (b->num_notes == 0) delete b; else b->del = 1;
  }
  selected.clear ();
  num_selected = 0;
  cons << console::cyan << "Deleted " << (nb - num_balls) << " balls" << eol;
}

void mondrian::move_balls (float dx, float dy) {
  for (balls_iterator p = selected.begin (), q = selected.end (); p != q; ++p) {
    ball* b = *p;
    b->x += dx;
    b->y += dy;
    if (inbox (b->R->extents, b->x, b->y)); else {
      finder:
      rect* nbr = find (root, b->x, b->y).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 finder;
      }
    }
  }
}

void mondrian::toggle_flag (int& flag, const string& poz, const string& neg) {
  flag = !flag;
  if (flag)
    cons.cmd_line = mesg (poz, console::green);
  else {
    cons.cmd_line = mesg ();
    cons << console::red << neg << eol;
  }
}

void mondrian::do_add_balls (int type) {
  adding_balls = 1;
  added_ball_type = type;
  if (new_ball) set_ball_type (new_ball, added_ball_type);
  stringstream ss; ss << "Click, drag and release to launch " << ball::types_str[added_ball_type] << "s. ESC to stop.";
  cons.cmd_line = mesg (ss.str(), console::green);
}

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

void mondrian::delete_all_balls () {
  select_all_balls ();
  delete_selected_balls ();
}

void mondrian::make_notes () {
  vector<string> notes;
  extern string NOTATION;
  extern int NUM_INTERVALS;
  extern const char* WESTERN_FLAT [];
  extern vector<string> INTERVAL_NAMES;
  extern vector<float> INTERVAL_VALUES;
  int western = scaleinfo.western;
  if (NOTATION == "western") {
    for (int i = 0; i < NUM_INTERVALS; ++i) notes.push_back (WESTERN_FLAT[(western + i) % 12]);
  } else {
    for (int i = 0; i < NUM_INTERVALS; ++i) notes.push_back (INTERVAL_NAMES[i]);
  }
  int lh = get_line_height (), lh1 = 2 * lh, mcw = fnt.max_char_width;
  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;
  int _n_mrk = NUM_INTERVALS * 4 * 2;
  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));
  }
}

rect::rect () {
  parent = child1 = child2 = 0;
  total_slits = 0;
  for (int i = 0; i < 4; ++i) nslits[i] = 0;
  split = split::NONE;
  make_random_color ();
  ++ref;
}

void rect::erase (ball* b) {
  ::erase (balls, b);
  /*balls_iterator re = balls.end (), rf = std::find (balls.begin(), re, b);
  if (rf != re) balls.erase (rf);*/

}

void rect::calc_intervals (rect* _root) {
  hint.first = extents.bottom;
  hint.second = extents.top;
  vint.first = extents.left;
  vint.second = extents.right;
}

void rect::get_vertical_interval (pair<float, float>& invl, rect* _root) {
  invl.first = 1.0f + (vint.first - _root->extents.left) * _root->extents.width_1;
  invl.second = 1.0f + (vint.second - _root->extents.left) * _root->extents.width_1;
}

void rect::get_horizontal_interval (pair<float, float>& invl, rect* _root) {
  invl.first = 1.0f + (hint.first - _root->extents.bottom) * _root->extents.height_1;
  invl.second = 1.0f + (hint.second - _root->extents.bottom) * _root->extents.height_1;
}

int rect::add_slit (slit* s, int e) {
  int result = 1;
  int& nse = nslits [e]; // number of slits on this edge
  // for all edges
  float lows [rect::nedges] = {extents.left, extents.bottom, extents.left, extents.bottom};
  float highs [rect::nedges] = {extents.right, extents.top, extents.right, extents.top};
  float low = lows[e];
  if (nse) { // slits already on this edge
    list<slit*>& ls = slits [e]; // slits of this edge
    for (slit_iterator i = ls.begin (), j = ls.end (); i != j; ++i) {
      slit* si = *i;
      if (s->mid < si->start) {
        s->start = max (s->start, low);
        s->end = min (s->end, si->start);
        i = ls.insert (i, s); // inserts sorted
        // continue editing slit inspite of new inserted slit
        //
        if (mondrian0.editing_slit) {
          slit* eds = mondrian0.slit_lip.slitt; // the edited slit
          slit_iterator ip (i); --ip; // slit b4 inserted slit
          slit_iterator in (i); ++in; // slit after inserted slit
          int hl = 0;
          if ((ip != j) && (*ip == eds)) { // edited slit is b4 inserted slit
            // slits editable high is inserted slits low
            mondrian0.slit_lip.set_high (s->start);
            hl = 1;
          } else {
            if ((in != j) && (*in == eds)) { // slit after inserted slit is edited slit
              // slit's editable low is inserted slits high
              mondrian0.slit_lip.set_low (s->end);
              hl = 1;
            }
          }
          if (hl && mondrian0.slit_lip.slitt->is_too_small()) mondrian0.remove_slit (mondrian0.slit_lip.slitt);
        }
        goto adder;
      }
      low = si->end;
    }

    // append slit
    // if last slit is currently edited slit, adjusts its high
    //
    slit* sl = ls.back ();
    if (sl == mondrian0.slit_lip.slitt) mondrian0.slit_lip.set_high (s->start);

  } else cons << console::cyan << "No slits on edge, adding first slit" << eol;

  // append slit
  s->start = max (s->start, low);
  s->end = min (s->end, highs[e]);
  slits[e].push_back (s);
  adder:
    ++nse;
    ++total_slits;
    cons << console::yellow << "Slit size = " << (s->end - s->start) << eol;
    cons << console::yellow << "Slits on edge = " << nse << eol;
    if (s->is_too_small()) result = 0; else s->calc_mid ();
  return result;
}

void rect::update_slits (int e, float minn, float maxx) {
  list<slit*>& ls = slits [e];
  for (slit_iterator i = ls.begin (), j = ls.end (); i != j;) {
    slit* si = *i;
    clamp (minn, si->start, maxx);
    clamp (minn, si->end, maxx);
    if (si->is_too_small()) {
      mondrian0.remove_slit (si);
      i = ls.begin ();
      j = ls.end ();
    } else {
      ++i;
      si->calc_mid ();
    }
  }
}

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) {
        leaves.push_back (R); // a leaf
        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.change_size (t);
      f >> b->auto_rotate >> b->dtheta >> b->type;
      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 == "poly") {
      f >> ignore >> poly.radius >> ignore >> poly.points;
      change_poly_points (0);
    } else if (what == "slits") {
      load_slits (f, boxes);
    } 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 (root);
  }

  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 = (int) *r;
          *r = get_slit_from_index (ix);
        }
      }
    }
  }

}

void mondrian::save_balls (ofstream& f) {
  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.max_points <<  spc << b.auto_rotate << spc << b.dtheta << spc << b.type << 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
    }
    f << si->type << spc << si->start << spc << si->end << spc << si->mid << 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;
    slits.push_back (s);
  }
}

void mondrian::toggle_balls_type (int T) {
  static const char* names [] = {" bouncers ", " wreckers ", " healers "};
  if (num_selected) {
    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;
      }
      set_ball_type (pb, Tt);
    }
    cons << console::green << "Made " << t << names[T] << "and " << b << names[0] << eol;
  } else cons << console::red << "Please select some balls!" << eol;
}

int mondrian::modulate_balls (int w) {
  if (num_selected) {
    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);
      cons << console::yellow << "ball " << ++b << ", pitch_multiplier = " << pb->pitch_mult << eol;
      pb->mod += w;
      color_ball_using_modulation (pb);
    }
  }
  return num_selected;
}

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;
    cons << "Ball " << ++n << ", delta_rotate_velocity = " << pb->dtheta << eol;
  }
}

void mondrian::change_trail_size (int delta) {
  list<ball*>& _balls = get_balls ();
  int n = 0;
  for (balls_iterator p = _balls.begin (), q = _balls.end (); p != q; ++p) {
    ball* pb = *p;
    pb->trail.change_size (delta);
    cons << console::yellow << "Ball " << ++n << ", trail size = " << pb->trail.num_points << eol;
  }
}

int mondrian::change_poly_points (int delta) {
  int ret = 1;
  poly.points += delta;
  if (poly.points < 2) {
    poly.points = 2;
    ret = 0;
  }
  poly.coss.resize (poly.points);
  poly.sinn.resize (poly.points);
  int n = poly.points;
  extern 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 << "Note polygon points = " << poly.points << eol;
  return ret;
}

int mondrian::change_poly_radius (int dir) {
  int ret = 1;
  poly.radius += (dir * poly.delta_radius);
  if (poly.radius < 0) {
    poly.radius = 0;
    ret = 0;
  }
  cons << "Note polygon radius = " << poly.radius << 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 () {}

slit::slit () {
  boxes[0] = 0; edges[0] = -1;
  boxes[1] = 0; edges[1] = -1;
  start = end = mid = 0;
  type = INVALID;
  ++ref;
}

slit::slit (rect** bxs, float x, float y, float sz) {
  // slit can be on 2 boxes only
  boxes [0] = bxs [0]; // box 0
  boxes [1] = bxs [1]; // box 1
  rect* b0 = boxes [0];
  int e0 = b0->extents.get_edge_hit (x, y, mondrian::gutter2); // edge 0
  edges [0] = e0;
  edges [1] = (e0 + 2) % 4; // edge 1 = opposite edge on 2nd box
  int typs [] = {HORIZONTAL, VERTICAL, HORIZONTAL, VERTICAL};
  type = typs [e0];
  if (type == VERTICAL) mid = y; else mid = x;
  start = mid - sz;
  end = mid + sz;
  ++ref;
}

slit::~slit () {
  --ref;
}

int slit::is_too_small () {
  float sz = end - start;
  if (sz > MIN_SIZE) return 0; else return 1;
  return 0;
}

rect* get_other_rect_of_slit (rect* ib, int e, float v, slit** os) {
  list<slit*>& slits = ib->slits[e];
  rect* or_ = 0;
  for (slit_iterator i = slits.begin (), j = slits.end (); i != j; ++i) {
    slit* si = *i;
    if (v >= si->start && v <= si->end) {
      if (si->boxes[0] == ib) or_ = si->boxes[1]; else or_ = si->boxes[0];
      *os = si;
      return or_;
    }
  }
  return 0;
}

rect* get_other_rect_of_slit (slit* s, rect* t) {
  if (s->boxes[0] == t) return s->boxes[1]; else return s->boxes[0];
}

int get_slit_lip (slit_lip_t& slit_lip, rect* R, int e, float v) {
  list<slit*>& slits = R->slits[e];
  static const int tol = 4;
  for (slit_iterator i = slits.begin (), j = slits.end (); i != j; ++i) {
    slit* si = *i;
    if (abs (v - si->start) < tol) {
      slit_lip.slitt = si;
      slit_lip.lip = &si->start;
      rect* R1 = get_other_rect_of_slit (si, R);
      box<float>& RE = R->extents, RE1 = R1->extents;
      float lows [rect::nedges] = {RE.left, RE.bottom, RE.left, RE.bottom};
      float lows1 [rect::nedges] = {RE1.left, RE1.bottom, RE1.left, RE1.bottom};
      float low = max (lows[e], lows1[e]);
      slit_iterator i1 = i; --i1;
      if (i1 != j) slit_lip.low = max ((*i1)->end, low); else slit_lip.low = low;
      slit_lip.high = si->end;
      return 1;
    } else if (abs (si->end - v) < tol) {
      slit_lip.slitt = si;
      slit_lip.lip = &si->end;
      slit_lip.low = si->start;
      rect* R1 = get_other_rect_of_slit (si, R);
      box<float>& RE = R->extents, RE1 = R1->extents;
      float highs [rect::nedges] = {RE.right, RE.top, RE.right, RE.top};
      float highs1 [rect::nedges] = {RE1.right, RE1.top, RE1.right, RE1.top};
      float high = min (highs[e], highs1[e]);
      slit_iterator i1 = i; ++i1;
      if (i1 != j) slit_lip.high = min ((*i1)->start, high); else slit_lip.high = high;
      return 1;
    }
  }
  return 0;
}

slit* slit_hit (rect* R, int e, float v) {
  list<slit*>& slits = R->slits[e];
  for (slit_iterator i = slits.begin (), j = slits.end (); i != j; ++i) {
    slit* si = *i;
    if (v >= si->start && v <= si->end) return si;
  }
  return 0;
}

slit* slit_hit (rect** bxs, float x, float y) {
  rect* b0 = bxs [0];
  int e0 = b0->extents.get_edge_hit (x, y, mondrian::gutter2);
  int e1 = (e0 + 2) % 4;
  int e[2] = {e0, e1};
  float v[4] = {x, y, x, y};
  for (int i = 0; i < 2; ++i) {
    rect* b = bxs[i];
    int ei = e[i];
    slit* sh = slit_hit (b, ei, v[ei]);
    if (sh) return sh;
  }
  return 0;
}


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;
      cons << console::yellow << "Slits on edge = " << nse << eol;
    }
  }

  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

  delete s; // free mem
}

void slit_lip_t::edit () {
  float delta = *cur - *prev;
  *lip += delta;
  clamp (low, *lip, high);
  slitt->calc_mid ();
}

void slit_lip_t::set_high (float h) {
  high = h;
  edit ();
}

void slit_lip_t::set_low (float l) {
  low = l;
  edit ();
}

void slit_lip_t::clear () {
  slitt = 0;
  lip = prev = cur = 0;
  low = high = 0;
}

int mondrian::try_slitting () {
  if (slitting) {
    add_remove_slit (win.mousex, win.mousey);
    return 1;
  }
  return 0;
}

int mondrian::stop_adding_balls () {
  if (adding_balls) {
    stringstream ss; ss << "Stopped adding " << ball::types_str [added_ball_type] << "s.";
    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");
    return 1;
  }
  return 0;
}


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

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

int mondrian::stop_editing_edge () {
  if (editing_edge) {
    hit = 0;
    edge = edge::NONE;
    toggle_flag (editing_edge, "", "Stopped editing edge.");
    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) {
  clear_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.push_back (b);
      ++num_selected;
    }
  }
}

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)
        set_ball_type (b, ball::HEALER);
      else
        set_ball_type (b, ball::WRECKER);
    }
  }
}

void mondrian::start_slitting () {
  cons.cmd_line = mesg ("Click on edge of a box to add or remove slit. ESC to stop.", console::green);
  slitting = 1;
}

void mondrian::change_slit_size (float d) {
  slit::HALF_SIZE += d;
  if (slit::HALF_SIZE < slit::MIN_HALF_SIZE) slit::HALF_SIZE = slit::MIN_HALF_SIZE;
  cons << console::green << "Default slit size = " << slit::HALF_SIZE << eol;
}

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);
    }
    cons << console::green << "Removed all slits on this edge" << eol;
  }
}

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 << console::red << "Please get on the edge of this box to remove its slits" << eol;
        return;
      }
    }
  }
  cons << console::red << "Please get on an edge of a box that has slits" << eol;
}

void mondrian::remove_slits_on_current_box () { // ie box under cursor
  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);
      cons << console::green << "Removed all slits in this box" << eol;
      return;
    }
  }
}

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);
  }
  cons << console::green << "Removed all slits" << eol;
}

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

void mondrian::delete_all_rects () {
  // put mouse in center of box
  float midx = root->extents.midx, midy = root->extents.midy;
  float wx, wy; obj2win (midx, midy, wx, wy);
  extern int mousex, mousey; win2view (wx, wy, mousex, mousey, win, view);
  SDL_WarpMouse (mousex, mousey);
  win.update_mouse ();

  while (delete_rect ()); // delete box under cursor until only root is left

}

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 j = scaleinfo.num_notes;
  for (int i = 0; i < j; ++i) {
    const string& name = scaleinfo.notes[i];
    float value = INTERVALS [name];
    if (value >= intervals.first) {
      note_ids.first = i + 1;
      break;
    }
  }
  for (int k = j - 1; k > -1; --k) {
    const string& name = scaleinfo.notes[k];
    float value = INTERVALS [name];
    if (value <= intervals.second) {
      note_ids.second = k;
      break;
    }
  }
}

void mondrian::make_splits (int n, int type, rect* B, list<rect*> *pr) {
  if (B == 0) {
    B = find_box_to_split ();
    if (B == 0) {
      cons << console::red << "Cant split as you are outside all boxes :(" << eol;
      return;
    }
  }
  float num;
  if (type == split::VERTICAL) num = B->extents.width; else num = B->extents.height;
  float sz = num / n;
  if (sz < min_box_size) cons << console::red << "Cannot split as the boxes will be too small [" << " < " << min_box_size << " ] :(" << eol;
  float pos;
  for (int i = 0, j = n - 1; i < j; ++i) {
    if (type == split::VERTICAL) {
      pos = B->extents.left + sz;
      split_rect (split::VERTICAL, pos, B);
    } else {
      pos = B->extents.bottom + sz;
      split_rect (split::HORIZONTAL, pos, B);
    }
    if (pr) pr->push_back (B->child1);
    B = B->child2;
  }
  if (pr) pr->push_back (B);
}

void mondrian::make_splits (int type, rect* B, list<rect*> *pr) {
  if (B == 0) B = find_box_to_split ();
  if (B) {
    pair<float, float> intervals;
    pair<int, int> note_ids;
    if (type == split::VERTICAL)
      B->get_vertical_interval (intervals, root);
    else
      B->get_horizontal_interval (intervals, root);
    get_note_ids_from_intervals (note_ids, intervals);
    int nsplits = 0;
    for (int i = note_ids.first, j = note_ids.second; i < j; ++i) {
      const string& name = scaleinfo.notes[i];
      float value = INTERVALS [name];
      float delta = value - 1.0f;
      float pos;
      if (type == split::VERTICAL)
        pos = root->extents.left + delta * root->extents.width;
      else
        pos = root->extents.bottom + delta * root->extents.height;
     
      split_rect (type, pos, B);
      ++nsplits;
      if (pr) pr->push_back (B->child1);
      B = B->child2;
    }
    if (pr) pr->push_back (B);
    if (nsplits)
      cons << console::green << "Created " << nsplits << " boxes on the notes of this scale" << eol;
    else
      cons << console::red << "No notes found to make splits :(" << eol;
  } else {
    cons << console::red << "Cant split as you are outside all boxes :(" << eol;
  }
}

void mondrian::make_note_grid () {
  if (root->child1 == 0 && root->child2 == 0) { // we only want to make note grid if there is just root
    rect* B = root;
    list<rect*> columns;
    make_splits (split::VERTICAL, B, &columns); // split on notes along horizontal axis
    // split each column along vertical axis
    for (box_iterator s = columns.begin (), t = columns.end (); s != t; ++s) {
      rect* C = *s;
      make_splits (split::HORIZONTAL, C);
    }
  } else {
    cons << console::red << "Please delete all boxes, cannot create note grid :(" << eol;
  }
}

void mondrian::make_grid () {
  if (root->child1 == 0 && root->child2 == 0) { // we only want to make box grid if there is just root
    rect* B = root;
    list<rect*> columns;
    make_splits (num_boxes, split::VERTICAL, B, &columns); // split on notes along horizontal axis
    // split each column along vertical axis
    for (box_iterator s = columns.begin (), t = columns.end (); s != t; ++s) {
      rect* C = *s;
      make_splits (num_boxes, split::HORIZONTAL, C);
    }
  } else {
    cons << console::red << "Please delete all boxes, cannot create box grid :(" << eol;
  }

}

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 set_ball_type (ball* pb, int T) {
  pb->type = T;
  switch (T) {
    case ball::BOUNCER:
      color_ball_using_modulation (pb);
      cons << console::yellow << "Ball "  << "is a bouncer" << eol;
      break;
    case ball::WRECKER:
      pb->r = 1.0f; pb->g = 0.25f; pb->b = 0.25f;
      cons << console::red << "Ball "  << "is a wrecker" << eol;
      break;
    case ball::HEALER:
      pb->r = 0.0f; pb->g = 1.0f; pb->b = 1.0f;
      cons << console::cyan << "Ball "  << "is a healer" << eol;
  }
}

void color_ball_using_modulation (ball* pb) {
  if (pb->type != ball::BOUNCER) return;
  if (pb->mod < 0) {
    pb->r = pb->g = pb->b = 0.0f;
  } else if (pb->mod > 0) {
    pb->r = pb->g = pb->b = 1.0f;
  } else {
    pb->r = pb->g = pb->b = 0.75f;
  }
}