/*
    Copyright (C) 2001 Paul Davis
    
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

    $Id: ksi.cc,v 1.79 2004/11/29 13:40:03 pauld Exp $
*/

#include <string>
#include <algorithm>
#include <iomanip>
#include <cmath>
#include <ctime>
#include <cerrno>
#include <sys/socket.h>
#include <netinet/in.h>
#include <cstdlib>
#include <string>
#include <unistd.h>
#include <netdb.h>
#include <fcntl.h>

#include <pbd/error.h>
#include <pbd/failed_constructor.h>
#include <pbd/lockmonitor.h>
#include <pbd/undo.h>

#include <sigc++/signal_system.h>
#include <sigc++/bind.h>

#include <ardour/ardour.h>
#include <ardour/audioengine.h>
#include <ardour/session.h>
#include <ardour/session_route.h>
#include <ardour/configuration.h>
#include <ardour/ladspa_plugin.h>
#include <ardour/plugin_manager.h>
#include <ardour/diskstream.h>
#include <ardour/route.h>
#include <ardour/audio_track.h>
#include <ardour/insert.h>

#include "physical_keyboard.h"
#include "ksi.h"
#include "opts.h"

#include "i18n.h"

using namespace SigC;
using namespace KSI_ARDOUR;
using namespace ARDOUR;
using namespace std;

sigset_t KSI::signals;
bool KSI::_die;
bool KSI::_saveable;
PBD::Lock KSI::die_lock;
PBD::NonBlockingLock KSI::screen_lock;
extern void die (void);
extern void die_gracefully (void);


void *
KSI::butler_thread (void *arg)

{
	struct sched_param sp;
	static KSI *ksi = (KSI *) arg;

		memset (&sp, 0, sizeof sp);
		sp.sched_priority = 0;
		if (sched_setscheduler(0, SCHED_OTHER, &sp) != 0) {
			error << _("KSI_BUTLER: can't give up RT scheduling") << endmsg;
		}
		
                /* we are now ready to start. */
		{
			PBD::LockMonitor lm(ksi->butler_lock, __LINE__, __FILE__);
			pthread_cond_signal (&ksi->cond);
		}

		while (1) {
			char buf[12];
			struct timespec req;
			SMPTE_Time smpte;
			
			memset(&req, 0, sizeof req);
			memset (&buf, 0, 12);
			req.tv_sec = 0;
			req.tv_nsec = 100000000; // 100 ms
			if (int err = nanosleep(&req, NULL) != 0) {
				error << _("BUTLER: nanosleep failed...exiting") << strerror(err) << endmsg;
				return 0;
			}
			mvwaddstr (ksi->lower_window, 4, 0, "Session Time: ");
			
			ksi->session->smpte_time(smpte);
			if (smpte.negative) {
				sprintf (buf, "-%02ld:%02ld:%02ld:%02ld", smpte.hours, smpte.minutes, smpte.seconds, smpte.frames);
			} else {
				sprintf (buf, "%02ld:%02ld:%02ld:%02ld", smpte.hours, smpte.minutes, smpte.seconds, smpte.frames);
			}
			mvwaddstr(ksi->lower_window, 4, 14, buf);
			ksi->redraw_screen(false, true);
		}
}

void *
KSI::signal_thread (void *arg)

{
	int sig;
	int err;

	err = sigwait (&KSI::signals, &sig);

	if (err) {
		exit (1);
	}

	if (sig == 2) { // we got an interrupt
		fprintf(stderr, "INTERRUPT!\n");
		die_gracefully();
		/*NOTREACHED*/
		return 0;
	} else {
		fprintf (stderr, "SIGNAL %d CAUGHT!\n", sig);
		die ();
		/*NOTREACHED*/
		return 0;
	}
}

int
KSI::smpte_time_str(char *buf, jack_nframes_t when) {

	if (session) {
		SMPTE_Time smpte;

		session->smpte_time(when, smpte);
		if (smpte.negative) {
			sprintf (buf, "-%02ld:%02ld:%02ld:%02ld", smpte.hours, smpte.minutes, smpte.seconds, smpte.frames);
		} else {
			sprintf (buf, "%02ld:%02ld:%02ld:%02ld", smpte.hours, smpte.minutes, smpte.seconds, smpte.frames);
		}
		return 0;
	}
	return -1;
}

KSI::KSI (AudioEngine &eng, const string& server)
	: engine (eng),
	  selection ("selection"),
	  _track("track_group")

{
	string host;
	int port;
	
	pthread_cond_init(&cond, 0);
	_die = false;
	_saveable = false;
	_yn = false;
	display_is_remote = false;
	session = 0;
	scrub_speed = 1.00;
	_scrubbing = false;
	_location_page = 0;
	_location_cnt = 0;
	selection_cnt = 0;
	group_cnt = 0;
	p_amount = 0;
	g_amount = 0;

	if (server.length()) {

		int socket;
		string::size_type n;
		
		if ((n = server.find (':')) != string::npos) {
			host = server.substr (0, n);
			port = atoi (server.substr (n+1).c_str());
		} else {
			struct servent *srv;


			host = server;
			if ((srv = getservbyname("remote_kbd", "tcp")) != 0) {
				port = ntohs(srv->s_port);
				free(srv);
			} else {
				port = 9763;
			}
		}

		if ((socket = tcp_connect (host, port)) < 0) {
			error << compose(_("cannot connect to %1"), server) << endmsg;
			throw failed_constructor();
		}
		
		try {
			keyboard = new PhysicalKeyboard (socket);
		}
		
		catch (failed_constructor& err) {
			error << _("could not create proxy keyboard object") << endmsg;
			throw err;
		}

		display_is_remote = true;
		if (catch_signals ()) {
			throw failed_constructor ();
		}
		
		if (dup2 (socket, 1) < 0) {
			error << compose(_("cannot reopen stdout as socket (%1"), strerror (errno)) << endmsg;
			endwin ();
			throw failed_constructor();
		}

		if (initialize_ui ()) {
			throw failed_constructor ();
		}

	} else {

		if (initialize_ui ()) {
			throw failed_constructor ();
		}
		
		try {
			keyboard = new PhysicalKeyboard;
		}
		
		catch (failed_constructor& err) {
			error << _("could not create direct keyboard object") << endmsg;
			endwin ();
			throw err;
		}

		if (catch_signals ()) {
			throw failed_constructor ();
		}
	}

	push_selection_mode (Route);

	current_group = 0;
	paused = false;
	route_start = 0;
	io_start = 0;
	editing_insert = 0;

	keyboard->key_press.connect (slot (this, &KSI::key_press_handler));

	string::size_type commapos;

	if ((commapos = master_outs.find (',')) == string::npos) {
		error << compose(_("bad format for master outputs [%1]"), master_outs) << endmsg;
		throw failed_constructor();
	}

	/* allow user to use start-at-one channel ids */

	master_lr_channels.push_back (master_outs.substr (0, commapos));
	master_lr_channels.push_back (master_outs.substr (commapos+1));

	if ((commapos = control_outs.find (',')) == string::npos) {
		error << compose(_("bad format for control outputs [%1]"), control_outs) << endmsg;
		throw failed_constructor();
	}

	control_lr_channels.push_back (control_outs.substr (0, commapos));
	control_lr_channels.push_back (control_outs.substr (commapos+1));

	if (control_lr_channels[0] != master_lr_channels[0] ||
	    control_lr_channels[1] != master_lr_channels[1]) {
		need_control_room_outs = true;
	} else {
		need_control_room_outs = false;
	}

	update_channel_range_display ();
	update_selection_mode_display ();
}

KSI::~KSI ()

{
	if (session) {
		if (safe_to_save()) {
			fprintf (stderr, "saving session state.\n");
			session->save_state("default");
		} else {
			fprintf (stderr, "WARNING! session state saving disallowed!\n");
			}
		delete session;
	}

	/* XXX problems here with the remote keyboard: we shut 
	   the socket and so the UI dies. Need to keep stdout
	   around as a different file descriptor, then
	   switch back to it, or something.
	*/

	engine.stop ();
	delete keyboard;
	endwin ();
}

