Subversion Repositories DIN Is Noise

Rev

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

/*
* mondrian.h
* 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/
*/


#ifndef __mondrian__
#define __mondrian__

#include "point.h"
#include "viewwin.h"
#include "ui.h"
#include "box_selector.h"
#include "textboard.h"
#include "triggered_note.h"
#include "listeners.h"
#include "curve_editor.h"
#include "help.h"
#include "instrument.h"
#include "rect.h"
#include "ball.h"
#include "slit.h"
#include "alarm.h"
#include "spinner.h"
#include <string>
#include <vector>

typedef std::list<ball*>::iterator balls_iterator;

struct draw_ball_t {
  int position;
  int heading;
  int trails;
  draw_ball_t () { position = heading = trails = 1;}
};

struct field;
struct mondrian : instrument, scale_listener, region_listener, split_listener, typing_listener {

  mondrian ();
  ~mondrian ();

  void enter ();
  void leave ();
  void typing (field& f);

  multi_curve wave; // waveform shared by all balls
  curve_editor waved;  // waveform editor
  wave_listener wavlis;
  void update_waveform (multi_curve& crv);

  multi_curve attack, decay; // attack and decay curves shared by all balls
  curve_editor attacked, decayed; // attack and decay curve editors
  attack_listener attacklis;
  decay_listener decaylis;
  void update_attack ();
  void update_decay ();
  float delta_attack_time, delta_decay_time;

  window win; // edit window
  // object space <> window space mapping
  //
  point<int> win_chunk;
  point<float> obj_chunk;
  point<float> win_per_obj;
  point<float> obj_per_win;
  float win_resolution;
  float obj_resolution;
  void obj2win (const point<float>& p, float& wx, float& wy);
  void obj2win (const float& ox, const float& oy, float& wx, float& wy);
  void win2obj (const float& dx, const float& dy, float& cx, float& cy);

  int pan, zoom;
  void do_panx (int dir);
  void do_pany (int dir);
  void do_zoom (int dir);

  void calc_win_mouse ();
  int lmb_clicked;

  //
  // file
  void load_settings ();
  void load_settings (std::ifstream& file);
  void save_settings (std::ofstream& file);
  box_from_disk_t get_box_from_disk (std::list<box_from_disk_t>& boxes, const std::string& id);
  void load_boxes_and_balls ();
  void save_boxes (std::ofstream& f, rect* R, std::string& id);
  void save_balls (std::ofstream& f);

  void setup ();

  int handle_input ();

  // draw
  //

  int draw__boxes;
  int fill_boxes;
  int label_notes;
  int label_hz_vol;
  int draw__notes;
  int draw_slit_cutter;
  draw_ball_t draw_ball;

  void draw_rect (rect* which);
  void draw_leaves ();
  void draw_balls ();
  void draw_notes ();
  void draw ();

  // boxes
  //

  rect* root; // the main box
  std::list <rect*> leaves; // boxes that dont have children and can bear slits
  rect* get_sibling (rect* R); // return sibling of R
  void find (rect* R, float x, float y, finding& f); // find box where x,y is found. starting with R and down
  rect* box_under_cursor ();
  finding make_finding (rect* R); // R and its sibling
  int bad_rect (rect* R) { return R == 0 || R == root; }

  // box splitting
  //

  rect* split_rect (int type, float where, rect* B = 0); // split box [type = horizontal or vertical]
  int num_boxes;
  int get_note_ids (std::pair<int, int>& nids, rect* R, int type);
  void get_note_ids_from_intervals (std::pair<int, int>& idp, const std::pair<float, float>& ip);
  int multi_split_rect (split_data& sd);
  void multi_split_rect (int n, int type, rect* B = 0, std::list<rect*> *pr = 0, split_listener* sl = 0); // split B [or current] into n boxes
  void multi_split_rect (int type, rect* B = 0, std::list<rect*> *lr = 0, split_listener* sl = 0); // split B [or current] at note intervals
  int splitting_rects;
  std::list<split_data> lsd;
  void split_over (split_data& sd);
  std::list<rect*> columns;
  void make_note_grid (); // split the root box both horizontally and vertically at the notes of the scale
  void make_nxn_grid (); // split the root box into a N x N grid of boxes

  int nleaves;
  void add_leaf (rect* L); // splitting a box makes 2 leaves
  void remove_leaf (rect* L);

