/*************************************************************************/
/*                                                                       */
/*                Centre for Speech Technology Research                  */
/*                     University of Edinburgh, UK                       */
/*                    Copyright (c) 1994,1995,1996                       */
/*                        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 :  Paul Taylor                           */
/*                       Date   :  April 1994                            */
/* -------------------------------------------------------------------   */
/*                      EST_Track Class source file                      */
/*                                                                       */
/*************************************************************************/

#include <fstream>
#include <iostream>
#include <cmath>
#include "EST_unix.h"
#include "EST_Track.h"
#include "EST_string_aux.h"
#include "EST_TrackFile.h"
#include "EST_error.h"

const int EST_Track::default_sample_rate=16000; // occasionally needed for xmg files
const float EST_Track::default_frame_shift=0.005; // default frame spacing.

const EST_String DEF_FILE_TYPE = "est";

EST_Track::EST_Track()
{
    default_vals();
}

EST_Track::EST_Track(const EST_Track &a)
{ 
    default_vals();
    copy(a);
}

EST_Track::EST_Track(int n_frames, int n_channels)
{
    default_vals();
    p_values.resize(n_frames, n_channels);
    p_times.resize(n_frames);
    p_is_val.resize(n_frames);
    p_channel_names.resize(n_channels);

    p_aux.resize(n_frames, 1);
    p_aux_names.resize(1);

    char d = 0;
    p_is_val.fill(d);
}

EST_Track::EST_Track(int n_frames, EST_TrackMap &map)
{
  int n_channels = map.last_channel()+1;

  default_vals();
  p_values.resize(n_frames, n_channels);
  p_times.resize(n_frames);
  p_is_val.resize(n_frames);
  p_channel_names.resize(n_channels);
  char d = 0;
  p_is_val.fill(d);
  assign_map(map);
}

EST_Track::~EST_Track(void)
{
  //  clear_features();
}

void EST_Track::default_channel_names()
{
    for (int i = 0; i < num_channels(); ++i)
	set_channel_name("track" + itoString(i), i);
}

void EST_Track::default_vals(void)
{
    p_equal_space = FALSE;
    p_single_break = FALSE;
    p_values.resize(0, 0);
    p_times.resize(0);
    p_is_val.resize(0);
    p_aux.resize(0, 0);
    p_aux_names.resize(0);
    p_channel_names.resize(0);
    p_map = NULL;
    p_t_offset=0;
    
    init_features();
}

void EST_Track::set_break(int i) // make location i hold a break
{
    if (i >= num_frames())
	cerr << "Requested setting of break value of the end of the array\n";
    
    p_is_val[i] = 1;
}    


void EST_Track::set_value(int i) // make location i hold a value
{
    p_is_val[i] = 0;
}    

const EST_String EST_Track::channel_name(int i, const EST_ChannelNameMap &map, int strings_override) const
{
    (void)map;
    (void)strings_override;
    return p_channel_names(i);
}    

/* OLD ENUM VERSION 

const EST_String EST_Track::channel_name(int i, const EST_ChannelNameMap &map, int strings_override) const
{
    EST_ChannelType type = channel_unknown;
    
    if (strings_override && p_channel_names(i) != "")
	return p_channel_names(i);
    else if (p_map!=NULL && ((type = p_map->channel_type(i)) != channel_unknown))
    {
	const char *name = map.name(type);
	if (!name)
	    name  = EST_default_channel_names.name(type);
	if (name != NULL)
	  return EST_String(name);
	return "unnamed_channel" + itoString(type);
    }
    else if (!strings_override && p_channel_names(i) != "")
	return p_channel_names(i);
    else 
	return "track" + itoString(i);
}    
*/
void EST_Track::set_channel_name(const EST_String &fn, int i)
{
    p_channel_names[i] = fn;
}    

void EST_Track::set_aux_channel_name(const EST_String &fn, int i)
{
    p_aux_names[i] = fn;
}    

ostream& operator << (ostream& s, const EST_Track &tr)
{
    int i, j;
    for (i = 0; i < tr.num_frames(); ++i)
    {
	s << tr.t(i);
	for (j = 0; j < tr.num_channels(); ++j)
	    s << "\t" << tr(i, j);
	for (j = 0; j < tr.num_aux_channels(); ++j)
	    s << "\t" << tr.aux(i, j);
	s << "\t" << !tr.track_break(i) << endl;
    }
    return s;
}

void EST_Track::copy(const EST_Track& a)
{
    copy_setup(a);
    p_values = a.p_values;
    p_times = a.p_times;
    p_is_val = a.p_is_val;
    p_t_offset = a.p_t_offset;
    p_aux = a.p_aux;
    p_aux_names = a.p_aux_names;
}

void EST_Track::copy_setup(const EST_Track& a)
{
    p_equal_space = a.p_equal_space;
    p_single_break = a.p_single_break;
    p_channel_names = a.p_channel_names;
    p_map = a.p_map;
    copy_features(a);
}    

