/*
* command.cc
* DIN Is Noise is copyright (c) 2006-2024 Jagannathan Sampath
* DIN Is Noise is released under GNU Public License 2.0
* For more information, please visit https://dinisnoise.org/
*/

#include "globals.h"
#include "din.h"
#include "command.h"
#include "console.h"
#include "math.h"
#include "delay.h"
#include "scalelist.h"
#include "font.h"
#include "glyph.h"
#include "chrono.h"
#include "utils.h"
#include "ui_list.h"
#include "curve_editor.h"
#include "tokenizer.h"
#include "tcl_interp.h"
#include "chrono.h"
#include "scale_info.h"
#include "keyboard_keyboard.h"
#include "main.h"
#include "audio.h"
#include "midi_in.h"
#include "fft.h"
#include "mondrian.h"
#include "binaural_drones.h"
#include "curve_mixer.h"
#include "recorder.h"
#include "circler.h"
#include "noiser.h"
#include "sine_mixer.h"

#include <string>
#include <fstream>
using namespace std;

extern ofstream dlog;

#define result cmdlst.result

extern i_binaural_drones binaural_drones0;

extern string SCALE;
extern string TUNING;
extern int NOTATION; 

extern int TRAILSIZE;
extern int NUM_OCTAVES;

extern float VOICE_VOLUME;

extern float NOTE_VOLUME;
extern float ATTACK_TIME;

extern float DECAY_TIME;

extern float DELTA_TIME;

extern string INSTRUMENT;
extern const char* INSTRUMENTS [];
extern int CURRENT_INSTRUMENT;

extern int PITCH_BEND;
extern float PITCH_BEND_PER_PIXEL;

extern int NUM_NOTES;
extern const char* WESTERN_FLAT [];
extern const char* WESTERN_SHARP [];
extern float WIKIPEDIA_KEY_FREQUENCIES [];

extern load_globals lg;

extern scale_info scaleinfo;



extern string LOCATION;
extern string VERSION_NUMBER;

extern char BUFFER [];

extern const char spc;

extern map <string, float> INTERVALS;

extern scale_info all_notes;

template <class T> void load_vector_from_string (const string& s, vector<T>& data) {
  tokenizer tz (s);
  data.clear ();
  stringstream ss;
  string t;
  T tt;
  while (1) {
    ss.clear (); tz >> t;
    if (t == "") break;
    ss << t; ss >> tt; data.push_back (tt);
  }
}

void cmdlist::add (command* cmd) {
	dlog << "added command: " << cmd->longname << " aka " << cmd->shortname << endl;
  cmds.push_back (cmd);
  ncmds = cmds.size ();
}

cmdlist::~cmdlist () {
  dlog << "--- destroyed cmdlist ---" << endl;
}

int set_var::set_scroll (string& varn, tokenizer& tz) {
  while (1) {
    tz >> varn;
    if (varn == "r" || varn == "rate") {
      tz >> d->dinfo.scroll.rate;
      d->dinfo.scroll.calc_repeat_time ();
    } else if (varn == "x") {
      tz >> d->dinfo.scroll.dx;
    } else if (varn == "y") {
      tz >> d->dinfo.scroll.dy;
    } else {
      if (varn != "") {
        result = varn + ": bad scroll param";
        return 0;
      } else return 1;
    }
  }
}

int set_var::set_zoom_pan (int& rate, float& amount, string& varn, tokenizer& tz) {
  while (1) {
    tz >> varn;
    if (varn == "r" || varn == "rate") {
      tz >> rate;
      window::calc_repeats ();
      MENU.set_pan_repeat (window::PAN_REPEAT);
    } else if (varn == "a" || varn == "amount") {
      tz >> amount;
      window::calc_repeats ();
      MENU.set_zoom_repeat (window::ZOOM_REPEAT);
      if (uis.crved) uis.crved->win.calc_panxy ();
    } else {
      if (varn != "") {
        result = varn + ": bad zoom/pan param";
        return 0;
      } else return 1;
    }
  }
}

