// cae.cpp
//
// The Core Audio Engine component of Rivendell
//
//   (C) Copyright 2002-2015 Fred Gleason <fredg@paravelsystems.com>
//
//   This program is free software; you can redistribute it and/or modify
//   it under the terms of the GNU General Public License version 2 as
//   published by the Free Software Foundation.
//
//   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.
//


#include <qapplication.h>
#include <qobject.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <pwd.h>
#include <grp.h>
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
#include <string.h>
#include <math.h>
#include <ctype.h>
#include <errno.h>
#include <dlfcn.h>

#include <qdir.h>
#include <qsqldatabase.h>
#include <qsessionmanager.h>

#include <rdsocket.h>
#include <rdconf.h>
#include <rdcheck_daemons.h>
#include <rddb.h>
#include <rdescape_string.h>
#include <rddebug.h>
#include <rdcmd_switch.h>
#include <rdsvc.h>
#include <rdsystem.h>

#include <cae_socket.h>
#include <cae.h>

volatile bool exiting=false;
RDConfig *rd_config;
#ifdef JACK
extern jack_client_t *jack_client;
#endif  // JACK

#define PRINT_COMMANDS

#ifndef HAVE_SRC_CONV
void src_int_to_float_array (const int *in, float *out, int len)
{
  for(int i=0;i<len;i++) {
    out[i]=(double)in[i]/2147483648.0;
  }
}


void src_float_to_int_array (const float *in, int *out, int len)
{
  for(int i=0;i<len;i++) {
    out[i]=(int)(in[i]*2147483648.0);
  }
}
#endif  // HAVE_SRC_CONV


void SigHandler(int signum)
{
  switch(signum) {
      case SIGINT:
      case SIGTERM:
      case SIGHUP:
	exiting=true;
	break;
  }
}


MainObject::MainObject(QObject *parent,const char *name)
  :QObject(parent,name)
{
  //
  // Read Command Options
  //
  RDCmdSwitch *cmd=
    new RDCmdSwitch(qApp->argc(),qApp->argv(),"caed",CAED_USAGE);
  delete cmd;

  //
  // LogLine references rd_config
  // 
  rd_config=new RDConfig(RD_CONF_FILE);
  rd_config->load();
  rd_config->setModuleName("caed");

  //
  // Make sure we're the only instance running
  //
  if(CheckDaemon(RD_CAED_PID)) {
    rd_config->log("caed",RDConfig::LogErr,
		   "ERROR caed aborting - multiple instances not allowed");
    exit(1);
  }

  //
  // Initialize Data Structures
  //
  debug=false;
  twolame_handle=NULL;
  mad_handle=NULL;
  if(qApp->argc()>1) {
    for(int i=1;i<qApp->argc();i++) {
      if(!strcmp(qApp->argv()[i],"-d")) {
	debug=true;
      }
    }
  }
  for(int i=0;i<256;i++) {
    play_handle[i].card=-1;
    play_handle[i].stream=-1;
    play_handle[i].owner=-1;
  }
  next_play_handle=0;
  for(int i=0;i<CAE_MAX_CONNECTIONS;i++) {
    socket[i]=NULL;
    meter_port[i]=0;
    istate[i]=0;
    argnum[i]=0;
    argptr[i]=0;
    auth[i]=false;
    for(int j=0;j<RD_MAX_CARDS;j++) {
      update_meters[j][i]=false;
    }
  }
  for(int i=0;i<RD_MAX_CARDS;i++) {
    cae_driver[i]=RDStation::None;
    for(int j=0;j<RD_MAX_STREAMS;j++) {
      record_length[i][j]=0;
      record_threshold[i][j]=-10000;
      record_owner[i][j]=-1;
      play_owner[i][j]=-1;
      play_length[i][j]=0;
      play_speed[i][j]=100;
      play_pitch[i][j]=false;
      for(int k=0;k<RD_MAX_PORTS;k++) {
	output_status_flag[i][k][j]=false;
      }
#ifdef HAVE_TWOLAME
      twolame_lameopts[i][j]=NULL;
#endif  // HAVE_TWOLAME
#ifdef HAVE_MAD
      mad_active[i][j]=false;
#endif  // HAVE_MAD
    }
  }
#ifdef JACK
  jack_client=NULL;
#endif  // JACK

  server=new CaeSocket(CAED_TCP_PORT,0,this,"socket");
  if(!server->ok()) {
    rd_config->log("caed",RDConfig::LogErr,
		   "ERROR caed aborting - CaeSocket() server not ok");
    exit(1);
  }
  connect(server,SIGNAL(connection(int)),this,SLOT(newConnection(int)));

  if(!debug) {
    RDDetach(rd_config->logCoreDumpDirectory());
  }

  signal(SIGHUP,SigHandler);
  signal(SIGINT,SigHandler);
  signal(SIGTERM,SigHandler);

  if(!RDWritePid(RD_PID_DIR,"caed.pid",rd_config->uid())) {
    rd_config->log("caed",RDConfig::LogErr,"can't write pid file");
    fprintf(stderr,"caed: can't write pid file\n");
    exit(1);
  }

  //
  // Allowcate Meter Socket
  //
  meter_socket=new Q3SocketDevice(Q3SocketDevice::Datagram);

  //
  // Open Database
  //
  QSqlDatabase db=QSqlDatabase::addDatabase(rd_config->mysqlDriver());
  db.setDatabaseName(rd_config->mysqlDbname());
  db.setUserName(rd_config->mysqlUsername());
  db.setPassword(rd_config->mysqlPassword());
  db.setHostName(rd_config->mysqlHostname());
  if(!db.open()) {
    rd_config->log("caed",RDConfig::LogErr,"unable to connect to mySQL Server");
    printf("caed: unable to connect to mySQL Server");
    exit(1);
  }

  //
  // Provisioning
  //
  InitProvisioning();

  //
  // Start Up the Drivers
  //
  cae_station=new RDStation(rd_config->stationName());
  RDSystem *sys=new RDSystem();
  system_sample_rate=sys->sampleRate();
  delete sys;
  hpiInit(cae_station);
  alsaInit(cae_station);
  jackInit(cae_station);
  ClearDriverEntries(cae_station);

  //
  // Probe Capabilities
  //
  ProbeCaps(cae_station);

  //
  // Close Database Connection
  //
  cae_station->setScanned(true);
  /*
  delete cae_station;
  cae_station=NULL;
  db->removeDatabase(rd_config->mysqlDbname());
  */
  //
  // Initialize Mixers
  //
  InitMixers();

  //
  // Meter Update Timer
  //
  QTimer *timer=new QTimer(this,"meter_update_timer");
  connect(timer,SIGNAL(timeout()),this,SLOT(updateMeters()));
  timer->start(RD_METER_UPDATE_INTERVAL);

  //
  // Initialize Thread Priorities
  //
  bool jack_running=false;
  int sched_policy=SCHED_OTHER;
  struct sched_param sched_params;
  int result = 0;
  memset(&sched_params,0,sizeof(struct sched_param));
#ifdef JACK
  if(jack_client!=NULL) {
    pthread_getschedparam(jack_client_thread_id(jack_client),&sched_policy,
			  &sched_params);
#ifdef ALSA
    for(int i=0;i<RD_MAX_CARDS;i++) {
      if(cae_driver[i]==RDStation::Alsa) {
	if (!alsa_play_format[i].exiting) {
	  int r = pthread_setschedparam(alsa_play_format[i].thread,sched_policy,
					&sched_params);
	  if (r) {
	    result = r;
	  }	
	}
	if (!alsa_capture_format[i].exiting) {
	  int r = pthread_setschedparam(alsa_capture_format[i].thread,sched_policy,
					&sched_params);
	  if (r) {
	    result = r;
	  }
	}
      }
    }
#endif  // ALSA
    jack_running=true;
  }
#endif  // JACK
  if(rd_config->useRealtime()) {
    if(!jack_running) {
      sched_params.sched_priority=rd_config->realtimePriority();
    }
    sched_policy=SCHED_FIFO;
#ifdef ALSA
    for(int i=0;i<RD_MAX_CARDS;i++) {
      if(cae_driver[i]==RDStation::Alsa) {
	if (!alsa_play_format[i].exiting) {
	  int r = pthread_setschedparam(alsa_play_format[i].thread,sched_policy,
					&sched_params);
	  if (r) {
	    result = r;
	  }
	}
	if (!alsa_capture_format[i].exiting) {
	  int r = pthread_setschedparam(alsa_capture_format[i].thread,sched_policy,
					&sched_params);
	  if (r) {
	    result = r;
	  }
	}
      }
    }
#endif  // ALSA
    if(sched_params.sched_priority>sched_get_priority_min(sched_policy)) {
      sched_params.sched_priority--;
    }
    int r = pthread_setschedparam(pthread_self(),sched_policy,&sched_params);
    if (r) {
      result = r;
    }
    mlockall(MCL_CURRENT|MCL_FUTURE);
    if (result){
          rd_config->log("caed",RDConfig::LogWarning,QString().
		  sprintf("Unable to set realtime scheduling: %s",
			  strerror (result)));
    } else {
    rd_config->log("caed",RDConfig::LogInfo,QString().
	    sprintf("using realtime scheduling, priority=%d",
		    sched_params.sched_priority));
    }
  }

  //
  // Relinquish Root Permissions (if present)
  //
/*
  if(getuid()==0) {
    if(setuid(rd_config->uid())<0) {
      perror("cae");
      exit(1);
    }
//  if(setegid(rd_config->gid())<0) {
//    perror("cae");
//    exit(1);
//  }
  }
*/
  if(rd_config->enableMixerLogging()) {
    rd_config->log("caed",RDConfig::LogNotice,"mixer logging enabled");
  }
  rd_config->log("caed",RDConfig::LogInfo,"cae started");
}