void EST_Track::resize(int new_num_frames, int new_num_channels, bool set)
{
    int old_num_frames = num_frames();

    if (new_num_frames<0)
	new_num_frames = num_frames();

    if (new_num_channels<0)
	new_num_channels = num_channels();

    p_channel_names.resize(new_num_channels);

    // this ensures the new channels have a default name
    if (new_num_channels > num_channels())
	for (int i = num_channels(); i < new_num_channels; ++i)
	    set_channel_name("track_" + itoString(i), i);

    p_values.resize(new_num_frames, new_num_channels, set);
    p_times.resize(new_num_frames, set);
    p_is_val.resize(new_num_frames, set);

    p_aux.resize(new_num_frames, num_aux_channels(), set);
  
    // Its important that any new vals get set to 0
    for (int i = old_num_frames; i < num_frames(); ++i)
	p_is_val.a_no_check(i) = 0;

}

static void map_to_channels(EST_StrList &channel_map, 
		     EST_StrList &channel_names)
{
    EST_Litem *p;
    EST_String b, type, first, last;
    int n_f, n_l;

    for (p = channel_map.head(); p; p = next(p))
    {
	b = channel_map(p);
	if (b.matches("$", 0))
	{
	    // do this backwards as types may have "_" in them
	    b = b.after("$");
	    if (!b.contains("-"))
	    {
		cerr<<"Ill formed coefficient range in map: " << b << "\n";
		return;
	    }
	    type = b.before("-");
	    first = b.after("-");

	    if (!first.contains("+"))
	    {
		cerr<<"Ill formed coefficient range in map: "<<first<<"\n";
		return;
	    }

	    last = first.after("+");
	    first = first.before("+");
		
	    n_f = Stringtoi(first);
	    n_l = Stringtoi(last);

	    for (int i = n_f; i < n_l; ++i)
		channel_names.append(type + "_" + itoString(i));
	    channel_names.append(type + "_N");
	}
	else
	    channel_names.append(b);
    }
}

void EST_Track::resize(int new_num_frames, EST_StrList &new_channels, bool set)
{
    EST_StrList x;
    map_to_channels(new_channels, x);
    int i;
    EST_Litem *p;

    int new_num_channels;
    new_num_channels = x.length();

    if (new_num_frames<0)
	new_num_frames = num_frames();


    p_channel_names.resize(new_num_channels);
    // this ensures the new channels have a default name

    for (i = 0, p = x.head(); p ; p = next(p), ++i)
	set_channel_name(x(p), i);

    p_values.resize(new_num_frames, new_num_channels, set);
    p_times.resize(new_num_frames, set);
    p_is_val.resize(new_num_frames, set);
  
//  for (int i = 0; i < new_num_frames; ++i)
//    p_is_val.a_no_check(i) = 1;
}

void EST_Track::resize_aux(EST_StrList &new_aux_channels, bool set)
{
    int i;
    EST_Litem *p;

    int new_num_channels;
    new_num_channels = new_aux_channels.length();

    p_aux_names.resize(new_num_channels);

    // this ensures the new channels have a default name
    for (i = 0, p = new_aux_channels.head(); p ; p = next(p), ++i)
	set_aux_channel_name(new_aux_channels(p), i);

    p_aux.resize(num_frames(), new_num_channels, set);
}

EST_Track& EST_Track::operator+=(const EST_Track &a) // add to existing track
{
    int i, j, k;
    
    if (num_frames() == 0)	// i.e. no existing EST_Track to add to
    {
	*this = a;
	return *this;
    }
    
    if (a.num_channels() != num_channels())
    {
	cerr << "Error: Tried to add " << a.num_channels() << 
	    " channel EST_Track to "<<num_channels() << " channel EST_Track\n";
	return *this;
    }
    
    int old_num = num_frames();
    float old_end = end();
    this->resize(a.num_frames()+ this->num_frames(), this->num_channels());
    for (i = 0, j = old_num; i < a.num_frames(); ++i, ++j)
    {
	for (k = 0; k < num_channels(); ++k)
	    p_values.a_no_check(j, k) = a(i, k);
	p_times[j] = old_end + a.t(i);
	p_is_val[j] = a.p_is_val(i);
    }
    
    return *this;
}

EST_Track& EST_Track::operator|=(const EST_Track &a)
{				// add to existing track in parallel
    int i, j, k;
    
    if (num_channels() == 0)	// i.e. no existing EST_Track to add to
    {
	*this = a;
	return *this;
    }
    
    if (a.num_frames() != num_frames())
    {
	cerr << "Error: Tried to add " << a.num_frames() << 
	    " channel EST_Track to "<<num_frames()<< " channel EST_Track\n";
	return *this;
    }
    
    int old_num = num_channels();
    this->resize(a.num_frames(), this->num_channels() + a.num_channels());
    for (i = 0, j = old_num; i < a.num_channels(); ++i, ++j)
	for (k = 0; k < num_frames(); ++k)
	    p_values.a_no_check(k, j) = a(k, i);
    
    return *this;
}

