/*************************************************************************/
/*                                                                       */
/*                Centre for Speech Technology Research                  */
/*                 (University of Edinburgh, UK) and                     */
/*                           Korin Richmond                              */
/*                         Copyright (c) 2002                            */
/*                         All Rights Reserved.                          */
/*                                                                       */
/*  Permission is hereby granted, free of charge, to use and distribute  */
/*  this software and its documentation without restriction, including   */
/*  without limitation the rights to use, copy, modify, merge, publish,  */
/*  distribute, sublicense, and/or sell copies of this work, and to      */
/*  permit persons to whom this work is furnished to do so, subject to   */
/*  the following conditions:                                            */
/*                                                                       */
/*   1. The code must retain the above copyright notice, this list of    */
/*      conditions and the following disclaimer.                         */
/*   2. Any modifications must be clearly marked as such.                */
/*   3. Original authors' names are not deleted.                         */
/*   4. The authors' names are not used to endorse or promote products   */
/*      derived from this software without specific prior written        */
/*      permission.                                                      */
/*                                                                       */
/*  THE UNIVERSITY OF EDINBURGH AND THE CONTRIBUTORS TO THIS WORK        */
/*  DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING      */
/*  ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT   */
/*  SHALL THE UNIVERSITY OF EDINBURGH NOR THE CONTRIBUTORS BE LIABLE     */
/*  FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES    */
/*  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN   */
/*  AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,          */
/*  ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF       */
/*  THIS SOFTWARE.                                                       */
/*                                                                       */
/*************************************************************************/
/*                                                                       */
/*                 Author: Korin Richmond                                */
/*                   Date: August 2002                                   */
/* --------------------------------------------------------------------- */
/*             Generalis[ed/able] Unit Selection synthesis               */
/*                                                                       */
/*************************************************************************/

#include "siod.h"
#include "EST.h"
#include "UnitSelection.h"
#include "DiphoneUnitVoice.h"
#include "DiphoneVoiceModule.h"
#include "EST_JoinCost.h"
#include "EST_TargetCost.h"
#include "safety.h"


static LISP FT_voice_get_units(LISP l_voice, LISP l_utt)
{
  EST_Utterance *u = get_c_utt(l_utt);
  VoiceBase *v = voice( l_voice );
  
  v->getUnitSequence( u );
  
  // Find units and put in utterance
  return l_utt;
}

static LISP FT_voice_get_name(LISP l_voice)
{
  VoiceBase *v = voice( l_voice );
  EST_String n = v->name();

  return strintern(n);
}

static LISP FT_voice_set_name(LISP l_voice, LISP l_name)
{
  EST_String n = get_c_string(l_name);
  VoiceBase *v = voice( l_voice );
  
  v->set_name( n );
  
  return NIL;
}

static LISP FT_voice_init( LISP l_voice )
{
  VoiceBase *v = voice( l_voice );
  v->initialise();
  return NIL;
}


static LISP FT_voice_debugLevel( LISP l_voice, LISP l_level )
{
  VoiceBase *v = voice( l_voice );

  if( l_level != NIL )
    v->setVerbosity( get_c_int(l_level) );

  return flocons( v->verbosity() );
}

static void parseVoiceDataParams( LISP l_dataparams, 
				  EST_String *uttDir, 
				  EST_String *wavDir,
				  EST_String *pmDir,
				  EST_String *coefDir,
				  EST_String *uttExt,
				  EST_String *wavExt,
				  EST_String *pmExt,
				  EST_String *coefExt )
{
  int listlen = siod_llength( l_dataparams );

  if( listlen == 8 ){
    *uttExt  = get_c_string( CAR1(CDR4(l_dataparams)) );
    *wavExt  = get_c_string( CAR2(CDR4(l_dataparams)) );
    *pmExt   = get_c_string( CAR3(CDR4(l_dataparams)) );
    *coefExt = get_c_string( CAR4(CDR4(l_dataparams)) );
  }
  else if( listlen == 4 ){ //set some defaults
    *uttExt  = ".utt";
    *wavExt  = ".wav";
    *pmExt   = ".pm";
    *coefExt = ".coef"; 
  }
  else
    EST_error( "Incorrect number of voice data parameters" );    

  *uttDir  = get_c_string( CAR1(l_dataparams) );
  *wavDir  = get_c_string( CAR2(l_dataparams) );
  *pmDir   = get_c_string( CAR3(l_dataparams) );
  *coefDir = get_c_string( CAR4(l_dataparams) );    
}