int
KSI::run ()
{
	string spath = session_path;
	string sname;
	string::size_type pos;
	
	if ((pos = spath.find_last_of ('/')) == string::npos) {
		sname = spath;
	} else {
		sname = spath.substr(pos+1);
	}

	try { 
//		string path, name;
//		tokenize_fullpath(session_path, path, name);
		session = new Session (engine, spath, "default", 0);
	}
	
	catch (failed_constructor& err) {
		error << _("could not load new session") << endmsg;
		return -1;
	}

        // if we're not running, we lose! we cant tell how many output channels there are.

	info << _("starting engine") << endmsg;
	engine.start ();

	unsigned long limit = max (engine.n_physical_outputs(), session->nroutes());

	session->set_input_auto_connect(true);
	for (unsigned long n = 0; n < limit; ++n) {

		char buf[32];
		bool want_signals_and_paints = true;

		sprintf (buf, "Audio %lu", n+1);	 /* XXX gack. this naming is private to the session */

                /* JHALL * * * W A R N I N G * * *
		 *
		 * renaming these initially created routes might cause a population explosion 
		 */
		
		ARDOUR::Route* route = session->route_by_name (buf);
		ARDOUR::AudioTrack* track = 0;

		if (route == 0) {
			route = new_route(with_diskstreams);
/* JHALL: to delete
			if (with_diskstreams) {
				route = session->new_audio_track ();
				track = dynamic_cast<AudioTrack*> (route);
			} else {
				route = session->new_audio_route ();
			}
*/
			want_signals_and_paints = false;
		}
			if (with_diskstreams)
				track = dynamic_cast<AudioTrack*> (route);

		if (route != 0 && want_signals_and_paints) {
			paint_channel_gain (n, route);
			paint_channel_mute (n, route);
			paint_channel_solo (n, route);

			if (with_diskstreams) {
				paint_channel_record_enable (n, track);
				track->record_enable_changed.connect 
					(bind (slot (this, &KSI::route_record_enable_changed), track, n));
			}

			route->solo_changed.connect (bind (slot (this, &KSI::route_solo_changed), route, n));
			route->mute_changed.connect (bind (slot (this, &KSI::route_mute_changed), route, n));
			route->gain_changed.connect (bind (slot (this, &KSI::route_gain_changed), route, n));
		}

		want_signals_and_paints = true;
	}
	
	if (need_control_room_outs) {

		if ((control_route = session->new_audio_route (0, 0)) == 0) {
			error << _("ERROR unable to create control room bus") << endmsg;
			throw failed_constructor();
		} 

		control_route->set_name ("CONTROL ROUTE", this);
	
		while (control_route->n_inputs() < 2) {
			control_route->add_input_port ("", this);
		}

		while (control_route->n_outputs() < 2) {
			control_route->add_output_port ("", this);
		}
	
		control_route->connect_output (control_route->output (0), control_lr_channels[0], this);
		control_route->connect_output (control_route->output (1), control_lr_channels[1], this);
	}
	
	session->mix_group_added.connect (slot (this, &KSI::add_group));
	session->RecordEnabled.connect (slot (this, &KSI::session_record_enable_changed));
	session->RecordDisabled.connect (slot (this, &KSI::session_record_enable_changed));
	session->TransportStateChange.connect (slot (this, &KSI::session_transport_state_change));
//	session->save_state ("default");

	/* set up 10 mix groups */

	for (guint32 x = 0; x < 10; x++) {
		char buf[32];
		
		sprintf (buf, "Group %u", x + 1);
		if (session->mix_group_by_name(buf) == 0) {
			session->add_mix_group (buf);
		}
	}
	session->set_auto_play (false);

	update_transport_state_display ();
	start_butler_thread();
	Config->set_use_hardware_monitoring(false);

	return keyboard->main ();
}

int
KSI::catch_signals (void)

{
	pthread_t thread_id;

	sigemptyset (&signals);
	sigaddset(&signals, SIGALRM);
	sigaddset(&signals, SIGHUP);
	sigaddset(&signals, SIGINT);
	sigaddset(&signals, SIGQUIT);
	sigaddset(&signals, SIGILL);
	sigaddset(&signals, SIGTRAP);
	sigaddset(&signals, SIGABRT);
	sigaddset(&signals, SIGIOT);
	sigaddset(&signals, SIGFPE);
	sigaddset(&signals, SIGKILL);
	sigaddset(&signals, SIGUSR1);
	sigaddset(&signals, SIGSEGV);
	sigaddset(&signals, SIGUSR2);
	sigaddset(&signals, SIGPIPE);
	sigaddset(&signals, SIGTERM);
	sigaddset(&signals, SIGCHLD);
	sigaddset(&signals, SIGCONT);
	sigaddset(&signals, SIGSTOP);
	sigaddset(&signals, SIGTSTP);
	sigaddset(&signals, SIGTTIN);
	sigaddset(&signals, SIGTTOU);

	/* all child threads will inherit this mask */

	pthread_sigmask (SIG_BLOCK, &signals, 0);

	/* start a thread to wait for signals */

	if (pthread_create (&thread_id, 0, KSI::signal_thread, 0)) {
		error << _("cannot create signal catching thread") << endmsg;
		return -1;
	}

	pthread_detach (thread_id);

	return 0;
}

void
KSI::route_selected (guint32 which, SelectionType type, RouteGroup *grp)
{
	ARDOUR::Route *route;
	
	if (grp == &selection) {
		which += route_start;
	}
	
	char buf[32];
	snprintf (buf, sizeof(buf)-1, "Audio %u", which + 1);

	if ((route = session->route_by_name (buf)) != 0) {

		if (type == Replace) {
			grp->clear ();
			grp->add (route);
		} else if (type == Remove) {
			grp->remove (route);
		} else {
			grp->add (route);
                        // JHALL: should this be a set?
		}
		
		if ((grp == &selection) || (grp == &_track)) {
			update_selection_display(grp);
		} else {
			update_group_content_display (grp);
		}
		
	} else {
		info << compose(_("no such route %1"), which) << endmsg;
	}

}

void
KSI::plugin_selected (guint32 which, SelectionType type)

{
	ARDOUR::Route *mx = get_first_selected_route ();
	
	pop_selection_mode ();

	if (mx == 0) {
		return;
	}

	switch (type) {
	case Add:
	case Replace:
		add_plugin (mx, which + (plugin_page * selection_size));
		break;
		
	case Remove:
//		remove_plugin(mx, which);
		break;
	} 
}

void
KSI::insert_selected (guint32 which, SelectionType type)
{
	Redirect *redirect;

	ARDOUR::Route *mx = get_first_selected_route ();

	if (mx == 0) {
		return;
	}

	redirect = mx->nth_redirect (which);

	if (redirect == 0) {
		editing_insert = 0;
		info << compose(_("no such insert (%1)"), which) << endmsg;
		return;
	}
	
	if ((editing_insert = dynamic_cast<ARDOUR::PluginInsert *> (redirect)) == 0) {
		error << _("the selected insert is actually a send!") << endmsg;
		return;
	}

	info << compose(_("selected insert %1"), which + 1) << endmsg;
	
	switch (type) {
	case Remove:
		mx->remove_redirect (editing_insert, this);
		editing_insert = 0;
		insert_page = 0;
		show_inserts (insert_page, true);
		break;

	default:
		ensure_selection_mode (Parameter);
		parameter_page = 0;
		show_parameters (parameter_page, editing_insert);
	}
}

void
KSI::remove_location() 
{
	ARDOUR::Location *loc;

	if (session == 0) return;
	loc = session->locations()->current();
	if (loc == 0) {
		error << _("unable to remove location") << endmsg;
		return;
	}
	session->locations()->remove(loc);
}

void
KSI::add_location() {
	
	if (session) {
		ARDOUR::Location *loc;
		jack_nframes_t now = session->audible_frame();

		loc = new ARDOUR::Location (now, now, "UNNAMED");
		session->locations()->add(loc, true);
	}
	return;
}

void
KSI::remove_insert (ARDOUR::Route *mx, guint32 which)
{
}

void
KSI::start_insert_activate ()

{
}

void
KSI::start_insert_edit ()
{
	ensure_selection_mode (Insert);
	insert_page = 0;
	show_inserts (insert_page, true);
}

void
KSI::start_insert_add ()

{
	ensure_selection_mode (Plugin);
	plugin_page = 0;
	show_possible_plugins (plugin_page);
}

void
KSI::remove_inserts ()

{
	ARDOUR::Route *mx = get_first_selected_route ();

	if (mx == 0) {
		return;
	}

	mx->clear_redirects (this);

	/* XXX what will update the display if we're looking
	   at this particular Route's insert list ?
	*/
}

void
KSI::set_insert_state (bool yn)
{
	if (editing_insert == 0) {
		/* XXX programming error */
		return;
	}

	editing_insert->set_active (yn, this);
}

/* JHALL: make sure that insert_cnt == 0 before calling this function */

void
KSI::show_insert (Redirect *redirect)

{
	ARDOUR::PluginInsert *insert;

	if ((insert = dynamic_cast<ARDOUR::PluginInsert *> (redirect)) == 0) {
		/* XXX should handle sends one day */
		return;
	}

	wprintw (inserts_window, "%d %s %s\n", ++insert_cnt, 
		 insert->active() ? " (ON)" : " (OFF)",
		 insert->name().c_str());
}

int
KSI::show_inserts (guint32 page, bool show_active)
{
	ARDOUR::Route *mx = get_first_selected_route ();

	if (mx == 0) {
		return -1;
	}

	mvwaddstr (inserts_box, 0, 15, mx->name().c_str());
	werase (inserts_window);

	insert_cnt = 0;
	mx->foreach_redirect (this, &KSI::show_insert);

	redraw_screen ();
	return 0;
}

void
KSI::location_selected(guint32 which) {

	_location_cnt = which;
	if (session) {
			session->locations()->apply(*this, &KSI::_location_selected);
		}
}

void
KSI::show_locations() {

	if (session) {
		session->locations()->apply(*this, &KSI::update_locations_display);
	}
}

int
KSI::update_current_location_display ()
{
	ARDOUR::Location *loc;
	char s_buf[12];

	if (session == 0) return -1;

	loc = session->locations()->current();

	if (loc == 0) {
		error << _("ERROR cannot use the current location") << endmsg;
		return -1;
	}

	mvwaddstr (locations_box, 0, 15, loc->name().c_str());
	werase (locations_window);

	if (smpte_time_str(s_buf, loc->start()) < 0) {
		error << _("PROGRAMMER ERROR! no session!") << endmsg;
		return -1;
	}
	mvwprintw(locations_window, 0, 0, "Start: %s", s_buf);
	if (smpte_time_str(s_buf, loc->end()) < 0) {
		error << _("PROGRAMMER ERROR! no session!") << endmsg;
		return -1;
	}
	mvwprintw(locations_window, 1, 0, "ENd: %s", s_buf);
	mvwprintw(locations_window, 2, 0, "CD marker: %s", loc->is_cd_marker() ? "Yes" : "No");
	mvwprintw(locations_window, 3, 0, "AutoLoop: %s", loc->is_auto_loop() ? "Yes" : "No");
	mvwprintw (locations_window, 4, 0, "AutoPunch: %s", loc->is_auto_punch() ? "Yes" : "No");
	mvwprintw (locations_window, 5, 0, "Name: %s",loc->name().c_str());

	redraw_screen ();
	return 0;
}