EST_Track &EST_Track::operator=(const EST_Track& a)
{
    copy(a);
    return *this;
}    


int EST_Track::channel_position(const char *name, int offset) const
{
    int c;
    
    for (c=0; c<num_channels(); c++)
	if (channel_name(c) == name)
	    return c+offset;
    
    return -1;
}

float &EST_Track::a(int i, const char *name, int offset)
{ 
    int c;
    
    for (c=0; c<num_channels(); c++)
	if (channel_name(c) == name)
	    return p_values.a_no_check(i, c+offset);
    
    cerr << "no channel '" << name << "'\n";
    return *(p_values.error_return);
}

EST_Val &EST_Track::aux(int i, const char *name)
{ 
    for (int c = 0; c < num_aux_channels(); c++)
	if (aux_channel_name(c) == name)
	    return p_aux.a_no_check(i, c);
    
    cerr << "no auxiliary channel '" << name << "' found\n";
    return *(p_aux.error_return);
}

EST_Val &EST_Track::aux(int i, int c)
{ 
    return p_aux(i, c);
}

EST_Val &EST_Track::aux(int i, int c) const
{ 
    return ((EST_Track *)this)->aux(i,c);
}

#define EPSILON (0.0001)

float &EST_Track::a(float t, int c, EST_InterpType interp)
{
    static float ia = 0.0;
    
    if (interp == it_nearest)
	return p_values.a_no_check(index(t), c);
    else if (interp == it_linear)
    {
	int i = index_below(t);
	if (i < 0)
	    return a(0,c);
	
	float n = a(i,c), n1 = a(i+1,c);
	float tn = p_times(i), tn1 = p_times(i+1);
	ia = n + (n1-n)*(t-tn)/(tn1-tn);
	return ia;
    }
    else if (interp == it_linear_nz)
    {
	int i = index_below(t);
	if (i < 0)
	    return a(0,c);
	
	float n = a(i,c), n1 = a(i+1,c);
	if (fabs(n) < EPSILON || fabs(n1) < EPSILON)
	    return p_values.a_no_check(index(t), c);
	float tn = p_times(i), tn1 = p_times(i+1);
	ia = n + (n1-n)*(t-tn)/(tn1-tn);
	return ia;
    }
    return ia;
}

int EST_Track::index(float x) const
{
    if (equal_space())
    {
	float s = shift();
	int f = (int)( (x-t(0))/s+0.5 ); //don't assume track starts at t=0.0
	if (f<0)
	    f=0;
	else if (f>= num_frames())
	    f=num_frames()-1;
	return f;
    }
    else if (num_frames() > 1) //if single frame, return that index (0)
    {
	int bst, bmid, bend;
	bst = 1;
	bend = num_frames();
	if (x < p_times.a_no_check(bst))
	    bmid=bst;
	if (x >= p_times.a_no_check(bend-1))
	    bmid=bend-1;
	else
	{
	    while (1)
	    {
		bmid = bst + (bend-bst)/2;
		if (bst == bmid)
		    break;
		else if (x < p_times.a_no_check(bmid))
		{
		    if (x >= p_times.a_no_check(bmid-1))
			break;
		    bend = bmid;
		}
		else
		    bst = bmid;
	    }
	}
	if (fabs(x - p_times.a_no_check(bmid)) < 
	    fabs(x - p_times.a_no_check(bmid-1)))
	    return bmid;
	else
	    return bmid - 1;
    }
    
    return num_frames() -1;
}

int EST_Track::index_below(float x) const
{
    if (equal_space())
    {
	float s = shift();
	int f = (int)(x/s);
	if (f<0)
	    f=0;
	else if (f>= num_frames())
	    f=num_frames()-1;
	return f;
    }
    else
    {
	for (int i = 1; i < num_frames(); ++i)
	    if (x <= p_times.a_no_check(i))
		return i - 1;
	return num_frames()-1;
    }
}

int EST_Track::val(int i) const
{
    return !p_is_val(i);
}
/*
   "p_equal_space" indicates whether the x-axis values are evenly spaced
   (FIXED) or spaced arbitrarily (VARI).
   
   "p_single_break" describes the break format. F0 contours are seldom
   continuous - often breaks occur due to unvoicing etc. These are a
   marked  in the data arrays by break values, "i_break" for ints
   and "f_break" for floats. The "p_single_break" channel specifies whether
   a break is represented by a single break value, or as a break
   value for every frame. eg
   (SINGLE)
   800  100
   810  105
   BREAK BREAK
   850  130
   860  135
   or
   (MANY)
   800  100
   810  105
   820 BREAK
   830 BREAK
   840 BREAK
   850  130
   860  135
   
   In the MANY case, only the y value is specified as a break, in the
   SINGLE case the x value may or may not be specified as a break. For
   this reason, when checking for breaks, it is useful to only rely
   on the y value being set to the i_break value. Not that if the single_break
   is MANY and the equal_space is FIXED, you dont really need x-axis
   values.
   
   Different functions naturally work better on different representations
   and that is why all these different types are supported. A
   general function mod_cont() is supplied to change from one
   type to another. Not all conversions are currently
   supported however.
   
   */