int set_var::operator() (tokenizer& tz) {

  string varn;
  int extract = 1;

  while (1) {

    if (extract) {
      tz >> varn;
      result = "";
    } else extract = 1;

    //
    // variables

    if (varn == "spar" || varn == "show_parameters") {
      int what; tz >> what;
      if (what) uis.ab_parameters.set_dir (arrow_button::right); else uis.ab_parameters.set_dir (arrow_button::down);
      uis.pal.clicked (uis.ab_parameters);
    } else if (varn == "su" || varn == "sustain") {
      float g; tz >> g; _gotog.set (g);
    } else if (varn == "vv" || varn == "voice_volume") {
      tz >> VOICE_VOLUME;
    } else if (varn == "ins" || varn == "instrument") {
      tz >> INSTRUMENT;
      find_instrument ();
		} else if (varn == "tu" || varn == "tuning") {
      string name; tz >> name;
      string fname = name + ".tuning";
      if (lg.load_intervals (fname)) {
        TUNING = name;
        result = name;
				all_notes.intervals = INTERVALS;
				din0.tuning_changed ();
        keybd2.setup_notes ();
        keybd2.setup_midi_notes ();
        keybd2.calc_visual_params ();
				mondrian0.calc_visual_params ();
        uis.settings_scr.sn_scale_notes.refresh ();
        cons ("list2var [get-intervals]");
      } else {
        result = "bad tuning";
        return 0;
      }
    } else if (varn == "sc" || varn == "scroll") {
      extract = set_scroll (varn, tz);
    } else if (varn == "zoom" || varn == "z") {
      extract = set_zoom_pan (window::ZOOM_RATE, window::ZOOM_AMOUNT, varn, tz);
    } else if (varn == "pan" || varn == "p") {
      extract = set_zoom_pan (window::PAN_RATE, window::PAN_AMOUNT, varn, tz);
    } else if (varn == "fps") {
      extern int FPS; 
			tz >> FPS; 
			if (FPS < 1) FPS = 120;
      extern double TIME_PER_FRAME; TIME_PER_FRAME = 1.0f / FPS;
			uis.settings_scr.sp_fps.set_value (FPS);
    } else if (varn == "ips") {
			extern int IPS; tz >> IPS; if (IPS <= 0) IPS = 100;
			extern double TIME_PER_INPUT; TIME_PER_INPUT = 1.0 / IPS;
			uis.settings_scr.sp_ips.set_value (IPS);
		} else if (varn == "mixt" || varn == "mixing_time") {
			tz >> curve_mixer::TIME;
    } else if (varn == "tl" || varn == "trail_length") {
      tz >> TRAILSIZE;
      if (TRAILSIZE < 1) TRAILSIZE = 0;
    } else if (varn == "fc" || varn == "fold_console") {
      int fold; tz >> fold;
      cons.rollup (fold);
    } else if (varn == "midi_in") {
			tz >> midiin.input_port;
		} else if (varn == "stepz") {
			string s; tz >> s;
			MENU.lf_conn_steps.set_text (s);
			MENU.stepsl.typing (MENU.lf_conn_steps.fld);
			MENU.stepsl.changed (MENU.lf_conn_steps.fld);
    } else if (varn == "current_plugin") {
      int c; tz >> c;
			uis.plugin__browser.set_cur (c);
    } else if (varn == "ds" || varn == "mondrian.delta_speed") {
			tz >> mondrian0.delta_speed;
		} else if (varn == "drv" || varn == "mondrian.delta_rotate_velocity") {
			tz >> mondrian0.delta_rotate_velocity;
		} else if (varn == "mondrian.texture") {
      tz >> mondrian::patstr;
      mondrian::patlen = mondrian::patstr.length ();
      mondrian0.fillpatbuf ();
    } else if (varn == "mondrian.texstep") {
      tz >> mondrian::patstep;
      mondrian0.fillpatbuf ();
    } else if (varn == "version" || varn == "ver") {
			tz >> VERSION_NUMBER;
			set_window_caption ();
		} else if (varn == "min_mag" || varn == "mm") {
      tz >> drone::posafxvelt::minmag;
    } else if (varn == "wand" || varn == "drone_wand_dist") {
      double d; tz >> d;
      drone::wand.set (d);
    } else if (varn == "curve-samples-nsec") {
      float t; tz >> t;
      curve_samples::nsec = t;
    } else {
      if (varn != "") {
        result = varn + ": variable not found.";
        return 0;
      } else 
        break;
    }
	}
  return 1;
}

int get_var::operator() (tokenizer& tz) {
  stringstream ss;
  string varn;
  while (1) {
    tz >> varn;
    if (varn == "spar" || varn == "show_parameters") {
      int show_parameters = 0;
      if (uis.ab_parameters.dir == arrow_button::down) show_parameters = 1;
      ss << show_parameters << spc;
    } else if (varn == "vv" || varn == "voice_volume") {
      ss << VOICE_VOLUME << spc;
    } else if (varn == "su" || varn == "sustain") {
      ss << _gotog.g << spc;
    } else if (varn == "ins" || varn == "instrument") {
      ss << INSTRUMENTS [CURRENT_INSTRUMENT] << spc;
    } else if (varn == "tu" || varn == "tuning") {
      ss << TUNING << spc;
    } else if (varn == "sc" || varn == "scroll") {
      sprintf (BUFFER, "rate %d x %d y %d ", d->dinfo.scroll.rate, d->dinfo.scroll.dx, d->dinfo.scroll.dy);
      ss << BUFFER << spc;
    } else if (varn == "zoom") {
      ss << "rate " << window::ZOOM_RATE << " amount " << window::ZOOM_AMOUNT << spc;
    } else if (varn == "pan") {
      ss << "rate " << window::PAN_RATE << " amount " << window::PAN_AMOUNT << spc;
    } else if (varn == "fps") {
      extern int FPS; ss << FPS << spc;
		} else if (varn == "gfps") {
      extern double LAST_FRAME_TIME;
      ss << 1.0 / LAST_FRAME_TIME << spc;
    } else if (varn == "ips") {
			extern int IPS; ss << IPS << spc;
		} else if (varn == "gips") {
			extern double LAST_INPUT_TIME;
			ss << 1.0 / LAST_INPUT_TIME << spc;
		} else if (varn == "mixt"  || varn == "mixing_time") {
			ss << curve_mixer::TIME << spc;
		} else if (varn == "mixs" || varn == "mixing_samples") {
			ss << int (curve_mixer::TIME * SAMPLE_RATE + 0.5) << spc;
		} else if (varn == "tl" || varn == "trail_length") {
      ss << TRAILSIZE << spc;
    } else if (varn == "fc" || varn == "fold_console") {
      ss << cons.rollup () << spc;
    } else if (varn == "midi_in") {
			ss << midiin.input_port << spc;
		} else if (varn == "stepz") {
			ss << MENU.lf_conn_steps.fld.text << spc;
    } else if (varn == "curve-samples-nsec") {
      ss << curve_samples::nsec << spc;
    } else if (varn == "current_plugin") {
      ss << uis.plugin__browser.cur << spc;
    } else if (varn == "mondrian.delta_speed") {
      ss << mondrian0.delta_speed << spc;
    } else if (varn == "mondrian.delta_rotate_velocity") {
			ss << mondrian0.delta_rotate_velocity << spc;
		} else if (varn == "mondrian.texture") {
      ss << mondrian0.patstr << spc;
    } else if (varn == "mondrian.texstep") {
      ss << mondrian0.patstep << spc;
    } else if (varn == "microtonal_keyboard.scale") {
			ss << din0.scaleinfo.name << spc;
		} else if (varn == "keyboard_keyboard.scale") {
			ss << keybd2.scaleinfo.name << spc;
		} else if (varn == "mondrian.scale") {
			ss << mondrian0.scaleinfo.name << spc;
		} else if (varn == "notation") {
			ss << NOTATION << spc;
		} else if (varn == "version") {
			ss << VERSION_NUMBER << spc;
		} else if (varn == "min_mag" || varn == "mm") {
      ss << drone::posafxvelt::minmag << spc;
    } else if (varn == "wand" || varn == "drone_wand_dist") {
      ss << drone::wand.dist << spc;
    } else if (varn == "binaural_drones.scale") {
      ss << binaural_drones0.scaleinfo.name << spc;
    } else if (varn == "binaural_drones.master_volume") {
      ss << binaural_drones0.master_volume << spc;
    } else if (varn == "binaural_drones.justification") {
      ss << binaural_drones0.just << spc;
    } else if (varn == "is_curve_editor") {
      ss << uis.current->ed << spc;
    } else if (varn == "curve_editor_id") {
      sprintf (BUFFER, "%u", (uintptr_t) uis.crved);
      ss << BUFFER << spc;
    }
		else {
      if (varn != "") {
        result = varn + ": not found.";
				dlog << result << endl;
        return 0;
      } else break;
    }
  }
  result = ss.str().substr (0, ss.str().length() - 1);
  return 1;
}