void
KSI::add_plugin (ARDOUR::Route *mx, guint32 which)
{
	list<PluginInfo *>& pluginfo = ARDOUR::PluginManager::the_manager()->ladspa_plugin_info ();
	list<PluginInfo *>::iterator infoiter;
	ARDOUR::PluginInsert *insert;
	LadspaPlugin *plugin;
	guint32 n;

	for (n = 0, infoiter = pluginfo.begin(); ((infoiter != pluginfo.end()) && (n < which)); n++, infoiter++);

	info << compose(_("loading plugin %1"), (*infoiter)->name) << endmsg;

	try {
		plugin = dynamic_cast<LadspaPlugin*> (ARDOUR::PluginManager::the_manager()->load (*session, *infoiter));
	}
	
	catch (failed_constructor& err) {
		info << compose(_("could not load plugin module for %1"), (*infoiter)->name) << endmsg;
		return;
	}
	
	insert = new ARDOUR::PluginInsert (*session, *plugin, PreFader);
	mx->add_redirect (insert, this);
	insert->set_active (true, this);
	
	insert_cnt = 0;
	mx->foreach_redirect (this, &KSI::show_insert);
	
	redraw_screen ();
}

void
KSI::update_record_display(ARDOUR::Route *rt) {

	ensure_selection_mode(RecordParameter);
	mvwprintw (record_box, 4, 0, " Route %s", rt->name().c_str());
	wclrtoeol(record_box);
	werase(record_window);
	mvwprintw(record_window, 0, 0, "Route: %s", rt->active() ? "On" : "Off");
	mvwprintw(record_window, 1, 0, "Record Enabled: %s", rt->record_enabled() ? "Yes" : "No");
	mvwprintw(record_window, 2, 0, "Automation Record Enable: %s", rt->gain_automation_recording() ? "Yes" : "No");
	mvwprintw (record_window, 3, 0, "Automation Playing Enable: %s", rt->gain_automation_playback() ? "Yes" : "No");
	mvwprintw (record_window, 4, 0, "Phase Inverter: %s", rt->phase_invert() ? "On" : "Off");
	mvwprintw(record_window, 5, 0, "Mute Affects Pre-Fader: %s", rt->get_mute_config (ARDOUR::PRE_FADER) ? "Yes" : "No");
	mvwprintw(record_window, 6, 0, "Mute Affects Post-Fader: %s", rt->get_mute_config (ARDOUR::POST_FADER) ? "Yes" : "No");
	mvwprintw(record_window, 7, 0, "Mute Affects Control Outs: %s", rt->get_mute_config (ARDOUR::CONTROL_OUTS) ? "Yes" : "No");
	mvwprintw(record_window, 8, 0, "Mute Affects Main Outs: %s", rt->get_mute_config (ARDOUR::MAIN_OUTS) ? "Yes" : "No");
	mvwprintw (record_window, 9, 0, "10: Create New Playlist");
	ensure_panel_on_top(record_panel);
	redraw_screen();
}

void
KSI::record_selected (guint32 which, SelectionType type, OpType op)

{
	char buf[32];
	ARDOUR::Route *rt;
	ARDOUR::AudioTrack* at;
	bool yn;
	
	/* XXX gack, this is supposed to be private to the session */

	snprintf (buf, sizeof(buf)-1, "Audio %u", which+1);
	
	if (selection_mode() == RecordParameter) {
		rt = _record_routeptr;
	} else {
		rt = session->route_by_name (buf);
		_record_routeptr = rt;
	}
	
	if (rt == 0) {
		error << compose(_("Channel %1not found!"), which + 1) << endmsg;
		pop_selection_mode ();
		return;
	}

	if ((at = dynamic_cast<AudioTrack*>(rt)) == 0) {
		error << compose(_("Channel %1 is not an audio track"), which+1) << endmsg;
		pop_selection_mode ();
		return;
	}

	switch (type) {
	case Add:
		at->set_record_enable (true, this);
		pop_selection_mode ();
		break;

	case Replace:
		
                // JHALL: oh yeah, like THIS isn't overloading a function

		if (selection_mode() == KSI::Record) {
			update_record_display (at);
			return;
		} else if (selection_mode() != KSI::RecordParameter) {
			error << compose(_("PROGRAMMER ERROR! %1called but selection_mode is not Record or RecordParameter"), __PRETTY_FUNCTION__) << endmsg;
			return;
		}

		switch (op) {
			case increment:
				yn = true;
			break;
			case decrement:
				yn = false;
			break;
			default:
				return;
			}

		switch((which % selection_size) + 1) {
		case 1:
			at->set_active(yn);
			break;
		case 2:
			at->set_record_enable(yn, this);
			break;
		case 3:
			at->set_gain_automation_state ((yn ? Write : Off));
			break;
		case 4:
			at->set_gain_automation_state(yn ? Play : Off);
			break;
		case 5:
			at->set_phase_invert(yn, this);
			break;
		case 6:
			at->set_mute_config(ARDOUR::PRE_FADER, yn, this);
			break;
		case 7:
			at->set_mute_config(ARDOUR::POST_FADER, yn, this);
			break;
		case 8:
			rt->set_mute_config(ARDOUR::CONTROL_OUTS, yn, this);
			break;
		case 9:
			rt->set_mute_config(ARDOUR::MAIN_OUTS, yn, this);
			break;
		case 10:
			at->disk_stream().use_new_playlist();
			break;
		}
		
		break;

	case Remove:
		at->set_record_enable (false, this);
		pop_selection_mode ();
		break;
	} 
}

void
KSI::process_selection (guint32 which, SelectionType type, OpType op)
{
	RouteGroup *grp;

	grp = &selection;

	switch (selection_mode()) {
	case Group:
		grp = current_group;
		/* fallthru */
	case Route:
		route_selected (which, type, grp);
		break;

	case IO:
		io_selected (which + io_start, type);
		break;

	case Plugin:
		plugin_selected (which, type);
		break;

	case Insert:
		insert_selected (which, type);
		break;

	case RecordParameter:
	case Record:
		record_selected(route_start + which, type, op);
		break;

	case Parameter:
		/* do nothing */
		break;
	case Locations:
		location_selected ((_location_page * selection_size) + which);
		break;
		case Location:
			// do nothing
		break;
	}
}

void
KSI::transport_rewind ()
{
	float current_transport_speed;
 
       	if (session) {
		current_transport_speed = session->transport_speed();
		
		if (current_transport_speed > 0.0f) {
			/* reset to regular reverse speed */
			session->request_transport_speed (-1.0f);
		} else if (current_transport_speed == 0.0f) {
			session->request_transport_speed (-1.2f);
		} else {
			/* speed up */
//			session->request_transport_speed (current_transport_speed * 1.2f);
		session->request_transport_speed (max((double) -25, current_transport_speed - 0.25));
		}
	}
}

void
KSI::transport_forward ()
{
	float current_transport_speed;
	
	if (session) {
		current_transport_speed = session->transport_speed();
		
		if (current_transport_speed < 0.0f) {
			/* reset to regular forward speed */
			session->request_transport_speed (1.0f);
		} else if (current_transport_speed == 0.0f) {
			session->request_transport_speed (1.2f);
		} else {
			/* speed up */
//			session->request_transport_speed (current_transport_speed * 1.2f);
		session->request_transport_speed (min ((double) 25, current_transport_speed + 0.25));
		}
	}
}

