Subversion Repositories DIN Is Noise

Rev

Rev 543 | Rev 657 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

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



#include "ui_list.h"
#include "din.h"
#include "morse_code.h"
#include "console.h"
#include "curve_editor.h"
#include "tcl_interp.h"
#include "command.h"
#include "chrono.h"
#include "keyboard_keyboard.h"
#include "viewwin.h"
#include "authors_note.h"
#include "fft.h"
#include "main.h"
#include "curve_picker.h"
#include "fractaliser.h"
#include "warper.h"
#include "recorder.h"
#include "mondrian.h"
#include "binaural_drones.h"
#include "spiraler.h"
#include "circler.h"
#include "rose_milker.h"
#include "sine_mixer.h"
#include <fstream>
#include <string>

using namespace std;

extern string user_data_dir;
extern curve_editor waved, comed, octed, veled, dmod_ed, delayed;

extern int mousex, mousey, mouseyy;

extern string INSTRUMENT;

extern int rmb;

extern const int MILLION;

extern oscilloscope scope;

template <typename S, typename T> static void load_parameter_limits (ifstream& fptr, T** lfs, int n) {
  S lo, hi;
  string name;
  for (int i = 0; i < n; ++i) {
    fptr >> name >> lo >> hi;
    T* t = lfs[i];
    t->set_name (name);
    t->set_limits (lo, hi);
  }
}

template <typename S, typename T> static void save_parameter_limits (ofstream& fptr, T** lfs, int n) {
  S lo, hi;
  for (int i = 0; i < n; ++i) {
    T* t = lfs[i];
    t->get_limits (lo, hi);
    fptr << t->name << ' ' << lo << ' ' << hi << endl;
  }
}

ui_list::ui_list () :
fed (fnt, "font.ed", "font.hlp"),
lfs_attack_time ("Attack Time", 70, 15, atv),
lfs_decay_time ("Decay Time", 70, 15, dkv),
lfs_num_voices ("Voices", 70, 15, nvv),
d_min_max("Min/Max"), mml_min ("Min"), mml_max ("Max"),
lmm_attack (lfs_attack_time),
lmm_decay (lfs_decay_time),
lmm_num_voices (lfs_num_voices) {
  current = 0;
  prev = 0;
  crved = 0;
  esct = -1;
  rmb_clicked = 0;
}

void ui_list::set_current (ui* u) {

  if (current) {
    current->leave ();
    prev = current;
  }

  current = u;

  cons << YELLOW << "@ " << current->name << eol;

  current->enter ();

  if (u->ed) {
    crved = static_cast<curve_editor*>(u);
    if (crved->is_waveform_editor) dofft ();
  } else {
    //crved = 0;
    uis[0] = current;
    //if (_is_inst) uis[0] = current;
  }

}

void ui_list::bg () {

  current->calc_win_mouse ();

  for (vector<ui*>::size_type i = 1, j = uis.size(); i < j; ++i) uis[i]->bg (); // bg for instruments and editors

  // fade fx for voice, gater & delay
  eval_fade (fdr_voice, cb_voice);
  flash_gater (); // flashes in sync with gater value
  eval_fade (fdr_delay, cb_delay);

}

int ui_list::is_widget_on_screen (widget* w, ui* scr) {
  vector<widget*>& widgets = widgets_of [scr];
  for (int i = 0, j = widgets.size (); i < j; ++i) if (w == widgets[i]) return 1;
  return 0;
}