int set_delay::operator() (tokenizer& tz) {

  string name;
  float value;

  delay* d[2] = {left, right};
  delay** pd = d;
  int n = -1;

  tz >> name >> value;

  if (name == "a" || name == "all") n = 2;
  else if (name == "l" || name == "left") n = 1;
  else if (name == "r" || name == "right") {
    pd++;
    n = 1;
  } else {
    result = "bad delay name";
    return 0;
  }

  for (int i = 0; i < n; ++i, pd++) {
    (*pd)->set (value);
  }

  return 1;

}

int get_delay::operator() (tokenizer& tz) {

  delay* d;
  string s; tz >> s;
  if (s == "left" || s == "l") d = left;
  else if (s == "right" || s == "r") d = right;
  else {
    result = "bad delay name";
    return 0;
  }

  float t; d->get (t);
  sprintf (BUFFER, "%0.2f", t);
  result = BUFFER;

  return 1;

}

int bpm_com::get_bpm_id_from_name (const string& name) {
  for (int i = 0; i < NUM; ++i) {
    if (name == str[i]) {
      return i;
    }
  }
  return -1;
}

int ls_bpm::operator() (tokenizer& tz) {
  for (int i = 0;i < NUM; ++i) {
    result = result + str[i] + spc;
  }
  return 1;
}

int set_bpm::operator() (tokenizer& tz) {
  string name;
  float bpm;
  tz >> name >> bpm;
  int id = get_bpm_id_from_name (name);
  if (id != -1) {
    bv[id]->set_bpm (bpm);
    MENU.update_bpm (name, bpm);
  }
  return 1;
}

int get_bpm::operator() (tokenizer& tz) {
  vector<string> names;
  string s; tz >> s; load_vector_from_string (s, names);
  stringstream ss;
  for (int i = 0, j = names.size (); i < j; ++i) {
    int id = get_bpm_id_from_name (names[i]);
    if (id != -1) ss << bv[id]->bpm << spc;
  }
  result = ss.str ();
  return 1;

}

int set_beat::operator() (tokenizer& tz) {
  vector<string> names;
  vector<float> nows;
  string s;
  tz >> s; load_vector_from_string (s, names);
  tz >> s; load_vector_from_string (s, nows);

  for (int i = 0, j = names.size (), k = nows.size (), l = k - 1; i < j; ++i) {
    int id = get_bpm_id_from_name (names[i]);
    if (id != -1) {
      if (i < k) bv[id]->now = nows[i]; else if (l > -1) bv[id]->now = nows[l];
    }
  }
  return 1;
}

int get_beat::operator() (tokenizer& tz) {
  vector<string> names;
  stringstream ss;
  string s; tz >> s; load_vector_from_string (s, names);
  string what; tz >> what;

  for (int i = 0, j = names.size (); i < j; ++i) {
    int id = get_bpm_id_from_name (names[i]);
    if (id != -1) {
      if (what == "") ss << bv[id]->now << spc;
      else if (what == "first") ss << bv[id]->sol.firstx << spc;
      else if (what == "last") ss << bv[id]->sol.lastx << spc;
      else if (what == "all") ss << '{' << bv[id]->now << spc << bv[id]->sol.firstx << spc << bv[id]->sol.lastx << "} ";
    }
  }
  result = ss.str ();
  return 1;
}