int
KSI::key_press_handler (int keyboard_state, int key)
{

	double amount;
	guint32 which = 0;
	bool group = false;
	bool yn;
	bool save = false;
	OpType op = none;

        //keyboard->print_key(key);

	switch (key) {

	case 27: /* Esc */
		pop_selection_mode ();
		break;

	case 28: /* PrintScreen/SysRq */
		if ((keyboard_state & PhysicalKeyboard::Control) &&
		    (keyboard_state & PhysicalKeyboard::Shift)) {

			/* Ctrl-Alt-28: We're done */

			return 1;
		}

		totally_redraw_screen ();
		break;

	case K_ENTER:
		if (keyboard_state & PhysicalKeyboard::Shift_L) {
			/* unsolo all but current selection */
		} else if (keyboard_state & PhysicalKeyboard::Shift_R) {
			/* resolo all previously soloed */
		} else if (keyboard_state & PhysicalKeyboard::Control) {
			set_solo (false);
			save = true;
		} else {
			set_solo (true);
			save = true;
		}
		break;
		
	case ' ':
		if (session->transport_rolling()) {
			
			if (session->get_record_enabled() == false)
				session->set_auto_play (false);
			session->request_stop();
			
			if (_scrubbing) {
 				session->request_transport_speed (0.0);
				_scrubbing = false;
			}
		} else {
			if (session->get_record_enabled() == false)
			session->set_auto_play (true);		
			session->request_transport_speed (1.0);
		}
		break;

	case '\\':
		shift_selection_right ();
		break;

	case '\t':
		shift_selection_left ();
		break;

	case '[':
		if (keyboard_state & PhysicalKeyboard::Shift) {
			if (keyboard_state & PhysicalKeyboard::Control) {
				pan_left (0.25);
			} else {
				pan_hard_left ();
			} 
		} else {
			if (keyboard_state & PhysicalKeyboard::Alt) {
				pan_left (0.5);
			} else {
				pan_left (0.05);
			}
		}
		save = true;
		break;

	case ']':
		if (keyboard_state & PhysicalKeyboard::Shift) {
			if (keyboard_state & PhysicalKeyboard::Control) {
				pan_right (0.25);
			} else {
				pan_hard_right ();
			} 
		} else {
			if (keyboard_state & PhysicalKeyboard::Alt) {
				pan_right (0.5);
			} else {
				pan_right (0.05);
			}
		}
		save = true;
		break;

	/* increase the relevant channel */

	case 'q':
		which = 0;
		op = increment;
		break;
	case 'w':
		which = 1;
		op = increment;
		break;
	case 'e':
		which = 2;
		op = increment;
		break;
	case 'r':
		if (keyboard_state & PhysicalKeyboard::Alt) {
			if (keyboard_state & PhysicalKeyboard::Shift) {
				session->maybe_enable_record();
				save = true;
			} else if (keyboard_state & PhysicalKeyboard::Control) {
				session->disable_record();
				save = true;
			}
			goto save_state;
		}
		which = 3;
		op = increment;
		break;
	case 't':
		if (keyboard_state & PhysicalKeyboard::Alt) {
//			start_io_selection ();
			error << _("ERROR IO selection not possible yet.") << endmsg;
			goto save_state;
		}
		which = 4;
		op = increment;
		break;
	case 'y':
		which = 5;
		op = increment;
		break;
	case 'u':
		if (keyboard_state & PhysicalKeyboard::Alt) {
			if (keyboard_state & PhysicalKeyboard::Shift) {
				session->begin_reversible_command("SESSION::Roll");
				//session->add_undo((UndoAction)bind(slot(this, &KSI::request_transport_state),ARDOUR::Session::Stopped));
				//session->add_redo((UndoAction)bind(slot(this, &KSI::request_transport_state),ARDOUR::Session::Roll));
				session->commit_reversible_command();
				}
			} else {
		which = 6;
		op = increment;
		}
		break;
	case 'i':
		if (keyboard_state & PhysicalKeyboard::Alt) {
			inputs_selected();
			selection.clear();
			save = true;
			goto save_state;
		}
		which = 7;
		op = increment;
		break;
	case 'o':
		if (keyboard_state & PhysicalKeyboard::Alt) {
			outputs_selected();
			selection.clear();
			save = true;
			goto save_state;
		}
		which = 8;
		op = increment;
		break;
	case 'p':
		which = 9;
		op = increment;
		break;

        /* reset the relevant channel */

	case 'a':
		which = 0;
		op = reset;
		break;
	case 's':
		if (keyboard_state & PhysicalKeyboard::Alt) {
			session->save_state("default");
		} else {
		which = 1;
		op = reset;
		}
		break;
	case 'd':
		which = 2;
		op = reset;
		break;
	case 'f':
		which = 3;
		op = reset;
		break;
	case 'g':
		which = 4;
		op = reset;
		break;
	case 'h':
		op = reset;
		which = 5;
		break;
	case 'j':
		op = reset;
		which = 6;
		break;
	case 'k':
		op = reset;
		which = 7;
		break;
	case 'l':
		if (keyboard_state & PhysicalKeyboard::Alt) {
			if (keyboard_state & PhysicalKeyboard::Shift) {
				add_location();
			save = true;
			goto save_state;
			} else if (keyboard_state & PhysicalKeyboard::Control) {
				remove_location();
			save = true;
			goto save_state;
			} else {
				ensure_selection_mode(Locations);
			} // ctrl_shift
			break;
			} // alt
		op = reset;
		which = 8;
		break;

	case ';':
		op = reset;
		which = 9;
		break;

        /* decrease the relevant channel */

	case 'z':
		op = decrement;
		which = 0;
		break;
	case 'x':
		op = decrement;
		which = 1;
		break;
	case 'c':
		op = decrement;
		which = 2;
		break;
	case 'v':
		op = decrement;
		which = 3;
		break;
	case 'b':
		if (keyboard_state & PhysicalKeyboard::Alt) {
			save = false;
			goto save_state;
		}
		op = decrement;
		which = 4;
		break;
	case 'n':
		op = decrement;
		which = 5;
		break;
	case 'm':
		op = decrement;
		which = 6;
		break;
	case ',':
		op = decrement;
		which = 7;
		break;
	case '.':
		op = decrement;
		which = 8;
		break;
	case '/':
		op = decrement;
		which = 9;
		break;

	case '1':
	case '2':
	case '3':
	case '4':
	case '5':
	case '6':
	case '7':
	case '8':
	case '9':
	case '0':
		if (key > '0') {
			which = key - '1';
		} else {
			which = 9;
		}

		if (keyboard_state & PhysicalKeyboard::Alt) {
			ensure_selection_mode(Record);
		}
		if (keyboard_state & PhysicalKeyboard::Shift) {
			process_selection (which, Add, op);
		} else if (keyboard_state & PhysicalKeyboard::Control) {
			process_selection (which, Remove, op);
		} else {
			process_selection (which, Replace, op);
		}
		break;

	case K_F1:
		group_selected (1, Replace);
		break;

	case K_F2:
		group_selected (2, Replace);
		break;

	case K_F3:
		group_selected (3, Replace);
		break;

	case K_F4:
		group_selected (4, Replace);
		break;

	case K_F5:
		group_selected (5, Replace);
		break;

	case K_F6:
		group_selected (6, Replace);
		break;

	case K_F7:
		group_selected (7, Replace);
		break;

	case K_F8:
		group_selected (8, Replace);
		break;

	case K_F9:
		group_selected (9, Replace);
		break;

	case K_F10:
		group_selected (10, Replace);
		break;

	case K_F11:
		group_selected (11, Replace);
		break;

	case K_F12:
		group_selected (12, Replace);
		break;

	case K_F13:
		break;
	case K_F14:
		break;
	case K_F15:
		break;
	case K_F16:
		break;
	case K_F17:
		break;
	case K_F18:
		break;
	case K_F19:
		break;
	case K_FIND:
		break;

	case K_INSERT:
		if (keyboard_state & PhysicalKeyboard::Shift) {
			start_insert_add ();
		} else if (keyboard_state & PhysicalKeyboard::Alt) {
			start_insert_activate ();
		} else if (keyboard_state & PhysicalKeyboard::Control) {
			remove_inserts ();
		} else {
			start_insert_edit ();
		}
		break;

	case K_REMOVE:
		if (keyboard_state & PhysicalKeyboard::Control) {
			yn = false;
		} else {
			yn = true;
		}
		switch (selection_mode()) {
		case Parameter:
			set_insert_state (!yn); /* logic is reversed here */
//			pop_selection_mode ();
			break;
		case Route:
			set_mute (yn);
			save = true;
			break;
		default:
			break;
		}
		break;

	case K_PGUP:
		shift_selection_left ();
		break;

	case K_PGDN:
		shift_selection_right ();
		break;

	case K_MACRO:
		break;
	case K_HELP:
		break;
	case K_DO:
		break;
	case K_F20:
	case K_PAUSE:
		if (paused) {
			engine.start ();
			paused = false;
		} else {
			engine.stop ();
			paused = true;
		}
		break;

	case K_UP:
		if (selection_mode() == Group) {
			op = increment;
			group = true;
		}
		break;
	case K_DOWN:
		if (selection_mode() == Group) {
			op = decrement;
			group = true;
		}
		break;
	case K_LEFT:
		if (keyboard_state & PhysicalKeyboard::Alt) {
		if ((keyboard_state &PhysicalKeyboard::Control) && (keyboard_state &PhysicalKeyboard::Shift)) {
			scrub_speed -= 0.001f;
		} else if (keyboard_state & PhysicalKeyboard::Control) {
			scrub_speed -= 0.01f;
		} else if (keyboard_state & PhysicalKeyboard::Shift) {
			scrub_speed -= 0.5f;
		} else {
			scrub_speed -= 0.25f;
		}
/*
		if (scrub_speed == 1.00)  {
			_scrubbing = false;
		}
*/
/*
		if (scrub_speed <= 0) {
		scrub_speed = 0.00;
		}
*/
		if (scrub_speed < -25.00)
			scrub_speed = -25.00;
			info << compose(_("Scrubbing: %1"), scrub_speed) << endmsg;
			scrub();
		} else {
		scrub_speed = 1.0f;
/*
		_scrubbing = false;
*/
//		session->request_transport_speed (max((double) -25, session->transport_speed() - 0.25));
		transport_rewind();
		}
		break;
	case K_RIGHT:
		if (keyboard_state & PhysicalKeyboard::Alt) {
		if ((keyboard_state &PhysicalKeyboard::Control) && (keyboard_state &PhysicalKeyboard::Shift)) {
			scrub_speed += 0.001f;
		} else if (keyboard_state & PhysicalKeyboard::Control) {
			scrub_speed += 0.01f;
		} else if (keyboard_state & PhysicalKeyboard::Shift) {
			scrub_speed += 0.50f;
		} else {
			scrub_speed += 0.25f;
		}
			info << compose(_("Scrubbing: %1"), scrub_speed) << endmsg;
/*
		if (scrub_speed == 1.00)  {
			session->request_transport_speed (1.0);
			_scrubbing = false;
		}
*/
		if (scrub_speed > 25.00)
			scrub_speed = 25; // sanity
			scrub();
		} else {
		scrub_speed = 1.0f;
//		session->request_transport_speed (min ((double) 25, session->transport_speed() + 0.25));
		transport_forward();
		}
		break;
	}

	if (op == none) {
		goto save_state;
	}


	if (selection_mode() == KSI::RecordParameter) {
		process_selection(which, Replace, op);
		goto save_state;
		}

	if (selection_mode() == Route) {
		route_selected (which, Replace, &selection);
	}

	if (selection_mode() == Group || selection_mode() == Route) {
		if (keyboard_state & PhysicalKeyboard::Shift) {
			if (keyboard_state & PhysicalKeyboard::Control) {
				amount = 0.003;
			} else {
				amount = 0.10;
			}
		} else if (keyboard_state & PhysicalKeyboard::Control) {
			amount = 0.006;
		} else {
			amount = 0.014;
		}
		switch (op) {
		case reset:
			if (group) group_unity_gain ();
			else channel_unity_gain ();
			break;
		case increment:
			if (group) group_adjust_gain (amount);
			else channel_adjust_gain (amount);
			break;
		case decrement:
			amount *= -1.0;
			if (group) group_adjust_gain (amount);
			else channel_adjust_gain (amount);
			break;
		default:
			break;
		}

		save = true;

	} else if (selection_mode() == Parameter) {

		which += (parameter_page * selection_size);

		if (keyboard_state & PhysicalKeyboard::Shift) {
			if (keyboard_state & PhysicalKeyboard::Control) {
				amount = 0.05;
			} else {
				amount = 1.0;
			}
		} else if (keyboard_state & PhysicalKeyboard::Control) {
			amount = 0.5;
		} else {
			amount = 0.2;
		}

		switch (op) {
		case reset:
			adjust_parameter (which, 0.0);
			break;
		case increment:
			adjust_parameter (which, amount);
			break;
		case decrement:
			adjust_parameter (which, -amount);
			break;
		default:
			break;
		}

		save = true;
	}

  save_state:
	/*
		JHALL: temporarily deactivated because of xruns, use alt_s to 
		JHALL: force a save. SAVE DOES NOT OCCUR NATURALLY!
	if (save) {
		session->save_state ("default");
	}
*/

	return 0;
}