MainObject::~MainObject() {
  delete server;
}

void MainObject::newConnection(int fd)
{
  int i=0;

  while((i<CAE_MAX_CONNECTIONS)&&(socket[i]!=NULL)) {
    i++;
  }
  if(i==CAE_MAX_CONNECTIONS) {      // Table full, drop it on the floor
    rd_config->log("caed",RDConfig::LogErr,"connection refused due to full socket table");
    close(fd);
    return;
  }
  socket[i]=new RDSocket(i,this);
  socket[i]->setSocket(fd);
  connect(socket[i],SIGNAL(readyReadID(int)),this,SLOT(socketData(int)));
  connect(socket[i],SIGNAL(connectionClosedID(int)),
	  this,SLOT(socketKill(int)));
}


void MainObject::socketData(int ch)
{
  ParseCommand(ch);
}


void MainObject::socketKill(int ch)
{
  KillSocket(ch);
}


void MainObject::statePlayUpdate(int card,int stream,int state)
{
  int handle=GetHandle(card,stream);

  if(handle<0) {
    return;
  }
  if(play_owner[card][stream]!=-1) {
    switch(state) {
	case 1:   // Playing
	  EchoCommand(play_owner[card][stream],(const char *)QString().
		      sprintf("PY %d %d %d +!",handle,
			      play_length[card][stream],
			      play_speed[card][stream]));
	  break;
	case 2:   // Paused
	  EchoCommand(play_owner[card][stream],(const char *)QString().
		      sprintf("SP %d +!",handle));
	  break;
	case 0:   // Stopped
	  EchoCommand(play_owner[card][stream],(const char *)QString().
		      sprintf("SP %d +!",handle));
	  break;
    }
  }
}


void MainObject::stateRecordUpdate(int card,int stream,int state)
{
  if(record_owner[card][stream]!=-1) {
    switch(state) {
	case 0:    // Recording
	  EchoCommand(record_owner[card][stream],(const char *)QString().
		      sprintf("RD %d %d %d %d +!",card,stream,
			      record_length[card][stream],
			      record_threshold[card][stream]));
	  break;

	case 4:    // Record Started
	  EchoCommand(record_owner[card][stream],(const char *)QString().
		      sprintf("RS %d %d +!",card,stream));
	  break;

	case 2:    // Paused
	case 3:    // Stopped
	  EchoCommand(record_owner[card][stream],(const char *)QString().
		      sprintf("SR %d %d +!",card,stream));
	  break;
    }
  }
}


void MainObject::updateMeters()
{
  short levels[2];
  unsigned positions[RD_MAX_STREAMS];

  if(exiting) {
    jackFree();
    alsaFree();
    hpiFree();
    RDDeletePid(RD_PID_DIR,"caed.pid");
    rd_config->log("caed",RDConfig::LogInfo,"cae exiting");
    exit(0);
  }

  AlsaClock();
  JackClock();

  for(int i=0;i<RD_MAX_CARDS;i++) {
    switch(cae_driver[i]) {
	case RDStation::Hpi:
	  for(int j=0;j<RD_MAX_PORTS;j++) {
	    if(hpiGetInputStatus(i,j)!=port_status[i][j]) {
	      port_status[i][j]=hpiGetInputStatus(i,j);
	      if(port_status[i][j]) {
		BroadcastCommand(QString().sprintf("IS %d %d 0!",i,j));
	      }
	      else {
		BroadcastCommand(QString().sprintf("IS %d %d 1!",i,j));
	      }
	    }
	    if(hpiGetInputMeters(i,j,levels)) {
	      SendMeterLevelUpdate("I",i,j,levels);
	    }
	    if(hpiGetOutputMeters(i,j,levels)) {
	      SendMeterLevelUpdate("O",i,j,levels);
	    }      
	  }
	  hpiGetOutputPosition(i,positions);
	  SendMeterPositionUpdate(i,positions);
	  for(int j=0;j<RD_MAX_STREAMS;j++) {
	    if(hpiGetStreamOutputMeters(i,j,levels)) {
	      SendStreamMeterLevelUpdate(i,j,levels);
	    }      
	  }
	  break;

	case RDStation::Jack:
	  for(int j=0;j<RD_MAX_PORTS;j++) {
	    if(jackGetInputStatus(i,j)!=port_status[i][j]) {
	      port_status[i][j]=!port_status[i][j];
	      BroadcastCommand(QString().sprintf("IS %d %d %d",i,j,
						 port_status[i][j]));
	    }
	    if(jackGetInputMeters(i,j,levels)) {
	      SendMeterLevelUpdate("I",i,j,levels);
	    }
	    if(jackGetOutputMeters(i,j,levels)) {
	      SendMeterLevelUpdate("O",i,j,levels);
	    }
	  }
	  jackGetOutputPosition(i,positions);
	  SendMeterPositionUpdate(i,positions);
	  for(int j=0;j<RD_MAX_STREAMS;j++) {
	    if(jackGetStreamOutputMeters(i,j,levels)) {
	      SendStreamMeterLevelUpdate(i,j,levels);
	    }      
	  }
	  break;

	case RDStation::Alsa:
	  for(int j=0;j<RD_MAX_PORTS;j++) {
	    if(alsaGetInputStatus(i,j)!=port_status[i][j]) {
	      port_status[i][j]=!port_status[i][j];
	      BroadcastCommand(QString().sprintf("IS %d %d %d",i,j,
						 port_status[i][j]));
	    }
	    if(alsaGetInputMeters(i,j,levels)) {
	      SendMeterLevelUpdate("I",i,j,levels);
	    }
	    if(alsaGetOutputMeters(i,j,levels)) {
	      SendMeterLevelUpdate("O",i,j,levels);
	    }
	  }
	  alsaGetOutputPosition(i,positions);
	  SendMeterPositionUpdate(i,positions);
	  for(int j=0;j<RD_MAX_STREAMS;j++) {
	    if(alsaGetStreamOutputMeters(i,j,levels)) {
	      SendStreamMeterLevelUpdate(i,j,levels);
	    }      
	  }
	  break;

	case RDStation::None:
	  break;
    }
    //
  }
  //  SendMeterOutputStatusUpdate();
}


void MainObject::InitProvisioning() const
{
  QString sql;
  RDSqlQuery *q;
  QString err_msg;

  //
  // Provision a Host
  //
  if(rd_config->provisioningCreateHost()) {
    if(!rd_config->provisioningHostTemplate().isEmpty()) {
      sql=QString("select NAME from STATIONS where ")+
	"NAME=\""+RDEscapeString(rd_config->stationName())+"\"";
      q=new RDSqlQuery(sql);
      if(!q->first()) {
	if(RDStation::create(rd_config->stationName(),&err_msg,rd_config->provisioningHostTemplate(),rd_config->provisioningHostIpAddress())) {
	  syslog(LOG_INFO,"created new host entry \"%s\"",
		 (const char *)rd_config->stationName());
	  if(!rd_config->provisioningHostShortName(rd_config->stationName()).
	     isEmpty()) {
	    RDStation *station=new RDStation(rd_config->stationName());
	    station->setShortName(rd_config->
		     provisioningHostShortName(rd_config->stationName()));
	    delete station;
	  }
	}
	else {
	  fprintf(stderr,"caed: unable to provision host [%s]\n",
		  (const char *)err_msg);
	  exit(256);
	}
      }
      delete q;
    }
  }

  //
  // Provision a Service
  //
  if(rd_config->provisioningCreateService()) {
    if(!rd_config->provisioningServiceTemplate().isEmpty()) {
      QString svcname=
	rd_config->provisioningServiceName(rd_config->stationName());
      sql=QString("select NAME from SERVICES where ")+
	"NAME=\""+RDEscapeString(svcname)+"\"";
      q=new RDSqlQuery(sql);
      if(!q->first()) {
	if(RDSvc::create(svcname,&err_msg,
			 rd_config->provisioningServiceTemplate(),rd_config)) {
	  syslog(LOG_INFO,"created new service entry \"%s\"",
		 (const char *)svcname);
	}
	else {
	  fprintf(stderr,"caed: unable to provision service [%s]\n",
		  (const char *)err_msg);
	  exit(256);
	}
      }
      delete q;
    }
  }
}