  rect* earliest_leaf ();
  rect* latest_leaf ();
  rect* rnd_leaf ();
  rect* biggest_leaf ();
  rect* balled_leaf ();
  rect* pick_leaf (int& what);
  int split_leaf, delete_leaf;

  void recreate_slits (rect* b); // to recreate slits of a just deleted box in its two children ie leaves

  int delete_rect (const finding& f); // delete found box
  void delete_current_rect (); // delete box under cursor
  void update_children (rect* R); // update extents of children of box R
  void delete_children (rect* R); // delete children of box R
  void update_parent (rect* C); // update parent (and ancestors) of box C
  int delete_all_rects; // when active, once every frame

  alarm_t auto_del_rect; // auto delete box?
  alarm_t auto_split_rect; // auto split box?

  int auto_split_orient;
  int auto_split_at;
  int auto_split_which;


  rect* hit; // box hit when clicked
  int edge; // edge hit
  void set_edge (rect* R, int e, float x, float y); // set vertical (using x) or horizontal edge (using y) of box R

  int sel_tar; // selection target
  enum {SELECT_SLITS = 0, SELECT_BALLS};

  // balls
  //
  std::list<ball*> balls;
  int num_balls;

  int adding_balls;
  int added_ball_type;
  int started_making_ball;
  ball* new_ball;
  void do_add_balls (int _type = ball::BOUNCER);

  void split_balls (rect* P, rect* C1, rect* C2); // split balls among children C1 and C2 when parent P is deleted

  void delete_ball (ball* b);
  void delete_all_balls ();
  void delete_selected_balls ();

  float delta_speed;
  void change_speed (spinner<float>& s, float d);

  // ball selection
  //
  std::list<ball*> selected_balls;
  int num_selected_balls;
  void select_balls (const box<float>& rgn);
  void select_box_balls ();
  void after_selection ();
  void select_type (int t);
  struct browse_t {
    int which, last, n;
    std::vector<ball*> balls;
    browse_t ();
    void clear ();
  } browse;
  void browse_ball (int i);

  std::list<ball*>& get_box_balls ();
  std::list<ball*>& get_balls ();
  std::list<ball*>& get_balls (int);
  ball* get_one_selected_ball ();
  void switch_balls_type ();

  // freeze and thaw
  void freeze_thaw_balls (std::list<ball*>& _balls);
  void freeze_balls (std::list<ball*>& _balls);
  void thaw_balls (std::list<ball*>& _balls);
  void freeze_all_balls ();

  void clear_modulations (std::list<ball*>& _balls);

  // ball movement
  int moving_balls;
  void move_balls (float dx, float dy);
  void do_move_balls ();
  void locate_ball (ball* b);

  // ball course
  float delta_rotate_velocity;
  void rotate_velocity (int dir);
  void toggle_auto_rotate (int ar);
  void set_auto_rotate (int ar);
  void flip_velocity ();
  void change_ball_dtheta (int dir);

  // ball misc
  void set_ball_param (int what, float value);
  void change_ball_vol_mult (spinner<float>& s);
  void clone_ball (ball* b);
  void toggle_triggered_sound ();

  // slits
  //

  enum {NOTHING, JUST_SLIT, ANIMATE_SLIT};
  int slitting;
  fader fdr; // for animating slit

  int num_slits;
  std::list<slit*> slits; // all slits
  std::list<slit*> selected_slits;
  int num_selected_slits;

  void load_slits (std::ifstream& f, std::list<box_from_disk_t>& boxes);
  void save_slits (std::ofstream& f);
  int get_index_of_slit (slit* s);
  slit* get_slit_from_index (int q);

  rect* bxs[2]; // a slit can only be on 2 boxes

  int find_hit_slit (float x, float y); // under cursor

  slit_lip_t slit_lip;
  int editing_slit;
  int editing_edge;

  void start_slitting ();
  int try_slitting ();
  int add_remove_slit (float x, float y, fader* fdr = 0, float sz = slit::HALF_SIZE);
  void change_slit_size (spinner<float>& s);
  void change_slit_anim_time (spinner<float>& s);
  void remove_all_slits ();
  void remove_selected_slits ();
  void remove_slit (slit* s);
  void remove_slits (rect* R);
  void remove_slits_on_edge (rect* R, int e);
  void remove_slits_on_current_edge ();
  void remove_slits_on_current_box ();
  void remove_slits_on_boxes_with_balls ();
  void select_box_slits ();
  void toggle_slit_anim ();
  void remove_slit_anim ();
  void start_slit_anim ();