float EST_Track::end() const
{
    if (num_frames() == 0)
	return 0.0;
    else
	return (p_times(prev_non_break(num_frames())));
}
float EST_Track::start() const
{
    if (num_frames() == 0)
	return 0.0;
    else
	return (track_break(0) ? p_times(next_non_break(0)) : p_times(0));
}

float EST_Track::shift() const
{
    int j1 = 0;
    int j2 = 0;
    
    if (!p_equal_space)
	EST_error("Tried to take shift from non-fixed contour\n");

    do
    {
	j1 = next_non_break(++j1);
	j2 = next_non_break(j1);
	//	cout << "j1:" << j1 << " j2:" << j2 << endl;
    }
    while ((j2 != 0) && (j2 != (j1 +1)));
    
    if (j2 == 0)
    {
	if (num_frames() > 1)
	    return p_times(1) - p_times(0);
	else
	    EST_error("Couldn't determine shift size\n");	    

    }
    return (p_times(j2) - p_times(j1));
}

/* tries to find the next value that isnt a break. Dont really
   know what to do on a fail, so just return 0 */

int EST_Track::next_non_break(int j) const
{
    int i = j;
    for (++i; i < num_frames(); ++i)
    {
	//	cout << "i: " << i << " " << value[i] << endl;
	if (!track_break(i))
	    return i;
    }
    
    return 0;
}

/* give the current point, returns the previous non-break */

int EST_Track::prev_non_break(int j) const
{
    int i = j;
    for (--i; i >= 0 ; --i)
	if (!track_break(i))
	    return i;
    return 0;
}

void EST_Track::change_type(float nshift, bool single_break)
{
    if (nshift != 0.0)
    {
	if (!p_equal_space || nshift != shift())
	    sample(nshift);
	p_equal_space = TRUE;
    }
    
    if (single_break != p_single_break)
    {
	if (!p_single_break)
	    pad_breaks();
	else 
	    rm_excess_breaks();
    }
}

void EST_Track::sample(float f_interval)
{
    EST_FVector new_times;
    EST_FMatrix new_values;
    EST_CVector new_is_break;
    int i, j, n;
    
    n = (int) rint(((end())/ f_interval));
    
    new_times.resize(n);
    new_values.resize(n, num_channels());
    new_is_break.resize(n);
    
    // REORG - can this be replaced with fill_time()?
    for (i = 0; i < n; ++i)
	new_times[i] = (float) ((i + 1) * f_interval);
    
    for (i = 0; i < n; ++i)
    {
	new_is_break[i] = !interp_value(new_times(i), f_interval);
	for (j = 0; j < num_channels(); ++j)
	    new_values(i, j) = !new_is_break(i) ? interp_amp(new_times(i), j, f_interval): 0.0;
    }
    
    p_times = new_times;
    p_values = new_values;
    p_is_val = new_is_break;
    p_single_break = FALSE;
    p_equal_space = TRUE;
}

float EST_Track::interp_amp(float x, int c, float fl)
{
    int i;
    float x1, x2, y1, y2, m;
    
    for (i = 0; i < num_frames(); ++i)
	if ((p_times(i) + (fl / 2.0))> x)
	    break;
    
    if (i == num_frames())
	return p_values.a_no_check(i - 1,c);
    if (i == 0)
	return p_values.a_no_check(0, c);
    
    if (track_break(i) && track_break(i - 1))
	return 0.0;
    
    if (track_break(i))
	return p_values.a_no_check(i - 1, c);
    
    else if (track_break(i - 1))
	return p_values.a_no_check(i, c);
    
    x1 = p_times(i - 1);
    y1 = p_values.a_no_check(i - 1, c);
    x2 = p_times(i);
    y2 = p_values.a_no_check(i, c);
    
    m =  (y2 - y1) / (x2 -x1);
    return ((x - x1) * m) + y1;
}		

int EST_Track::interp_value(float x, float fl)
{
    int i;
    int p, n;
    float cf;
    
    if (p_equal_space)
	cf = shift();
    else
	cf = estimate_shift(x);
    
    for (i = 0; i < num_frames(); ++i)
	if ((p_times(i) + (fl / 2.0))> x)
	    break;
    // This was:    
    //    for (i = 0; i < num_frames(); ++i)
    //	if (p_times[i] > x)
    //	    break;
    
    if (i == 0)			// must be a break for the first value. (can't have i -1).
	return FALSE;
    
    if ((!track_break(i)) && (!track_break(i -1)))
	return TRUE;
    
    p = prev_non_break(i);
    n = next_non_break(i);
    
    if ((x < p_times(p) + (cf / 2.0)) || (x > p_times(n) - (cf / 2.0)))
	return TRUE;		// rounding at edges
    
    return FALSE;
}