void MainObject::InitMixers()
{
  for(int i=0;i<RD_MAX_CARDS;i++) {
    switch(cae_driver[i]) {
	case RDStation::Hpi:
	  for(int j=0;j<RD_MAX_PORTS;j++) {
	    for(int k=0;k<RD_MAX_PORTS;k++) {
	      hpiSetPassthroughLevel(i,j,k,RD_MUTE_DEPTH);
	    }
	  }
	  break;

	case RDStation::Jack:
	  for(int j=0;j<RD_MAX_PORTS;j++) {
	    for(int k=0;k<RD_MAX_PORTS;k++) {
	      jackSetPassthroughLevel(i,j,k,RD_MUTE_DEPTH);
	    }
	  }
	  break;

	case RDStation::Alsa:
	  for(int j=0;j<RD_MAX_PORTS;j++) {
	    for(int k=0;k<RD_MAX_PORTS;k++) {
	      alsaSetPassthroughLevel(i,j,k,RD_MUTE_DEPTH);
	    }
	  }
	  break;

	case RDStation::None:
	  break;
    }
  }
}


void MainObject::ParseCommand(int ch)
{
  char buf[256];
  int c;

  while((c=socket[ch]->readBlock(buf,256))>0) {
    buf[c]=0;
    // rd_config->log("caed",QString().sprintf("RCVD: %s",buf));
    for(int i=0;i<c;i++) {
      if(buf[i]==' ') {
	if(argnum[ch]<CAE_MAX_ARGS) {
	  args[ch][argnum[ch]][argptr[ch]]=0;
	  argnum[ch]++;
	  argptr[ch]=0;
	}
	else {
	  if(debug) {
	    printf("Argument list truncated!\n");
	  }
	}
      }
      if(buf[i]=='!') {
	args[ch][argnum[ch]++][argptr[ch]]=0;
	DispatchCommand(ch);
	argnum[ch]=0;
	argptr[ch]=0;
	if(socket[ch]==NULL) {
	  return;
	}
      }
      if((isgraph(buf[i]))&&(buf[i]!='!')) {
	if(argptr[ch]<CAE_MAX_LENGTH) {
	  args[ch][argnum[ch]][argptr[ch]]=buf[i];
	  argptr[ch]++;
	}
	else {
	  if(debug) {
	    printf("WARNING: argument truncated!\n");
	  }
	}
      }
    }
  }
}