void
KSI::adjust_parameter (guint32 which, double amt)
{
	ARDOUR::Route *mx = get_first_selected_route ();
	LADSPA_Data value, upper, lower;
	LADSPA_PortRangeHintDescriptor hint;
	unsigned long port;
	bool port_ok;

	if (mx == 0) {
		return;
	}

	if (editing_insert == 0) {
		fatal << _("programming error: no editing insert") << endmsg;
		/*NOTREACHED*/
		return;
	}
	
	LadspaPlugin* plugin = dynamic_cast<LadspaPlugin*> (&editing_insert->plugin());

	port = plugin->nth_parameter (which, port_ok);

	if (!port_ok) {
		info << _("no such parameter") << endmsg;
		return;
	}

	value = plugin->get_parameter (port);

	hint = plugin->port_range_hints()[port].HintDescriptor;

	/* XXX how to handle unhinted ports ? */
	
	if (LADSPA_IS_HINT_TOGGLED(hint)) {

		if (amt > 0.0) {
			value = 1.0;
		} else {
			value = 0.0;
		}

	} else {

		if (LADSPA_IS_HINT_BOUNDED_ABOVE(hint)) {
			upper = plugin->port_range_hints()[port].UpperBound;
		} else {
			upper = 1.0f;
		}
		
		if (LADSPA_IS_HINT_BOUNDED_BELOW(hint)) {
			lower = plugin->port_range_hints()[port].LowerBound;
		} else {
			lower = 0.0f;
		}

		if (LADSPA_IS_HINT_SAMPLE_RATE(hint)) {
			lower *= engine.frame_rate();
			upper *= engine.frame_rate();
		}

		if (amt == 0.0) {

			/* "reset": use midpoint, since LADSPA has no defaults */
			value = lower + ((upper - lower) / 2.0);

		} else {

			if (LADSPA_IS_HINT_INTEGER(hint)) {
				if (amt > 0.0) {
					amt = 1.0;
				} else {
					amt = -1.0;
				}
			}
			
			if (LADSPA_IS_HINT_SAMPLE_RATE(hint)) {
				amt *= engine.frame_rate()/ 1000;
			}

			if (value == 0) {

				/* don't get stuck with 0 * N = 0 */

				value = amt;

			} else {

				if (LADSPA_IS_HINT_LOGARITHMIC(hint)) {
					value *= (1.0 + amt);
				} else {
					value += amt;
				}
			}

			value = min (value, upper);
			value = max (value, lower);
		}
	}

	plugin->set_parameter (port, value);
	show_parameters (parameter_page, editing_insert);
}

void
KSI::group_adjust_gain (gain_t amt)

{
	if (current_group) {
		LockMonitor lm(gain_lock, __LINE__, __FILE__);
		g_amount = amt;
		current_group->foreach_route(this, &KSI::adjust_gain);
	}
}

void
KSI::channel_adjust_gain (gain_t amt)

{
	LockMonitor lm(gain_lock, __LINE__, __FILE__);
	g_amount = amt;
	selection.foreach_route(this, &KSI::adjust_gain);
}

ARDOUR::Route *
KSI::new_route (bool want_diskstream) 
{
	ARDOUR::Route *route;
	ARDOUR::AudioTrack *track = 0;
	unsigned long nroutes = session->nroutes();
	pan_t pan_amt;

	if (want_diskstream) {
		if ((route = session->new_audio_track (1, 1)) == 0) {
			error << _("cannot create new track.") << endmsg;
			return 0;
		}

		track = dynamic_cast<AudioTrack*>(route);

	} else {
		if ((route = session->new_audio_route (1, 1)) == 0) {
			error << _("cannot create new route.") << endmsg;
			return 0;
		}
	}

	route->disconnect_outputs(this);
	route->connect_output (route->output(0), master_lr_channels[0], this);
	route->add_output_port (master_lr_channels[1], this);
	if ((nroutes + 1) % 2) {
		pan_amt = 0.0f;
	} else {
		pan_amt = 1.0f;
	}
	
	StreamPanner *panner = route->panner()[0];
	panner->set_position(pan_amt);

	if (need_control_room_outs) {
		route->connect_output (route->output (0), control_route->output (0)->name(), this);
		route->connect_output (route->output (1), control_route->output (1)->name(), this);
		route->control_outs()->panner()[0]->set_position(pan_amt);
	}
	
	paint_channel_gain (nroutes, route);
	paint_channel_mute (nroutes, route);
	paint_channel_solo (nroutes, route);
	
	if (want_diskstream) {
		paint_channel_record_enable (nroutes, track);
		track->record_enable_changed.connect (bind (slot (this, &KSI::route_record_enable_changed), track, nroutes));
	}
	route->solo_changed.connect (bind (slot (this, &KSI::route_solo_changed), route, nroutes));
	route->mute_changed.connect (bind (slot (this, &KSI::route_mute_changed), route, nroutes));
	route->gain_changed.connect (bind (slot (this, &KSI::route_gain_changed), route, nroutes));
	
	return route;
}

int
KSI::get_route_channel (ARDOUR::Route *mx)
{
	int chn;
	sscanf ((mx->name().substr (mx->name().find_last_of (' '))).c_str(), "%d", &chn);
	return chn - 1;
}
	
void
KSI::adjust_gain (ARDOUR::Route& route)
{
	gfloat current_amt = 0;
	gfloat amt_adjustment = 0;

	/* XXX fix me to use dB */
	
	/* XXX this doesn't seem right to me. but i don't have a better answer ... */
	current_amt = pow((6*log(route.gain())/log(2.0)+192)/198, 8);
	if (g_amount + current_amt <= 0) {
		amt_adjustment = 0;
	} else {
		amt_adjustment = current_amt + g_amount;
	}
	route.set_gain (pow (2,(sqrt(sqrt(sqrt(amt_adjustment)))*198-192)/6), this);
}

int
KSI::io_mode_selected(RouteGroup *trk) 
{
	ARDOUR::Route *route;
	
	if (trk->empty()) { // new route time
		if ((route = new_route()) == 0) {
			error << _("ERROR new route not added.") << endmsg;
			return -1;
		}
		_track.clear();
		_track.add(route);
	} else {
		trk->foreach_route(this, &KSI::_add_track);
	}

        update_io_display();

	return 0;
}

void
KSI::update_io_display ()
{
}

void
KSI::_add_track (ARDOUR::Route& route) 
{
	_track.add (&route);
}

void
KSI::start_input_selection ()
{
//	push_selection_mode (Output);
	io_page = 0;
	show_possible_outputs (io_page);
}

void
KSI::start_output_selection ()
{
//	push_selection_mode (Input);
	io_page = 0;
	show_possible_inputs (io_page);
}

int
KSI::show_possible_ports (int page, unsigned long flags)
{
	const char **ports = engine.get_ports ("", "", flags);
	unsigned long x, i;

	if (ports == 0) {
		return -1;
	}

	if (ports[0] == 0) {
		return -1;
	}
	
	x = page * selection_size;
	
	/* check that the page range is valid */

	for (i = 0; i < x && ports[i]; ++i);
	
	if (i != x) {
		free (ports);
		return -1;
	}
	      
	for (x = 0; (x < selection_size) && ports[x]; x++) {
		wprintw (io_window, "%d %s\n", 
			 1 + (x + (page * selection_size)),
			 ports[x]);
	}

	redraw_screen ();
	return 0;
}

int
KSI::show_possible_outputs (int page)
{
	return show_possible_ports (page, JackPortIsInput);
}

int
KSI::show_possible_inputs (int page)
{
	return show_possible_ports (page, JackPortIsInput);
}