int ui_list::handle_input () {

  // handle quit
  //
  #ifdef __MACOSX_CORE__
  if (keydown (SDLK_LMETA) && keydown (SDLK_q)) { // command key + q  on mac os x
    quit = IMMEDIATE;
    return 1;
  }
  #else
  if (alt_down () && keydown (SDLK_F4)) { // alt + f4 on windows & linux
    quit = IMMEDIATE;
    return 1;
  }
  #endif

  // toggle menu
  //
  #ifdef __EVALUATION__
    if (current != &anote) { // no menu on author's note
  #endif
    if (rmb) {
      if (!rmb_clicked) {
        rmb_clicked = 1;
        if (escape_from_things ());
        else main_menu.toggle ();
      }
    } else rmb_clicked = 0;
  #ifdef __EVALUATION__
  }
  #endif

  // handle widgets
  //
  basic_editor::hide_cursor = mouse_slider0.active;
  if (widget::focus == 0) { // find a widget that has focus
    vector<widget*> widgets = widgets_of [current];
    for (vector<widget*>::size_type i = 0, j = widgets.size (); i < j; ++i) {
      widget* wi = widgets[i];
      if (wi->visible && wi->enabled) wi->handle_input ();
      if (widget::focus) return 1;
    }
  } else { // handle input of widget with focus
    widget::focus->handle_input ();
    return 1;
  }

  // handle current screen
  current->handle_input ();

  // handle common
  if (keypressed (SDLK_1)) {
    if (current->ed == 0) goto_next_instrument (); // switch instrument
    load_instrument ();
  } else { // switch to editor
    for (int i = 0; i < MAX_EDITORS; ++i) {
      if (keypressed (key[i])) {
        ui* edi = ed[i];
        if (edi) {
          main_menu.setup_tabs (edi);
          set_current (edi);
          return 1;
        }
      }
    }
  }
 
  if (keypressed(SDLK_RETURN)) {
    if (alt_down ()) { // clear recording
      main_menu.recl.clicked (main_menu.b_clear_record);
    } else if (ctrl_down()) { // start recording
      if (cb_record.state == 0) cb_record.turn_on (); else cb_record.turn_off ();
    }
  }

  if (keypressed (SDLK_F2)) {if (TURN_OFF_UI) turn_on_ui (); else turn_off_ui ();}
  if (keypressed (SDLK_PAUSE)) {
    scope.visible = !scope.visible;
    main_menu.cb_scope.set_state (scope.visible, 0);
  }
  if (keypressed (SDLK_MENU)) main_menu.clicked (main_menu.b_menu);
  if (keypressed (SDLK_ESCAPE)) {
    if (TURN_OFF_UI) turn_on_ui ();
    else if (escape_from_things()) ;
    else if (current->ed) {
      if (crved->todo != curve_editor::NOTHING) {
        crved->do_nothing ();
        return 1;
      } else load_instrument ();
    } else if (current == &settings_scr) {
      load_instrument ();
    } else { // ask to press esc again to really quit
      esc:
      if (esct == -1) {
        cons << console::red << "Press ESC again to quit" << eol;
        esct = ui_clk();
      } else {
        static const float ESC_TIMEOUT = 1; // in seconds
        double dt = ui_clk() - esct;
        if (dt > ESC_TIMEOUT) {
          esct = -1;
          goto esc;
        } else if (quit != TRY) try_quit ();
      }
    }
  }

  return 1;

}

int ui_list::set_editor (const string& name, int screen) {
  if (screen > 0) {
    ui* ei = 0;
    for (int i = 0, j = uis.size(); i < j; ++i) {
      if (uis[i]->name == name) {
        ei = uis[i];
        break;
      }
    }
    if (ei) {
      ed [screen - 2] = ei;
      return 1;
    } else {
      cmdlst.result = "bad editor name";
      return 0;
    }
  } else {
    cmdlst.result = "bad screen number (valid: 2 to 8)";
    return 0;
  }
}

void ui_list::draw () {
  glClear (GL_COLOR_BUFFER_BIT);
  glMatrixMode (GL_MODELVIEW);
  glLoadIdentity ();
  current->draw ();
  glMatrixMode (GL_PROJECTION);
  glLoadIdentity ();
  glOrtho (0, view.xmax, 0, view.ymax, 0, 1);
  vector<widget*> widgets = widgets_of [current];
  for (vector<widget*>::size_type i = 0, j = widgets.size (); i < j; ++i) {
    widget* wi = widgets[i];
    if (wi->visible) wi->draw ();
  }
}

// all screens
ui* SCREENS [] = {
  &din0.waved,
  &din0.moded,
  &din0.gated,
  &delayed,
  &octed,
  &din0.droneed,
  &keybd2,
  &keybd2.waved,
  &keybd2.attacked,
  &keybd2.decayed,
  &keybd2.veled,
  &comed,
  &mc.ed,
  &dmod_ed,
  &uis.fed,
  &fractaliser_.ed,
  &warper_.ed,
  &mondrian0,
  &din0,
  &mondrian0.waved,
  &mondrian0.attacked,
  &mondrian0.decayed,
  &binaural_drones0,
  &binaural_drones0.waved,
  &spiraler_.scr.sin_ed,
  &spiraler_.scr.cos_ed,
  &spiraler_.scr.rad_ed,
  &rosemilker.scr.sin_ed,
  &rosemilker.scr.cos_ed,
  &circler_.scr.sin_ed,
  &circler_.scr.cos_ed,
  &circler_.scr.rad_ed,
  &sinemixer.sin_ed,
  0
};