void MainObject::DispatchCommand(int ch)
{
  int card=-1;
  int stream=-1;
  int port=0;
  int in_port=0;
  int out_port=0;
  int new_stream=-1;
  int pos=0;
  int coding=0;
  int sample_rate=0;
  int channels=0;
  int bit_rate=0;
  int flag=0;
  int level=0;
  int length=0;
  int mode=0;
  int type=0;
  QString wavename;
  char temp[256];
  int handle;
  QString in_jport;
  QString out_jport;

#ifdef PRINT_COMMANDS
  printf("CAE: connection %d receiving ",ch);
  for(int i=0;i<argnum[ch];i++) {
    printf("%s ",args[ch][i]);
  }
  printf("\n");
#endif  // PRINT_COMMANDS

  //
  // Calculate Card and Stream Numbers
  //
  sscanf(args[ch][1],"%d",&card);
  sscanf(args[ch][2],"%d",&stream);

/*
  printf("RECEIVED:");
  for(int i=0;i<argnum[ch];i++) {
    printf(" %s",args[ch][i]);
  }
  printf("\n");
*/

  //
  // Common Commands
  // Authentication not required to execute these!
  //
  if(!strcmp(args[ch][0],"DC")) {  // Drop Connection
    socket[ch]->close();
    KillSocket(ch);
    return;
  }
  if(!strcmp(args[ch][0],"PW")) {  // Password Authenticate
    if(!strcmp(args[ch][1],rd_config->password())) {
      auth[ch]=true;
      EchoCommand(ch,"PW +!");
      return;
    }
    else {
      auth[ch]=false;
      EchoCommand(ch,"PW -!");
      return;
    }
  }

  //
  // Priviledged Commands
  // Authentication required to execute these!
  //
  if(!auth[ch]) {
    EchoArgs(ch,'-');
    return;
  }

  if(!strcmp(args[ch][0],"LP")) {  // Load Playback
    if(card<0) {
      sprintf(temp,"LP %d %s -1 -1 -!",card,args[ch][2]);
      EchoCommand(ch,temp);
      return;
    }
    wavename = rd_config->audioFileName (QString(args[ch][2]));
    switch(cae_driver[card]) {
	case RDStation::Hpi:
	  if(!hpiLoadPlayback(card,wavename,&new_stream)) {
	    sprintf(temp,"LP %d %s -1 -1 -!",card,args[ch][2]);
	    EchoCommand(ch,temp);
	    rd_config->log("caed",RDConfig::LogErr,
		    QString().sprintf("unable to allocate stream for card %d",
				      card));
	    return;
	  }
	  break;

	case RDStation::Alsa:
	  if(!alsaLoadPlayback(card,wavename,&new_stream)) {
	    sprintf(temp,"LP %d %s -1 -1 -!",card,args[ch][2]);
	    EchoCommand(ch,temp);
	    rd_config->log("caed",RDConfig::LogErr,QString().
		    sprintf("unable to allocate stream for card %d",
			    card));
	    return;
	  }
	  break;

	case RDStation::Jack:
	  if(!jackLoadPlayback(card,wavename,&new_stream)) {
	    sprintf(temp,"LP %d %s -1 -1 -!",card,args[ch][2]);
	    EchoCommand(ch,temp);
	    rd_config->log("caed",RDConfig::LogErr,QString().
		    sprintf("unable to allocate stream for card %d",
			    card));
	    return;
	  }
	  break;

	default:
	  sprintf(temp,"LP %d %s -1 -1 -!",card,args[ch][2]);
	  EchoCommand(ch,temp);
	  return;
    }
    if((handle=GetHandle(card,new_stream))>=0) {
      rd_config->log("caed",RDConfig::LogErr,QString().
	      sprintf("*** clearing stale stream assignment, card=%d  stream=%d ***",card,new_stream));
      play_handle[handle].card=-1;
      play_handle[handle].stream=-1;
      play_handle[handle].owner=-1;
    }
    handle=GetNextHandle();
    play_handle[handle].card=card;
    play_handle[handle].stream=new_stream;
    play_handle[handle].owner=ch;
    play_owner[card][new_stream]=ch;
    rd_config->log("caed",RDConfig::LogInfo,QString().
	    sprintf("LoadPlayback  Card: %d  Stream: %d  Name: %s  Handle: %d",
		    card,new_stream,(const char *)wavename,handle));
    sprintf(temp,"LP %d %s %d %d +!",card,args[ch][2],new_stream,handle);
    EchoCommand(ch,temp);
    return;
  }

  if(!strcmp(args[ch][0],"UP")) {  // Unload Playback
    if((handle=GetHandle(ch,&card,&stream))<0) {
      EchoArgs(ch,'-');
      return;
    }
    card=play_handle[handle].card;
    stream=play_handle[handle].stream;
    if((play_owner[card][stream]==-1)||(play_owner[card][stream]==ch)) {
      switch(cae_driver[card]) {
	  case RDStation::Hpi:
	    if(hpiUnloadPlayback(card,stream)) {
	      play_owner[card][stream]=-1;
	      rd_config->log("caed",RDConfig::LogInfo,QString().
		      sprintf("UnloadPlayback - Card: %d  Stream: %d  Handle: %d",
			      card,stream,handle));
	      EchoArgs(ch,'+');
	    }
	    else {
	      EchoArgs(ch,'-');
	    }
	    break;
	    
	  case RDStation::Alsa:
	    if(alsaUnloadPlayback(card,stream)) {
	      play_owner[card][stream]=-1;
	      rd_config->log("caed",RDConfig::LogInfo,QString().
		      sprintf("UnloadPlayback - Card: %d  Stream: %d  Handle: %d",
			      card,stream,handle));
	      EchoArgs(ch,'+');
	    }
	    else {
	      EchoArgs(ch,'-');
	    }
	    break;
	    
	  case RDStation::Jack:
	    if(jackUnloadPlayback(card,stream)) {
	      play_owner[card][stream]=-1;
	      rd_config->log("caed",RDConfig::LogInfo,QString().
		      sprintf("UnloadPlayback - Card: %d  Stream: %d  Handle: %d",
			      card,stream,handle));
	      EchoArgs(ch,'+');
	    }
	    else {
	      EchoArgs(ch,'-');
	    }
	    break;
	    
	  default:
	    EchoArgs(ch,'-');
	    return;
      }
      play_handle[handle].card=-1;
      play_handle[handle].stream=-1;
      play_handle[handle].owner=-1;
      return;
    }
    else {
      EchoArgs(ch,'-');
    }
  }

  if(!strcmp(args[ch][0],"PP")) {  // Playback Position
    if((handle=GetHandle(ch,&card,&stream))<0) {
      EchoArgs(ch,'-');
      return;
    }
    card=play_handle[handle].card;
    stream=play_handle[handle].stream;
    if(play_owner[card][stream]==ch) {
      if(sscanf(args[ch][2],"%d",&pos)!=1) {
	EchoArgs(ch,'-');
	return;
      }
      switch(cae_driver[card]) {
	  case RDStation::Hpi:
	    if(hpiPlaybackPosition(card,stream,pos)) {
	      rd_config->log("caed",RDConfig::LogInfo,QString().
		   sprintf("PlaybackPosition - Card: %d  Stream: %d  Pos: %d  Handle: %d",
			   card,stream,pos,handle));
	      EchoArgs(ch,'+');
	    }
	    else {
	      rd_config->log("caed",RDConfig::LogNotice,QString().
		   sprintf("*** PlaybackPosition out of bounds - Card: %d  Stream: %d  Pos: %d   Handle: %d***",
			   card,stream,pos,handle));
	      EchoArgs(ch,'-');
	    }
	    break;

	  case RDStation::Alsa:
	    if(alsaPlaybackPosition(card,stream,pos)) {
	      rd_config->log("caed",RDConfig::LogInfo,QString().
		   sprintf("PlaybackPosition - Card: %d  Stream: %d  Pos: %d  Handle: %d",
			   card,stream,pos,handle));
	      EchoArgs(ch,'+');
	    }
	    else {
	      EchoArgs(ch,'-');
	    }
	    break;

	  case RDStation::Jack:
	    if(jackPlaybackPosition(card,stream,pos)) {
	      rd_config->log("caed",RDConfig::LogInfo,QString().
		   sprintf("PlaybackPosition - Card: %d  Stream: %d  Pos: %d  Handle: %d",
			   card,stream,pos,handle));
	      EchoArgs(ch,'+');
	    }
	    else {
	      EchoArgs(ch,'-');
	    }
	    break;

	  default:
	    EchoArgs(ch,'-');
	    return;
      }
      return;
    }
    EchoArgs(ch,'-');
    return;
  }

  if(!strcmp(args[ch][0],"PY")) {  // Play
    if((handle=GetHandle(ch,&card,&stream))<0) {
      EchoArgs(ch,'-');
      return;
    }
    card=play_handle[handle].card;
    stream=play_handle[handle].stream;
    if(sscanf(args[ch][2],"%d",&play_length[card][stream])!=1) {
      EchoArgs(ch,'-');
      return;
    }
    if(sscanf(args[ch][3],"%d",&play_speed[card][stream])!=1) {
      EchoArgs(ch,'-');
      return;
    }
    if(sscanf(args[ch][4],"%d",&flag)!=1) {
      EchoArgs(ch,'-');
      return;
    }
    switch(flag) {
	case 0:
	  play_pitch[card][stream]=false;
	  break;
	case 1:
	  play_pitch[card][stream]=true;
	  break;
	default:
	  EchoArgs(ch,'-');
	  return;
    }
    if(play_owner[card][stream]==ch) {
      switch(cae_driver[card]) {
	  case RDStation::Hpi:
	    if(!hpiPlay(card,stream,play_length[card][stream],
			play_speed[card][stream],play_pitch[card][stream],
			RD_ALLOW_NONSTANDARD_RATES)) {
	      EchoArgs(ch,'-');
	      return;
	    }
	    break;

	  case RDStation::Alsa:
	    if(!alsaPlay(card,stream,play_length[card][stream],
			play_speed[card][stream],play_pitch[card][stream],
			RD_ALLOW_NONSTANDARD_RATES)) {
	      EchoArgs(ch,'-');
	      return;
	    }
	    break;

	  case RDStation::Jack:
	    if(!jackPlay(card,stream,play_length[card][stream],
			play_speed[card][stream],play_pitch[card][stream],
			RD_ALLOW_NONSTANDARD_RATES)) {
	      EchoArgs(ch,'-');
	      return;
	    }
	    break;

	  default:
	      EchoArgs(ch,'-');
	      return;
      }
      rd_config->log("caed",RDConfig::LogInfo,QString().
       sprintf("Play - Card: %d  Stream: %d  Handle: %d  Length: %d  Speed: %d  Pitch: %d",
	       card,stream,handle,play_length[card][stream],
	       play_speed[card][stream],flag));
      // No command echo for success -- statePlayUpdate() sends it!
      return;
    }
    EchoArgs(ch,'-');
    return;
  }

  if(!strcmp(args[ch][0],"SP")) {  // Stop Playback
    if((handle=GetHandle(ch,&card,&stream))<0) {
      EchoArgs(ch,'-');
      return;
    }
    card=play_handle[handle].card;
    stream=play_handle[handle].stream;
    if(play_owner[card][stream]==ch) {
      switch(cae_driver[card]) {
	  case RDStation::Hpi:
	    if(!hpiStopPlayback(card,stream)) {
	      EchoArgs(ch,'-');
	      return;
	    }
	    break;

	  case RDStation::Alsa:
	    if(!alsaStopPlayback(card,stream)) {
	      EchoArgs(ch,'-');
	      return;
	    }
	    break;

	  case RDStation::Jack:
	    if(!jackStopPlayback(card,stream)) {
	      EchoArgs(ch,'-');
	      return;
	    }
	    break;

	  default:
	    EchoArgs(ch,'-');
	    return;
      }
      rd_config->log("caed",RDConfig::LogInfo,QString().
	      sprintf("StopPlayback - Card: %d  Stream: %d  Handle: %d",
		      card,stream,handle));
      //      EchoArgs(ch,'+');
      return;
    }
    EchoArgs(ch,'-');
    return;
  }

  if(!strcmp(args[ch][0],"TS")) {  // Timescale Support
    switch(cae_driver[card]) {
	case RDStation::Hpi:
	  if(!hpiTimescaleSupported(card)) {
	    EchoArgs(ch,'-');
	    return;
	  }
	  break;

	case RDStation::Jack:
	  if(!jackTimescaleSupported(card)) {
	    EchoArgs(ch,'-');
	    return;
	  }
	  break;

	case RDStation::Alsa:
	  if(!alsaTimescaleSupported(card)) {
	    EchoArgs(ch,'-');
	    return;
	  }
	  break;

	default:
	  EchoArgs(ch,'-');
	  return;
    }
    EchoArgs(ch,'+');
    return;
  }

  if(!strcmp(args[ch][0],"LR")) {  // Load Record
    if((card<0)||(stream<0)) {
      EchoArgs(ch,'-');
      return;
    }
    if(record_owner[card][stream]==-1) {
      if(sscanf(args[ch][3],"%d",&coding)!=1) {
	EchoArgs(ch,'-');
	return;
      }
      if(sscanf(args[ch][4],"%d",&channels)!=1) {
	EchoArgs(ch,'-');
	return;
      }
      if(sscanf(args[ch][5],"%d",&sample_rate)!=1) {
	EchoArgs(ch,'-');
	return;
      }
      if(sscanf(args[ch][6],"%d",&bit_rate)!=1) {
	EchoArgs(ch,'-');
 	return;
      }
      wavename = rd_config->audioFileName(QString(args[ch][7]));
      unlink(wavename);  // So we don't trainwreck any current playouts!
      unlink(wavename+".energy");
      switch(cae_driver[card]) {
	  case RDStation::Hpi:
	    if(!hpiLoadRecord(card,stream,coding,channels,sample_rate,bit_rate,
			      wavename)) {
	      EchoArgs(ch,'-');
	      return;
	    }
	    break;

	  case RDStation::Alsa:
	    if(!alsaLoadRecord(card,stream,coding,channels,sample_rate,
			       bit_rate,wavename)) {
	      EchoArgs(ch,'-');
	      return;
	    }
	    break;

	  case RDStation::Jack:
	    if(!jackLoadRecord(card,stream,coding,channels,sample_rate,
			       bit_rate,wavename)) {
	      EchoArgs(ch,'-');
	      return;
	    }
	    break;

	  default:
	    EchoArgs(ch,'-');
	    return;
      }
      rd_config->log("caed",RDConfig::LogInfo,QString().
	      sprintf("LoadRecord - Card: %d  Stream: %d  Coding: %d  Chans: %d  SampRate: %d  BitRate: %d  Name: %s",
		      card,stream,coding,channels,sample_rate,bit_rate,
		      (const char *)wavename));
      record_owner[card][stream]=ch;
      EchoArgs(ch,'+');
    }
    else {
      EchoArgs(ch,'-');
    }
    return;
  }

  if(!strcmp(args[ch][0],"UR")) {  // Unload Record
    if((card<0)||(stream<0)) {
      EchoArgs(ch,'-');
      return;
    }
    if((record_owner[card][stream]==-1)||(record_owner[card][stream]==ch)) {
      unsigned len=0;
      switch(cae_driver[card]) {
	  case RDStation::Hpi:
	    if(!hpiUnloadRecord(card,stream,&len)) {
	      EchoArgs(ch,'-');
	      return;
	    }
	    break;

	  case RDStation::Alsa:
	    if(!alsaUnloadRecord(card,stream,&len)) {
	      EchoArgs(ch,'-');
	      return;
	    }
	    break;

	  case RDStation::Jack:
	    if(!jackUnloadRecord(card,stream,&len)) {
	      EchoArgs(ch,'-');
	      return;
	    }
	    break;

	  default:
	    EchoArgs(ch,'-');
	    return;
      }
      record_owner[card][stream]=-1;
      rd_config->log("caed",RDConfig::LogInfo,QString().
	      sprintf("UnloadRecord - Card: %d  Stream: %d, Length: %u",
		      card,stream,len));
      EchoCommand(ch,(const char *)QString().sprintf("UR %d %d %u +!",
						     card,stream,
	  (unsigned)((double)len*1000.0/(double)system_sample_rate)));
      return;
    }
    else {
      EchoArgs(ch,'-');
      return;
    }
  }

  if(!strcmp(args[ch][0],"RD")) {  // Record
    if((card<0)||(stream<0)) {
      EchoArgs(ch,'-');
      return;
    }
    if(sscanf(args[ch][3],"%d",&record_length[card][stream])!=1) {
      EchoArgs(ch,'-');
      return;
    }
    if(sscanf(args[ch][4],"%d",&record_threshold[card][stream])!=1) {
      EchoArgs(ch,'-');
      return;
    }
    if(record_owner[card][stream]==ch) {
      switch(cae_driver[card]) {
	  case RDStation::Hpi:
	    if(!hpiRecord(card,stream,record_length[card][stream],
			  record_threshold[card][stream])) {
	      EchoArgs(ch,'-');
	      return;
	    }
	    break;

	  case RDStation::Alsa:
	    if(!alsaRecord(card,stream,record_length[card][stream],
			  record_threshold[card][stream])) {
	      EchoArgs(ch,'-');
	      return;
	    }
	    break;

	  case RDStation::Jack:
	    if(!jackRecord(card,stream,record_length[card][stream],
			  record_threshold[card][stream])) {
	      EchoArgs(ch,'-');
	      return;
	    }
	    break;

	  default:
	    EchoArgs(ch,'-');
	    return;
      }
      rd_config->log("caed",RDConfig::LogInfo,QString().
	      sprintf("Record - Card: %d  Stream: %d  Length: %d  Thres: %d",
		      card,stream,record_length[card][stream],
		      record_threshold[card][stream]));
//      EchoArgs(ch,'+');
      return;
    }
    EchoArgs(ch,'-');
    return;
  }

  if(!strcmp(args[ch][0],"SR")) {  // Stop Record
    if((card<0)||(stream<0)) {
      EchoArgs(ch,'-');
      return;
    }
    switch(cae_driver[card]) {
	case RDStation::Hpi:
	  if(!hpiStopRecord(card,stream)) {
	    EchoArgs(ch,'-');
	    return;
	  }
	  break;

	case RDStation::Alsa:
	  if(!alsaStopRecord(card,stream)) {
	    EchoArgs(ch,'-');
	    return;
	  }
	  EchoArgs(ch,'+');
	  break;

	case RDStation::Jack:
	  if(!jackStopRecord(card,stream)) {
	    EchoArgs(ch,'-');
	    return;
	  }
	  EchoArgs(ch,'+');
	  break;

	default:
	  EchoArgs(ch,'-');
	  return;
    }
    rd_config->log("caed",RDConfig::LogInfo,QString().
	    sprintf("StopRecord - Card: %d  Stream: %d",
		    card,stream));
    return;
  }

  if(!strcmp(args[ch][0],"CS")) {  // Set Clock Source
    if((card<0)||(stream<0)) {
      EchoArgs(ch,'-');
      return;
    }
    switch(cae_driver[card]) {
	case RDStation::Hpi:
	  if(!hpiSetClockSource(card,stream)) {
	    EchoArgs(ch,'-');
	    return;
	  }

	default:
	  EchoArgs(ch,'+');
	  return;
    }
    if(rd_config->enableMixerLogging()) {
      rd_config->log("caed",RDConfig::LogInfo,QString().
	      sprintf("SetClockSource - Card: %d  Source: %d",card,stream));
    }
    EchoArgs(ch,'+');
    return;
  }

  if(!strcmp(args[ch][0],"IV")) {  // Set Input Volume
    if((card<0)||(stream<0)) {
      EchoArgs(ch,'-');
      return;
    }
    sscanf(args[ch][3],"%d",&level);
    switch(cae_driver[card]) {
	case RDStation::Hpi:
	  if(!hpiSetInputVolume(card,stream,level)) {
	    EchoArgs(ch,'-');
	    return;
	  }

	case RDStation::Alsa:
	  if(!alsaSetInputVolume(card,stream,level)) {
	    EchoArgs(ch,'-');
	    return;
	  }

	case RDStation::Jack:
	  if(!jackSetInputVolume(card,stream,level)) {
	    EchoArgs(ch,'-');
	    return;
	  }

	default:
	  EchoArgs(ch,'-');
	  return;
    }
    if(rd_config->enableMixerLogging()) {
      rd_config->log("caed",RDConfig::LogInfo,QString().
	      sprintf("SetInputVolume - Card: %d  Stream: %d Level: %d",
		      card,stream,level));
    }
    EchoArgs(ch,'+');
    return;
  }

  if(!strcmp(args[ch][0],"OV")) {  // Set Output Volume
    if((card<0)||(stream<0)) {
      EchoArgs(ch,'-');
      return;
    }
    sscanf(args[ch][3],"%d",&port);
    sscanf(args[ch][4],"%d",&level);
    switch(cae_driver[card]) {
	case RDStation::Hpi:
	  if(!hpiSetOutputVolume(card,stream,port,level)) {
	    EchoArgs(ch,'-');
	    return;
	  }
	  break;
	  
	case RDStation::Alsa:
	  if(!alsaSetOutputVolume(card,stream,port,level)) {
	    EchoArgs(ch,'-');
	    return;
	  }
	  break;
	  
	case RDStation::Jack:
	  if(!jackSetOutputVolume(card,stream,port,level)) {
	    EchoArgs(ch,'-');
	    return;
	  }
	  break;
	  
	default:
	  EchoArgs(ch,'-');
	  return;
    }
    if(rd_config->enableMixerLogging()) {
      rd_config->log("caed",RDConfig::LogInfo,QString().
	      sprintf("SetOutputVolume - Card: %d  Stream: %d  Port: %d  Level: %d",
		      card,stream,port,level));
    }
    EchoArgs(ch,'+');
    return;
  }

  if(!strcmp(args[ch][0],"FV")) {  // Fade Output Volume
    if((card<0)||(stream<0)) {
      EchoArgs(ch,'-');
      return;
    }
    sscanf(args[ch][3],"%d",&port);
    sscanf(args[ch][4],"%d",&level);
    sscanf(args[ch][5],"%d",&length);
    switch(cae_driver[card]) {
	case RDStation::Hpi:
	  if(!hpiFadeOutputVolume(card,stream,port,level,length)) {
	    EchoArgs(ch,'-');
	    return;
	  }
	  break;

	case RDStation::Alsa:
	  if(!alsaFadeOutputVolume(card,stream,port,level,length)) {
	    EchoArgs(ch,'-');
	    return;
	  }
	  break;

	case RDStation::Jack:
	  if(!jackFadeOutputVolume(card,stream,port,level,length)) {
	    EchoArgs(ch,'-');
	    return;
	  }
	  break;

	default:
	  EchoArgs(ch,'-');
	  return;
    }
    if(rd_config->enableMixerLogging()) {
      rd_config->log("caed",RDConfig::LogInfo,QString().
	      sprintf("FadeOutputVolume - Card: %d  Stream: %d  Port: %d  Level: %d  Length: %d",
		      card,stream,port,level,length));
    }
    EchoArgs(ch,'+');
    return;
  }

  if(!strcmp(args[ch][0],"IL")) {  // Set Input Level
    sscanf(args[ch][2],"%d",&port);
    sscanf(args[ch][3],"%d",&level);
    if((card<0)||(port<0)) {
      EchoArgs(ch,'-');
      return;
    }
    switch(cae_driver[card]) {
	case RDStation::Hpi:
	  if(!hpiSetInputLevel(card,port,level)) {
	    EchoArgs(ch,'-');
	    return;
	  }
	  break;
	  
	case RDStation::Alsa:
	  if(!alsaSetInputLevel(card,port,level)) {
	    EchoArgs(ch,'-');
	    return;
	  }
	  break;
	  
	case RDStation::Jack:
	  if(!jackSetInputLevel(card,port,level)) {
	    EchoArgs(ch,'-');
	    return;
	  }
	  break;
	  
	default:
	  EchoArgs(ch,'-');
	  return;
    }
    if(rd_config->enableMixerLogging()) {
      rd_config->log("caed",RDConfig::LogInfo,QString().
	      sprintf("SetInputLevel - Card: %d  Port: %d  Level: %d",
		      card,port,level));
    }
    EchoArgs(ch,'+');
    return;
  }

  if(!strcmp(args[ch][0],"OL")) {  // Set Output Level
    sscanf(args[ch][2],"%d",&port);
    sscanf(args[ch][3],"%d",&level);
    if((card<0)||(port<0)) {
      EchoArgs(ch,'-');
      return;
    }
    switch(cae_driver[card]) {
	case RDStation::Hpi:
	  if(!hpiSetOutputLevel(card,port,level)) {
	    EchoArgs(ch,'-');
	    return;
	  }
	  break;

	case RDStation::Alsa:
	  if(!alsaSetOutputLevel(card,port,level)) {
	    EchoArgs(ch,'-');
	    return;
	  }
	  break;

	case RDStation::Jack:
	  if(!jackSetOutputLevel(card,port,level)) {
	    EchoArgs(ch,'-');
	    return;
	  }
	  break;

	default:
	  EchoArgs(ch,'-');
	  return;
    }
    if(rd_config->enableMixerLogging()) {
      rd_config->log("caed",RDConfig::LogInfo,QString().
	      sprintf("SetOutputLevel - Card: %d  Port: %d  Level: %d",
		      card,port,level));
    }
    EchoArgs(ch,'+');
    return;
  }

  if(!strcmp(args[ch][0],"IM")) {  // Set Input Mode
    sscanf(args[ch][2],"%d",&port);
    sscanf(args[ch][3],"%d",&mode);
    if((card<0)||(port<0)) {
      EchoArgs(ch,'-');
      return;
    }
    switch(cae_driver[card]) {
	case RDStation::Hpi:
	  if(!hpiSetInputMode(card,port,mode)) {
	    EchoArgs(ch,'-');
	    return;
	  }
	  break;

	case RDStation::Alsa:
	  if(!alsaSetInputMode(card,port,mode)) {
	    EchoArgs(ch,'-');
	    return;
	  }
	  break;

	case RDStation::Jack:
	  if(!jackSetInputMode(card,port,mode)) {
	    EchoArgs(ch,'-');
	    return;
	  }
	  break;

	default:
	  EchoArgs(ch,'-');
	  return;
    }
    if(rd_config->enableMixerLogging()) {
      rd_config->log("caed",RDConfig::LogInfo,QString().
	      sprintf("SetInputMode - Card: %d  Port: %d  Mode: %d",
		      card,port,mode));
    }
    EchoArgs(ch,'+');
    return;
  }

  if(!strcmp(args[ch][0],"OM")) {  // Set Output Mode
    sscanf(args[ch][2],"%d",&port);
    sscanf(args[ch][3],"%d",&mode);
    if((card<0)||(port<0)) {
      EchoArgs(ch,'-');
      return;
    }
    switch(cae_driver[card]) {
	case RDStation::Hpi:
	  if(!hpiSetOutputMode(card,port,mode)) {
	    EchoArgs(ch,'-');
	    return;
	  }
	  break;

	case RDStation::Alsa:
	  if(!alsaSetOutputMode(card,port,mode)) {
	    EchoArgs(ch,'-');
	    return;
	  }
	  break;

	case RDStation::Jack:
	  if(!jackSetOutputMode(card,port,mode)) {
	    EchoArgs(ch,'-');
	    return;
	  }
	  break;

	default:
	  EchoArgs(ch,'-');
	  return;
    }
    if(rd_config->enableMixerLogging()) {
      rd_config->log("caed",RDConfig::LogInfo,QString().
	      sprintf("SetOutputMode - Card: %d  Port: %d  Mode: %d",
		      card,port,mode));
    }
    EchoArgs(ch,'+');
    return;
  }

  if(!strcmp(args[ch][0],"IX")) {  // Set Input VOX Level
    if((card<0)||(stream<0)) {
      EchoArgs(ch,'-');
      return;
    }
    sscanf(args[ch][3],"%d",&level);
    switch(cae_driver[card]) {
	case RDStation::Hpi:
	  if(!hpiSetInputVoxLevel(card,stream,level)) {
	    EchoArgs(ch,'-');
	    return;
	  }
	  break;

	case RDStation::Alsa:
	  if(!alsaSetInputVoxLevel(card,stream,level)) {
	    EchoArgs(ch,'-');
	    return;
	  }
	  break;

	case RDStation::Jack:
	  if(!jackSetInputVoxLevel(card,stream,level)) {
	    EchoArgs(ch,'-');
	    return;
	  }
	  break;

	default:
	  EchoArgs(ch,'-');
	  return;
    }
    if(rd_config->enableMixerLogging()) {
      rd_config->log("caed",RDConfig::LogInfo,QString().
	      sprintf("SetInputVOXLevel - Card: %d  Stream: %d  Level: %d",
		      card,stream,level));
    }
    EchoArgs(ch,'+');
    return;
  }

  if(!strcmp(args[ch][0],"IT")) {  // Set Input Type
    sscanf(args[ch][2],"%d",&port);
    sscanf(args[ch][3],"%d",&type);
    if((card<0)||(port<0)) {
      EchoArgs(ch,'-');
      return;
    }
    switch(cae_driver[card]) {
	case RDStation::Hpi:
	  if(!hpiSetInputType(card,port,type)) {
	    EchoArgs(ch,'-');
	    return;
	  }
	  break;

	case RDStation::Alsa:
	  if(!alsaSetInputType(card,port,type)) {
	    EchoArgs(ch,'-');
	    return;
	  }
	  break;

	case RDStation::Jack:
	  if(!jackSetInputType(card,port,type)) {
	    EchoArgs(ch,'-');
	    return;
	  }
	  break;

	default:
	  EchoArgs(ch,'-');
	  return;
    }
    if(rd_config->enableMixerLogging()) {
      rd_config->log("caed",RDConfig::LogInfo,QString().
	      sprintf("SetInputType - Card: %d  Port: %d  Type: %d",
		      card,port,type));
    }
    EchoArgs(ch,'+');
    return;
  }

  if(!strcmp(args[ch][0],"AL")) {  // Set Passthrough Level
    sscanf(args[ch][2],"%d",&in_port);
    sscanf(args[ch][3],"%d",&out_port);
    sscanf(args[ch][4],"%d",&level);
    if((card<0)||(in_port<0)||(out_port<0)) {
      EchoArgs(ch,'-');
      return;
    }
    switch(cae_driver[card]) {
	case RDStation::Hpi:
	  if(!hpiSetPassthroughLevel(card,in_port,out_port,level)) {
	    EchoArgs(ch,'-');
	    return;
	  }
	  break;
	  
	case RDStation::Alsa:
	  if(!alsaSetPassthroughLevel(card,in_port,out_port,level)) {
	    EchoArgs(ch,'-');
	    return;
	  }
	  break;
	  
	case RDStation::Jack:
	  if(!jackSetPassthroughLevel(card,in_port,out_port,level)) {
	    EchoArgs(ch,'-');
	    return;
	  }
	  break;
	  
	default:
	  EchoArgs(ch,'-');
	  return;
    }
    if(rd_config->enableMixerLogging()) {
      rd_config->log("caed",RDConfig::LogInfo,QString().
	      sprintf("SetPassthroughLevel - Card: %d  InPort: %d  OutPort: %d Level: %d",
		      card,in_port,out_port,level));
    }
    EchoArgs(ch,'+');
    return;
  }

  if(!strcmp(args[ch][0],"IS")) {  // Input Status
    sscanf(args[ch][2],"%d",&port);
    if((card<0)||(port<0)) {
      EchoArgs(ch,'-');
      return;
    }
    if(hpiGetInputStatus(card,port)) {
      EchoCommand(ch,QString().sprintf("IS %d %d 0 +!",card,port));
    }
    else {
      EchoCommand(ch,QString().sprintf("IS %d %d 1 +!",card,port));
    }
    return;
  }

  if(!strcmp(args[ch][0],"ME")) {  // Meter Enable
    sscanf(args[ch][1],"%d",&port);
    if((port<0)||(port>0xFFFF)) {
      EchoArgs(ch,'-');
      return;
    }
    meter_port[ch]=port;
    for(int i=2;i<argnum[ch];i++) {
      sscanf(args[ch][i],"%d",&card);
      if((card<0)||(card>=RD_MAX_CARDS)) {
	EchoArgs(ch,'-');
	return;
      }
      update_meters[card][ch]=true;
    }

    EchoArgs(ch,'+');
    SendMeterOutputStatusUpdate();
    return;
  }

  if(!strcmp(args[ch][0],"OS")) {  // Set Output Status Flag
    sscanf(args[ch][1],"%d",&card);
    sscanf(args[ch][2],"%d",&port);
    sscanf(args[ch][3],"%d",&stream);
    if((card<0)||(card>=RD_MAX_CARDS)||
       (port<0)||(port>=RD_MAX_PORTS)||
       (stream<0)||(stream>=RD_MAX_STREAMS)) {
      EchoArgs(ch,'-');
      return;
    }
    output_status_flag[card][port][stream]=args[ch][4][0]=='1';
    SendMeterOutputStatusUpdate(card,port,stream);
    EchoArgs(ch,'+');
    return;
  }

  if(!strcmp(args[ch][0],"JC")) {  // Connect JACK Ports
    pos=-1;
    for(int i=0;i<argnum[ch];i++) {
      if(args[ch][i][0]=='|') {
	pos=i;
      }
    }
    if(pos<0) {
      return;
    }
    for(int i=1;i<pos;i++) {
      out_jport+=args[ch][i];
      out_jport+=" ";
    }
    out_jport=out_jport.left(out_jport.length()-1);
    for(int i=pos+1;i<argnum[ch];i++) {
      in_jport+=args[ch][i];
      in_jport+=" ";
    }
    in_jport=in_jport.left(in_jport.length()-1);
    jackConnectPorts(out_jport,in_jport);
  }

  if(!strcmp(args[ch][0],"JD")) {  // Disconnect JACK Ports
    pos=-1;
    for(int i=0;i<argnum[ch];i++) {
      if(args[ch][i][0]=='|') {
	pos=i;
      }
    }
    if(pos<0) {
      return;
    }
    for(int i=1;i<pos;i++) {
      out_jport+=args[ch][i];
      out_jport+=" ";
    }
    out_jport=out_jport.left(out_jport.length()-1);
    for(int i=pos+1;i<argnum[ch];i++) {
      in_jport+=args[ch][i];
      in_jport+=" ";
    }
    in_jport=in_jport.left(in_jport.length()-1);
    jackDisconnectPorts(out_jport,in_jport);
  }
}