void 
KSI::io_selected (guint32 which, SelectionType type) 
{
	switch(type) {
	case Add:
		io_selection.push_back (engine.get_nth_physical_output (which));
		break;

	case Replace:
		io_selection.clear ();
		io_selection.push_back (engine.get_nth_physical_output (which));
		break;

	case Remove:
		vector<string>::iterator i;
		string name = engine.get_nth_physical_output (which);

		if ((i = find (io_selection.begin(), io_selection.end(), name)) != io_selection.end()) {
			io_selection.erase (i);
		}

		break;
	}
	return;
}

void
KSI::inputs_selected() 
{
	LockMonitor lm(chns_lock, __LINE__, __FILE__);

	if (_track.empty()) {
		error << _("ERROR no tracks to add inputs selected.") << endmsg;
		return;
	}

	if (io_selection.empty()) {
		error << _("ERROR no channels to set inputs for tracks.") << endmsg;
		return;
	}

	_track.foreach_route(this, &KSI::_inputs_selected);
}

void
KSI::_inputs_selected(ARDOUR::Route& route)
	
{
	vector<string>::size_type n;
	vector<string>::iterator i;

	route.disconnect_inputs (this);
	
	for (i = io_selection.begin(), n = 0; i != io_selection.end(); ++i, ++n) {
		
		if (route.n_inputs() < n+1) {
			route.add_input_port (io_selection[n], this);
		} else {
			route.connect_input (route.input (n), io_selection[n], this);
		}
	}
}

void
KSI::outputs_selected() 
{
	LockMonitor lm(chns_lock, __LINE__, __FILE__);

	if (_track.empty()) {
		error << _("ERROR no tracks to add outputs selected.") << endmsg;
		return;
	}

	if (io_selection.empty()) {
		error << _("ERROR no channels to set output for tracks.") << endmsg;
		return;
	}
	
	_track.foreach_route(this, &KSI::_outputs_selected);
}

void
KSI::_outputs_selected(ARDOUR::Route& route)
{
	vector<string>::size_type n;
	vector<string>::iterator i;
pan_t x, y, z;

	route.panner()[0]->get_position (x,y,z);

	/* XXX pans may not be the correct size after the reset */

	route.disconnect_outputs (this);

	for (i = io_selection.begin(), n = 0; i != io_selection.end(); ++i, ++n) {
		
		if (route.n_outputs() < n+1) {
			route.add_output_port (io_selection[n], this);
		} else {
			route.connect_output (route.output (n), io_selection[n], this);
		}
	}

	route.panner()[0]->set_position (x,y,z);
}

void
KSI::group_selected (guint32 which, SelectionType type)

{
	char buf[16];
	RouteGroup *group = 0;
	
	sprintf (buf, "Group %u", which);
	
	if ((group = session->mix_group_by_name (buf)) != 0){
		current_group = group;
		ensure_selection_mode (Group);
		update_group_display (group);
	} else {
		error << compose(_("ERROR group %1 not found for %2"), buf, __PRETTY_FUNCTION__) << endmsg;
		}
}

void
KSI::pan_left (pan_t amount)
	
{
	if (selection_mode() == KSI::Route) {
		if (selection.empty()) {
			info << _("no selection or group") << endmsg;
			return;
		} else {
			LockMonitor lm(pan_lock, __LINE__, __FILE__);
			p_amount = amount;
			selection.foreach_route(this, &KSI::_pan_left);
		}
	} else if (selection_mode() == KSI::Group) {
		if (current_group->empty()) {
			info << _("no selection or group") << endmsg;
			return;
		} else {
			LockMonitor lm (pan_lock, __LINE__, __FILE__);
			p_amount = amount;
			current_group->foreach_route(this, &KSI::_pan_left);
		}
	} else {
		error << _("PROGRAMMING ERROR asked to pan left something that is not a group or channel") << endmsg;
	}
}

void
KSI::_pan_left(ARDOUR::Route& rt)
{
	pan_t current_x, current_y, current_z, x;
	
	rt.panner()[0]->get_position(current_x, current_y, current_z);
	x = MAX(current_x - p_amount, 0.0f);
	rt.panner()[0]->set_position(x,current_y,current_z);

	if (need_control_room_outs) {
		rt.control_outs()->panner()[0]->set_position(x,current_y,current_z);
	}
}

void
KSI::scrub()
{
	if (! session) {
		error << _("ERROR no session for scrub!") << endmsg;
		return;
	}
	if (! _scrubbing) {
		session->request_locate (session->audible_frame());
		_scrubbing = true;
	}
	session->request_transport_speed(scrub_speed);
/*
	if (selection.empty()) {
		error << _("ERROR no routes selected for scrub!") << endmsg;
		return;
	}
	
	selection.foreach_route(this, &KSI::_scrub);
*/
	return;
}

void
KSI::_scrub (ARDOUR::Route& route) 
{
	if (session) {
		session->request_transport_speed (scrub_speed);
	}
}

void
KSI::pan_right (pan_t amount)
{
	
	if (selection_mode() == KSI::Route) {
		if (selection.empty()) {
			info << _("no selection or group") << endmsg;
			return;
		} else {
			LockMonitor lm(pan_lock, __LINE__, __FILE__);
			p_amount = amount;
			selection.foreach_route(this, &KSI::_pan_right);
		}
	} else 
		if (selection_mode() == KSI::Group) {
			if (current_group->empty()) {
				info << _("no selection or group") << endmsg;
				return;
			} else {
				LockMonitor lm (pan_lock, __LINE__, __FILE__);
				p_amount = amount;
				current_group->foreach_route(this, &KSI::_pan_right);
			}
		} else {
			error << _("PROGRAMMING ERROR asked to pan right something that is not a group or channel") << endmsg;
		}
	return;
}

void
KSI::_pan_right (ARDOUR::Route& rt)
	
{
	pan_t current_x, current_y, current_z, x;

	rt.panner()[0]->get_position(current_x,current_y,current_z);

	x = MAX(p_amount+current_x,1.0f);
	rt.panner()[0]->set_position(x,current_y,current_z);
	if (need_control_room_outs) {
	rt.control_outs()->panner()[0]->set_position(x,current_y,current_z);
	}
}

void
KSI::pan_hard_right ()
	
{
	pan_right(1.0);
}

void
KSI::pan_hard_left ()
	
{
	pan_left(0.0);
}

void
KSI::set_mute (bool yn)
{
	if (selection_mode() == KSI::Route) {
		if (selection.empty()) {
			info << _("no selection or group") << endmsg;
			return;
		} else {
			LockMonitor lm(choice_lock, __LINE__, __FILE__);
			_yn = yn;
			selection.foreach_route(this, &KSI::_set_mute);
		}
	} else if (selection_mode() == KSI::Group) {
		if (current_group->empty()) {
			info << _("no selection or group") << endmsg;
			return;
		} else {
			LockMonitor lm (choice_lock, __LINE__, __FILE__);
			_yn = yn;
			current_group->foreach_route(this, &KSI::_set_mute);
		}
	} else {
		error << _("PROGRAMMING ERROR asked to mute something that is not a group or channel") << endmsg;
	}
	return;
}

void
KSI::_set_mute (ARDOUR::Route& route)
{
	route.set_mute (_yn, this);
}

void
KSI::unset_solo (ARDOUR::Route& route)
{
	route.set_solo (false, this);
}

void
KSI::set_solo (bool yn)
{
	/* semantics: if unsetting solo, apply to every route. if
	   setting solo, apply to current selection.
	*/
	
	if (!yn) {
		session->foreach_route (this, &KSI::unset_solo);
		return;
	} 
	
	if ((current_group) && (selection_mode() == KSI::Group)) {
		if (! current_group->empty()) {
			current_group->foreach_route(this, &KSI::_set_solo);
		} else {
			info << _("no selection or group") << endmsg;
			return;
		}
	} else {
		if (! selection.empty()) {
			selection.foreach_route(this, &KSI::_set_solo);
		} else {
			info << _("no selection or group") << endmsg;
			return;
		}
	}
	
}

void
KSI::_set_solo (ARDOUR::Route& rt) 
{
	rt.set_solo (true, this);
}

void
KSI::group_unity_gain ()

{
	if (current_group) {
		current_group->foreach_route(this, &KSI::unity_gain);
	}
}

void
KSI::channel_unity_gain ()
{
	selection.foreach_route(this, &KSI::unity_gain);
}

void
KSI::unity_gain (ARDOUR::Route& route)
{
	route.set_gain (1.0, 0);
}

int
KSI::show_possible_plugins (guint32 page)
	
{
	list<PluginInfo *>& plugins = PluginManager::the_manager()->ladspa_plugin_info();
	list<PluginInfo *>::iterator i;
	
	guint32 x = page * selection_size;
	
	/* count forward to the start of the requested "page" */

	for (i = plugins.begin(); ((x > 0) && (i != plugins.end())); i++, x--);

	if (i == plugins.end()) {
		info << _("no more plugins") << endmsg;
		return -1;
	}

	wclear (plugins_window);
	
	for (x = 0; ((x < selection_size) && (i != plugins.end())); i++, x++) {
		wprintw (plugins_window, "%d %s\n", 
			 1 + (x + (page * selection_size)),
			 (*i)->name.c_str());
	}

	redraw_screen ();
	return 0;
}