static LISP FT_make_du_voice( LISP l_bnames, LISP l_datadirs, LISP l_srate ) 
{ 
  EST_String uttDir, wavDir, pmDir, coefDir;
  EST_String uttExt, wavExt, pmExt, coefExt;
  
  int wav_srate = get_c_int( l_srate );
  if( wav_srate <= 0 )
    EST_error( "Waveform sample rate set to %d", wav_srate );
  
  parseVoiceDataParams( l_datadirs,
			&uttDir, &wavDir, &pmDir, &coefDir,
			&uttExt, &wavExt, &pmExt, &coefExt );

  EST_StrList bnames;
  siod_list_to_strlist( l_bnames, bnames );
  
  DiphoneUnitVoice *v;
  v = new DiphoneUnitVoice( bnames, 
			    uttDir, wavDir, pmDir, coefDir,
			    static_cast<unsigned int>(wav_srate),
			    uttExt, wavExt, pmExt, coefExt );

  CHECK_PTR(v);
  
  return siod( static_cast<VoiceBase*>(v) );
}


static LISP FT_make_du_voice_module( LISP l_bnames, LISP l_datadirs, LISP l_srate )
{
  EST_String uttDir, wavDir, pmDir, coefDir;
  EST_String uttExt, wavExt, pmExt, coefExt;

  int wav_srate = get_c_int( l_srate );
  if( wav_srate <= 0 )
    EST_error( "Waveform sample rate set to %d", wav_srate );

  parseVoiceDataParams( l_datadirs,
			&uttDir, &wavDir, &pmDir, &coefDir,
			&uttExt, &wavExt, &pmExt, &coefExt );
  
  EST_StrList bnames;
  siod_list_to_strlist( l_bnames, bnames );
 
  DiphoneVoiceModule *vm;
  vm = new DiphoneVoiceModule( bnames, 
			       uttDir, wavDir, pmDir, coefDir,
			       static_cast<unsigned int>(wav_srate),
			       uttExt, wavExt, pmExt, coefExt );
  CHECK_PTR(vm);

  return siod( vm );
}

static LISP FT_voice_add_module( LISP l_duv, LISP l_bnames, LISP l_datadirs, LISP l_srate )
{
  EST_String uttDir, wavDir, pmDir, coefDir;
  EST_String uttExt, wavExt, pmExt, coefExt;


  int wav_srate = get_c_int( l_srate );
  if( wav_srate <= 0 )
    EST_error( "Waveform sample rate set to %d", wav_srate );

  parseVoiceDataParams( l_datadirs,
			&uttDir, &wavDir, &pmDir, &coefDir,
			&uttExt, &wavExt, &pmExt, &coefExt );
  
  EST_StrList bnames;
  siod_list_to_strlist( l_bnames, bnames );

  if( DiphoneUnitVoice* duv = dynamic_cast<DiphoneUnitVoice*>(voice(l_duv)) ){
    if( ! duv->addVoiceModule(bnames, uttDir, wavDir, pmDir, coefDir,
			      static_cast<unsigned int>(wav_srate),
			      uttExt, wavExt, pmExt, coefExt ) )
      EST_error( "voice.addModule failed" );
  }
  else
    EST_error( "voice_add_module: expects DiphoneUnitVoice for now" );
  
  return NIL;
}


static LISP FT_du_voice_function( LISP x )
{
  if( DiphoneUnitVoice *duv = dynamic_cast<DiphoneUnitVoice*>(voice(x)) ){
    // do things you can only do to a DiphoneUnitVoice
    (void) duv;
  }
  else
    EST_error( "du_voice_function: expects DiphoneUnitVoice" );

  return NIL;
}


static LISP FT_du_voice_precomputeJoinCosts( LISP l_voice, LISP l_phones )
{
  if( DiphoneUnitVoice *duv = dynamic_cast<DiphoneUnitVoice*>(voice(l_voice)) ){
    EST_StrList phones;
    siod_list_to_strlist( l_phones, phones );
    duv->precomputeJoinCosts( phones  );
  }
  else
    EST_error( "du_voice_function: expects DiphoneUnitVoice" );

  return NIL;
}

static LISP FT_du_voice_set_pruning_beam( LISP l_voice, LISP l_width )
{
  if( DiphoneUnitVoice *duv = dynamic_cast<DiphoneUnitVoice*>(voice(l_voice)) ){
    duv->set_pruning_beam( get_c_float( l_width ) );
  }
  else
    EST_error( "du_voice_set_pruning: expects DiphoneUnitVoice" );

  return NIL;
}

static LISP FT_du_voice_set_ob_pruning_beam( LISP l_voice, LISP l_width )
{
  if( DiphoneUnitVoice *duv = dynamic_cast<DiphoneUnitVoice*>(voice(l_voice)) ){
    duv->set_ob_pruning_beam( get_c_float( l_width ) );
  }
  else
    EST_error( "du_voice_set_pruning: expects DiphoneUnitVoice" );

  return NIL;
}