void MainObject::KillSocket(int ch)
{
  istate[ch]=0;
  argnum[ch]=0;
  argptr[ch]=0;
  auth[ch]=false;
  meter_port[ch]=0;
  for(int i=0;i<RD_MAX_CARDS;i++) {
    update_meters[i][ch]=false;
    for(int j=0;j<RD_MAX_STREAMS;j++) {
      if(record_owner[i][j]==ch) {
	unsigned len=0;
	switch(cae_driver[i]) {
	    case RDStation::Hpi:
	      hpiUnloadRecord(i,j,&len);
	      break;

	    case RDStation::Jack:
	      jackUnloadRecord(i,j,&len);
	      break;

	    case RDStation::Alsa:
	      alsaUnloadRecord(i,j,&len);
	      break;

	    default:
	      rd_config->log("caed",RDConfig::LogNotice,"tried to kill unowned socket!");
	      break;
	}
	record_length[i][j]=0;
	record_threshold[i][j]=-10000;
	record_owner[i][j]=-1;
      }
      if(play_owner[i][j]==ch) {
	switch(cae_driver[i]) {
	    case RDStation::Hpi:
	      hpiUnloadPlayback(i,j);
	      break;

	    case RDStation::Jack:
	      jackUnloadPlayback(i,j);
	      break;

	    case RDStation::Alsa:
	      alsaUnloadPlayback(i,j);
	      break;

	    case RDStation::None:
	      break;
	}
	play_owner[i][j]=-1;
	play_length[i][j]=0;
	play_speed[i][j]=100;
	play_pitch[i][j]=false;
      }
    }
    for(int i=0;i<256;i++) {
      if(play_handle[i].owner==ch) {
	play_handle[i].card=-1;
	play_handle[i].stream=-1;
	play_handle[i].owner=-1;
      }
    }
  }
  delete socket[ch];
  socket[ch]=NULL;
}