void ui_list::add_widgets () {

  // add fft display on waveform editors
  ui* uwav [] = {&din0.waved, &keybd2.waved, &din0.droneed, &mondrian0.waved, &binaural_drones0.waved};
  for (int i = 0; i < 5; ++i) widgets_of[uwav[i]].push_back (&fft0);

  // add plugin browser to these editors
  ui* ueds [] = {
    &din0.waved,
    &din0.moded,
    &din0.droneed,
    &dmod_ed,
    &keybd2.waved,
    &fractaliser_.ed,
    &warper_.ed,
    &mondrian0.waved,
    &din0.gated,
    &binaural_drones0.waved,
    &circler_.scr.sin_ed,
    &circler_.scr.cos_ed,
    &sinemixer.sin_ed,
    &rosemilker.scr.sin_ed,
    &rosemilker.scr.cos_ed,
    &spiraler_.scr.sin_ed,
    &spiraler_.scr.cos_ed
  };
  for (int i = 0; i < 17; ++i) widgets_of[ueds[i]].push_back (&plugin__browser);

  // add oscilloscope to all instruments
  extern const int NUM_INSTRUMENTS;
  extern instrument* INSTRUMENT_PTR[];
  for (int i = 0; i < NUM_INSTRUMENTS; ++i) widgets_of[(ui *)INSTRUMENT_PTR[i]].push_back (&scope);

  // add selectors to microtonal keyboard and mondrian
  mkb_selector.set_listener (&din0);
  mon_selector.set_listener (&mondrian0);

  widget* wall [] = {&cons, &main_menu, &b_close}; // appears on everything
  for (ui** p = SCREENS; *p != 0; ++p) {
    ui* u = *p;
    if (is_instrument (u) == 0) widgets_of[u].push_back (&curve_picker); // picker on all editors
    for (int i = 0; i < 3; ++i) widgets_of[u].push_back (wall[i]);
  }
  b_close.hide ();

  // no menu on author's note & settings screen
  widgets_of[&anote].push_back(&cons);
  widgets_of[&settings_scr].push_back (&cons);

  widget* wdin [] = {
    &cb_voice,
    &cb_gater,
    &cb_delay,
    &cb_compress,
    &b_settings,
    &ab_scroll_left,
    &ab_scroll_right,
    &ab_scroll_up,
    &ab_scroll_down,
    &cb_show_pitch_volume_board,
    &cb_show_pitch_volume_drones,
    &cb_record,
    &din0.dinfo.gravity,
    &mkb_selector
 };

  widget* wkeybd2 [] = {
    &cb_delay,
    &cb_compress,
    &b_settings,
    &lfs_attack_time,
    &lfs_decay_time,
    &lfs_num_voices,
    &l_pitch_bend,
    &sp_pitch_bend,
    &cb_show_nearby_notes,
    &d_parameters,
    &ab_parameters,
    &l_waveform_display,
    &ab_prev_wav,
    &ab_next_wav,
    &cd_waveform_display,
    &d_min_max,
    &l_octave_shift,
    &ab_octave_down,
    &ab_octave_up,
    &sp_octave_shift_bpm,
    &cb_record
   };

  widget* wmondrian [] = {
    &l_mondrian_voices,
    &cb_delay,
    &cb_compress,
    &b_settings,
    &cb_record,
    &mon_selector,
  };

  widget* wsbd [] = {
    &cb_delay,
    &cb_compress,
    &b_settings,
    &cb_record
  };

  for (int i = 0, j = 14; i < j; ++i) widgets_of [&din0].push_back (wdin[i]);
  for (int i = 0, j = 21; i < j; ++i) widgets_of [&keybd2].push_back (wkeybd2[i]);
  for (int i = 0, j = 6; i < j; ++i) widgets_of [&mondrian0].push_back (wmondrian[i]); l_mondrian_voices.set_label ("Voices: ** / **");
  for (int i = 0, j = 4; i < j; ++i) widgets_of [&binaural_drones0].push_back (wsbd[i]);

  // min/max for sliders
  widget* wmmx [] = {&mml_min, &mml_max, &mmf_min, &mmf_max};
  for (int i = 0; i < 4; ++i) {
    widget* w = wmmx [i];
    widgets_of[&keybd2].push_back(w);
  }

}