int set_style::operator() (tokenizer& tz) {
  vector<string> names;
  vector<string> styles;
  string s;
  tz >> s; load_vector_from_string (s, names);
  tz >> s; load_vector_from_string (s, styles);
  for (int i = 0, j = names.size (), k = styles.size (), l = k - 1; i < j; ++i) {
    int id = get_bpm_id_from_name (names[i]);
    if (id != -1) {
      string style;
      if (i < k) style = styles[i]; else if (l > -1) style = styles[l];
      xhandler *xmin = 0, *xmax = 0;
      if (style == "loop") {
        xmin = &_loopmin;
        xmax = &_loopmax;
      } else if (style == "pong") {
        xmin = &_pongmin;
        xmax = &_pongmax;
      } else {
        result = "bad style: " + style;
        return 0;
      }
      beat2value* b = bv[id];
      b->xmin = xmin;
      b->xmax = xmax;
      b->style = style;
    }
  }
  return 1;
}

int get_style::operator() (tokenizer& tz) {
  vector<string> names;
  string s; tz >> s; load_vector_from_string (s, names);
  stringstream ss;
  for (int i = 0, j = names.size (); i < j; ++i) {
    int id = get_bpm_id_from_name (names[i]);
    if (id != -1) ss << bv[id]->style << spc;
  }
  result = ss.str ();
  return 1;
}

int add_scale::operator() (tokenizer& tz) {
  extern scalelist scalelst;
  scalelst.add (tz);
  return 1;
}

int ls_scales::operator() (tokenizer& tz) {
  extern scalelist scalelst;
  scalelst.ls ();
  return 1;
}

int remove_scale::operator() (tokenizer& tz) {
  string nam; tz >> nam;
  if (nam != "") {
    extern scalelist scalelst;
    scalelst.remove (nam);
    return 1;
  }
  result = "bad scale name";
  return 0;
}

int load_scale::operator() (tokenizer& tz) {
  string inst, scale; 
	tz >> inst >> scale;
  if (scale != "") {
		instrument* instr = find_instrument (inst);
		if (instr) {
			instr->scaleinfo.load_scale (scale);
     	SCALE = scale;
     	result = "scale is " + scale;
     	return 1;
		} else return 0;
  } else {
    result = scale + ": not found";
    return 0;
  }
}

int ls_notes::operator() (tokenizer& tz) {
  string name; tz >> name;
  extern scalelist scalelst;
  scale r;
  if (scalelst.get (name, r)) {
    result = r.notes;
    return 1;
  } else {
    result = name + ": bad scale";
    return 0;
  }
}

int set_kern::operator() (tokenizer& tz) {

  extern font fnt;

  char a, b;
  int k;

  tz >> a >> b >> k;

  if (a == '.') {
    map<char, glyph>& m = fnt.characters;
    for (map<char, glyph>::iterator i = m.begin(), j = m.end(); i != j; ++i) fnt.kern[(*i).first][b] = k * fnt.cellsize.x;
    fnt.mod = 1;
  } else if (b == '.') {
    map<char, glyph>& m = fnt.characters;
    for (map<char, glyph>::iterator i = m.begin(), j = m.end(); i != j; ++i) fnt.kern[a][(*i).first] = k * fnt.cellsize.x;
    fnt.mod = 1;
  } else {
    fnt.kern[a][b] = k * fnt.cellsize.x;
    fnt.mod = 1;
  }

  return 1;

}

int get_kern::operator() (tokenizer& tz) {

  extern font fnt;

  char a, b;
  int k;

  tz >> a >> b >> k;

  if (a == '.') {
    map<char, glyph>& m = fnt.characters;
    for (map<char, glyph>::iterator i = m.begin(), j = m.end(); i != j; ++i) {
      a = (*i).first;
      cons << a << spc << b << spc << fnt.kern[a][b] / fnt.cellsize.x << eol;
    }
  } else if (b == '.') {
    map<char, glyph>& m = fnt.characters;
    for (map<char, glyph>::iterator i = m.begin(), j = m.end(); i != j; ++i) {
      b = (*i).first;
      cons << a << spc << b << spc << fnt.kern[a][b] / fnt.cellsize.x << eol;
    }
  } else {
    cons << a << spc << b << spc << fnt.kern[a][b] / fnt.cellsize.x << eol;
  }

  return 1;

}

int set_font_size::operator() (tokenizer& tz) {

  extern font fnt;

  int xsize, ysize, ch, headroom;
  tz >> xsize >> ysize >> ch >> headroom;

  if (xsize < 1) xsize = fnt.cellsize.x;
  if (ysize < 1) ysize = fnt.cellsize.y;
  if (ch < 1) ch = fnt.spacing.ch;
  if (headroom < 1) headroom = fnt.headroom;

  fnt.cellsize.x = xsize;
  fnt.cellsize.y = ysize;
  fnt.load (fnt.fname);

  fnt.spacing.ch = ch;
  fnt.headroom = headroom;
  fnt.spacing.word = 2 * fnt.spacing.ch;

  return 1;

}

int get_font_size::operator() (tokenizer& tz) {
  sprintf (BUFFER, "%d %d %d %d", fnt.cellsize.x, fnt.cellsize.y, fnt.spacing.ch, fnt.headroom);
  result = BUFFER;
  return 1;
}