void MainObject::BroadcastCommand(const char *command)
{
  for(int i=0;i<CAE_MAX_CONNECTIONS;i++) {
    if(auth[i]) {
      EchoCommand(i,command);
    }
  }
}


void MainObject::EchoCommand(int ch,const char *command)
{
  if(socket[ch]->state()==Q3Socket::Connection) {
#ifdef PRINT_COMMANDS
    printf("CAE: Connection %d sending %s\n",ch,command);
#endif  // PRINT_COMMANDS
    socket[ch]->writeBlock(command,strlen(command));
  }
}


void MainObject::EchoArgs(int ch,const char append)
{
  char command[CAE_MAX_LENGTH+2];
  int l;

  command[0]=0;
  for(int i=0;i<argnum[ch];i++) {
    strcat(command,args[ch][i]);
    strcat(command," ");
  }
  l=strlen(command);
  command[l]=append;
  command[l+1]='!';
  command[l+2]=0;
  EchoCommand(ch,command);
}


pid_t MainObject::GetPid(QString pidfile)
{
  FILE *handle;
  pid_t ret;

  if((handle=fopen((const char *)pidfile,"r"))==NULL) {
    return -1;
  }
  if(fscanf(handle,"%d",&ret)!=1) {
    ret=-1;
  }
  fclose(handle);
  return ret;
}