void ui_list::setup () {

  uis.push_back (0); // will be filled by load_instrument later
  for (ui** p = SCREENS; *p != 0; ++p) {
    ui* u = *p;
    uis.push_back (u);
  }
 
  int dirs [] = {arrow_button::left, arrow_button::right, arrow_button::up, arrow_button::down};
  arrow_button* scrl [] = {&ab_scroll_left, &ab_scroll_right, &ab_scroll_up, &ab_scroll_down};
  for (int i = 0; i < 4; ++i) {
    arrow_button* si = scrl[i];
    si->set_direction (dirs[i]);
    si->set_size (12);
    si->set_listener (&sal);
    si->click_repeat = 1;
    si->first_repeat_time = 0.005;
    si->subsequent_repeat_time = 0.015;
  }

  cb_voice.set_listener (&vlis);
  cb_voice.colorize (0);

  cb_gater.set_listener (&glis);
  cb_gater.colorize (0);

  cb_delay.set_listener (&dlis);
  cb_delay.colorize (0);

  cb_compress.set_listener (&clis);

  b_settings.set_listener (&slis);

  cb_record.set_listener (&main_menu.recl);

  // min/max for sliders
  widget* wmmx [] = {&mml_min, &mml_max, &mmf_min, &mmf_max};
  for (int i = 0; i < 4; ++i) {
    widget* w = wmmx [i];
    d_min_max.add_child (w);
  }
  //d_min_max.set_moveable (1);

  d_min_max.hide ();
  widget* pw1[5] = {&d_min_max, &mml_min, &mml_max, &mmf_min, &mmf_max};
  widget_load ("d_min_max", pw1, 5);

  // pitch bend
  //

  l_pitch_bend.set_text ("Pitch Bend");
  l_pitch_bend.add_child (&sp_pitch_bend);

  sp_pitch_bend.set_label ("Hz/Pixel");
  sp_pitch_bend.set_delta (0.01f);
  sp_pitch_bend.set_limits (0.0f, MILLION); // upto 1 MILLION hz/pixel

  widget* wpb [] = {&l_pitch_bend, &sp_pitch_bend, &cb_show_nearby_notes};
  widget_load ("d_pitch_bend", wpb, 3);
  sp_pitch_bend.add_child (&cb_show_nearby_notes);
  sp_pitch_bend.set_listener (&pbl);

  cb_show_nearby_notes.set_listener (&pbl);
  cb_show_nearby_notes.turn_off ();

  d_parameters.add_child (&l_pitch_bend);

  // waveform display
  //

  l_waveform_display.set_text ("Waveform");

  ab_prev_wav.set_direction (arrow_button::left);
  ab_prev_wav.set_listener (&wdl);
  ab_next_wav.set_direction (arrow_button::right);
  ab_next_wav.set_listener (&wdl);

  ab_prev_wav.click_repeat = ab_next_wav.click_repeat = 1;
  ab_prev_wav.first_repeat_time = ab_next_wav.first_repeat_time = 0.33;
  ab_prev_wav.subsequent_repeat_time = ab_next_wav.subsequent_repeat_time = 1/20.;

  widget* wwd [] = {&l_waveform_display, &cd_waveform_display, &ab_prev_wav, &ab_next_wav};
  widget_load ("d_waveform_display", wwd, 4);

  //l_waveform_display.set_moveable (1);

  cd_waveform_display.set_size (96, 96);
  cd_waveform_display.crv = &keybd2.wave; // keyboard-keyboard's waveform
  cd_waveform_display.calc_bbox ();
 
  for (int i = 1; i < 4; ++i) l_waveform_display.add_child (wwd[i]);
  d_parameters.add_child (&l_waveform_display);

  l_octave_shift.set_text ("Octave Shift");
  d_parameters.add_child (&l_octave_shift);

  ab_octave_down.set_direction (arrow_button::left);
  ab_octave_down.set_listener (&osl);
  ab_octave_up.set_direction (arrow_button::right);
  ab_octave_up.set_listener (&osl);
  int arrow_size = 24;
  ab_octave_up.set_size (arrow_size);
  ab_octave_down.set_size (arrow_size);
  sp_octave_shift_bpm.set_label ("BPM");
  sp_octave_shift_bpm.set_delta (1);
  sp_octave_shift_bpm.set_listener (&osl);

  widget* wos [] = {&l_octave_shift, &ab_octave_down, &ab_octave_up, &sp_octave_shift_bpm};
  for (int i = 1; i < 4; ++i) l_octave_shift.add_child (wos[i]);
  sp_octave_shift_bpm.set_limits (0.0f, MILLION); // max 1 MILLION beats/min

  label_field_slider<float>* lfs1 [2] = {&lfs_attack_time, &lfs_decay_time};
  label_field_slider<int>* lfs2 [1] = {&lfs_num_voices};

  ifstream fptr ((user_data_dir + "d_parameter_limits").c_str(), ios::in);
  load_parameter_limits<float, label_field_slider<float> > (fptr, lfs1, 2);
  load_parameter_limits<int, label_field_slider<int> > (fptr, lfs2, 1);

  d_parameters.add_child (&d_min_max);
  d_parameters.add_child (&lfs_attack_time);
  d_parameters.add_child (&lfs_decay_time);
  d_parameters.add_child (&lfs_num_voices);
  d_parameters.add_child (&ab_parameters);

  widget* pw2[9] = {&d_parameters, &lfs_attack_time, &lfs_decay_time, &lfs_num_voices, &ab_parameters, &l_octave_shift, &ab_octave_up, &ab_octave_down, &sp_octave_shift_bpm};
  widget_load ("d_parameters", pw2, 9);

  d_parameters.set_moveable (1);

  ab_parameters.set_listener (&pal);

  d_parameters.set_name ("parameters");
  lfs_attack_time.set_name ("attack_time");
  lfs_decay_time.set_name ("decay_time");
  lfs_num_voices.set_name ("num_voices");

  cb_voice.set_state (din0.dinfo.voice);
  cb_gater.set_state (din0.dinfo.gater);
  cb_delay.set_state (din0.dinfo.delay);
  cb_compress.set_state (din0.dinfo.compress);

#ifdef __EVALUATION__
  anote.setup ();
#endif

  plugin__browser.setup ();
  settings_scr.setup ();
  main_menu.setup ();

  b_close.set_label ("Close");

}