void
KSI::shift_selection_right ()
{
	switch (selection_mode()) {
	case Group:
		break;
		
	case IO:
/*
		if (show_possible_io (++io_page)) {
			io_page--;
		}
*/
		break;

	case RecordParameter:
// do nothing
	break;
	case Record:
	case Route:
		if ((route_start + selection_size) < session->nroutes()) {
			route_start += selection_size;
		}
		update_channel_range_display ();
		break;

	case Locations:
		_location_page ++;
		show_locations();
		break;
		
	case Location:
		// we will never have THAT many ways to change a location
		break;
		
	case Plugin:
		if (show_possible_plugins (++plugin_page)) {
			plugin_page--;
		}
		break;

	case Insert:
		if (show_inserts (++insert_page, true)) {
			insert_page--;
		}
		break;

	case Parameter:
		if (editing_insert) {
			if (show_parameters (++parameter_page, editing_insert)) {
				parameter_page--;
			}
		}
		break;
	}
}

void
KSI::shift_selection_left ()
{
	switch (selection_mode()) {
	case Group:
	case Location:
		break;

	case IO:
		if (io_start > selection_size) {
			io_start -= selection_size;
		} else {
			io_start = 0;
		}
		break;

	case RecordParameter:
// do nothing
	break;

	case Route:
	case Record:
		if (route_start > selection_size) {
			route_start -= selection_size;
		} else {
			route_start = 0;
		}
		update_channel_range_display ();
		break;
		
	case Locations:
		if (_location_page) _location_page --;
		show_locations();
	break;

	case Insert:
		if (insert_page) insert_page--;
		show_inserts (insert_page, true);
	break;

	case Plugin:
		if (plugin_page) plugin_page--;
		show_possible_plugins (plugin_page);
		break;

	case Parameter:
		if (editing_insert) {
			if (parameter_page) parameter_page--;
			show_parameters (parameter_page, editing_insert);
		}
		break;
	}
}

void
KSI::pop_selection_mode ()
{
	if (selection_mode_stack.size() > 1) {
		selection_mode_stack.pop();
	} 

	update_selection_mode_display ();
}

int
KSI::show_parameters (guint32 parameter_page, ARDOUR::PluginInsert *insert)
{
	LadspaPlugin* plugin = dynamic_cast<LadspaPlugin*> (&insert->plugin ());
	guint32 n;

	mvwprintw (insert_box, 0, 4, "insert: %s", plugin->name());
	wclear (insert_window);

	for (n = parameter_page * selection_size; n < plugin->parameter_count(); n++) {
		LADSPA_PortDescriptor pd;

		pd = plugin->port_descriptor(n);

		if (LADSPA_IS_PORT_CONTROL(pd)) {
			wprintw (insert_window, "%s = %.3f\n",
				 plugin->port_names()[n],
				 plugin->get_parameter (n));
		}
	}
	
	redraw_screen ();
	return 0;
}

ARDOUR::Route *
KSI::get_first_selected_route ()
{
	RouteGroup *grp;
	if ((selection_mode() == KSI::Group) && (current_group)) {
		grp = current_group;
	} else {
		grp = &selection;
		}

	if (grp->empty()) {
		info << _("nothing selected") << endmsg;
		return 0;
	}

	if (grp->size() > 1) {
		info << _("selection contains more than one channel") << endmsg;
		return 0;
	}

	return grp->first();
}

int
KSI::initialize_ui ()
{
	initscr();
	cbreak();
	noecho();
	nonl();
	werase (stdscr);

	message_box = newwin (4, COLS, 0, 0);
	wborder (message_box, 0, 0, 0, 0, 0, 0, 0, 0);
	mvwaddstr (message_box, 0, 4, " messages ");
//	leaveok (message_box, true);
	wnoutrefresh (message_box);
	
	message_window = derwin (message_box, 2, COLS-2, 1, 1);
	scrollok (message_window, true);
	syncok (message_window, true);
//	leaveok (message_window, true);

	channels_box = newwin (12, COLS, 4, 0);
	wborder (channels_box, 0, 0, 0, 0, 0, 0, 0, 0);
	mvwaddstr (channels_box, 0, 4, " faders ");
//	leaveok (channels_box, true);
	wnoutrefresh (channels_box);
	
	channels_window = derwin (channels_box, 10, COLS-2, 1, 1);
	syncok (channels_window, true);
//	leaveok (channels_window, true);

	record_box = newwin (12, COLS, 4, 0);
	wborder (record_box, 0, 0, 0, 0, 0, 0, 0, 0);
	mvwaddstr (record_box, 0, 4, " route ");
//	leaveok (record_box, true);
	wnoutrefresh (record_box);
	
	record_window = derwin (record_box, 10, COLS-2, 1, 1);
	syncok (record_window, true);
//	leaveok (record_window, true);
	channel_gain_unity_position = (COLS * 0.66) - 6;

	io_box = newwin (12, COLS, 4, 0);
	wborder (io_box, 0, 0, 0, 0, 0, 0, 0, 0);
	mvwaddstr (io_box, 0, 4, " I/O ");
//	leaveok (io_box, true);
	wnoutrefresh (io_box);
	
	io_window = derwin (io_box, 10, COLS-2, 1, 1);
	syncok (io_window, true);
//	leaveok (io_window, true);

	inserts_box = newwin (12, COLS, 4, 0);
	wborder (inserts_box, 0, 0, 0, 0, 0, 0, 0, 0);
	mvwaddstr (inserts_box, 0, 4, " inserts: ");
//	leaveok (inserts_box, true);
	wnoutrefresh (inserts_box);

	inserts_window = derwin (inserts_box, 10, COLS-2, 1, 1);
//	leaveok (inserts_window, true);
	syncok (inserts_window, true);

	insert_box = newwin (12, COLS, 4, 0);
	wborder (insert_box, 0, 0, 0, 0, 0, 0, 0, 0);
	mvwaddstr (insert_box, 0, 4, " insert: ");
//	leaveok (insert_box, true);
	wnoutrefresh (insert_box);

	insert_window = derwin (insert_box, 10, COLS-2, 1, 1);
//	leaveok (insert_window, true);
	syncok (insert_window, true);

	plugins_box = newwin (12, COLS, 4, 0);
	wborder (plugins_box, 0, 0, 0, 0, 0, 0, 0, 0);
	mvwaddstr (plugins_box, 0, 4, " plugins ");
//	leaveok (plugins_box, true);
	wnoutrefresh (plugins_box);

	plugins_window = derwin (plugins_box, 10, COLS-2, 1, 1);
//	leaveok (plugins_window, true);
	syncok (plugins_window, true);

	locations_box = newwin (12, COLS, 4, 0);
	wborder (locations_box, 0, 0, 0, 0, 0, 0, 0, 0);
	mvwaddstr (locations_box, 0, 4, " locations ");
//	leaveok (locations_box, true);
	wnoutrefresh (locations_box);

	locations_window = derwin (locations_box, 10, COLS-2, 1, 1);
//	leaveok (locations_window, true);
	syncok (locations_window, true);

	message_panel = new_panel (message_box);
	route_panel = new_panel (channels_box);
	io_panel = new_panel (io_box);
	inserts_panel = new_panel (inserts_box);
	insert_panel = new_panel (insert_box);
	plugins_panel = new_panel (plugins_box);
	locations_panel = new_panel (locations_box);
	record_panel = new_panel(record_box);

	lower_window = newwin (LINES-16, COLS, 16, 0);
	lower_panel = new_panel (lower_window);
//	leaveok(lower_window, true);

	show_panel (lower_panel);
	show_panel (message_panel);

	status_box = derwin (lower_window, 3, COLS, 0, 0);
	wborder (status_box, 0, 0, 0, 0, 0, 0, 0, 0);
//	leaveok (status_box, true);
	wnoutrefresh (status_box);

	status_window = derwin (status_box, 1, COLS-2, 1, 1);
//	leaveok (status_window, true);
	syncok (status_window, true);

	my_top_panel = 0;
	ensure_panel_on_top (route_panel);

	return 0;
}

void
KSI::paint_channel_gain (unsigned long chn, ARDOUR::Route *mx)
{

	int xpos;

	chn %= KSI::selection_size;
	
	/* unity gain roughly 2/3 of the way across the screen */
	
	xpos = 7 + (int) floor (mx->gain() * channel_gain_unity_position);
	
	wmove (channels_window, chn, 7);
	wclrtoeol (channels_window);
	if (xpos > COLS - 4) {
		mvwaddch (channels_window, chn, COLS-4, '>');
	} else {
		mvwaddch (channels_window, chn, xpos, ACS_BLOCK);
	}
	redraw_screen ();
}

void
KSI::receive (Transmitter::Channel chn, const char *str)
{
	if (display_is_remote) {
		/* duplicate the error message on stderr */
		cerr << str << endl;
	}

	waddch (message_window, '\n');
	waddstr (message_window, str);
	redraw_screen ();
}