  void clear_selected_targets ();
  void select_all_targets ();
  void select_box_targets ();
  void invert_selected_targets ();
  void delete_selected_targets ();
  void delete_all_targets ();
 
  // visual
  //
  int n_pts;
  float* pts; // balls are points
  float* pts_d; // ball direction are lines
  float* pts_clr; // ball colors
  int n_mrk;
  float* mrk;
  float crsr [8];
  int cursor;
  textboard tb, tb_hz_vol;
  void make_notes ();
  void calc_visual_params ();
  void randomise_box_color (); // of the box under cursor
  void toggle_flag (int& flag, const std::string& poz, const std::string& neg = "");

  // opengl polygon stipple box fill
  //
  static unsigned char patbuf [1024];
  static std::string patstr;
  static int patstep, patlen;
  void fillpatbuf ();

  slit_drawer slit_drawerr;

  static const float gutter, gutter2; // for edge selection & slit making
  static float min_split_size; // for auto box splitting

  struct poly_t {
    std::vector<float> coss, sinn;
    int points;
    float radius;
    float delta_radius;
    poly_t () {
      radius = 0;
      delta_radius = 0;
      points = 0;
    }
  } poly;
  int set_note_poly_points (int p);
  int set_note_poly_radius (float r);

  // notes
  float note_volume;
  int voices;
  int min_voices;
  int auto_adjust_voices;
  note N;
  int num_triggered_notes;
  std::list<triggered_note> triggered_notes;
  std::list<ball*> triggering_balls;
  void launch_note (ball* _ball, float t, float t0, float dt, const std::pair<float, float>& invl);
  void remove_finished_notes ();
  void print_num_triggered_notes ();
  int change_param (float& param, float value, float& recent);
  void change_attack_time_kb (spinner<float>& s); // using kb shortcut
  void change_decay_time_kb (spinner<float>& s);
  void change_trail_size (spinner<int>& s);
  void change_min_voices (int d);

  // others
  //

  int stop_editing_slit ();
  int stop_editing_edge ();
  int stop_adding_balls ();
  int stop_moving_balls ();
  int stop_slitting ();
  int stop_doing_stuff ();
  int recting () {return editing_edge || editing_slit || splitting_rects; }

  void scale_changed ();
  void scale_loaded ();
  void tonic_changed ();

  int modulate_balls (int p);

  void toggle_balls_type (int _type);

  int render_audio (float* L, float* R);

  void bg ();

  void mark_selected_slits ();

  box<float> rgn;
  void region_begin ();
  void region_end ();
  const box<float>& region_update ();

  help _help;

};

int edge_on_root (rect* root, rect** boxes, int* edges);
void draw_slit_cutter (int yesno);

extern mondrian mondrian0;

template<class T> void clear_selected (list<T*>& targets, int& num_targets, int aft_sel = 1) {
  for (typename list<T*>::iterator p = targets.begin (), q = targets.end (); p != q; ++p) {
    T* t = *p;
    t->select = 0;
  }
  targets.clear ();
  num_targets = 0;
  if (aft_sel) mondrian0.after_selection ();
}

template <class T> void select_all (list<T*>& items, list<T*>& selection, int& num_selected) {
  clear_selected<T> (selection, num_selected);
  for (typename list<T*>::iterator p = items.begin (), q = items.end (); p != q; ++p) {
    T* t = *p;
    t->select = 1;
    selection.push_back (t);
    ++num_selected;
  }
  mondrian0.after_selection ();
}

template <class T> void invert_selection (list<T*>& items, list<T*>& selection, int& num_selected) {
  num_selected = 0;
  selection.clear ();
  for (typename list<T*>::iterator p = items.begin(), q = items.end(); p != q; ++p) {
    T* b = *p;
    b->select = !b->select;
    if (b->select) {
      selection.push_back (b);
      ++num_selected;
    }
  }
  mondrian0.after_selection ();
}

template <class T> void select_using_modifiers (T* b, int ctrl, list<T*>& selection, int& num_selected) {
  typename list<T*>::iterator se = selection.end (), f = ::find (selection.begin (), se, b);
  if (f == se) {
    sel:
    b->select = 1;
    push_back (selection, b);
    ++num_selected;
  } else {
    if (ctrl) {
      if (b->select) {
        b->select = 0;
        selection.erase (f);
        --num_selected;
      } else
        goto sel;
    }
  }
}


#endif