static LISP FT_du_voice_set_diphonebackoff(LISP l_voice, LISP l_list)
{

  DiphoneBackoff *dbo;


 if( DiphoneUnitVoice *duv = dynamic_cast<DiphoneUnitVoice*>(voice(l_voice)) )
   {

     dbo = new DiphoneBackoff(l_list);
     CHECK_PTR(dbo);
     duv->set_diphone_backoff(dbo);
   }
 else
   EST_error( "du_voice_set_diphone_backoff: expects DiphoneUnitVoice" );

 return NIL;
}

static LISP FT_du_voice_setTargetCost(LISP l_voice, LISP l_tc)
{
  EST_TargetCost *tc=0;

  if( DiphoneUnitVoice *duv = dynamic_cast<DiphoneUnitVoice*>(voice(l_voice)) )
    {
      if( l_tc == NIL ){
	tc = new EST_TargetCost();
	CHECK_PTR(tc);
      }
      else if(l_tc == truth){
	tc = new EST_DefaultTargetCost();
	CHECK_PTR(tc);
      }
      else if(TYPE(l_tc) == tc_closure) {
	tc = new EST_SchemeTargetCost(l_tc);
	CHECK_PTR(tc);
      }
      else
	EST_error( "du_voice_setTargetcost: expects t, nil or a closure." );

      duv->setTargetCost(tc,true);
    }
  else
    EST_error( "du_voice_setTargetcost: expects DiphoneUnitVoice" );
  
  return NIL;
}

static LISP FT_du_voice_setJoinCost(LISP l_voice, LISP l_tc)
{
  if( DiphoneUnitVoice *duv = dynamic_cast<DiphoneUnitVoice*>(voice(l_voice)) )
    {
      EST_JoinCost *jc=0;
      if( l_tc == truth ){
	jc = new EST_JoinCost();
	CHECK_PTR(jc);
      }
      else
	EST_error( "du_voice_setJoinCost: currently t is the only supported second arguement" );
      
      duv->setJoinCost(jc,true);
    }
  else
    EST_error( "du_voice_setJoinCost: expects DiphoneUnitVoice" );
  
  return NIL;
}

static LISP FT_voicemodule_getUtterance( LISP l_vm, LISP l_n )
{
  EST_Utterance *utt = 0;

  if( DiphoneVoiceModule *dvm = dynamic_cast<DiphoneVoiceModule*>(voice(l_vm)) ){
    dvm->getUtterance( &utt, get_c_int(l_n) );
  }
  else
    EST_error( "du_voicemodule_function: expects DiphoneVoiceModule" );

  EST_warning( "EST_Utterance = %x\n", utt );
  
  return siod( utt );
}


static LISP FT_voice_getUtteranceByFileID( LISP l_vm, LISP l_fileid )
{
  EST_Utterance *utt = 0;

  if( DiphoneUnitVoice *duv = dynamic_cast<DiphoneUnitVoice*>(voice(l_vm)) ){
    duv->getCopyUnitUtterance( get_c_string(l_fileid), &utt );
  }
  else
    EST_error( "du_voicemodule_function: expects DiphoneVoiceModule" );
  
  EST_warning( "EST_Utterance = %x\n", utt );
  
  return siod( utt );
}


static LISP FT_voice_unit_count( LISP l_voice )
{
  VoiceBase *v = voice( l_voice );
  return flocons( static_cast<double>(v->numDatabaseUnits()) );
}

static LISP FT_voice_unit_type_count( LISP l_voice )
{
  VoiceBase *v = voice( l_voice );
  return flocons( static_cast<double>(v->numUnitTypes()) );
}

static LISP FT_voice_unit_available( LISP l_voice, LISP l_unit )
{
  VoiceBase *v = voice( l_voice );
  
  if( v->unitAvailable( get_c_string( l_unit ) ) )
    return truth;
  
  return NIL;
}

static LISP FT_voice_num_available_candidates( LISP l_voice, LISP l_unit )
{
  VoiceBase *v = voice( l_voice );

  unsigned int number = v->numAvailableCandidates( get_c_string(l_unit) ); 

  return flocons( number );
}