float EST_Track::estimate_shift(float x)
{
    int i, j;
    for (j = 0; j < num_frames(); ++j)
	if (p_times(j) > x)
	    break;
    
    for (i = j; i > 0; --i)
	if ((!track_break(i)) && (!track_break(i - 1)))
	    return p_times(i) - p_times(i - 1);
    
    for (i = j; i < num_frames() - 1; ++i)
	if ((!track_break(i)) && (!track_break(i + 1)))
	    return p_times(i + 1) - p_times(i);
    
    return 5.0;			// default value
}

void EST_Track::fill_time( float t, int start )
{
  unsigned int nframes = num_frames();
  
  for( unsigned int i=0; i<nframes; ++i )
    p_times.a_no_check(i) = t * (float) (i + start);
}

void EST_Track::fill_time( float t, float startt )
{
  unsigned int nframes = num_frames();
  
  for( unsigned int i=0; i<nframes; ++i )
    p_times.a_no_check(i) = startt + (t * (float)i);
}

void EST_Track::fill_time( const EST_Track &t )
{
  unsigned int nframes = num_frames();

  for( unsigned int i=0; i<nframes; ++i )
    p_times.a_no_check(i) = t.t(i);
}

void EST_Track::rm_excess_breaks()
{
    int i, j, k;
    EST_FVector new_times;
    EST_CVector new_is_break;
    EST_FMatrix new_values;
    
    new_values.resize(num_channels(), num_frames());
    new_times.resize(num_frames());
    new_is_break.resize(num_frames());
    
    for (i = 0; track_break(i); ++i); //rm leading breaks
    
    for (j = 0; i < num_frames(); ++i, ++j)
    {
	for (k = 0; k < num_channels(); ++k)
	    new_values(j, k) = p_values.a_no_check(i, k);
	new_times[j] = p_times(i);
	new_is_break[j] = p_is_val(i);
	while ((!new_is_break(j)) && (!val(i + 1)))
	    ++i;
    }
    p_times = new_times;
    p_values = new_values;
    p_is_val = new_is_break;
    for (--j; track_break(j); --j) // "rm" trailing breaks
	;
    p_times.resize(num_frames());
    p_values.resize(num_frames(), num_channels());
    p_is_val.resize(num_frames());
    
    p_single_break = TRUE;
}    

void EST_Track::rm_trailing_breaks()
{
    if (num_frames() <=0 )
	return;
    int start, end;

    for (start = 0; start < num_frames(); ++start)
	if (!track_break(start))
	    break;

    for(end=num_frames(); end>0; end--)
      if (!track_break(end-1))
	    break;

    if (start==0 && end==num_frames())
      return;
    
    for (int i=start, j = 0; i < end; ++i, ++j)
    {
	p_times[j] = p_times(i);
	for (int k = 0; k < num_channels(); k++)
	    a_no_check(j, k) = a_no_check(i, k);
	p_is_val[j] = p_is_val(i);
    }

    p_values.resize(end-start, EST_CURRENT, 1);
    
    p_times.resize(num_frames());
    p_is_val.resize(num_frames());
}    

void EST_Track::add_trailing_breaks()
{
    int i, j, k;
    EST_FVector new_times;
    EST_FMatrix new_values;
    int new_num = num_frames();
    
    if (!track_break(0))
	new_num++;
    if (!track_break(num_frames() - 1))
	new_num++;
    
    if (new_num == num_frames()) /*ie trailing breaks already there */
	return;
    
    new_times.resize(new_num);
    new_values.resize(num_channels(), new_num);
    
    j = 0;
    if (!track_break(j))
	set_break(j);
    
    for (i = 0; i < num_frames(); ++i, ++j)
    {
	new_times[j] = p_times(i);
	for (k = 0; k < num_channels(); ++k)
	    new_values(j, k) = p_values.a_no_check(i, k);
    }
    
    if (!track_break(num_frames() - 1))
	set_break(j);
    
    p_times = new_times;
    p_values = new_values;
    p_times.resize(num_frames());
    p_values.resize(num_frames(), num_channels());
}    

void EST_Track::pad_breaks()
{
    if (!p_single_break)
	return;
    
    if (!p_equal_space)
	EST_error("pad_breaks: Can only operate on fixed data\n");
    
    EST_FVector new_times;
    EST_FMatrix new_values;
    EST_CVector new_is_break;
    int i, j, k, n;
    
    n = (int)(((end())/ shift()) + 1.0);
    int s = int(start()/ shift());
    
    for (i = 0; i < n; ++i)
    {
	new_times[i] = (float) (i * shift());
	for (k = 0; k < num_channels(); ++k)
	    new_values(k, i) = 0.0;
	new_is_break[i] = 0;
    }
    
    for (i = 0, j = s; j < n; ++i, ++j)
    {
	if (track_break(i))
	{
	    for (; new_times(j) < p_times(i + 1); ++j);
	    --j;
	}
	else
	{
	    new_is_break[j] = 1;
	    for (k = 0; k < num_channels(); ++k)
		new_values(j, k) = p_values.a_no_check(i, k);
	}
    }
    new_is_break[j] = 1;
    for (k = 0; k < num_channels(); ++k)
	new_values(j, k) = p_values.a_no_check(i, k);
    
    p_times = new_times;
    p_values = new_values;
    p_is_val = new_is_break;
    
    p_times.resize(num_frames());
    p_is_val.resize(num_frames());
    p_values.resize(num_frames(), num_channels());
    
    p_single_break = FALSE;
}    