void ui_list::update_widgets (int wnow, int hnow, int wprev, int hprev) {

  main_menu.update ();
  plugin__browser.update ();

  cb_voice.set_label ("Voice");
  cb_gater.set_label ("Gater");
  cb_delay.set_label ("Delay");
  cb_compress.set_label ("Compressor");
  b_settings.set_label ("Settings");
  cb_record.set_label ("Record");
  l_mondrian_voices.update ();

  d_min_max.set_text ("Min/Max");
  mml_min.set_text ("Min");
  mml_max.set_text("Max");
  mmf_min.set_text (mmf_min.get_text());
  mmf_max.set_text (mmf_max.get_text());
 
  int lh = get_line_height ();
  int y = update_bottom_line ();
  y += lh;

  arrow_button* scrl [] = {&ab_scroll_left, &ab_scroll_right, &ab_scroll_up, &ab_scroll_down};
  int nscrls = 4;
  int w = ab_scroll_left.extents.width;
  int dw [] = {5, 7, 0, 0};
  int x = (view.xmax - nscrls * w) / 2;
  for (int i = 0; i < nscrls; ++i) {
    arrow_button* ab = scrl[i];
    ab->set_pos (x, y);
    x = x + w + dw[i];
  }

  x += w;
  cb_show_pitch_volume_board.set_pos (x, ab_scroll_down.extents.bottom - fnt.lift);
  cb_show_pitch_volume_board.set_label ("i");
  cb_show_pitch_volume_board.set_listener (&spvl);

  x += w;
  cb_show_pitch_volume_drones.set_pos (x + 1, ab_scroll_down.extents.bottom - fnt.lift);
  cb_show_pitch_volume_drones.set_label ("j");
  cb_show_pitch_volume_drones.set_listener (&spvl);

  d_parameters.set_text ("Parameters");

  lfs_attack_time.update ();
  lfs_decay_time.update ();
  lfs_num_voices.update ();

  l_pitch_bend.update ();
  sp_pitch_bend.update ();
  cb_show_nearby_notes.set_label ("Show nearby notes");

  l_waveform_display.update ();

  l_octave_shift.update ();
  ab_octave_up.update ();
  ab_octave_down.update ();
  sp_octave_shift_bpm.update ();

  settings_scr.update_widgets ();

  b_close.update ();

}