void
KSI::update_selection_mode_display ()
{
	wmove (status_window, 0, 0);
	wclrtoeol (status_window);

	switch (selection_mode()) {
	case KSI::Plugin:
		mvwaddstr (status_window, 0, 0, "Mode: plugin");
		ensure_panel_on_top (plugins_panel);
		break;
	case KSI::Route:
		update_selection_display(&selection);
		mvwaddstr (status_window, 0, 0, "Mode: channel");
		ensure_panel_on_top (route_panel);
		break;
	case KSI::RecordParameter:
		mvwaddstr (status_window, 0, 0, "Mode: recordParameter");
		ensure_panel_on_top (record_panel);
		break;
	case KSI::Record:
		update_selection_display(&selection);
		mvwaddstr (status_window, 0, 0, "Mode: record");
		ensure_panel_on_top (route_panel);
		break;
	case KSI::Group:
		update_group_content_display (current_group);
		mvwaddstr (status_window, 0, 0, "Mode: group");
		ensure_panel_on_top (route_panel);
		break;
	case KSI::Parameter:
		mvwaddstr (status_window, 0, 0, "Mode: parameter");
		ensure_panel_on_top (insert_panel);
		break;
	case KSI::Insert:
		update_insert_display ();
		mvwaddstr (status_window, 0, 0, "Mode: insert");
		ensure_panel_on_top (inserts_panel);
		break;
	case KSI::IO:
		update_selection_display(&selection);
		mvwaddstr(status_window, 0, 0, "Mode: io");
		ensure_panel_on_top(io_panel);
		break;
	case KSI::Locations:
		show_locations();
		mvwaddstr(status_window, 0, 0, "Mode: locations");
		ensure_panel_on_top(locations_panel);
		break;
	case KSI::Location:
		update_current_location_display();
		mvwaddstr(status_window, 0, 0, "Mode: location");
		ensure_panel_on_top(locations_panel);
		break;
	}
	
	redraw_screen ();
}

void
KSI::_location_selected (Locations::LocationList& locations)
{
	Locations::LocationList::iterator i;
	ARDOUR::Location *location;

        /* move through to the correct location */
	for (i = locations.begin(); ((i != locations.end()) && (_location_cnt > 0)); i++, _location_cnt--);

	if (i == locations.end()) {
		error << _("ERROR location not found") << endmsg;
		return;
	}

	location = *i;
	session->locations()->set_current(location, false);
	session->request_locate(location->start());
	ensure_selection_mode(Location);
}

void
KSI::update_locations_display (Locations::LocationList& locations)
{

	Locations::LocationList::iterator i;
	ARDOUR::Location *location;
	guint32 n; 
	guint32 x = (selection_size * _location_page);

	werase(locations_window);

        /* move through to the correct location */
	for (i = locations.begin(); ((i != locations.end()) && (x > 0)); i++, x--);

	if (i == locations.end()) {
		error << _("no more locations to display") << endmsg;
		return;
	}

	for (n = 0; ((i != locations.end()) && (n < selection_size)); n++, i++) {

		location = *i;

		mvwprintw(locations_window, n, 0, "%3d %s", (_location_page * selection_size) + n + 1, location->name().c_str());
	}

	wclrtobot(locations_window);
	redraw_screen();
	return;
}

void
KSI::update_channel_range_display ()
{

	guint32 i, x;

	/* relabel channel columns */

	for (x = 0, i = route_start; x < selection_size; i++, x++) {
		if (session)
		if (i == session->nroutes()) {
			wclrtobot(channels_window);
			break;
		}
		mvwprintw (channels_window, x, 0, "%3d", i+1);
	}
	redraw_screen ();
}

void
KSI::paint_channel_mute (unsigned long chn, ARDOUR::Route *mx)
{

	chn %= KSI::selection_size;

	if (mx->muted()) {
		mvwaddch (channels_window, chn, 5, 'M');
	} else {
		mvwaddch (channels_window, chn, 5, ' ');
	}
	redraw_screen ();
}

void
KSI::paint_channel_solo (unsigned long chn, ARDOUR::Route *mx)
{

	chn %= KSI::selection_size;

	if (mx->soloed()) {
		mvwaddch (channels_window, chn, 6, 'S');
	} else {
		mvwaddch (channels_window, chn, 6, ' ');
	}
	redraw_screen ();
}

void
KSI::paint_channel_record_enable (unsigned long chn, ARDOUR::AudioTrack* track)
{

	chn %= KSI::selection_size;

	if (track->record_enabled()) {
		mvwaddch (channels_window, chn, 7, 'R');
	} else {
		mvwaddch (channels_window, chn, 7, ' ');
	}
	redraw_screen ();
}

void
KSI::update_selection_display(RouteGroup *grp)
{

	wmove (status_window, 0, 15);
	wclrtoeol (status_window);
	mvwaddstr (status_window, 0, 18, " Selection: ");
	if (! grp->empty()) {
		LockMonitor lm (selection_lock, __LINE__, __FILE__);
	selection_cnt = 0;
		grp->foreach_route(this, &KSI::_update_selection_display);
		selection_cnt = 0;
	}
	
	update_transport_state_display ();

	redraw_screen ();
}

void
KSI::_update_selection_display (ARDOUR::Route& rt)
{
	if (selection_cnt > 0) {
		waddstr (status_window, "/");
	}
	waddstr (status_window, rt.name().c_str());
	selection_cnt ++;
}

void
KSI::update_insert_display ()
{
	show_inserts (insert_page, true);
}

void
KSI::update_group_content_display (RouteGroup *grp)
{
	wmove (status_window, 0, 25);
	wclrtoeol (status_window);
	wmove (status_window, 0, 25);

	if (! grp->empty()) {
		LockMonitor lm (group_lock, __LINE__, __FILE__);
		group_cnt = 0;
		grp->foreach_route(this, &KSI::_update_group_content_display);
		group_cnt = 0;
	} else {
		waddstr (status_window, "empty");
	}

	redraw_screen ();
}

void
KSI::_update_group_content_display (ARDOUR::Route& rt)
{
	if (group_cnt > 0) {
		waddstr (status_window, "/");
	}
	waddstr (status_window, rt.name().c_str());
	group_cnt ++;
}

void
KSI::update_group_display (RouteGroup *grp)
{
	wmove (status_window, 0, 15);
	wclrtoeol (status_window);
	waddstr (status_window, grp->name().c_str());
	waddstr (status_window, ": ");
	update_group_content_display (grp);
}

void
KSI::update_transport_state_display ()
{
	if (session == 0) {
		return;
	}

	mvwaddstr (status_window,0, 40, "Rec: ");
	mvwaddstr (status_window, 0, 45, session->get_record_enabled() ? "ON " : "off");
	
	if (session->transport_stopped()) { 
		mvwaddstr (status_window, 0, 50, "Stopped");
	} else if (session->transport_rolling()) {
		mvwaddstr (status_window, 0, 50, "Rolling");
	} else if (session->transport_speed() > 1.0) {
		mvwaddstr (status_window, 0, 50, "Forwarding");
	} else {
		mvwaddstr (status_window, 0, 50, "Rewinding");
	}
	
	redraw_screen ();
}

void
KSI::add_group(RouteGroup *rg) 
{
	rg->changed.connect (bind(slot (this, &KSI::update_group_display), rg));
	groups.push_back(rg);
}

void
KSI::remove_group (RouteGroup *n) 
{
	list <RouteGroup *>::iterator i;
	for (i = groups.begin(); i != groups.end(); i++) {
		if (*i == n) {
			groups.erase(i);
		}
	}
}

void
KSI::ensure_panel_on_top (PANEL *p)
{
	if (my_top_panel) {
		hide_panel (p);
	}

	show_panel (p);
	top_panel (p);
	my_top_panel = p;

	redraw_screen ();
}

void
KSI::redraw_screen (bool wait, bool hide_cursor)
{
	int x, y;
	PBD::TentativeLockMonitor tm(screen_lock, __LINE__, __FILE__);
	x = y = 0;
	if (!tm.locked()) {
		if (wait) {
			PBD::LockMonitor lm(screen_lock, __LINE__, __FILE__);
			if (hide_cursor)
				getsyx (y, x);
			update_panels ();
			if (hide_cursor)
				setsyx (y, x);
			doupdate ();
			} else {
			return;
		}
	} else {
		if (hide_cursor)
			getsyx (y, x);
		update_panels ();
		if (hide_cursor)
			setsyx (y, x);
		doupdate ();
	}

}

void
KSI::totally_redraw_screen (void)

{
	PBD::LockMonitor lm (screen_lock, __LINE__, __FILE__);
	clearok (stdscr, 1);
	update_panels ();
	wrefresh (stdscr);
}

void
KSI::push_selection_mode (SelectionMode m)

{
	selection_mode_stack.push (m);
	update_selection_mode_display ();
}

int
KSI::tcp_connect (const string& host, int port)
{
	int fd;
	struct sockaddr_in addr;
	struct hostent *hp;

	if ((hp = gethostbyname (host.c_str())) == 0) {
		error << compose(_("cannot get host by name for %1%2"), host, strerror (errno)) << endmsg;
		return -1;
	}

	if ((fd = socket (AF_INET, SOCK_STREAM, 6)) < 0) {
		error << _("cannot create server socket") << endmsg;
		return -1;
	}

	addr.sin_family = AF_INET;
	addr.sin_port = htons(port);
	memcpy((char *)&addr.sin_addr, hp->h_addr, hp->h_length);

	if (connect (fd, (struct sockaddr *) &addr, sizeof (addr)) < 0) {
		error << compose(_("cannot connect to %1:%2 (%3)"), host, port, strerror (errno)) << endmsg;
		close (fd);
		return -1;
	}

	if (fcntl (fd, F_SETFL, O_NONBLOCK) < 0) {
		error << compose(_("cannot set nonblocking mode on socket (%1)"), strerror (errno)) << endmsg;
		close (fd);
		return -1;
	}

	return fd;
}

void
KSI::start_butler_thread (void)
{
	pthread_t thread;
	
	{
		PBD::LockMonitor lm(butler_lock, __LINE__, __FILE__);
		if (pthread_create (&thread, 0, KSI::butler_thread, this)) {
			error << _("ERROR cannot create ksi's butler thread") << endmsg;
			butler_lock.unlock();
			return;
		}
		pthread_cond_wait(&cond, butler_lock.mutex());
	}
	pthread_detach (thread);
	return;
}