void festival_MultiSyn_init(void)
{
  proclaim_module("MultiSyn");

  init_subr_2("voice.getUnits", FT_voice_get_units,
  "(voice.getUnits VOICE UTT)\n\
    Voice object VOICE looks at the segment relation in utterance UTT\n\
    and adds a suitable unit sequence in the Unit relation.");

  init_subr_1("voice.getName", FT_voice_get_name,
  "(voice.getName VOICE)\n\
    Gets the name of a voice.");

  init_subr_2("voice.setName", FT_voice_set_name,
  "(voice.setName VOICE NAME)\n\
    Sets the name of a voice.");

  init_subr_2("voice.debugLevel", FT_voice_debugLevel,
  "(voice.debugLevel VOICE LEVEL)\n\
    Query and/or set the level of debugging for VOICE to LEVEL (positive int).\n\
    A level of 0 switches off all debugging messages in the voice.  Leaving\n\
    level unspecified simply returns the current level.");

  init_subr_3( "make_du_voice", FT_make_du_voice,
  "(make_du_voice BASENAMES DATADIRS SAMPLERATE)\n\
    Creates a Diphone UnitSelection Voice, using the list of file basenames\n\
    in LISP list BASENAMES, and the four directory strings in the DATADIRS list.\n\
    The voice waveform data files are sampled at SAMPLERATE." );

  init_subr_3( "make_du_voice_module", FT_make_du_voice_module,
  "(make_du_voice_module BASENAMES DATADIRS SAMPLERATE)\n\
    Creates a Diphone UnitSelection Voice Module, using the list of file basenames\n\
    in LISP list BASENAMES, and the four directory strings in the DATADIRS list.\n\
    The voice waveform data files are sampled at SAMPLERATE." );

  init_subr_4( "voice.addModule", FT_voice_add_module,
  "(voice.addModule VOICE BASENAMES DATADIRS SAMPLERATE)\n\
    Creates a Diphone UnitSelection Voice Module, using the list of file basenames\n\
    in LISP list BASENAMES, and the three directory strings in the remaining\n\
    argument DATADIRS and adds it to the current voice. The voice waveform data\n\
    files are sampled at SAMPLERATE." );
    
  init_subr_1("voice.init", FT_voice_init,
  "(voice.init VOICE)\n\
    Perform any necessary initialisation for the UnitSelection Voice object VOICE.");

  init_subr_2("voice.getUtteranceByFileID", FT_voice_getUtteranceByFileID,
  "(voice.getUtteranceByFileID VOICE FILEIDSTRING)\n\
    Returns copy of the Utterance in the voice module database, with\n\
    all the Unit relation filled in, ready for synthesis.");

  init_subr_2("voicemodule.getUtterance", FT_voicemodule_getUtterance,
  "(voicemodule.getUtterance VOICEMODULE UTTNUMBER)\n\
  Returns copy of UTTNUMBER Utterance in the voice module database." );

  init_subr_1("voice.numUnitTypes", FT_voice_unit_type_count,
  "(voice.numUnitTypes VOICE)\n\
    Number of different unit types available in Voice object VOICE.");

  init_subr_1("voice.numUnits", FT_voice_unit_count,
  "(voice.numUnits VOICE)\n\
    Total units available in Voice object VOICE.");

  init_subr_2("voice.unitAvailable", FT_voice_unit_available,
  "(voice.unitAvailable VOICE UNIT)\n\
    Returns true or false whether speech fragment UNIT (string) is\n\
    present in the VOICE");

  init_subr_2("voice.numAvailableCandidates", FT_voice_num_available_candidates,
  "(voice.numAvailableCandidates VOICE UNIT)\n\
    Returns the number of instances of speech fragment UNIT (string)\n\
    present in the VOICE");

  init_subr_1("du_voice_function", FT_du_voice_function,
  "(du_voice_function DU_VOICE)\n\
    Does something to a DU_VOICE only");

  init_subr_2("du_voice.precomputeJoinCosts", FT_du_voice_precomputeJoinCosts,
  "(du_voice.precomputeJoinCosts DU_VOICE PHONELIST)\n\
    Calculate and store the join costs for all instances of phones present\n\
    in the phone list.");

  init_subr_2("du_voice.set_pruning_beam", FT_du_voice_set_pruning_beam,
  "(du_voice.set_pruning_beam DU_VOICE BEAMFLOAT)\n\
    Sets the beam pruning parameter for Viterbi search");

  init_subr_2("du_voice.set_ob_pruning_beam", FT_du_voice_set_ob_pruning_beam,
  "(du_voice.set_ob_pruning_beam DU_VOICE BEAMFLOAT)\n\
    Sets the observation beam pruning parameter for Viterbi search");

  init_subr_2("du_voice.setDiphoneBackoff", FT_du_voice_set_diphonebackoff,
  "(du_voice.setDiphoneBackoff DU_VOICE LIST)\n\
    Adds diphone backoff rules to the voice.");

  init_subr_2("du_voice.setJoinCost", FT_du_voice_setJoinCost,
  "(du_voice.setJoinCost DU_VOICE JOINCOST)\n\
   Sets the voice joincost function.\n\
   If t is specified then the default joincost is used.");

  init_subr_2("du_voice.setTargetCost", FT_du_voice_setTargetCost,
  "(du_voice.setTargetCost DU_VOICE TARGETCOST)\n\
   Sets the voice targetcost  function.\n\
   If t is specified then the default targetcost is used.\n\
   If nil is specified then a null targetcost is used.\n\
   If a closure is specified, this is called as the target cost.");
}