void ui_list::flash_gater () {

  static color clr [2] = {color (1, 0, 0), color (0, 1, 0)};
  float dg = aout.gatr [aout.samples_per_channel - 1];
  if (fdr_gater.eval ()) {
    cb_gater.blend_on_off_color (fdr_gater.alpha);
    color& c = const_cast<color&> (cb_gater.clr);
    c *= dg;
  } else {
    int gi = din0.dinfo.gater;
    cb_gater.set_color (dg * clr [gi].r, dg * clr [gi].g, dg * clr[gi].b);
  }

}

float ui_list::eval_fade (fader& fdr, checkbutton& cb) {
  if (fdr.eval ()) cb.blend_on_off_color (fdr.alpha);
  return fdr.amount;
}

void ui_list::dofft () {
  if (crved && crved->num_curves && !fft0.folded ()) fft0.go (crved->curveinfo[0].curve);
}

void setup_fade (fader& fdr, int& target, int what) {
  if (fdr.on == 0) {
    target = what;
    if (what) fdr.set (0, 1); else fdr.set (1, 0);
  }
}

void voice__listener::changed (checkbutton& cb) {
  int what = cb.is_on ();
  setup_fade (uis.fdr_voice, din0.dinfo.voice, what);
}

void gater__listener::changed (checkbutton& cb) {
  int what = cb.is_on ();
  setup_fade (uis.fdr_gater, din0.dinfo.gater, what);
}

void delay__listener::changed (checkbutton& cb) {
  int what = cb.is_on ();
  setup_fade (uis.fdr_delay, din0.dinfo.delay, what);
}

void compress__listener::changed (checkbutton& cb) {
  din0.dinfo.compress = cb.is_on ();
}

void settings__listener::clicked (button& b) {
  uis.set_current (&uis.settings_scr);
}

void pitch_bend_listener::changed (field& f) {
  PITCH_BEND_PER_PIXEL = f;
  PITCH_BEND = PITCH_BEND_PER_PIXEL * 100;
}

void pitch_bend_listener::changed (checkbutton& cb) {
  keybd2.show_nearby_notes = cb.state;
}

void waveform_display_listener::clicked (button& b) {
  if (&b == &uis.ab_prev_wav) {
    keybd2.waved.win.calc ();
    keybd2.waved.load_curve (-1);
  } else {
    keybd2.waved.win.calc ();
    keybd2.waved.load_curve (+1);
  }
}

void parameters_listener::clicked (button& b) {
  arrow_button& ab = dynamic_cast<arrow_button&> (b);
  if (ab.direction == arrow_button::down) {
    d_min_max_visible = uis.d_min_max.visible;
    ab.set_direction (arrow_button::right);
    uis.d_parameters.hide (widget::only_children);
  } else {
    uis.d_parameters.show ();
    ab.set_direction (arrow_button::down);
    if (d_min_max_visible == 0) uis.d_min_max.hide ();
  }
  ab.show ();
}