int set_kb_layout::operator() (tokenizer& tz) {

  // keyboard layout; qwerty us/uk for now
  static string norm_us ("abcdefghijklmnopqrstuvwxyz0123456789 .=-/;\\,[]'`");
  static string shift_us ("ABCDEFGHIJKLMNOPQRSTUVWXYZ)!@#$%^&*( >+_?:|<{}\"~");
  static string norm_uk ("abcdefghijklmnopqrstuvwxyz0123456789 .=-/;\\,[]'`#");
  static string shift_uk ("ABCDEFGHIJKLMNOPQRSTUVWXYZ)!\"#$%^&*( >+_?:|<{}@~~");

  extern string CHARSET [];
  string name; tz >> name;
  if (name == "us" || name == "usa") {
    layout = name;
    CHARSET [0] = norm_us;
    CHARSET [1] = shift_us;
  } else if (name == "gb" || name == "uk") {
    layout = name;
    CHARSET [0] = norm_uk;
    CHARSET [1] = shift_uk;
  } else {
    result = layout;
    return 0;
  }

  return 1;

}

int get_note_num (const string& s) {
  for (int i = 0; i < NUM_NOTES; ++i) {
    if (s == WESTERN_SHARP [i] || s == WESTERN_FLAT[i]) return i;
  }
  return -1;
}

int note_distance::operator() (tokenizer& tz) {

  string note1, note2, verbose;
  tz >> note1 >> note2 >> verbose;
  if (note1.length() && note2.length()) {
    note1[0] = toupper (note1[0]);
    note2[0] = toupper (note2[0]);
  } else return 0;

  static const char* dist1 [] = {
    "1", "2b", "2", "3b", "3", "4", "4#", "5", "6b", "6", "7b", "7", "8"
  };
  static const char* dist2 [] = {
    "same", "minor_second", "second", "minor_third", "third", "perfect_fourth", "diminished_fifth", "perfect_fifth", "minor_sixth", "sixth", "minor_seventh", "seventh", "octave"
  };

  int i = get_note_num (note1), j = get_note_num (note2);
  if (i == -1 || j == -1) return 0; else {
    if (j <= i) j += 12;
    int ji = j - i;
    if (verbose.length()) {
      sprintf (BUFFER, "%s %s", dist1[ji], dist2[ji]);
      result = BUFFER;
      return 1;
    } else {
      result = dist1[ji];
      return 1;
    }
  }

}

int chord::operator() (tokenizer& tz) {

  string root, type;
  tz >> root >> type;

  if (root.length()) root[0] = toupper (root[0]); else return 0;
  int nn = get_note_num (root);
  if (nn == -1) {
    result = root + ": bad note";
    return 0;
  } else {
    const int ntypes = 20;
    static const char* types[] = {
      "", // major
      "m", // minor
      "maj7", // major 7
      "m7", // minor 7
      "sus4", // suspended 4th
      "7sus4", // dominant 7th, suspended 4th
      "6", // major 6th
      "m6", // minor 6th
      "7", // dominant 7th
      "9", // dominant 9th
      "5", // power chord
      "69", // maj 6th, add 9
      "11", // dominant 11th
      "13", // dominant 13th
      "add9", // major add 9th
      "m9", // minor 9th
      "maj9", // major 9th
      "+", // augmented
      "07", // diminished 7th
      "0" // diminished triad
    };

    static const int steps[][7] = {
      {3, 0, 4, 7}, // num_notes, root, note1, note2....
      {3, 0, 3, 7},
      {4, 0, 4, 7, 11},
      {4, 0, 3, 7, 10},
      {3, 0, 5, 7},
      {4, 0, 5, 7, 10},
      {4, 0, 4, 7, 9},
      {4, 0, 3, 7, 9},
      {4, 0, 4, 7, 10},
      {5, 0, 4, 7, 10, 14},
      {2, 0, 7},
      {5, 0, 4, 7, 9, 14},
      {6, 0, 4, 7, 10, 14, 17},
      {6, 0, 4, 7, 10, 14, 21},
      {4, 0, 4, 7, 14},
      {5, 0, 3, 7, 10, 14},
      {5, 0, 4, 7, 11, 14},
      {3, 0, 4, 8},
      {4, 0, 3, 6, 10},
      {3, 0, 3, 6}
    };

    int t = -1;
    for (int i = 0; i < ntypes; ++i) {
      if (type == types[i]) {
        t = i; break;
      }
    }

    if (t == -1) return 0; else {
      const int* ch = &steps[t][0];
      stringstream ss1, ss2;
      for (int i = 0, j = ch[0], k = 1; i < j; ++i, ++k) {
        int p = (nn + ch[k]) % 12;
				ss1 << WESTERN_FLAT [p] << spc;
        ss2 << WESTERN_SHARP [p] << spc;
      }
      result = ss1.str () + ss2.str ();
      return 1;
    }

  }
}

extern string NEAREST_NOTE;
extern float NEAREST_NOTE_FREQUENCY;
extern float NEAREST_NOTE_DISTANCE;