static bool bounds_check(const EST_Track &t, int f, int c, int set)
{
  const char *what = set? "set" : "access";

  if (f<0 || f >= t.num_frames())
    {
      cerr << "Attempt to " << what << " frame " << f << " of " << t.num_frames() << " frame track\n";
      return FALSE;
    }
  if (c<0 || c >= t.num_channels())
    {
      cerr << "Attempt to " << what << " channel " << c << " of " << t.num_channels() << " channel track\n";
      return FALSE;
    }

return TRUE;
}

static bool bounds_check(const EST_Track &t, 
			 int f, int nf,
			 int c, int nc,
			 int set)
{
  const char *what = set? "set" : "access";

  if (nf>0)
    {
      if (f<0 || f >= t.num_frames())
	{
	  cerr << "Attempt to " << what << " frame " << f << " of " << t.num_frames() << " frame track\n";
	  return FALSE;
	}
      if (f+nf-1 >= t.num_frames())
	{
	  cerr << "Attempt to " << what << " frame " << f+nf-1 << " of " << t.num_frames() << " frame track\n";
	  return FALSE;
	}
    }

  if (nc>0)
    {
      if (c<0 || c >= t.num_channels())
	{
	  cerr << "Attempt to " << what << " channel " << c << " of " << t.num_channels() << " channel track\n";
	  return FALSE;
	}
      if (c+nc-1 >= t.num_channels())
	{
	  cerr << "Attempt to " << what << " channel " << c+nc-1 << " of " << t.num_channels() << " channel track\n";
	  return FALSE;
	}
    }

return TRUE;
}

float &EST_Track::a(int i, int c)
{
  if (!bounds_check(*this, i,c,0))
      return *(p_values.error_return);

  return p_values.a_no_check(i,c);
}

float EST_Track::a(int i, int c) const
{
  return ((EST_Track *)this)->a(i,c);
}

int EST_Track::empty() const
{
    int i, num;
    
    for (i = num = 0; i < num_frames(); ++i)
	if (val(i))
	    return 0;		// i.e. false
    
    return 1;			// i.e. true
}

void EST_Track::channel(EST_FVector &cv, const char * name, int startf, int nf)
{
    int n;
    if ((n = channel_position(name)) == -1)
    {
	cerr << "No such channel " << name << endl;
	return;
    }
    channel(cv, n, startf, nf);
}

void EST_Track::sub_track(EST_Track &st,
			  int start_frame, int nframes,
			  const EST_String &start_chan_name, int nchans)
{
    int start_chan;
    if (start_chan_name == "")
	start_chan = 0;

    if ((start_chan = channel_position(start_chan_name)) == -1)
	EST_error("sub_track: No such channel %s\n", 
		  (const char *)start_chan_name);

    sub_track(st, start_frame, nframes, start_chan, nchans);
}

void EST_Track::sub_track(EST_Track &st,
			  int start_frame, int nframes,
			  const EST_String &start_chan_name,
			  const EST_String &end_chan_name) 
{
    int start_chan, end_chan, nchans=0;

    if ((start_chan = channel_position(start_chan_name)) == -1)
	EST_error("sub_track: No such channel %s\n", 
		  (const char *)start_chan_name);

    if (end_chan_name == "")
	nchans = EST_ALL;
    else
    {
	if ((end_chan = channel_position(end_chan_name)) == -1)
	    EST_error("sub_track: No such channel %s\n", 
		      (const char*)end_chan_name);
	else
	    nchans = end_chan - start_chan + 1;
    }

    sub_track(st, start_frame, nframes, start_chan, nchans);
}

void EST_Track::sub_track(EST_Track &st,
			  int start_frame, int nframes,
			  int start_chan, int nchans)
{
  if (nframes <0)
    nframes = num_frames() - start_frame;
  if (nchans <0)
    nchans = num_channels() - start_chan;

  if (!bounds_check(*this, start_frame, nframes, start_chan, nchans, 0))
    return;

  p_values.sub_matrix(st.p_values, start_frame, nframes, start_chan, nchans);

  p_times.sub_vector(st.p_times, start_frame, nframes);

  p_is_val.sub_vector(st.p_is_val, start_frame, nframes);

  p_channel_names.sub_vector(st.p_channel_names,   start_chan, nchans);

  // All auxiliary information is included. These are effectively
  // pointer statements

  p_aux.sub_matrix(st.p_aux, start_frame, nframes, 0, EST_ALL);
  p_aux_names.sub_vector(st.p_aux_names, 0, EST_ALL);

  st.p_t_offset = p_t_offset;
  
  st.p_equal_space = p_equal_space;
  st.p_single_break = p_single_break;
  st.copy_features(*this);

  if (p_map!=0)
    st.p_map = new EST_TrackMap(p_map, start_chan, EST_TM_REFCOUNTED);
  else
    st.p_map = NULL;
}


