/*
* main.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 <SDL/SDL.h>
#include <string>
#include <map>
#include <vector>
#include <iostream>
#include <fstream>
#include <list>
#include <algorithm>
#include <tcl.h>
#include "dingl.h"
#include "main.h"
#include "box.h"
#include "utils.h"
#include "font.h"
#include "ui_list.h"
#include "console.h"
#include "input.h"
#include "viewwin.h"
#include "curve.h"
#include "multi_curve.h"
#include "solver.h"
#include "chrono.h"
#include "curve_editor.h"
#include "font_editor.h"
#include "din.h"
#include "curve_library.h"
#include "console_iterator.h"
#include "delay.h"
#include "random.h"
#include "globals.h"
#include "command.h"
#include "scalelist.h"
#include "morse_code.h"
#include "sine_mixer.h"
#include "tcl_interp.h"
#include "compressor.h"
#include "oscilloscope.h"
#include "keyboard_keyboard.h"
#include "authors_note.h"
#include "midi_in.h"
#include "menu.h"
#include "curve_picker.h"
#include "fractaliser.h"
#include "rose_milker.h"
#include "circler.h"
#include "spiraler.h"
#include "starrer.h"
#include "lissajous.h"
#include "superformula.h"
#include "warper.h"
#include "recorder.h"
#include "mondrian.h"
#include "instrument.h"
#include "countries.h"
#include "morpher.h"
#include "number.h"
#include "binaural_drones.h"
#include "fft.h"
#include "capturer.h"
#include "noiser.h"
#include "defvelaccel.h"
using namespace std;

#if defined __GPL20__
	string home = getenv ("HOME");
	string user_data_dir (home + "/.din/"); // contains defaults, user savings and preferences
	string country_data_dir (user_data_dir + "country/"); // contains country data used by countries plugin (see countries.cc)
#elif defined (__MACOSX_CORE__) || defined (__LINUX_BIN__)
	string user_data_dir = "user/";
	string country_data_dir = "user/country/";
#elif __WINDOWS_DS__
	string user_data_dir = "user\\"; 
	string country_data_dir = "user\\country\\";
#endif

ofstream dlog ("log", ios::out); // DIN Is Noise log

extern const float GOLDEN_RATIO = 1.618033;
extern const float PI = 3.1415961;
extern const float TWO_PI = 2 * PI;
extern const float PI_BY_180 = PI / 180.;
extern const char spc = ' ';
char tokenizer::delim = spc;

// for attack, decay and deltas [see keyboard-keyboard.cc, mondrian.cc, triggered_note.cc]
extern const float MIN_TIME = 0.01f; // seconds

tcl_interp interpreter; // integrated TCL interpreter

int quit = DONT; 

struct startup_shutdown { 

  startup_shutdown () {
		dlog << "!!! started DIN Is Noise !!!" << endl;
    time_t t0 = time(0);
    seed_rand_gen (t0); 
    dlog << "+++ initialised random number generator @ seed = " << t0 << " +++" << endl;
  }

  ~startup_shutdown () {
    SDL_Quit ();
    dlog << "!!! cleaned up SDL !!!" << endl;
		dlog << "*** [Mondrian] box count = " << rect::ref << " ***" << endl;
		dlog << "*** [Mondrian] ball count = " << ball::ref << " ***" << endl;
		dlog << "*** [Mondrian] slit count = " << slit::ref << " ***" << endl;
		dlog << "*** [Microtonal-Keyboard] drone count = " << drone::ref << " ***" << endl;
		dlog << "\\o/ DIN Is Noise was up for " << interpreter ("uptime 0").result << " \\o/" << endl;
  }

} SS ;

help curve_editor::helptext ("curve_editor.hlp");

// DIN clocks
double TIME_NOW = 0; // in seconds on the audio clock stored in variable timenow on TCL interpreter
audio_clock clk; // audio clock
ui_clock ui_clk; // UI clock

// keyboard and mouse state
keyboard keybd;
int mousex = 0, mousey = 0, mouseyy = 0; // absolute mouse x, y & main viewport y
int lmb = 0, mmb = 0, rmb = 0; // left & right mouse buttons
int wheel = 0; // mouse wheel
int wheely = -60; // for moving menu, settings widgets
int SHIFT = 0; // shift button
int CTRL = 0; // ctrl button
int ALT = 0; // alt button
is_lmb_t is_lmb; // for acquiring lmb

// UI
int UI_OFF = 0;
int widget::HOVER = 0; // mouse hovering on any widget?
widget* widget::focus = 0; // widget that has focus
widget* widget::next_focus = 0; // widget waiting to have focus

// see spinner.h, spinner2.h
int SPACING = 6;
int VAR_MIN = -100, VAR_MAX = 100; // for variance @ spinner.h
int VAR_MIN2 = 0, VAR_MAX2 = 100; // for variance @ spinner2.h
string VARSTR, VARSTR2;

// widget defaults
// loaded from globals file (see globals.cc)
float widget::R, widget::G, widget::B; // default widget color
int widget::bb [16] = {0}; // widget bounding box
color checkbutton::on_color, checkbutton::off_color; // checkbutton on/off colors
const char* field::fmts [] = {"%0.3f", "%0.4f", "%0.6f", "%f"}; // precision formats for field

int FPS = 0; // requested frames per second
double TIME_PER_FRAME; // in seconds, calc @ command.cc
double LAST_FRAME_TIME = 0.0;
double FPSNOW = 0.0;

int IPS; // requested keyboard/mouse inputs per second
double TIME_PER_INPUT; // in seconds
double LAST_INPUT_TIME = 0.0;
double IPSNOW = 0.0;

int basic_editor::hide_cursor = 0; // to hide cursor when mouse is on a ui control
float* basic_editor::gl_pts = 0;
int basic_editor::n_pts = 0;
int basic_editor::ref = 0;

int plugin::change_curve_name = 1; // when applying plugin?

int capturer_t::ref = 0; // see capturer.h
int point_modulator::ref = 0; // see point_modulator.h

list<crvpt> LIST_OF_POINTS; // list of points (see curve.h/cc)

// see drone.h/cc
int drone::UID = 0; // unique id for drones
int drone::ref = 0; // for ref counting drones
map<drone*, bool> drone::proc_conn; // drone connections
float drone::STIFFNESS = 1.0f; // stiffness of inter-drone connections
double drone::LIFETIME = 3; // for launched drones (seconds)
int drone::HANDLESIZE = 3; // default drone handle size
double drone::INSERTTIME = 3; // for launched drones to get into orbit (seconds)
double drone::gabt = 1.0f; // for mute/unmute drones
float drone::mastervolume = 0.0f; // for all drones
point<float> mod_params::horizontal (1.0f,0.0f), mod_params::vertical (0.0f,1.0f); // drone modulation directions
int drone::IS = drone::DRONE;
drone::wandt drone::wand (5.0); // for wandded drones
anglet drone::chuckt::apt; // angle per turn (degrees) for chuck rotation
int drone::chuckt::outline = 1; // draw chuck outlines?
int drone::chuckt::autoresettrails = 1; // when chuck params changed?
int drone::ARE = drone::IMMORTAL; // type of drone
int drone::anchored = 0;
float drone::arrowt::U = 0.25, drone::arrowt::V = 0.25, drone::arrowt::K = 1.0f, drone::arrowt::CAP = 1;
defvelaccel drone::v0 (" Velocity"), drone::a0 (" Acceleration");
const char* menu::defvelaccelui::autopauset::tars[] = {" Target is Auto rotate", " Target is Auto flip"};
multi_curve drone::modvt::crv ("modv.crv");

// for drone <> noise conversion
drone::drone2noise drone::dnl;
drone::noise2drone drone::ndl;
drone::fin* drone::fins[3] = {0, &drone::dnl, &drone::ndl};

double drone::posafxvelt::minmag = 3.0f;

// for trails on drones and mondrian
float* trail_t::tpts = 0;
int trail_t::n_tpts = 0;

// drone mesh, see mesh.h/cc
int* mesh::gl_pts = 0;
float* mesh::clr = 0;
int mesh::n_glpts = 0;
int proximity_orderer::ROW = 0, proximity_orderer::COL = 0;

double fader::TIME; // loaded from din_info

// see curve_mixer.h/cc
float curve_mixer::TIME = 1.0f; // curve (waveform/envelope) mixing time in seconds
int curve_mixer::SAMPLES = 1024; // curve mixing time in samples, change one, changes the other
double curve_mixer::ELAPSEDT = 0.0;

// for sprintf
const int BUFFER_SIZE = 1 << 15;
char BUFFER [BUFFER_SIZE] = {0};

// music constants
//

int NUM_NOTES = 13;

// notations
int NOTATION = WESTERN;
const char* notation_types [] = {"numeric", "western", "indian"};
const char* WESTERN_SHARP [] = {"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B", "C"};
const char* WESTERN_FLAT [] = {"C", "Db", "D", "Eb", "E", "F", "Gb", "G", "Ab", "A", "Bb", "B", "C"};
const char* INDIAN_SWAR [] = {"s", "r", "R", "g", "G", "m", "M", "P", "d", "D", "n", "N", "S"};
//const char* SOLFEGE [] = {"d", "r", "R", "m", "M", "f", "F", "S", "l", "L", "t", "T", "D"};

// for the octave of Middle-C; from wikipedia - piano key frequencies page.
float WIKIPEDIA_KEY_FREQUENCIES [] = {
  261.626f, 277.183f, 293.665f, 311.127f, 329.628f, 349.228f, 369.994f, 391.995f, 415.305f, 440.0f, 466.164f, 493.883f, 523.251f
};

string NEAREST_NOTE; // note nearest to key of DIN
float NEAREST_NOTE_DISTANCE; // distance in Hz of the nearest note from the key of DIN
float NEAREST_NOTE_FREQUENCY; // frequency in Hz of the nearest note to the key of DIN

int SAMPLE_RATE; // sample rate of DIN
float SAMPLE_DURATION; // duration of an audio sample in seconds

string SCALE; // current scale
string TUNING; // current tuning
int NUM_INTERVALS; // number of intervals in tuning
string INTERVALS_FILE; // file name that contains interval info
map <string, float> INTERVALS; // interval name -> value
map <string, string> INT2IND;
vector<string> INTERVAL_NAMES; // sequential interval names (eg., 1 2b 2 3b 3 ... 7b 7 8)
vector<float> INTERVAL_VALUES; // sequential interval values (eg., value of 1 2b 3 3b ... 7b 7 8)
map <string, int> NOTE_POS; // interval name -> note number (eg., 1 => 0, 2b => 1, 3 => 2, etc)

scale_info all_notes; // used by change left/right note on range to move to any note

//
// microtonal keyboard
//
const char* s_drones = " drones";
int LEFT = 0, BOTTOM, TOP;
int WIDTH, HEIGHT; 
int TRAILSIZE = 0; // default drone trail size (== number of trail points), mondrian ball trail size
int NUM_OCTAVES = 3; // number of octaves

// random bpm init see range::init_mod()
// limits loaded from globals file, see globals.cc
rnd<int> RAN_MOD_BPM;

const char* din_info::cnn_opts [] = {" to scale note", " to any note"};
const char* din_info::cnno [] = {"Octave", "Note"}; 

// see curve_samples in curve_editor.h
float curve_samples::dm = 0.0f;
float curve_samples::nsec = 1.0f;

// screens - 1 is instrument; 2 - 8 are editors
//
const Uint8 ui_list::key [] = {SDLK_2, SDLK_3, SDLK_4, SDLK_5, SDLK_6, SDLK_7, SDLK_8}; // keys 2 - 8
ui* ui_list::ed [] = {0};
ui* ui::over = 0;

// solver xhandlers; see solver.cc
atmin _atmin;
atmax _atmax;
tomin _tomin;
tomax _tomax;
loopmin _loopmin;
loopmax _loopmax;
pongmin _pongmin;
pongmax _pongmax;

// see multi_curve.cc
multi_curve curve_editor::copy;
multi_curve mix;

color curve_editor::vtxlbl; // vertex label color
int hit_t::name_only = 0;

// see sine_mixer.cc
int sine_mixer::NUM_SINE_SAMPLES = 100;
const int sine_mixer::MIN_SINE_SAMPLES = 4;

// program info
string APP_NAME;
string VERSION_NUMBER;

//
// display
//
int SCREEN_WIDTH, SCREEN_HEIGHT; // initial screen width & height (from user/globals)
const int SCREEN_DEPTH = 24; // fixed color depth
int FULL_SCREEN = 0; // full screen?

// see viewwin.cc
viewport view; // display - using OpenGL
int viewport::handle_radius;
float viewport::handle_factor;
int window::PAN_RATE = 100, window::ZOOM_RATE = 100;
double window::PAN_REPEAT = 1.0 / PAN_RATE, window::ZOOM_REPEAT = 1.0 / ZOOM_RATE;
float window::PAN_AMOUNT = 0.1f, window::ZOOM_AMOUNT = 0.1f;

load_globals lg;

// 0 init correct for oscilloscope; see oscilloscope.cc
float oscilloscope::sample_t::lmin = 0, oscilloscope::sample_t::lmax = 0;
float oscilloscope::sample_t::rmin = 0, oscilloscope::sample_t::rmax = 0;
oscilloscope scope ("scope.osc");

// custom vector font
#ifdef __SVG_FNT__
  font fnt ("laser.fnt");
#elif __PLOTTER_FNT__
	font fnt ("plotter.fnt");
#else
  font fnt ("jag.fnt");
#endif
int line_height; // of text

// console
//

// possible text colors of console
const float cc = 0.50f;
const color console::yellow (1, 1, cc);
const color console::green (cc, 1, cc);
const color console::red (1, cc, cc);
const color console::cyan (cc, 1, 1);
const color console::white (1, 1, 1);

string CHARSET [] = { // list of input chars
  // default is US layout
  "abcdefghijklmnopqrstuvwxyz0123456789 .=-/;\\,[]'`", // normal
  "ABCDEFGHIJKLMNOPQRSTUVWXYZ)!@#$%^&*( >+_?:|<{}\"~", // shift
};
const int CHARSET_LENGTH = CHARSET[0].length ();
const char* console::precision = "%.3f";
console cons;

void set_window_caption () {
	make_app_name ();
	SDL_WM_SetCaption (APP_NAME.c_str(), APP_NAME.c_str());
}

char get_typed_char () { // get char typed on an input area
	const string& norm = CHARSET[0];
	const string& shift = CHARSET[1];
	for (int i = 0; i < CHARSET_LENGTH; ++i) {
		if (keypressedd (norm[i])) {
			if (SHIFT || keydown (SDLK_CAPSLOCK))
				return shift[i];
			else
				return norm[i];
		}
	}
	return 0;
}

// audio output
//
audio_out aout;

// list of scales
scalelist scalelst;

//
// L and R delays
//
delay left_delay (1000, "feedback-l.crv", "volume-l.crv"), right_delay (1000, "feedback-r.crv", "volume-r.crv");
curve_editor delayed ("delay.ed");

font_editor fed (fnt, "font.ed", "font.hlp");
multi_curve dummy ("dummy.crv");

// vars in Tcl interpreter
//

// midi bpm from external midi clock
double MIDI_BPM = 0;

// tap bpm from computer keyboard
double TAP_BPM = 0;

// microtonal-keyboard height (0 to 1)
float VOLUME = 0;

fft fft0; // FFT of waveform in waveform editors

cmdlist cmdlst; // list of din commands

// 1 oscillator / waveform for lead
//

float VOICE_VOLUME = 0.2f;

//
// curve libraries
//
curve_library wav_lib ("waveforms.lib"); // waveforms
curve_library sin_lib ("sin.lib"); // custom sine curves 
curve_library cos_lib ("cos.lib"); // custom cosine curves 
curve_library attack_lib ("attack.lib", 1); // attack curves [1 = has_sustain]
curve_library decay_lib ("decay.lib"); // decay curves

// compressor
compressor coml ("coml.crv"), comr ("comr.crv");
curve_editor comed ("compressor.ed");

// octave shift
//
beat2value octave_shift ("os", "octave-shift.crv");
curve_editor octed ("octave-shift.ed");
curve_library octlib ("octave-shift-patterns.lib");
beat2value_listener octlis;

// drone modulation
//
multi_curve drone_mod_am_crv ("drone-mod-am.crv");
multi_curve drone_mod_fm_crv ("drone-mod-fm.crv");
curve_editor drone_mod_ed ("drone-modulation.ed");
drone_mod_lis dmlis; 

// range modulation
multi_curve ran_mod_width_crv ("range-mod-width.crv");
multi_curve ran_mod_height_crv ("range-mod-height.crv");
curve_editor ran_mod_ed ("range-modulation.ed");

// point modulation
curve_editor pomo_ed ("point-modulation.ed");
multi_curve pomo_x_crv ("pomo-x.crv");
multi_curve pomo_y_crv ("pomo-y.crv");
pomo_lis pol;

// range width/height set
multi_curve ran_width_crv ("range-width.crv"), ran_height_crv ("range-height.crv");
solver sol_ran_width (&ran_width_crv), sol_ran_height (&ran_height_crv);
curve_editor ran_wh_ed ("range-width-height.ed");
range_wh_lis rwhl;

// pitch / volume distribution
multi_curve pitch_crv ("pitch.crv"), vol_crv ("vol.crv");
curve_editor pitch_vol_ed ("pitch-vol.ed");
solver warp_pitch (&pitch_crv), warp_vol (&vol_crv); 
multi_curve* warcrv [] = {&pitch_crv, &vol_crv};
solver* warsol_pv [] = {&warp_pitch, &warp_vol};
pitch_vol_lis pvl;

// noise interpolation
multi_curve noiser::interp ("noiser.crv");
curve_editor noiser::ed ("noiser.ed");
noise_interp_lis noiser::lis;

// drone pendulum depth & bpm
multi_curve dp_depth ("dp_depth.crv"), dp_bpm ("dp_bpm.crv");
curve_editor drone_pend_ed ("drone-pend.ed");
solver warp_depth (&dp_depth), warp_bpm (&dp_bpm);
solver* warsol_dp [] = {&warp_depth, &warp_bpm};
multi_curve* dp_crv [] = {&dp_depth, &dp_bpm};
drone_pend_ed_lis dpel;

recorder recorder0; // for recording sounds made in DIN

// microtonal-keyboard see din.cc/.h
//

const string din::PSD = "Please select some drones!";
const string din::A2D = "Please select at least 2 drones.";
const char* ol_fixed_lbls [] = {" with left fixed", " with center fixed", " with right fixed"};
const char* voice_is_lbls [] = {"Noise", "Voice"};
din din0 (cmdlst);

// mondrian (see mondrian.cc/h)
//
unsigned char mondrian::patbuf [1024] = {0};
string mondrian::patstr = "din is noise";
int mondrian::patstep = 1, mondrian::patlen = mondrian::patstr.length ();

rnd<float> rect::rd (0.0f, 1.0f); // to make box color
int rect::ref = 0, ball::ref = 0, slit::ref = 0;
const float mondrian::gutter = 2;
const float mondrian::gutter2 = mondrian::gutter * mondrian::gutter;
float mondrian::min_split_size = 16;
float slit::HALF_SIZE = 20;
float slit::MIN_HALF_SIZE = 1;
float slit::MIN_SIZE = 2 * slit::MIN_HALF_SIZE;
const char* ball::types_str [4] = {" bouncer", " wrecker", " healer", " bouncer or wrecker or healer"};
const char* ball::trigstr [2] = {"note", "noise"};
float ball::recent_attack_time = 0.01f; // secs
float ball::recent_decay_time = 2.0f; // secs
float ball::recent_pitch_mult = 1.0f; // modulation
const char* mondrian_listener::split_types [3] = {" into 2 boxes", " at notes", " into N boxes"};
const char* mondrian_listener::selection_targets [2] = {"Selection target = slits", "Selection target = balls"};
const char* mondrian_listener::pick_box_types [5] = {" Pick the oldest box", " Pick a random box", " Pick the biggest box", " Pick the youngest box", " Pick a box that has a ball"};
const char* mondrian_listener::auto_split_orient_types [4] = { "", " Split horizontally", " Split vertically", " Split horizontally & vertically"};
const char* mondrian_listener::auto_split_at_types [2] = {" Split at notes", " Split anywhere"};
const double slit::INITIAL_OPEN_CLOSE_TIME = 2.0;

// ball operations
const char* ball_op::names [] = {"All Ops", "Turn", "Speed", "Teleport", "Clone"};
float ball_op::TRIGGERT = 0.1; // seconds
float turn::CLOCKWISE = 60.0f, turn::ANTI_CLOCKWISE = 60.0f; // degrees
float speed::BRAKE = 1, speed::ACCELERATE = 1;
float teleport::MAX_RADIUS = 300.0f;
int Clone::max_balls = 64;
int Transform::rules [3] = {ball::WRECKER, ball::HEALER, ball::BOUNCER};

mondrian mondrian0;

// binaural drones see binaural_drones.cc/h)
//
i_binaural_drones binaural_drones0;
const char* binaural_drones_listener::justs [] = {" Justification = Left", " Justification = Right", " Justification = Center"};
gotog _gotomax (1, &mondrian0.attacked); 

// keyboard-keyboard
//
float NOTE_VOLUME = 0.75f * VOICE_VOLUME;

int PITCH_BEND; // in Hz 
float PITCH_BEND_PER_PIXEL;

// in seconds
float ATTACK_TIME = 0.05f;
float DECAY_TIME = 5.0f;
float DELTA_TIME = 0.025f;

keyboard_keyboard keybd2;
gotog _gotog (1, &keybd2.attacked); // default sustain (see keyboard-keyboard.cc)

// available instruments
const char* INSTRUMENTS [] = {"keyboard_keyboard", "microtonal_keyboard", "mondrian", "binaural_drones"};
const char* INSTRUMENTS_SHORT [] = {"kkb", "mkb", "mon", "bd"};
extern const int NUM_INSTRUMENTS = 4, LAST_INSTRUMENT = NUM_INSTRUMENTS - 1;
int CURRENT_INSTRUMENT = 0;
string INSTRUMENT = INSTRUMENTS [CURRENT_INSTRUMENT];
instrument* INSTRUMENT_PTR [] = {&keybd2, &din0, &mondrian0, &binaural_drones0};
checkbutton* LAST_TABS [NUM_INSTRUMENTS] = {0, 0, 0, 0};

mkb_selector_t mkb_selector; // selector for microtonal keyboard with drone mesh preview
box_selector mon_selector; // selector for mondrian

string style_listener::styles [] = {"loop", "pong"};

vector<multi_curve*> curve_list;
void setup_curve_list () {
  multi_curve* lst [] = {
    &din0.wave,
    &keybd2.wave,
    din0.fm.crv, 
		din0.am.crv, 
		din0.gatr.crv,
    octave_shift.crv, 
		&din0.drone_wave, 
		&left_delay.fbk_crv, 
		&left_delay.vol_crv,
    &right_delay.fbk_crv, 
		&right_delay.vol_crv
  };
  for (int i = 0; i < 11; ++i) curve_list.push_back (lst[i]);
}

const char* bpm_com::str [bpm_com::NUM] = {"os", "gr", "am", "fm"};
beat2value* bpm_com::bv [] = {&octave_shift, &din0.gatr, &din0.am, &din0.fm};

#define DEFINE_TCL_FUNC(X) inline int (X) (ClientData cd, Tcl_Interp* ti, int objc, Tcl_Obj* CONST objv[]) { return tcl_run (cd, ti, objc, objv);}

DEFINE_TCL_FUNC(tcl_key)
DEFINE_TCL_FUNC(tcl_setv)
DEFINE_TCL_FUNC(tcl_getv)
DEFINE_TCL_FUNC(tcl_set_delay)
DEFINE_TCL_FUNC(tcl_get_delay)
DEFINE_TCL_FUNC(tcl_set_bpm)
DEFINE_TCL_FUNC(tcl_get_bpm)
DEFINE_TCL_FUNC (tcl_set_beat);
DEFINE_TCL_FUNC (tcl_get_beat);
DEFINE_TCL_FUNC(tcl_set_style)
DEFINE_TCL_FUNC(tcl_get_style)
DEFINE_TCL_FUNC(tcl_set_kern)
DEFINE_TCL_FUNC(tcl_get_kern)
DEFINE_TCL_FUNC(tcl_set_font_size)
DEFINE_TCL_FUNC(tcl_get_font_size)
DEFINE_TCL_FUNC(tcl_note_distance)
DEFINE_TCL_FUNC(tcl_chord)
DEFINE_TCL_FUNC(tcl_notation)
DEFINE_TCL_FUNC(tcl_echo)
DEFINE_TCL_FUNC(tcl_curve_value)
DEFINE_TCL_FUNC(tcl_curve_name)
DEFINE_TCL_FUNC(tcl_curve_library)
DEFINE_TCL_FUNC(tcl_morse_code)
DEFINE_TCL_FUNC (tcl_set_editor);
DEFINE_TCL_FUNC (tcl_set_kb_layout);
DEFINE_TCL_FUNC (tcl_set_scope);
DEFINE_TCL_FUNC (tcl_get_scope);
DEFINE_TCL_FUNC (tcl_get_drone);
DEFINE_TCL_FUNC (tcl_set_drone);
DEFINE_TCL_FUNC (tcl_text_color);
DEFINE_TCL_FUNC (tcl_paste_gater);
DEFINE_TCL_FUNC (tcl_get_intervals);
DEFINE_TCL_FUNC (tcl_num_octaves);
DEFINE_TCL_FUNC (tcl_set_audio);
DEFINE_TCL_FUNC (tcl_load_scale);
DEFINE_TCL_FUNC (tcl_scale_curve);
DEFINE_TCL_FUNC (tcl_binaural_drone);
DEFINE_TCL_FUNC (tcl_set_sine_mixer);
DEFINE_TCL_FUNC (tcl_change_sine_mixer);
DEFINE_TCL_FUNC (tcl_update_sine_mixer);
DEFINE_TCL_FUNC (tcl_write_svg);
DEFINE_TCL_FUNC (tcl_write_trail);

// din commands
//
// format: long name, short name

// for L and R delays
set_delay sd (&left_delay, &right_delay, "set-delay", "sd");
get_delay gd (&left_delay, &right_delay, "get-delay", "gd");

// to load the scale
load_scale los (&din0, "load-scale", "los");

// set and get din variables
set_var sv (&din0, "set-var", "sv");
get_var gv (&din0, "get-var", "gv");

// bpm commands
//
set_bpm sb ("set-bpm", "sb");
get_bpm gb ("get-bpm", "gb");
set_beat sn ("set-beat", "sbt");
get_beat gn ("get-beat", "gbt");
set_style ss ("set-style", "ss");
get_style gs ("get-style", "gs");

// set key/tonic
key ky (&din0, "key", "key");

// display notation on the keyboard
notation no (&din0, "notation", "no");

// music utils
note_distance nd ("note-distance", "nd");
chord ch ("chord", "ch");

// font cmds
set_font_size sfs ("set-font-size", "sfs");
get_font_size gfs ("get-font-size", "gfs");
set_kern sk ("set-kern", "sk");
get_kern gk ("get-kern", "gk");

// curve cmds
curve_name cn ("curve-name", "cn");
curve_value cv ("curve-value", "cv");
curve__library cl ("curve-library", "cl");
set_curve_editor sced ("set-curve-editor", "sced");
paste_gater pasg ("paste-gater", "paste-gater");
scale_curve scrv ("scale-curve", "scrv");

// morse code
morse_code mc ("morse-code", "mc");

// to replace curves in a multi curve with a seed curve
fractaliser fractaliser_;

// to generate curve from polar equation R = sin (K * theta)
rose_milker rosemilker;

// to generate a regular polygon
circler circler_;

// to generate a spiral with R = A * theta
spiraler spiraler_;

// to connect dots to make star polygons
starrer starrer_;

// to generate lissajous curve
lissajous lissajous_;

// to generate superformula curve
superformula superformula_;

// to warp XY of segments of a curve
warper warper_;

// to turn country outlines into curves
countries countries_;

// to morph one curve into another
morpher morpher_;

// to convert a number into a curve
number number_;

// sine mixer for waveform edit
sine_mixer sinemixer; 

// plugin browser
const int plugin_browser::num_plugins = 12;
plugin* plugin_browser::plugins [] = {
	&circler_, 
	&sinemixer,
	&rosemilker, 
  &spiraler_, 
	&lissajous_, 
	&number_, 
	&fractaliser_, 
	&countries_, 
	&warper_,
	&morpher_,
	&superformula_,
  &starrer_, 
};

// unix like echo
echo ech ("echo", ".");

set_kb_layout kbl ("set-kb-layout", "kbl");

// oscilloscope
set_scope ssco ("set-scope", "ssco", &din0, &binaural_drones0);
get_scope gsco ("get-scope", "gsco", &din0);

// drone
get_drone gdro ("get-drone", "gdro", din0);
set_drone sdro ("set-drone", "sdro", din0);

// console
set_text_color stc ("set-text-color", "stc");

// get scale intervals
get_intervals gint ("get-intervals", "gint");

// set number of octaves
num_octaves noct ("num-octaves", "noct", din0);

// set audio
set_audio sau ("set-audio", "sa");

#ifdef __SVG__
  write_svg wsvg ("write-svg", "wsvg"); // write curve into svg file
  write_trail wtrl ("write-trail", "wtrl"); // write drone trails into svg file
#endif

#ifdef __HPGL__
  write_hpgl whpgl ("write-hpgl", "hpgl"); // write curve into hp-gl file for output on graphtec plotters
#endif

// used by binaural drones instrument
cmd_binaural_drone snd ("binaural-drone", "bd");

set_sine_mixer ssm ("set-sine-mixer", "ssm");
change_sine_mixer csm ("change-sine-mixer", "csm");
update_sine_mixer usm ("update-sine-mixer", "usm");

void add_commands (Tcl_Interp* interp) { // add din commands to Tcl interpreter

  unsigned int ncmds = 42;
  tclcmd cmd_funcs [] = {
    tcl_key, 
    tcl_setv, 
    tcl_getv, 
    tcl_set_delay, 
    tcl_get_delay, 
    tcl_set_bpm, 
    tcl_get_bpm, 
    tcl_set_beat, 
    tcl_get_beat,
    tcl_set_style, 
    tcl_get_style, 
    tcl_set_kern, 
    tcl_get_kern,
    tcl_set_font_size, 
    tcl_get_font_size, 
    tcl_note_distance, 
    tcl_chord, 
    tcl_notation, 
    tcl_echo, 
    tcl_curve_value, 
    tcl_curve_name,
    tcl_curve_library, 
    tcl_morse_code, 
    tcl_set_editor, 
    tcl_set_scope, 
    tcl_get_scope, 
    tcl_get_drone, 
    tcl_set_drone,
    tcl_text_color, 
    tcl_paste_gater, 
    tcl_get_intervals,
    tcl_num_octaves, 
    tcl_set_kb_layout, 
    tcl_set_audio,
    tcl_load_scale,
    tcl_scale_curve,
		tcl_binaural_drone,
		tcl_set_sine_mixer,
		tcl_change_sine_mixer,
		tcl_update_sine_mixer,
    tcl_write_svg,
    tcl_write_trail,
  };

  command* cmds [] = {
    &ky, 
    &sv, 
    &gv, 
    &sd, 
    &gd, 
    &sb, 
    &gb, 
    &sn, 
    &gn,
    &ss, 
    &gs, 
    &sk, 
    &gk,
    &sfs, 
    &gfs, 
    &nd, 
    &ch, 
    &no, 
    &ech, 
    &cv, 
    &cn,
    &cl, 
    &mc, 
    &sced, 
    &ssco, 
    &gsco, 
    &gdro,
    &sdro, 
    &stc, 
    &pasg, 
    &gint, 
    &noct, 
    &kbl, 
    &sau,
    &los,
    &scrv,
		&snd,
		&ssm,
		&csm,
		&usm,
    &wsvg,
    &wtrl,
  };

  extern cmdlist cmdlst;
  for (unsigned int i = 0; i < ncmds; ++i) {
    command* cmdp = cmds [i];
    cmdlst.add (cmdp);
    Tcl_CreateObjCommand (interp, cmdp->longname.c_str(), cmd_funcs[i], (ClientData) i, 0);
    Tcl_CreateObjCommand (interp, cmdp->shortname.c_str(), cmd_funcs[i], (ClientData) i, 0);
  }

  dlog << "+++ added " << ncmds << " din commands to the Tcl interpreter +++" << endl;

}

authors_note anote;
curve_picker_t curve_picker;

mouse_slider mouse_slider0;
int mouse_slider::warp = 1;

ui_list uis;

curve_editor drone::modvt::ed ("modv.ed");
modv_lis drone::modvt::lis;

void modulate_down () {
	instrument* instr = get_current_instrument ();
  if (instr->osd.active == 0) {
    static const string down = "down";
    start_octave_shift (instr, 0, down);
  }
}

void modulate_up () {
	instrument* instr = get_current_instrument ();
	if (instr->osd.active == 0) {
    static const string up = "up";
    start_octave_shift (instr, 1, up);
  }
}

void start_octave_shift (instrument* instr, int idir, const string& sdir) {
	octave_shift_data& osd = instr->osd;
  osd.tonic = instr->scaleinfo.tonic;
  osd.dir = idir;
  osd.active = 1;
	osd.now = octave_shift.sol.firstx;
}

void do_octave_shift () {
	for (int i = 0; i < NUM_INSTRUMENTS; ++i) {
		instrument* instr = INSTRUMENT_PTR [i];
		octave_shift_data& osd = instr->osd;
		if (osd.active) {
  		osd.now += (octave_shift.delta * aout.samples_per_channel);
  		static const string octave_shift_str = "Octave Shift ", percent = "%";
			float now = osd.now - octave_shift.sol.firstx;
			float delta = octave_shift.sol.lastx - octave_shift.sol.firstx;
  		osd.percent_complete = (int) (now / delta * 100);
  		stringstream ss; ss << octave_shift_str << osd.percent_complete << percent;
  		string txt (ss.str());
  		MENU.l_octave_shift.set_text (txt);
  		uis.l_octave_shift.set_text (txt);
  		if (osd.now > octave_shift.sol.lastx) { // finished
    		osd.now = octave_shift.sol.lastx;
    		osd.active = 0;
    		MENU.l_octave_shift.set_text (octave_shift_str);
    		uis.l_octave_shift.set_text (octave_shift_str);
  		}
  		float shift = octave_shift.sol (osd.now);
  		if (shift != 0) {
    		if (osd.dir) set_tonic (instr, osd.tonic * shift); // go up
    		else set_tonic (instr, osd.tonic / shift); // go down
  		}

  		if (!osd.active) {
				cons << GREEN;
				cons ("key");
			}

		}
	}

}

int abort_octave_shift (instrument* inst) {
	octave_shift_data& osd = inst->osd;
	if (osd.active) {
		osd.active = 0;
		cons << GREEN;
		cons ("key");

		static const char* oss = "Octave Shift";
		MENU.l_octave_shift.set_text (oss);
		uis.l_octave_shift.set_text (oss);
		return 1;
	} else cons << RED << "No octave shift in progress!" << eol;
	return 0;
}

int esc_octave_shift (instrument* inst) {
	if (SHIFT) 
		return abort_octave_shift (inst);
	return 0;
}

void setup_screens () {

  dlog << "*** setting up screens ***" << endl;

	left_delay.setup ();
	right_delay.setup ();

	coml.apply (&coml.crv);
	comr.apply (&comr.crv);

	// in delay editor load feedback & volume curves
	delayed.add (&left_delay.fbk_crv, &left_delay.fbk_lis);
	delayed.add (&left_delay.vol_crv, &left_delay.vol_lis);
	delayed.add (&right_delay.fbk_crv, &right_delay.fbk_lis);
	delayed.add (&right_delay.vol_crv, &right_delay.vol_lis);

  dlog << "+++ added curves to delay editor +++ " << endl;

	// add L & R compressor curves to compressor editor
	comed.add (&coml.crv, &coml.lis);
	comed.add (&comr.crv, &comr.lis);

	// octave shift
	octave_shift.setup ();
	octlis.set (&octave_shift);
	octave_shift.xmin = &_atmin;
	octave_shift.xmax = &_atmax;
	octed.add (octave_shift.crv, &octlis);
	octed.attach_library (&octlib);

	// drone modulation
	drone_mod_ed.add (&drone_mod_am_crv, &dmlis);
  drone_mod_ed.add (&drone_mod_fm_crv, &dmlis);
	drone_mod_ed.attach_library (&wav_lib);

	// range modulation
	ran_mod_ed.add (&ran_mod_width_crv, MENUP.rml);
  ran_mod_ed.add (&ran_mod_height_crv, MENUP.rml);
	ran_mod_ed.attach_library (&wav_lib);

	// range width+height
  ran_wh_ed.add (&ran_width_crv, &rwhl);
	ran_wh_ed.add (&ran_height_crv, &rwhl);

	// range volume & pitch
	pitch_vol_ed.add (warcrv[0], &pvl);
	pitch_vol_ed.add (warcrv[1], &pvl);

	// drone pendulum amplitude & bpm 
	drone_pend_ed.add (dp_crv[0], &dpel);
	drone_pend_ed.add (dp_crv[1], &dpel);

	// point modulation
	pomo_ed.add (&pomo_x_crv, &pol);
	pomo_ed.add (&pomo_y_crv, &pol);

	// noise interpolator
	noiser::ed.add (&noiser::interp, &noiser::lis);

  // drone speed modulation
  drone::modvt::init ();

  // font editor
  fed.add (&dummy, 0);

  // setup geometry > sound plugins of curve editor
	for (int i = 0; i < plugin_browser::num_plugins; ++i) {
    plugin* pi = plugin_browser::plugins[i];
    dlog << "setting up plugin: " << pi->name << endl; 
    pi->setup (); 
    dlog << pi->name << " setup complete." << endl;
  }

	din0.setup ();
	keybd2.setup ();
	mondrian0.setup ();

	uis.add_widgets ();
  uis.setup ();
  curve_picker.setup ();
  fft0.setup ();
  dlog << "+++ setup screens complete +++" << endl;

}

void setup_plugin_labels () {
  fractaliser_.b_edit.set_text ("Edit");
	warper_.b_edit.set_text ("Edit");
}

void goto_next_instrument () {
	scope.save_current_instrument ();
  ++CURRENT_INSTRUMENT;
  if (CURRENT_INSTRUMENT >= NUM_INSTRUMENTS) CURRENT_INSTRUMENT = 0;
}

void find_instrument () {
  for (int i = 0; i < NUM_INSTRUMENTS; ++i) {
    if (INSTRUMENT == INSTRUMENTS[i]) {
      CURRENT_INSTRUMENT = i;
      break;
    }
  }
}

int is_instrument (ui* u) {
  for (int i = 0; i < NUM_INSTRUMENTS; ++i) if ((void *) u == (void *) INSTRUMENT_PTR[i]) return 1;
	return 0;
}

instrument* find_instrument (const string& inst) {
  for (int i = 0; i < NUM_INSTRUMENTS; ++i) if (inst == INSTRUMENTS[i] || inst == INSTRUMENTS_SHORT[i]) return INSTRUMENT_PTR[i];
	return get_current_instrument ();
}

instrument* get_current_instrument () {
	return INSTRUMENT_PTR [CURRENT_INSTRUMENT];
}

void update_window (int w, int h, int wp, int hp) { // called when main window resized

  glViewport (0, 0, w, h);

  view (w, h);

	int w1 = w - 1, h1 = h - 1;

  cons.set_window (box<int>(0, 0, w1, h1));

  uis.update_widgets (w, h, wp, hp);

	keybd2.calc_visual_params ();

	din0.window_resized (w1, h1);
  //din0.window_resized (w, h);

  mondrian0.calc_visual_params ();

	if (uis.crved) 
		uis.crved->calc_visual_params (); 

	dlog << "+++ update window complete ++" << endl;

}

void setup_sdl_surface (SDL_Surface* surface, int w, int h, int fs = 0) {
  if (surface) SDL_FreeSurface (surface);
	if (fs) 
		surface = SDL_SetVideoMode (w, h, SCREEN_DEPTH, SDL_OPENGL | SDL_FULLSCREEN);
	else {
		char* sdl_pos = (char *) "SDL_VIDEO_CENTERED=center";
  	SDL_putenv (sdl_pos);
  	surface = SDL_SetVideoMode (w, h, SCREEN_DEPTH, SDL_OPENGL | SDL_RESIZABLE);
	}
  if (surface) dlog << "+++ setup video mode " << w << ' ' << h << ' ' << SCREEN_DEPTH << " +++" << endl; else {
    dlog << "!!! couldnt set video mode: " << w << ' ' << h << ' ' << SCREEN_DEPTH << endl;
    exit (1);
  }
}

void save_window () {
  string fname = user_data_dir + "window";
  ofstream file (fname.c_str(), ios::out);
  extern viewport view;
  if (file) {
    file << "view_width " << view.width << endl;
    file << "view_height " << view.height << endl;
		file << "full_screen " << FULL_SCREEN << endl;
		file << "mode " << uis.settings_scr.imode << endl;
    file << "win_left " << din0.win.left << endl;
    file << "win_bottom " << din0.win.bottom << endl;
  } else {
    dlog << "!!! couldnt save window in " << fname << " !!!" << endl;
    return;
  }

  dlog << "+++ saved window in " << fname << " +++" << endl;


}

void restore_last_window () {

	dlog << "*** reading last window ***" << endl;

  int width = 0, height = 0;

  string fname (user_data_dir + "window");
  ifstream file (fname.c_str(), ios::in); 

  string ignore;

  // last window size
  file >> ignore >> width;
  file >> ignore >> height;
	file >> ignore >> FULL_SCREEN;
	file >> ignore >> uis.settings_scr.imode;
	if (FULL_SCREEN == 0) {
		uis.settings_scr.add_display_mode (width, height);
		uis.settings_scr.imode = uis.settings_scr.num_modes - 1;
	} else 
		uis.settings_scr.add_display_mode (800, 600); // one windowed mode for sanity

  // last microtonal board position
  file >> ignore >> din0.win.left;
  file >> ignore >> din0.win.bottom;

  view (width, height);
  din0.prev_mousey = view.ymax;

}

SDL_Surface* surface = 0;

void setup_video_mode (int w, int h, int vw, int vh, int fs) {
	setup_sdl_surface (surface, w, h, fs);	
	update_window (w, h, vw, vh);
	glEnableClientState (GL_VERTEX_ARRAY);

}

void try_quit () {
  dlog << "*** started to quit DIN Is Noise ***" << endl;
	din0.save (); 
	keybd2.scaleinfo.save_scale ();
	mondrian0.scaleinfo.save_scale ();
	binaural_drones0.scaleinfo.save_scale ();
  save_window ();
  interpreter ("src save_settings");
  dlog << "+++ saved DIN settings +++ " << endl;
  uis.cb_voice.turn_off ();
  uis.cb_delay.turn_off ();
  din0.delete_all_drones ();
	binaural_drones0.save ();
	MENU.bdl.clicked (MENU.bbd_select_all);
	MENU.bdl.clicked (MENU.bbd_delete);
	scope.save_current_instrument ();
  quit = SOON;
  dlog << "!!! Quitting SOON !!!" << endl;
}

int read_input () {
  // handle window events
	wheel = 0;
	widget::HOVER = 0;
  static SDL_Event event;
  while (SDL_PollEvent(&event)) {
    switch(event.type) {
      case SDL_VIDEORESIZE:
				if (FULL_SCREEN == 0) {
					setup_video_mode (event.resize.w, event.resize.h, view.width, view.height, FULL_SCREEN);
					uis.settings_scr.update_windowed_mode (event.resize.w, event.resize.h);
				}
				break;

			case SDL_MOUSEBUTTONUP:
				switch (event.button.button) {
					case SDL_BUTTON_WHEELUP:
						wheel = 1;
						break;
					case SDL_BUTTON_WHEELDOWN:
						wheel = -1;
						break;
				}
				break;
        
     	case SDL_QUIT:
				quit = IMMEDIATE;
        break;

    }
  }

  // read keyboard
  keybd.read ();
	SHIFT = shift_down ();
	CTRL = ctrl_down ();
	ALT = alt_down ();

  // read mouse
  int buttons = SDL_GetMouseState (&mousex, &mousey);
  mouseyy = view.ymax - mousey;
  lmb = buttons & SDL_BUTTON_LMASK;
  mmb = buttons & SDL_BUTTON_MMASK;
  rmb = buttons & SDL_BUTTON_RMASK;

  return 1;

}

midi_in midiin;

void applyfx (float* out0, float* out1, int do_delay, int do_compress) {
	//
  // apply delays
  //
  float fdr = uis.fdr_delay.amount;
  float* outl = out0; left_delay (outl, aout.samples_per_channel, fdr);
  float* outr = out1; right_delay (outr, aout.samples_per_channel, fdr);

  // apply compression
  //
  if (do_compress) {
    float* outl = out0, *outr = out1;
    coml.apply (outl, aout.samples_per_channel);
    comr.apply (outr, aout.samples_per_channel);
  }

}

int n_pomo_eds = 35;
curve_editor* pomo_eds [] = {
	&din0.waved,
	&din0.moded,
	&din0.gated,
	&delayed,
	&octed,
	&din0.droneed,
	&keybd2.waved,
	&keybd2.attacked,
	&keybd2.decayed,
	&keybd2.veled,
	&comed,
	&mc.ed,
	&drone_mod_ed,
	&fed,
	&fractaliser_.ed,
	&warper_.ed,
	&mondrian0.waved,
	&mondrian0.attacked,
	&mondrian0.decayed,
	&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,
	&ran_mod_ed,
	&ran_wh_ed,
	&pitch_vol_ed,
	&pomo_ed,
	&noiser::ed,
  &drone_pend_ed,
};

const char* keys_trig_what [] = {" Keys trigger notes", " Keys trigger noise"};

const char* colorer_t::s_schemes [] = {" Top", " Bottom", " Blend", " Random"};
color_data_t get_color::data;

pressedt elsewhere; // see input.h, sine_mixer.cc, basic_editor.cc
pressedt& pressed = elsewhere;

int main (int argc, char** argv) {

	dlog << "*** Starting DIN Is Noise " << VERSION_NUMBER << " ***" << endl;


  // bring up OpenGL window
  //
  if (SDL_Init (SDL_INIT_VIDEO) < 0) {
    dlog << "!!! couldnt initialise SDL video !!!" << endl;
    exit (1);
  } else {
    dlog << "+++ initialised SDL video +++" << endl;
  }

	uis.settings_scr.load_fullscreen_modes ();

  if (SDL_GL_SetAttribute (SDL_GL_DOUBLEBUFFER, 1) < 0) {
    dlog << "!!! couldnt setup SDL/OpenGL double buffer !!!" << endl;
    exit(1);
  } else {
    dlog << "+++ setup SDL/OpenGL double buffer +++" << endl;
  }

  restore_last_window ();

  interpreter.add_din_specific ();
  interpreter ("src init");
  dlog << "+++ loaded DIN scripts (init.tcl) +++ " << interpreter.result << endl;

	setup_screens ();
  setup_curve_list ();

  MENU.riset.set_value (din0.dinfo.drone_rise_time);
  MENU.fallt.set_value (din0.dinfo.drone_fall_time);

  MENU.dpm.set_value (din0.dinfo.dpm);
  MENU.ddpl.set_value (din0.dinfo.dpl);

  interpreter ("src settings");
  dlog << "+++ loaded DIN settings (settings.tcl) +++ " << interpreter.result << endl;

	all_notes.load_scale ("all_notes");

  midiin.open ();

	binaural_drones0.load ();

  // init menu items
  uis.sp_attack_time.set_value (ATTACK_TIME);
  uis.sp_decay_time.set_value (DECAY_TIME);
  uis.sp_voices.set_value ((int)(1.0f / NOTE_VOLUME + 0.5f));
  uis.sp_pitch_bend.set_value (PITCH_BEND_PER_PIXEL);
	uis.cb_show_nearby_notes.set_state (keybd2.show_nearby_notes);
  float obpm = octave_shift.bpm;
  MENU.sp_octave_shift_bpm.set_value (obpm);
	uis.sp_octave_shift_bpm.set_value (obpm);
  MENU.sp_gater_bpm.set_value (din0.gatr.bpm);
	MENU.sp_voice_volume.set_value (VOICE_VOLUME);
  MENU.cb_show_anchors.set_state (din0.dinfo.anchor);
	MENU.cb_show_vel.set_state (din0.dinfo.vel);
	MENU.cb_show_accel.set_state (din0.dinfo.accel);
	MENU.cb_mark_ran.set_state (din0.dinfo.mark_sel_range, 0);
	MENU.sp_drone_master_vol.set_value (drone::mastervolume); 
  MENU.td_tap_display.set_bpm (din0.gatr.bpm);
  MENU.mark_tap_target ();
  MENU.sp_mesh_rows.set_value (din0.dinfo.rows);
  MENU.sp_mesh_cols.set_value (din0.dinfo.cols);
  MENU.sp_bounces.set_value (din0.dinfo.bounce.n);
  MENU.sp_rebound.set_value (din0.dinfo.bounce.speed);
	MENU.picked (MENU.ol_bounce_style.option, 0);
	MENU.picked (MENU.ol_drone_is.option, 0);
	MENU.sp_snap_left.set_value (din0.dinfo.snap.left);
	MENU.sp_snap_right.set_value (din0.dinfo.snap.right);
	MENU.sdl.picked (MENU.ol_snap_style.option, 0);
  MENU.sp_mondrian_min_voices.set_value (mondrian0.min_voices);
  MENU.sp_mondrian_change_attack_time.set_value (0);
  MENU.sp_mondrian_change_decay_time.set_value (0);
  MENU.sp_mondrian_change_speed.set_delta (mondrian0.delta_speed);
	MENU.sp_mondrian_change_note_poly_points.set_value (mondrian0.poly.points);
	MENU.sp_mondrian_change_note_poly_radius.set_value (mondrian0.poly.radius);
	MENU.sp_mondrian_change_note_poly_radius.set_delta (mondrian0.poly.delta_radius);
	MENU.cb_mondrian_auto_adjust_voices.set_state (mondrian0.auto_adjust_voices);
	MENU.cb_draw_ball_position.set_state (mondrian0.draw_ball.position);
	MENU.cb_draw_ball_heading.set_state (mondrian0.draw_ball.heading);
	MENU.cb_draw_ball_trails.set_state (mondrian0.draw_ball.trails);
	MENU.cb_draw_boxes.set_state (mondrian0.draw__boxes);
	MENU.cb_fill_boxes.set_state (mondrian0.fill_boxes);
	MENU.cb_draw_notes.set_state (mondrian0.draw__notes);
	MENU.cb_label_hz_vol.set_state (mondrian0.label_hz_vol);
	MENU.cb_label_notes.set_state (mondrian0.label_notes);
	MENU.ol_ball_types.set_text (ball::types_str[mondrian0.added_ball_type]);
	MENU.sp_mondrian_num_boxes.set_value (mondrian0.num_boxes);
	MENU.sp_auto_split_time.set_value (mondrian0.auto_split_rect.triggert);
	MENU.sp_auto_delete_time.set_value (mondrian0.auto_del_rect.triggert);
	MENU.ol_auto_split_at.set_text (mondrian_listener::auto_split_at_types [mondrian0.auto_split_at]);
	MENU.ol_auto_split_orient.set_text (mondrian_listener::auto_split_orient_types [mondrian0.auto_split_orient]);
	MENU.ol_auto_pick_box_split.set_text (mondrian_listener::pick_box_types [mondrian0.split_leaf]);
	MENU.ol_auto_pick_box_delete.set_text (mondrian_listener::pick_box_types [mondrian0.delete_leaf]);
	MENU.cb_auto_split_box.set_state (mondrian0.auto_split_rect.active);
	MENU.cb_auto_delete_box.set_state (mondrian0.auto_del_rect.active);
	MENU.sp_min_split_size.set_value (mondrian::min_split_size);
  MENU.text_ure.set_text (mondrian::patstr);
  MENU.texstep.set_value (mondrian::patstep);
  mondrian0.fillpatbuf ();
	uis.cb_show_pitch_volume_drones.set_state (din0.dinfo.show_pitch_volume.drones);
	uis.cb_show_pitch_volume_board.set_state (din0.dinfo.show_pitch_volume.board);
	MENU.lf_master_volume.fld.set_text (int (binaural_drones0.master_volume * 100.0f));
	MENU.lf_bd_start_pitch.fld.set_text (binaural_drones0.starting_pitch);
	MENU.sp_bd_separation.set_value (binaural_drones0.separation);
	MENU.sp_bd_pairs.set_value (binaural_drones0.pairs);
	MENU.lf_bd_spacing.fld.set_text (binaural_drones0.spacing);
	MENU.cb_close_octave.set_state (binaural_drones0.close_octave);
	MENU.cb_resize_separation.set_state (binaural_drones0.resize_separation);
	MENU.bdl.picked (MENU.ol_key_note.option, 0);
	MENU.bdl.picked (MENU.ol_justification.option, 0);
	MENU.lf_vol_fade_time.fld.set_text (binaural_drones0.vol_fader.duration);
	MENU.lf_pitch_fade_time.fld.set_text (binaural_drones0.pitch_fader.duration);
	MENU.lf_modulation_amount.fld.set_text (binaural_drones0.modulation_amount);
	MENU.bdl.picked (MENU.ol_select_what.option, 0);
	MENU.update_binaurals_list ();
	MENU.load_range (din0.dinfo.sel_range);
	MENU.sral.picked (MENU.ol_set_range.option, 0);
	MENU.sp_default_width.set_value (WIDTH);
	MENU.sp_default_height.set_value (HEIGHT);
	MENU.cnsl.set (MENU.ol_change_note_style.option, din0.dinfo.change_note_style);
	MENU.picked (MENU.ol_set_unset_toggle.option, 0);
	MENU.picked (MENU.ol_drone_order.option, 0);
	MENU.picked (MENU.ol_mesh_point.option, 0);
	MENU.sp_mesh_dur.set_value (din0.dinfo.mesh_vars.duration);
	MENU.cb_sync_rows_cols.set_state (din0.dinfo.mesh_vars.sync);
	MENU.cb_am_bpm.set_state (din0.dinfo.mesh_vars.apply_to.am);
	MENU.cb_fm_bpm.set_state (din0.dinfo.mesh_vars.apply_to.fm);
	MENU.sp_drones_per_pend.set_value (din0.dinfo.mesh_vars.dpp);
	MENU.picked (MENU.ol_create_this.option, 0);
	MENU.dp_numdrones.set_value (din0.dinfo.drone_pend.n);
	MENU.dp_bpm1.set_value (din0.dinfo.drone_pend.bpm);
	MENU.picked (MENU.dp_orient.option, 0);
	MENU.cb_pitch_dis.set_state (din0.dinfo.dist.pitch);
	MENU.cb_vol_dis.set_state (din0.dinfo.dist.vol);
	MENU.sp_lev_sz.set_value (din0.dinfo.dist.pix);
	MENU.s_phrase_position.set_right (din0.dinfo.phrasor_right);
	MENU.seloncre.set_state (din0.dinfo.seloncre);
	MENU.cnol.picked (MENU.ol_change_note.option, 0);
	set_label (uis.ol_trig_what.option, keys_trig_what, keybd2.trig_what);
	MENU.cb_draw_mesh.set_state (din0.meshh.draw, 0);
	MENU.cmod.changed (MENU.cb_modulation);
	MENU.sp_stiff.set_value (drone::STIFFNESS);
  MENU.gabt.set_value (drone::gabt);
	uis.dpeu.depth.set_value (50);
	uis.dpeu.bpm.set_value (din0.dinfo.drone_pend.bpm);
  din0.set_random_color ();
	get_color::update_data ();
	MENU.ol_color.set_text (MENU.colorer.get_scheme_name()); 
  MENU.handlesize.set_value (drone::HANDLESIZE);
  MENU.trailsize.set_value (TRAILSIZE);

  din0.dinfo.wand = !din0.dinfo.wand; 
  MENU.awdl.picked (MENU.ol_add_wand.option, 0); // flipped back here
  MENU.sp_wand_dist.set_value (drone::wand.dist);
  MENU.darl.picked (MENU.ol_drones_are.option, 0);
  MENU.lifetime.set_value (drone::LIFETIME);
  MENU.chapt.set_value (drone::chuckt::apt.deg);

  MENU.dva.which = din0.dinfo.menu.dva.which;
  MENU.dva.load ();

  MENU.cb_show_gravity.set_state (din0.dinfo.gravity.visible);
  MENU.dcl.changed (MENU.cb_show_gravity);

  MENU.choutline.set_state (drone::chuckt::outline);
  MENU.chautoresettrails.set_state (drone::chuckt::autoresettrails);

  MENU.ancl.picked (MENU.anchored.option, 0);

  MENU.dronearrowdefaults.cap.set_state (drone::arrowt::CAP, 0);

  MENU.gens.set_value (din0.dinfo.gens);

  MENU.revl.picked (MENU.revmod.option, 0);

	// finish init menu items
	
	const char* viv = voice_is_lbls[din0.dinfo.voice_is_voice];
	uis.cb_voice.set_text (viv);
	MENU.handle_voice_tab_items (viv);

	uis.settings_scr.lf_mixing_time.fld.set_text (curve_mixer::TIME);
	uis.settings_scr.sample_rate_changed ();

	options_list* ol [] = {MENUP.ol_bouncer, MENUP.ol_wrecker, MENUP.ol_healer};
	for (int i = 0; i < 3; ++i) {
		int j = Transform::rules[i];
		sprintf (BUFFER, "%s becomes %s", ball::types_str[i], ball::types_str[j]);  
		ol[i]->set_text (BUFFER);
	}

	uis.settings_scr.update_mode_display ();

  mondrian0.make_notes ();

  curve_picker.widget::hide();

  cons.home ();

	setup_video_mode (view.width, view.height, view.width, view.height, FULL_SCREEN);

  din0.dinfo.gravity.calcui ();

  glClearColor(0, 0, 0, 0); // black bg

	basic_editor::alloc_gl_pts (16);

  nagscr ();
  
  // ui loop
	double ft0 = 0;
	double it0 = 0;

	const string loop ("loop");
	const char* timenow = "timenow";
	const string s_goodbye ("Goodbye!"), s_drones (" | Drones = "), s_kbkb (" | Keyboard-Keyboard = "), s_mondrian (" | Mondrian = "), s_binaurals (" | Fading Binaurals = ");

	const char percent = '%';

	aout.start (); // start audio loop in a separate thread. see audio_wanted (..)

	it0 = ui_clk ();
	ft0 = ui_clk ();

  while (1) {

	  // try to write audio
    if (aout.can_write ()) {

      float* out0 = aout.writep; // left channel
      float* out1 = out0 + aout.samples_per_channel; // right channel

			do_octave_shift ();

			memset (aout.writep, 0, aout.samples_buffer_size); // silence everything

      interpreter (loop); // run loop on tcl proc
			
			// audio from instruments
			din0.render_audio (out0, out1);
      keybd2.render_audio (out0, out1);
      mondrian0.render_audio (out0, out1);
			binaural_drones0.render_audio (out0, out1);

			// delay and compressor
      applyfx (out0, out1, din0.dinfo.delay, din0.dinfo.compress);

      // to oscilloscope
			if (scope.visible) scope.add_samples (out0, out1, aout.samples_per_channel); 

			// record?
			if (uis.cb_record.state) {
				recorder0.add (aout.writep, aout.samples_buffer_size, aout.samples_per_buffer, uis.cb_record, MENU.cb_record);
			}

  		// stream status
  		aout.available [aout.writei] = 1;
  		if (++aout.writei >= aout.num_samples_buffers) {
    		aout.writep = aout.samples_buffers;
    		aout.writei = 0;
  		} else aout.writep += aout.samples_per_buffer;

  		// store timenow in TCL. timenow is seconds on din audio clock
  		TIME_NOW = clk.secs;
  		Tcl_UpdateLinkedVar (interpreter.interp, timenow); 

    }

  	if (recorder0.saving_started) { // save recording to disk
			if (recorder0.save_some (MENU.cb_record) == 0) 
				MENU.b_save.set_text ("Overwrite");
		}

		// draw frame
		double ft1 = ui_clk ();
		LAST_FRAME_TIME = ft1 - ft0;
		if (LAST_FRAME_TIME >= TIME_PER_FRAME) {
			ft0 = ui_clk ();
			FPSNOW = 1.0 / LAST_FRAME_TIME;
      uis.bg ();
      uis.draw ();
      SDL_GL_SwapBuffers ();
		}

		// handle input [mouse, keys and midi]
		double it1 = ui_clk ();
		LAST_INPUT_TIME = it1 - it0;
		if (LAST_INPUT_TIME > TIME_PER_INPUT) {
			it0 = ui_clk ();
			IPSNOW = 1.0 / LAST_INPUT_TIME;
			read_input ();
      if (quit == DONT) ;
      else if (quit == SOON) {
				cons << console::yellow << s_goodbye << s_drones << din0.num_drones << s_kbkb << keybd2.num_triggered_notes << s_mondrian << mondrian0.num_triggered_notes;
				if (binaural_drones0.num_binaural_drones) cons << s_binaurals << int (binaural_drones0.vol_fader.xt*100+0.5) << percent;
				cons << eol;
        if (
            (din0.num_drones == 0) && 
            (uis.fdr_voice.on == 0) && 
            (uis.fdr_delay.on == 0) && 
            (keybd2.num_triggered_notes == 0) && 
            (mondrian0.num_triggered_notes == 0) &&
						(binaural_drones0.vol_fader.active == 0)
           ) 
          break;
      }
      else if (quit == IMMEDIATE) break;

			uis.handle_input ();
      for (int i = 0; i < n_pomo_eds; ++i) pomo_eds[i]->pomo.bg (); // update point modulations on curve editors
			midiin.handle_input ();
			keybd.save ();
    }

  } // finish

  aout.close ();

  mesh::destroy ();
  trail_t::destroy ();

	drone_mod_am_crv.save ("drone-mod-am.crv");
	drone_mod_fm_crv.save ("drone-mod-fm.crv");
	ran_mod_height_crv.save ("range-mod-height.crv");
	ran_mod_width_crv.save ("range-mod-width.crv");
	ran_width_crv.save ("range-width.crv");
	ran_height_crv.save ("range-height.crv");
	vol_crv.save ("vol.crv");
	pitch_crv.save ("pitch.crv");
	pomo_x_crv.save ("pomo-x.crv");
	pomo_y_crv.save ("pomo-y.crv");
	noiser::interp.save ("noiser.crv");
	dp_depth.save ("dp_depth.crv");
	dp_bpm.save ("dp_bpm.crv");
  drone::modvt::crv.save ("modv.crv");

  #if defined (__SVG_FNT__) || defined (__PLOTTER_FNT__)
  ifstream textf ("text.txt", ios::in);
  if (textf) {
		int x, y;
		textf >> x >> y;
    string line;
    int lh = line_height;
    while (!textf.eof()) {
      getline (textf, line);
			#ifdef __SVG_FNT__
      	write_string (line, x, y);
				y += lh;
			#elif __PLOTTER_FNT__
				plot_string (line, x, y);
				y -= lh;
			#endif
    }
  }
  #endif

  return 0;

}

void set_tonic (instrument* instr, float f) {
	instr->scaleinfo.set_tonic (f);
	if (instr == get_current_instrument ()) instr->scaleinfo.update_settings ();
}

float get_tonic (instrument* instr) {
	return instr->scaleinfo.tonic;
}

void set_notation (int n) {
	NOTATION = n;
	din0.notate_all_ranges ();
	keybd2.setup_notes (0);
	mondrian0.calc_visual_params ();
	uis.settings_scr.sn_scale_notes.refresh ();
}

void set_num_octaves (int n) {
	int lno = NUM_OCTAVES;
	NUM_OCTAVES = n;
  #define DONTLOADFROMDISK 0
  din0.setup_ranges (lno, DONTLOADFROMDISK);
	din0.update_drone_ranges ();
	din0.refresh_all_drones ();
}

int hide_menu () {
  if (MENU.show) {
    MENU.toggle ();
    return 1;
  } 
  return 0;
}

void show_menu () {
	MENU.show = 0;
	MENU.toggle ();
}

void hz2step (float& hz, float& step) {
  step = hz * 1.0f / SAMPLE_RATE;
}

void step2hz (float& step, float& hz) {
	hz = step * SAMPLE_RATE;
}

void set_snap_drones (int what) {
  stringstream ss; ss << "set-var snap_drones " << what;
  cons (ss.str());
}

void dont_call_listener (checkbutton& cb, int state) {
  if (state) cb.turn_on (DONT_CALL_LISTENER); else cb.turn_off (DONT_CALL_LISTENER);
}

int find_nearest_note (string& note, float& frequency, float& dist) {

  float left = WIKIPEDIA_KEY_FREQUENCIES [0] / 2048, right = 2 * left;
  float outf = frequency;
  if (outf < left) return 0;

  // find octave
  while (1) {
    if ((left <= outf) && (right > outf)) break;
    else {
      left*=2;
      right*=2;
    }
  }

  float oct = left / WIKIPEDIA_KEY_FREQUENCIES[0];
  int id = 0;
  float tone = 0, newdist = 0;
  dist = outf - left;
  for (int i = 0; i < 13; ++i) {
    tone = WIKIPEDIA_KEY_FREQUENCIES[i] * oct;
    newdist = abs(outf - tone);
    if (newdist <= dist) {
      dist = newdist;
      id = i;
    } else {
      break;
    }
  }

  note = WESTERN_FLAT[id];
  frequency = WIKIPEDIA_KEY_FREQUENCIES[id] * oct;
  dist = outf - frequency;
	return id;

}

void turn_off_ui () {
	UI_OFF = 1;
	uis.show_hide_widgets (0);
}

void turn_on_ui () {
	UI_OFF = 0;
	uis.show_hide_widgets (1);
}

void tween (float* buf1, float* buf2, int n, float amount) {// interpolate buf2 -> buf1 and store in buf1
	for (int i = 0; i < n; ++i) {
		float b1 = buf1 [i], b2 = buf2[i];
		buf1[i] = amount * (b1 - b2) + b2;
	} 
}

void tween (float* buf1, float* buf2, int n, float* amount) {
	for (int i = 0; i < n; ++i) {
		float b1 = buf1 [i], b2 = buf2[i];
		buf1[i] = amount[i] * (b1 - b2) + b2;
	} 
}

void fill (float* buf, float start, float end, int n) {
	float es = end - start;
	float a = 0, da = 1./ (n - 1);
	for (int i = 0; i < n; ++i) {
		buf[i] = start + a * es;
		a += da;
	}
}

void multiply (float* out, float* mul, int n) { 
	for (int i = 0; i < n; ++i) out[i] *= mul[i];
}

void multiply (float* out, int n, float depth) {
	for (int i = 0; i < n; ++i) out[i] *= depth;
}

void warp_mouse (int x, int y) {
	SDL_WarpMouse (x, y);
	mousex = x;
	mousey = y;
	mouseyy = view.ymax - y;
}

int can_wheel () {
	return (wheel && !widget::HOVER && !MENU.show);
}

void range_wh_lis::edited (curve_editor* ed, int i) {
	if (i) {
    sol_ran_height.update ();
    din0.all_ranges_height_changed ();
	} else {
		sol_ran_width.update ();
		din0.all_ranges_width_changed ();
	}
}

void pitch_vol_lis::edited (curve_editor* ed, int i) {
	warsol_pv[i]->update ();
	din0.refresh_all_drones ();
}

void drone_pend_ed_lis::edited (curve_editor* ed, int i) {
	warsol_dp[i]->update ();
	din0.update_drone_pendulums ();
}

void pomo_lis::edited (curve_editor* ed, int j) {
	for (int i = 0; i < n_pomo_eds; ++i) pomo_eds[i]->pomo.update_solvers (j);
}

void noise_interp_lis::edited (curve_editor* ed, int i) {
	din0.noise_interpolator_changed ();
	update_triggered_noises (keybd2.triggered_notes);
	update_triggered_noises (mondrian0.triggered_notes);
}

void modv_lis::edited (curve_editor* ed, int i) {
  din0.update_drone_modv_solvers ();
}

void make_good_name (string& name) {
  for (int i = 0, j = name.length (); i < j; ++i) {
    char& ci = name[i];
    if (ci == spc) ci = '_';
  }
}

void drone::modvt::init () {
  ed.add (&crv, &lis);
}