int key::operator() (tokenizer& tz) {

	string inst, s; tz >> inst >> s;

	instrument* instr = find_instrument (inst);

  string note ("invalid-note");
  float tonic = get_tonic (instr);
	NEAREST_NOTE_FREQUENCY = tonic;

  if (s == "") {
    // no args, just print key
    find_nearest_note (NEAREST_NOTE, NEAREST_NOTE_FREQUENCY, NEAREST_NOTE_DISTANCE);
    sprintf (BUFFER, "Key @ %0.3f Hz, nearest note is %s @ distance %0.3f Hz", tonic, NEAREST_NOTE.c_str(), NEAREST_NOTE_DISTANCE);
    result = BUFFER;
    return 1;
  } else if (s == "value" || s == "v") {
    sprintf (BUFFER, "%0.3f", get_tonic (instr));
    result = BUFFER;
    return 1;
  } else if (s == "note" || s == "n") {
		find_nearest_note (NEAREST_NOTE, NEAREST_NOTE_FREQUENCY, NEAREST_NOTE_DISTANCE);
    result = NEAREST_NOTE;
    return 1;
  } else {
    float freq = 0;
    int oct = 0;
    float val = (float) atof (s.c_str());
    if (val <= 0) { // a note
      if (s[0] != '.') {
        s[0] = toupper(s[0]);
        for (int i = 0; i < 12; ++i) {
          if ((s == WESTERN_SHARP[i]) || (s == WESTERN_FLAT[i])) {
            freq = WIKIPEDIA_KEY_FREQUENCIES[i];
            break;
          }
        }
      } else freq = tonic;
    } else { // a value
      freq = val;
    }

    // octave
    tz >> s;
    oct = atoi (s.c_str());

    freq = freq * pow (2.0f, oct);

		set_tonic (instr, freq);

  }

  return 0;

}

int notation::operator () (tokenizer& tz) {
	tz >> NOTATION;
  return 1;
}

int echo::operator() (tokenizer& tz) {
  into_lines (tz.cur2end().c_str(), cons);
  return 1;
}

multi_curve* get_curve (const string& name) {

  extern vector<multi_curve *> curve_list;
  multi_curve* crv = 0;
  for (int i = 0, j = curve_list.size (); i < j; ++i) {
    multi_curve* mc = curve_list[i];
    if (mc->name == name) crv = mc;
  }
  return crv;

}

int curve_value::operator() (tokenizer& tz) {

  string name; tz >> name;

  multi_curve* crv = get_curve (name);

  if (crv) {

    string what;
    int id;

    string sx, sy;
    tz >> what >> id >> sx >> sy;
    float x, y, xs = (float) atof (sx.c_str()), ys = (float) atof (sy.c_str());

    if (what == "v") {
      int carry_tangents = 1;
      crv->get_vertex (id, x, y);
      if (sx == ".") crv->set_vertex (id, x, ys, carry_tangents); else
      if (sy == ".") crv->set_vertex (id, xs, y, carry_tangents); else
      crv->set_vertex (id, xs, ys, carry_tangents);
      crv->evaluate ();
      return 1;
    } else if (what == "lt") {
      crv->get_left_tangent (id, x, y);
      if (sx == ".") crv->set_left_tangent (id, x, ys); else
      if (sy == ".") crv->set_left_tangent (id, xs, y); else
      crv->set_left_tangent (id, xs, ys);
      crv->evaluate ();
      return 1;
    } else if (what == "rt") {
      crv->get_right_tangent (id, x, y);
      if (sx == ".") crv->set_right_tangent (id, x, ys); else
      if (sy == ".") crv->set_right_tangent (id, xs, y); else
      crv->set_right_tangent (id, xs, ys);
      crv->evaluate ();
      return 1;
    } else {
      result = "bad target: must be v lt OR rt";
      return 0;
    }

  }
  result = "curve " + name + " not found";
  return 0;
}

int curve_name::operator() (tokenizer& tz) {
  curve_editor* ced = uis.crved;
  if (ced && ced->pik()) {
    multi_curve* crv = ced->curveinfo[ced->pik.crv_id].curve;
    string name; tz >> name;
    if (name != "") crv->name = name;
  }
  return 1;
}

int curve__library::operator() (tokenizer& tz) {
  curve_editor* ced = uis.crved;
  if (!ced) {
    result = "not a curve editor";
    return 0;
  }
  curve_library* cl = ced->library;
  if (cl) {
    string cmd; tz >> cmd;
    if (cmd == "a" || cmd == "add") ced->add_curve (); else
    if (cmd == "d" || cmd == "delete") cl->del (); else
    if (cmd == "u" || cmd == "update") ced->replace_curve (); else
    if (cmd == "i" || cmd == "insert") ced->insert_curve (); else
    if (cmd == "+") {int n; tz >> n; cl->move (n);}
    else if (cmd == "-") {int n; tz >> n; cl->move (-n);} 
    else if (cmd == "n" || cmd == "next") {
      ced->load_curve (+1);
    } else if (cmd == "p" || cmd == "prev") {
      ced->load_curve (-1);
    }
    else {
      result = "bad sub-command. see help curve-library";
      return 0;
    }
  } else {
    result = "no curve library";
    return 0;
  }
  return 1;
}

int set_curve_editor::operator() (tokenizer& tz) {
  string name;
  int screen;
  tz >> name >> screen;
  if (uis.set_editor (name, screen)) {
    stringstream ss;
    ss << "update-editor " << name << spc << screen;
    interpreter (ss.str());
  }
  return 1;
}

extern oscilloscope scope;

int set_scope::operator() (tokenizer& tz) {
  string name, color; tz >> name >> color;
  float r, g, b; stringstream ss (color); ss >> r >> g >> b;
  if (name == "left" || name == "l") {
    scope.lr = r;
    scope.lg = g;
    scope.lb = b;
    return 1;
  } else if (name == "right" || name == "r") {
    scope.rr = r;
    scope.rg = g;
    scope.rb = b;
    return 1;
  } else return 0;
}