ui_list::~ui_list () {

  widget* pw1[9] = {&d_parameters, &lfs_attack_time, &lfs_decay_time, &lfs_num_voices, &ab_parameters, &l_octave_shift, &ab_octave_up, &ab_octave_down, &sp_octave_shift_bpm};
  widget_save ("d_parameters", pw1, 9);

  widget* pw2[5] = {&d_min_max, &mml_min, &mml_max, &mmf_min, &mmf_max};
  widget_save ("d_min_max", pw2, 5);

  widget* wwd [] = {&l_waveform_display, &cd_waveform_display, &ab_prev_wav, &ab_next_wav};
  widget_save ("d_waveform_display", wwd, 4);

  widget* wpb [] = {&l_pitch_bend, &sp_pitch_bend, &cb_show_nearby_notes};
  widget_save ("d_pitch_bend", wpb, 3);

  label_field_slider<float>* lfs1 [2] = {&lfs_attack_time, &lfs_decay_time};
  label_field_slider<int>* lfs2 [2] = {&lfs_num_voices};

  ofstream fptr ((user_data_dir + "d_parameter_limits").c_str(), ios::out);
  save_parameter_limits<float, label_field_slider<float> > (fptr, lfs1, 2);
  save_parameter_limits<int, label_field_slider<int> > (fptr, lfs2, 1);

}

void ui::enter () {
  warp_mouse (prev_mousex, prev_mousey);
  if (!ed) scope.load_current_instrument ();
}

void ui::leave () {
  if (hide_menu () == 0) {
    prev_mousex = mousex;
    prev_mousey = mousey;
  }
  if (!ed) scope.save_current_instrument ();
  if (mouse_slider0.active) mouse_slider0.deactivate ();
  if (TURN_OFF_UI) turn_on_ui ();
}

extern const float MIN_TIME;

void attack_val::operator() (const float& f) {
  if (equals<float> (f, 0.0f)) ATTACK_TIME = MIN_TIME; else ATTACK_TIME = f;
}

void decay_val::operator() (const float& f) {
  if (equals<float> (f, 0.0f)) DECAY_TIME = MIN_TIME; else DECAY_TIME = f;
}

void num_voices_val::operator() (const int& i) {
  int j = i; if (i < 1) j = 1;
  NOTE_VOLUME = 1.0f / j;
  keybd2.calc_visual_params ();
}

template <typename T> min_max_clicked<T>::min_max_clicked (label_field_slider<T>& _lfs) : lfs (_lfs) {
  lfs.set_button_listener (this);
}

template <typename T> void min_max_clicked<T>::clicked (button& b) {

  // toggle
  if (uis.d_min_max.visible) {
    if (&b == ui_list::mm_last) uis.d_min_max.hide ();
  } else {
    uis.d_min_max.show ();
  }

  // label min/max
  uis.d_min_max.set_text (lfs.get_text ());

  // load min/max fields
  T low, high;
  lfs.get_limits (low, high);

  uis.mmf_min.set_text (low);
  uis.mmf_max.set_text (high);

  // setup listeners
  uis.mmf_min.change_lsnr = this;
  uis.mmf_max.change_lsnr = this;

  ui_list::mm_last = &b;

}

template <typename T> void min_max_clicked<T>::changed (field& f) {

  stringstream ss;
  ss << f.get_text ();

  T l, h;
  if (ss.str () != "") {

    T t; ss >> t;
    lfs.get_limits (l, h);

    if (&f == &uis.mmf_min) {
      lfs.set_limits (t, h);
    } else {
      lfs.set_limits (l, t);
    }

  }

  lfs.get_limits (l, h);
  uis.mmf_min.set_text (l);
  uis.mmf_max.set_text (h);

}

void widget_load (const string& fname, widget** pw, int n) {
  ifstream f ((user_data_dir + fname).c_str(), ios::in);
  for (int i = 0; i < n; ++i) pw[i]->load (f);
}

void widget_save (const string& fname, widget** pw, int n) {
  ofstream f ((user_data_dir + fname).c_str(), ios::out);
  for (int i = 0; i < n; ++i) pw[i]->save (f);
}

void scroll_arrow_listener::clicked (button& b) {
  if (&b == &uis.ab_scroll_left) {
    din0.scroll (-din0.dinfo.scroll.dx, 0, 0);
    cons << console::yellow << "You can press a to scroll left" << eol;
  } else if (&b == &uis.ab_scroll_right) {
    din0.scroll (din0.dinfo.scroll.dx, 0, 0);
    cons << console::yellow << "You can press d to scroll right" << eol;
  } else if (&b == &uis.ab_scroll_up) {
    din0.scroll (0, -din0.dinfo.scroll.dy, 0);
    cons << console::yellow << "You can press s to scroll up" << eol;
  } else {
    din0.scroll (0, din0.dinfo.scroll.dy, 0);

    cons << console::yellow << "You can press w to scroll down" << eol;
  }
}