void EST_Track::copy_sub_track(EST_Track &st,
			       int start_frame, int nframes,
			       int start_chan, int nchans) const
{
  if (nframes <0)
    nframes = num_frames() - start_frame;
  if (nchans <0)
    nchans = num_channels() - start_chan;

  if (!bounds_check(*this, start_frame, nframes, start_chan, nchans, 0))
    return;

  st.resize(nframes, nchans);

  for (int ff=0; ff<nframes; ff++)
    {
      st.p_times.a(ff) = p_times.a(ff+start_frame);
      st.p_is_val.a(ff) = p_is_val.a(ff+start_frame);
      for (int c=0; c<nchans; c++)
	st.p_values.a(ff,c) = p_values.a(ff+start_frame,c+start_chan);
    }

  for (int c=0; c<nchans; c++)
    st.p_channel_names.a(c) = p_channel_names.a(c+start_chan);

  st.p_aux = p_aux;
  st.p_aux_names = p_aux_names;

  st.p_equal_space = p_equal_space;
  st.p_single_break = p_single_break;

  st.copy_features(*this);

  if (p_map!=0)
    st.p_map = new EST_TrackMap(p_map, start_chan, EST_TM_REFCOUNTED);
  else
    st.p_map = NULL;
}

void EST_Track::copy_sub_track_out( EST_Track &st, const EST_FVector& frame_times ) const
{
  int f_len = frame_times.length();
  int nchans = num_channels();
  st.resize( f_len, nchans );

  for( int i=0; i<f_len; ++i ){
    
    int source_index = index(frame_times(i));
    
    st.p_times.a(i) = p_times.a( source_index );
    st.p_is_val.a(i) = p_is_val.a( source_index );
      
    for( int c=0; c<nchans; c++ )
      st.p_values.a(i,c) = p_values.a(source_index,c);
  }
  
  st.copy_setup( *this );
  st.set_equal_space( false ); //might not be true, but it's a better default
  
  //  st.p_aux = p_aux;
  //  st.p_aux_names = p_aux_names;
}

void EST_Track::copy_sub_track_out( EST_Track &st, const EST_IVector& frame_indices ) const
{
  int f_len = frame_indices.length();
  int nchans = num_channels();
  st.resize( f_len, nchans );

  int last_index = num_frames()-1;
  
  for( int i=0; i<f_len; ++i ){

    int source_index = frame_indices(i);

    if( source_index <= last_index ){

      st.p_times.a(i) = p_times.a( source_index );
      st.p_is_val.a(i) = p_is_val.a( source_index );

      for( int c=0; c<nchans; c++ )
	st.p_values.a(i,c) = p_values.a(source_index,c);
    }
  }

  st.copy_setup( *this );
  st.set_equal_space( false ); //might not be true, but it's a better default

  //  st.p_aux = p_aux;
  //  st.p_aux_names = p_aux_names;
}



EST_write_status EST_Track::save(const EST_String filename, 
				 const EST_String type)
{
    EST_String save_type = (type == "") ? DEF_FILE_TYPE : type;

    EST_TrackFileType t = EST_TrackFile::map.token(save_type);
    
    if (t == tff_none)
    {
	cerr << "Unknown Track file type " << save_type << endl;
	return write_fail;
    }
    
    EST_TrackFile::Save_File * s_fun = EST_TrackFile::map.info(t).save;
    
    if (s_fun == NULL)
    {
	cerr << "Can't save tracks to files type " << save_type << endl;
	return write_fail;
    }
    
    return (*s_fun)(filename, *this);
}

EST_write_status EST_Track::save(FILE *fp, const EST_String type)
{
    EST_TrackFileType t = EST_TrackFile::ts_map.token(type);
    
    if (t == tff_none)
    {
	cerr << "Unknown Track file type " << type << endl;
	return write_fail;
    }
    
    EST_TrackFile::Save_TokenStream * s_fun = 
	EST_TrackFile::ts_map.info(t).save;
    
    if (s_fun == NULL)
    {
	cerr << "Can't save tracks to files type " << type << endl;
	return write_fail;
    }
    return (*s_fun)(fp, *this);
}

EST_read_status EST_Track::load(EST_TokenStream &ts, float ishift, float startt)
{
    EST_read_status stat = read_error;
    
    for (int n = 0; n < EST_TrackFile::ts_map.n(); n++)
    {
	EST_TrackFileType t = EST_TrackFile::ts_map.token(n);
	
	if (t == tff_none)
	    continue;

	EST_TrackFile::TS_Info *info = &(EST_TrackFile::ts_map.info(t));
	
	if (! info->recognise)
	    continue;
	
	EST_TrackFile::Load_TokenStream * l_fun =info->load;
	
	if (l_fun == NULL)
	    continue;
	
	stat = (*l_fun)(ts, *this, ishift, startt);
	
	if (stat != read_format_error)
	  {
	    if (stat == read_ok)
	      set_file_type(t);
	    break;
	  }
    }
    
    return stat;
}