bool MainObject::CheckDaemon(QString name)
{
  QDir dir;
  QString path;
  path=QString(RD_PROC_DIR)+QString("/")+QString().sprintf("%d",GetPid(name));
  dir.setPath(path);
  return dir.exists();
}


int MainObject::GetNextHandle()
{
  while(play_handle[next_play_handle].card>=0) {
    next_play_handle++;
    next_play_handle&=0xFF;
  }
  int handle=next_play_handle;
  next_play_handle++;
  next_play_handle&=0xFF;
  return handle;
}


int MainObject::GetHandle(int ch,int *card,int *stream)
{
  int handle;

  if(sscanf(args[ch][1],"%d",&handle)!=1) {
    return -1;
  }
  if((handle<0)||(handle>=256)) {
    return -1;
  }
  if((*card=play_handle[handle].card)<0) {
    return -1;
  }
  if((*stream=play_handle[handle].stream<0)) {
    return -1;
  }
  return handle;
}


int MainObject::GetHandle(int card,int stream)
{
  for(int i=0;i<256;i++) {
    if((play_handle[i].card==card)&&(play_handle[i].stream==stream)) {
      return i;
    }
  }
  return -1;
}


void MainObject::ProbeCaps(RDStation *station)
{
  //
  // Patent-clear codecs
  //
#ifdef HAVE_VORBIS
  station->setHaveCapability(RDStation::HaveOggenc,true);
  station->setHaveCapability(RDStation::HaveOgg123,true);
#else
  station->setHaveCapability(RDStation::HaveOggenc,false);
  station->setHaveCapability(RDStation::HaveOgg123,false);
#endif  // HAVE_VORBIS
#ifdef HAVE_FLAC
  station->setHaveCapability(RDStation::HaveFlac,true);
#else
  station->setHaveCapability(RDStation::HaveFlac,false);
#endif  // HAVE_FLAC

  //
  // MPEG Codecs
  //
  station->setHaveCapability(RDStation::HaveLame,CheckLame());
  station->setHaveCapability(RDStation::HaveTwoLame,LoadTwoLame());
  station->setHaveCapability(RDStation::HaveMpg321,LoadMad());

  //
  // MP4 Decoder
  //
  station->setHaveCapability(RDStation::HaveMp4Decode,CheckMp4Decode());

#ifdef HPI
  station->setDriverVersion(RDStation::Hpi,hpiVersion());
#else
  station->setDriverVersion(RDStation::Hpi,"[not enabled]");
#endif  // HPI
#ifdef JACK
  //
  // FIXME: How can we detect the current JACK version?
  //
  station->setDriverVersion(RDStation::Jack,"Generic");
#else
  station->setDriverVersion(RDStation::Jack,"");
#endif  // JACK
#ifdef ALSA
  station->setDriverVersion(RDStation::Alsa,
			    QString().sprintf("%d.%d.%d",
					      SND_LIB_MAJOR,
					      SND_LIB_MINOR,
					      SND_LIB_SUBMINOR));
#else
  station->setDriverVersion(RDStation::Alsa,"");
#endif  // ALSA
}


void MainObject::ClearDriverEntries(RDStation *station)
{
  for(int i=0;i<RD_MAX_CARDS;i++) {
    if(cae_driver[i]==RDStation::None) {
      station->setCardDriver(i,RDStation::None);
      station->setCardName(i,"");
      station->setCardInputs(i,-1);
      station->setCardOutputs(i,-1);
    }
  }
}


/* This is an overloaded virtual function to tell a session manager not to restart this daemon. */
void QApplication::saveState(QSessionManager &sm) {
  sm.setRestartHint(QSessionManager::RestartNever);
  rd_config->log("caed",RDConfig::LogDebug,"cae saveState(), set restart hint to Never");
  return;
};


bool MainObject::CheckLame()
{
#ifdef HAVE_LAME
  return dlopen("libmp3lame.so.0",RTLD_LAZY)!=NULL;
#else
  return false;
#endif  // HAVE_LAME
}


bool MainObject::CheckMp4Decode()
{
#ifdef HAVE_MP4_LIBS
  return (dlopen("libfaad.so.2",RTLD_LAZY)!=NULL)&&
    (dlopen("libmp4v2.so.2",RTLD_LAZY)!=NULL);
#else
  return false;
#endif  // HAVE_MP4_LIBS
}