void show_pitch_volume_listener::changed (checkbutton& cb) {
  if (&cb == &uis.cb_show_pitch_volume_board)
    din0.dinfo.show_pitch_volume.board = cb.state;
  else
    din0.dinfo.show_pitch_volume.drones = cb.state;
}

void fft::clicked (button& b) {
  if (ab_fold.direction == arrow_button::down) {
    ab_fold.set_direction (arrow_button::right);
    l_title.hide (widget::only_children);
  } else {
    ab_fold.set_direction (arrow_button::down);
    l_title.show ();
    uis.dofft ();
  }
  ab_fold.show ();
  ab_fold.update ();
}

int fft::folded () {
  return (ab_fold.direction == arrow_button::right);
}

void ui_list::handle_plugin (widget* which, int what) {
  enum {INSTALL = 1, REMOVE = 0};
  ui* ueds [] = {
    &din0.waved,
    &din0.moded,
    &din0.droneed,
    &keybd2.waved,
    &fractaliser_.ed
  };

  int num_editors = 5;
  if (what == INSTALL) {
    for (int i = 0; i < num_editors; ++i) widgets_of[ueds[i]].push_back (which);
  } else {
    for (int i = 0; i < num_editors; ++i) {
      vector<widget*>& widgets = widgets_of [ueds[i]];
      vector<widget*>::iterator iter = find (widgets.begin (), widgets.end(), which);
      if (iter != widgets.end()) widgets.erase (iter);
    }
  }
  main_menu.toggle ();
}

int ui_list::update_bottom_line () {
  widget** winst = 0;
  widget* wdin[] = {&cb_voice, &cb_gater, &cb_delay, &cb_compress, &b_settings, &main_menu.b_menu, &cb_record};
  widget* wmon[] = {&l_mondrian_voices, &cb_delay, &cb_compress, &b_settings, &main_menu.b_menu, &cb_record};
  widget* wcom[] = {&cb_delay, &cb_compress, &b_settings, &main_menu.b_menu, &cb_record};
  instrument* inst = get_current_instrument ();
  int n = 0;
  if (inst == &din0) {
    winst = wdin;
    n = 7;
  } else if (inst == &mondrian0) {
    winst = wmon;
    n = 6;
  } else {
    winst = wcom;
    n = 5;
  }
  int dx = 35, w = 0;
  for (int i = 0; i < n; ++i) w += winst[i]->extents.width;
  w += n * dx;
  int x = (view.xmax - w) / 2, y = (int)(0.25f * get_line_height() + 0.5);
  for (int i = 0; i < n; ++i) {
    widget& b = *winst[i];
    const box<int>& e = b.extents;
    b.set_extents (x, y, x + e.width, y + e.height);
    b.set_pos (x, y);
    x = e.right + dx;
  }
  return y;
}

void ui_list::remove (widget* w) {
  vector<widget*>& current_widgets = widgets_of [current];
  vector<widget*>::iterator end = current_widgets.end (), i = find (current_widgets.begin (), end, w);
  if (i != end) current_widgets.erase (i);
}

int ui_list::escape_from_things () {
  int ret = 1;
  if (TURN_OFF_UI) turn_on_ui ();
  else if (mkb_selector.exists ()) mkb_selector.abort ();
  else if (mon_selector.exists ()) mon_selector.abort ();
  else if (mouse_slider0.active) mouse_slider0.deactivate ();
  else if (main_menu.filtered) main_menu.unfilter ();
  else if (binaural_drones0.aborted ());
  else if (main_menu.show) main_menu.toggle ();
  else if (din0.moving_drones) din0.toggle_moving_drones ();
  else if (din0.adding_drones) din0.toggle_adding_drones ();
  else if (din0.create_mesh) { din0.create_mesh = mkb_selector.mesh = 0; cons << RED << "Stopped making drone mesh." << eol;}
  else if (mondrian0.stop_doing_stuff ());
  else ret = 0;
  return ret;
}

int is_menu_visible () {
  return uis.main_menu.show;
}

void abort_selectors () {
  mon_selector.abort ();
  mkb_selector.abort ();
}