int get_scope::operator() (tokenizer& tz) {
  string name; tz >> name;
  if (name == "left" || name == "l") {
    sprintf (BUFFER, "%f %f %f", scope.lr, scope.lg, scope.lb);
    result = BUFFER;
    return 1;
  } else if (name == "right" || name == "r") {
    sprintf (BUFFER, "%f %f %f", scope.rr, scope.rg, scope.rb);
    result = BUFFER;
    return 1;
  } else return 0;
}

int set_drone::operator() (tokenizer& tz) {

  string param; tz >> param;
  if (param == "volume" || param == "v") {
    vector<int> ids;
    vector<float> vols;
    string s;
    tz >> s; if (s != "") load_vector_from_string (s, ids); else return 0;
    tz >> s; if (s != "") load_vector_from_string (s, vols); else return 0;
    float v = 0;
    for (int i = 0, j = ids.size(), m = vols.size (); i < j; ++i) {
      int k = ids[i];
      if (i < m) v = vols[i];
      d.set_drone_volume (k, v);
    }
    return 1;
  } else {
    result = "bad param";
    return 0;
  }

}

int get_drone::operator() (tokenizer& tz) {
  int num_drones = d.drones.size ();
  string param; tz >> param;
  if (param == "n" || param == "num_drones") {
    sprintf (BUFFER, "%d", num_drones);
    result = BUFFER;
    return 1;
  } else if (param == "volume" || param == "v") {
    stringstream ss;
    string s; tz >> s;
    if (s != "") {
      vector<int> ids; load_vector_from_string (s, ids);
      for (int i = 0, j = ids.size (); i < j; ++i) {
        int id = ids[i];
        drone* pd = d.get_drone (id);
        if (pd) ss << pd->vol << spc; else ss << "0 ";
      }
      result = ss.str ();
      return 1;
    } else return 0;
  } else if (param == "selected" || param == "s") {
    result = d.get_selected_drones ();
    return 1;
  }
  return 0;
}

int set_text_color::operator() (tokenizer& tz) {
  tz >> cons.clr.r >> cons.clr.g >> cons.clr.b;
  return 1;
}

int paste_gater::operator() (tokenizer& tz) {

  if (curve_editor::copy.num_vertices) {
    extern din din0;
    float r, g, b;
    din0.gatr.crv->get_color (r, g, b);
    *din0.gatr.crv = curve_editor::copy;
    din0.gatr.crv->set_color (r, g, b);
    din0.gatr.sol.update ();
  }
  return 1;
}

int get_selection::operator () (tokenizer& tz) {
  extern ui_list uis;
  if (uis.crved) result = uis.crved -> selection ();
  return 1;
}

int get_intervals::operator() (tokenizer& tz) {

  string what; tz >> what;
  stringstream ss;
  if (what == "piano") {
    for (int i = 0, j = NUM_NOTES - 1; i < j; ++i) {
      ss << WESTERN_FLAT[i] << spc << WIKIPEDIA_KEY_FREQUENCIES[i] << spc;
    }
    result = ss.str ();
  } else {
    for (map<string, float>::iterator i = INTERVALS.begin (), j = INTERVALS.end (); i != j; ++i) {
      const pair<string, float>& p = *i;
      ss << p.first << spc << p.second << spc;
    }
  }
  result = ss.str ();
  return 1;

}

#define DONTLOADFROMDISK 0
int num_octaves::operator() (tokenizer& tz) {
	int lno = NUM_OCTAVES;
  tz >> NUM_OCTAVES;
  if (NUM_OCTAVES < 1) NUM_OCTAVES = 1;
  d.setup_ranges (lno, DONTLOADFROMDISK);
	if (NUM_OCTAVES < lno) d.update_drone_ranges ();
	d.refresh_all_drones ();
  return 1;
}

int set_audio::operator() (tokenizer& tz) {

  string varn;
  int cd = aout.current_device, sr = aout.sample_rate, spc = aout.samples_per_channel;
  int got = 0;
  while (1) {
    tz >> varn;
    if (varn == "device" || varn == "d") {
      tz >> cd;
      got = 1;
    } else if (varn == "sample_rate" || varn == "sr") {
      tz >> sr;
      got = 2;
    } else if (varn == "samples_per_channel" || varn == "spc") {
      tz >> spc;
      got = 3;
    } else break; 
  }

  if (got != 0) {
    aout.close ();
    int opened = aout.open (cd, sr, spc);
		if (opened) {
			//--cons;
			switch (got) {
				case 1:
				case 2:
					din0.sample_rate_changed ();
					keybd2.setup_notes(0);
					binaural_drones0.vol_fader.set_duration (MENU.lf_vol_fade_time.fld);
					uis.settings_scr.sample_rate_changed ();
					break;
				case 3:
					din0.samples_per_channel_changed ();
					update_triggered_notes (keybd2.triggered_notes);
					update_triggered_notes (mondrian0.triggered_notes);
					break;
			}
			//++cons;
			aout.start ();
    	return 1;
		} 
  } else {
		aout.list ();
	}

  return 0;

}

int list_midi::operator() (tokenizer& tz) {
  return 1;
}

int set_midi::operator() (tokenizer& tz) {
  int port; tz >> port;
  return 1;
}

int scale_curve::operator() (tokenizer& tz) {
  if (uis.crved) {
    float sx, sy;
    tz >> sx >> sy;
    uis.crved->scale (sx, sy); 
  }
  return 1;
}