EST_read_status EST_Track::load(const EST_String filename, float ishift, float startt)
{
    EST_read_status stat = read_error;
    
    for(int n=0; n< EST_TrackFile::map.n() ; n++)
    {
	EST_TrackFileType t = EST_TrackFile::map.token(n);
	
	if (t == tff_none)
	    continue;
	
	
	EST_TrackFile::Info *info = &(EST_TrackFile::map.info(t));
	
	if (! info->recognise)
	    continue;
	
	EST_TrackFile::Load_File * l_fun =info->load;
	
	if (l_fun == NULL)
	    continue;
	
	stat = (*l_fun)(filename, *this, ishift, startt);
	
	if (stat == read_ok)
	{
	    set_file_type(t);
	    break;
	}
	else if (stat == read_error)
	    break;
    }
    
    return stat;
}

EST_read_status EST_Track::load(const EST_String filename, const EST_String type, float ishift, float startt)
{
    EST_TrackFileType t = EST_TrackFile::map.token(type);
    
    if (t == tff_none)
    {
	cerr << "Unknown Track file type " << type << endl;
	return read_error;
    }
    
    EST_TrackFile::Load_File * l_fun = EST_TrackFile::map.info(t).load;
    
    if (l_fun == NULL)
    {
	cerr << "Can't load tracks from file type" << type << endl;
	return read_error;
    }
    
    set_file_type(t);
    return (*l_fun)(filename, *this, ishift, startt);
}

EST_write_status EST_Track::save_channel_names(const EST_String filename)
{
    FILE *file;
    
    if ((file=fopen(filename, "wb"))==NULL)
	return write_fail;
    
    for(int c=0; c<num_channels(); c++)
	fprintf(file, "%s\n", (const char *)channel_name(c));
    
    fclose(file);
    
    return write_ok;
}

EST_read_status EST_Track::load_channel_names(const EST_String filename)
{
    FILE *file;
    static const int buffer_length = 100;
    char buffer[buffer_length];
    
    if ((file=fopen(filename, "rb"))==NULL)
	return misc_read_error;
    
    for(int c=0; c<num_channels(); c++)
    {
	if (!fgets(buffer, buffer_length, file))
	    break;
	
	buffer[strlen(buffer)-1] = '\0';
	set_channel_name(buffer, c);
    }
    
    
    fclose(file);
    
    return format_ok;
}

/* code from here down should be deleted once tracp mapping is modified */


float &EST_Track::a(float t, EST_ChannelType type, EST_InterpType interp)
{ 
    short c = NO_SUCH_CHANNEL;
    
    if (p_map!=0 && (c = p_map->get(type)) != NO_SUCH_CHANNEL)
	return a(t, c, interp);
    else
    {
	cerr << "no channel '" << EST_default_channel_names.name(type) << "' = " << (int)type << "\n";
    }
    return *(p_values.error_return);
}


void EST_Track::assign_map(EST_TrackMap::P map)
{
    p_map = map;
}

void EST_Track::create_map(EST_ChannelNameMap &names)
{
    EST_TrackMap::P map = new EST_TrackMap(EST_TM_REFCOUNTED);
    
    for (int i = 0; i < num_channels(); i++)
    {
	EST_ChannelType type = names.token(p_channel_names(i));
	
	if (type != channel_unknown)
	  map->set(type, i);
    }
    
    assign_map(map);
}


void EST_Track::resize(int new_num_frames, EST_TrackMap &map)
{
    resize(new_num_frames, map.last_channel()+1);
    assign_map(map);
}



int EST_Track::channel_position(EST_ChannelType type, int offset) const
{ 
    if (p_map!=0)
    {
	int p = (*p_map)(type);
	return (p!= NO_SUCH_CHANNEL)?(p+offset): NO_SUCH_CHANNEL;
    }
    return channel_position(EST_default_channel_names.name(type), offset);
}

float &EST_Track::a(int i, EST_ChannelType type, int offset)
{ 
    short c = NO_SUCH_CHANNEL;
    
    if (p_map!=0 && ((c = p_map->get(type)) != NO_SUCH_CHANNEL))
	return p_values.a_no_check(i, c+offset);
    else
    {
	cerr << "no channel '" << EST_default_channel_names.name(type) << "' = " << (int)type << "\n";
    }
    
    return *(p_values.error_return);
}

EST_Track::IPointer_f::IPointer_f()
{
  frame = new EST_Track();
}

EST_Track::IPointer_f::IPointer_f(const IPointer_f &p)
{
  frame=new EST_Track(*(p.frame));
}

EST_Track::IPointer_f::~IPointer_f()
{
  if (frame != NULL)
    {
      delete frame;
      frame=NULL;
    }
}


#if defined(INSTANTIATE_TEMPLATES)

Instantiate_TIterator_T(EST_Track, EST_Track::IPointer_f, EST_Track, Track_itt)

#endif