bool MainObject::LoadTwoLame()
{
#ifdef HAVE_TWOLAME
  if((twolame_handle=dlopen("libtwolame.so.0",RTLD_NOW))==NULL) {
    rd_config->log("caed",RDConfig::LogInfo,
	 "TwoLAME encoder library not found, MPEG L2 encoding not supported");
    return false;
  }
  *(void **)(&twolame_init)=dlsym(twolame_handle,"twolame_init");
  *(void **)(&twolame_set_mode)=dlsym(twolame_handle,"twolame_set_mode");
  *(void **)(&twolame_set_num_channels)=
    dlsym(twolame_handle,"twolame_set_num_channels");
  *(void **)(&twolame_set_in_samplerate)=
    dlsym(twolame_handle,"twolame_set_in_samplerate");
  *(void **)(&twolame_set_out_samplerate)=
    dlsym(twolame_handle,"twolame_set_out_samplerate");
  *(void **)(&twolame_set_bitrate)=
    dlsym(twolame_handle,"twolame_set_bitrate");
  *(void **)(&twolame_init_params)=
    dlsym(twolame_handle,"twolame_init_params");
  *(void **)(&twolame_close)=dlsym(twolame_handle,"twolame_close");
  *(void **)(&twolame_encode_buffer_interleaved)=
    dlsym(twolame_handle,"twolame_encode_buffer_interleaved");
  *(void **)(&twolame_encode_buffer_float32_interleaved)=
    dlsym(twolame_handle,"twolame_encode_buffer_float32_interleaved");
  *(void **)(&twolame_encode_flush)=
    dlsym(twolame_handle,"twolame_encode_flush");
  *(void **)(&twolame_set_energy_levels)=
    dlsym(twolame_handle,"twolame_set_energy_levels");
  rd_config->log("caed",RDConfig::LogInfo,
	  "Found TwoLAME encoder library, MPEG L2 encoding supported");
  return true;
#else
  rd_config->log("caed",RDConfig::LogInfo,"MPEG L2 encoding not enabled");
  return false;
#endif  // HAVE_TWOLAME
}


bool MainObject::InitTwoLameEncoder(int card,int stream,int chans,int samprate,
				    int bitrate)
{
#ifdef HAVE_TWOLAME
  TWOLAME_MPEG_mode mpeg_mode=TWOLAME_STEREO;

  switch(chans) {
  case 1:
    mpeg_mode=TWOLAME_MONO;
    break;

  case 2:
    mpeg_mode=TWOLAME_STEREO;    
    break;
  }
  if((twolame_lameopts[card][stream]=twolame_init())==NULL) {
    rd_config->log("caed",RDConfig::LogErr,QString().sprintf("unable to initialize twolame instance, card=%d, stream=%d",card,stream));
    return false;
  }
  twolame_set_mode(twolame_lameopts[card][stream],mpeg_mode);
  twolame_set_num_channels(twolame_lameopts[card][stream],chans);
  twolame_set_in_samplerate(twolame_lameopts[card][stream],samprate);
  twolame_set_out_samplerate(twolame_lameopts[card][stream],samprate);
  twolame_set_bitrate(twolame_lameopts[card][stream],bitrate/1000);
  twolame_set_energy_levels(twolame_lameopts[card][stream],1);
  if(twolame_init_params(twolame_lameopts[card][stream])!=0) {
    rd_config->log("caed",RDConfig::LogErr,QString().sprintf("invalid twolame parameters, card=%d, stream=%d, chans=%d, samprate=%d  bitrate=%d",
					       card,stream,chans,samprate,bitrate));
    return false;
  }
  return true;
#else
  return false;
#endif  // HAVE_TWOLAME
}


void MainObject::FreeTwoLameEncoder(int card,int stream)
{
#ifdef HAVE_TWOLAME
  if(twolame_lameopts[card][stream]!=NULL) { 
    twolame_close(&twolame_lameopts[card][stream]);
    twolame_lameopts[card][stream]=NULL;
  }
#endif  // HAVE_TWOLAME
}


bool MainObject::LoadMad()
{
#ifdef HAVE_MAD
  if((mad_handle=dlopen("libmad.so.0",RTLD_NOW))==NULL) {
    rd_config->log("caed",RDConfig::LogInfo,
	 "MAD decoder library not found, MPEG L2 decoding not supported");
    return false;
  }
  *(void **)(&mad_stream_init)=
    dlsym(mad_handle,"mad_stream_init");
  *(void **)(&mad_frame_init)=
    dlsym(mad_handle,"mad_frame_init");
  *(void **)(&mad_synth_init)=
    dlsym(mad_handle,"mad_synth_init");
  *(void **)(&mad_stream_buffer)=
    dlsym(mad_handle,"mad_stream_buffer");
  *(void **)(&mad_frame_decode)=
    dlsym(mad_handle,"mad_frame_decode");
  *(void **)(&mad_synth_frame)=
    dlsym(mad_handle,"mad_synth_frame");
  *(void **)(&mad_frame_finish)=
    dlsym(mad_handle,"mad_frame_finish");
  *(void **)(&mad_stream_finish)=
    dlsym(mad_handle,"mad_stream_finish");
  rd_config->log("caed",RDConfig::LogInfo,
	  "Found MAD decoder library, MPEG L2 decoding supported");
  return true;
#else
  rd_config->log("caed",RDConfig::LogInfo,"MPEG L2 decoding not enabled");
  return false;
#endif  // HAVE_MAD
}


void MainObject::InitMadDecoder(int card,int stream,RDWaveFile *wave)
{
#ifdef HAVE_MAD
  if(mad_active[card][stream]) {
    FreeMadDecoder(card,stream);
  }
  mad_stream_init(&mad_stream[card][stream]);
  mad_frame_init(&mad_frame[card][stream]);
  mad_synth_init(&mad_synth[card][stream]);
  mad_frame_size[card][stream]=
    144*wave->getHeadBitRate()/wave->getSamplesPerSec();
  mad_left_over[card][stream]=0;
  mad_active[card][stream]=true;
#endif  // HAVE_MAD
}


void MainObject::FreeMadDecoder(int card,int stream)
{
#ifdef HAVE_MAD
  if(mad_active[card][stream]) {
    mad_synth_finish(&mad_synth[card][stream]);
    mad_frame_finish(&mad_frame[card][stream]);
    mad_stream_finish(&mad_stream[card][stream]);
    mad_frame_size[card][stream]=0;
    mad_left_over[card][stream]=0;
    mad_active[card][stream]=false;
  }
#endif  // HAVE_MAD
}


void MainObject::SendMeterLevelUpdate(const QString &type,int cardnum,
				      int portnum,short levels[])
{
  for(int l=0;l<CAE_MAX_CONNECTIONS;l++) {
    if((meter_port[l]>0)&&update_meters[cardnum][l]) {
      SendMeterUpdate(QString().sprintf("ML %s %d %d %d %d",
	       (const char *)type,cardnum,portnum,levels[0],levels[1]),l);
    }
  }
}


void MainObject::SendStreamMeterLevelUpdate(int cardnum,int streamnum,
					    short levels[])
{
  for(int l=0;l<CAE_MAX_CONNECTIONS;l++) {
    if((meter_port[l]>0)&&update_meters[cardnum][l]) {
      SendMeterUpdate(QString().sprintf("MO %d %d %d %d",
		  cardnum,streamnum,levels[0],levels[1]),l);
    }
  }
}


void MainObject::SendMeterPositionUpdate(int cardnum,unsigned pos[])
{
  for(unsigned k=0;k<RD_MAX_STREAMS;k++) {
    for(int l=0;l<CAE_MAX_CONNECTIONS;l++) {
      if((meter_port[l]>0)&&update_meters[cardnum][l]) {
	SendMeterUpdate(QString().sprintf("MP %d %d %d",cardnum,k,pos[k]),l);
      }
    }
  }
}


void MainObject::SendMeterOutputStatusUpdate()
{
  for(unsigned i=0;i<RD_MAX_CARDS;i++) {
    if(cae_driver[i]!=RDStation::None) {
      for(unsigned j=0;j<RD_MAX_PORTS;j++) {
	for(unsigned k=0;k<RD_MAX_STREAMS;k++) {
	  for(unsigned l=0;l<CAE_MAX_CONNECTIONS;l++) {
	    if((meter_port[l]>0)&&update_meters[i][l]) {
	      SendMeterUpdate(QString().sprintf("MS %d %d %d %d",i,j,k,
					  output_status_flag[i][j][k]),l);
	    }
	  }
	}
      }
    }
  }
}


void MainObject::SendMeterOutputStatusUpdate(int card,int port,int stream)
{
  for(unsigned l=0;l<CAE_MAX_CONNECTIONS;l++) {
    if((meter_port[l]>0)&&update_meters[card][l]) {
      SendMeterUpdate(QString().sprintf("MS %d %d %d %d",card,port,stream,
				  output_status_flag[card][port][stream]),l);
    }
  }
}

void MainObject::SendMeterUpdate(const QString &msg,int conn_id)
{
  meter_socket->writeBlock(msg,msg.length(),socket[conn_id]->peerAddress(),
			   meter_port[conn_id]);
}


int main(int argc,char *argv[])
{
  int rc;
  QApplication a(argc,argv,false);
  new MainObject(NULL,"main");
  rc=a.exec();
  rd_config->log("caed",RDConfig::LogDebug,QString().sprintf("cae post a.exec() rc:%d", rc));
  return rc;
}