#ifdef __SVG__

int write_svg::operator() (tokenizer& tz) {
	float h, t, left, top;
	string fn;
	if (uis.crved) {
		tz >> h >> t >> fn >> left >> top;
		uis.crved->write_svg (h, t, fn, left, top);
	}
	return 1;
}

int write_trail::operator() (tokenizer& tz) {
  din0.write_trail ();
  return 1;
}

#endif

#ifdef __HPGL__

int write_hpgl::operator() (tokenizer& tz) {
	if (uis.crved) {
		float scale, penmag;
		tz >> scale >> penmag;
		if (scale <= 0 || penmag <= 0) {
			cons << console::red << "write-hpgl <scale> <pen-switch-mag>" << eol;
			return 1;
		}
		uis.crved->write_hpgl (scale, penmag);
	}
	return 1;
}

#endif

int cmd_binaural_drone::operator() (tokenizer& tz) {
	string cmd; tz >> cmd;
	if (cmd == "c" || cmd == "create") {
		float l_hz, r_hz, vol, sep; 
		int just;
		tz >> l_hz >> r_hz >> vol >> just >> sep;
		int id = binaural_drones0.add (l_hz, r_hz, vol, just, sep);
		sprintf (BUFFER, "%d", id);
		result = BUFFER;
	} else if (cmd == "e" || cmd == "edit") {
		int which; tz >> which;
		string what; tz >> what;
		float value; tz >> value;
		int num_binaural_drones = binaural_drones0.binaural_drones.size ();
		if (which > -1 && which < num_binaural_drones) {
			binaural_drone* bw = binaural_drones0.binaural_drones [which];
			if (what == "v" || what == "vol") {
				bw->set_vol (value);
				binaural_drones0.vol_fader.start ("Volume change");
			} else if (what == "l" || what == "left") {
				bw->set_hz (binaural_drone::LEFT, value);
				binaural_drones0.pitch_fader.start ("Set L Hz");
			} else if (what == "r" || what == "right") {
				bw->set_hz (binaural_drone::RIGHT, value);
				binaural_drones0.pitch_fader.start ("Set R Hz");
			} else if (what == "b" || what == "both") {
				float value2; tz >> value2;
				bw->set_hz (binaural_drone::LEFT, value);
				bw->set_hz (binaural_drone::RIGHT, value2);
				binaural_drones0.pitch_fader.start ("Set L & R Hz");
			} else if (what == "s" || what == "separation") {
				bw->set_sep (value);
				binaural_drones0.pitch_fader.start ("Separation set");
			} else if (what == "m" || what == "modulate") {
				bw->set_hz (binaural_drone::LEFT, bw->l_hz * value);
				bw->set_hz (binaural_drone::RIGHT, bw->r_hz * value);
				binaural_drones0.pitch_fader.start ("Modulation");
			}
		} else cons << "bad binaural drone id" << eol;
	} else if (cmd == "d" || cmd == "delete") {
		int which; tz >> which;
		binaural_drones0.remove (which);
	} else if (cmd == "s" || cmd == "sync") {
		int n; tz >> n;
		string list; tz >> list;
		binaural_drones0.sync (n, list);
	} else if (cmd == "n" || cmd == "number") {
		stringstream ss; ss << (int)binaural_drones0.binaural_drones.size ();
		result = ss.str ();
	} else if (cmd == "g" || cmd == "get") {
		string what; tz >> what; int w;
		if (what == "left") w = binaural_drone::LEFT;
		else if (what == "right") w = binaural_drone::RIGHT;
		else if (what == "separation") w = binaural_drone::SEPARATION;
		else if (what == "volume") w = binaural_drone::VOLUME;
		else if (what == "selected_volumes") {
			result = binaural_drones0.get_sel_vol ();
			return 1;
		} else if (what == "selected") {
			result = MENU.il_binaural_drones.get_selected ();
			return 1;
		} else {
			cons << RED << "syntax error: please type help binaural-drone for more information" << eol;
			return 0;
		}
		stringstream ss;
		for (int i = 0; i < binaural_drones0.num_binaural_drones; ++i) {
			binaural_drone* bdi = binaural_drones0.binaural_drones[i];
			float whata [] = {bdi->l_hz, bdi->r_hz, bdi->sep, bdi->vol * 100.0f};
			ss << whata[w] << spc;
		}
		result = ss.str ();
	} else if (cmd == "update-list" || cmd == "ul" ) {
		MENU.update_binaurals_list ();
	} else if (cmd	== "update-selection" || cmd == "us" ) {
		MENU.bdl.selected (MENU.il_binaural_drones, MENU.il_binaural_drones.cur);
	}	else if (cmd == "l" || cmd == "list") {
		binaural_drones0.list ();
	} 
	return 1;
}

int set_sine_mixer::operator() (tokenizer& tz) {
	int harmonic;
	float value;
	tz >> harmonic >> value;
	sinemixer.harms.set_only (harmonic, value);
	return 1;
}

int change_sine_mixer::operator() (tokenizer& tz) {
	int harmonic;
	float amount;
	tz >> harmonic >> amount;
	int res = sinemixer.harms.change (harmonic, amount);
	sprintf (BUFFER, "%d", res);
	result = BUFFER;
	return 1;
}

int update_sine_mixer::operator() (tokenizer& tz) {
	sinemixer.harms.update ();
	return 1;
}
