#ifndef __ELASTIN_H
#define __ELASTIN_H

#include <inttypes.h>
#include <math.h>
#include <stdlib.h>

typedef float el_sample_t;
typedef int32_t el_nframes_t;

// Uncomment this to enable verbose debug messages
#define PRINT_DEBUG_MESSAGES
#ifdef PRINT_DEBUG_MESSAGES
#define DEBUGPRINT printf
#else
#define DEBUGPRINT donothing
#endif
  
inline void donothing(...) {
  return;
};

// Convert cents to frequency ratio
#define CENTS_TO_FREQ_RATIO(n) (powf(2.0,((float) (n)/1200)))

// SynthMaxScore causes certain chunks with bad scores to be disregarded
// for crossfading during stretching. But it requires an extra 'calibration'
// step to calibrate the chunk scores after computation.
// #define USE_SYNTHMAX_SCORE

// Loop points can be computed for smooth, continuous looping of the sample
#define COMPUTE_LOOP_POINTS

class Elastin_Jump {
#define JUMPFLAG_STATIC 1 // Jump is static (not to be repeated/skipped)
#define JUMPFLAG_LOOPPT 2 // Jump is a loop point jump

 public:
  Elastin_Jump(el_nframes_t j1, el_nframes_t j2, el_nframes_t jlen, 
	       float score, char jumpflags = 0) : 
    j1(j1), j2(j2), jlen(jlen), score(score), jumpflags(jumpflags), next(0) {
    if (jumpflags & JUMPFLAG_STATIC)
      DEBUGPRINT("static len: %d\n",jlen);
  };

  // Returns nonzero if j1>j2 specifies a -static- chunk
  inline char IsStatic() {
    return (jumpflags & JUMPFLAG_STATIC);
  };

  // Returns nonzero if j2>j1 specifies a -loop point- jump
  inline char IsLoopPt() {
    return (jumpflags & JUMPFLAG_LOOPPT);
  };
  
  // Returns nonzero if j1>j2 specifies a -static- chunk,
  // or if this chunk's score in relation to 'maxscore' effectively makes
  // this chunk static
  inline char IsStatic(float maxscore) {
#ifdef USE_SYNTHMAX_SCORE
    return 
      ((jumpflags & JUMPFLAG_STATIC) || score > maxscore);
#else
    return (jumpflags & JUMPFLAG_STATIC);
#endif
  };

  // Jump point from j1 -> j2, corresponding length and score
  el_nframes_t j1, j2, jlen;
  float score; 
  unsigned char jumpflags; // Special flags for this jump

  Elastin_Jump *next;
};

class Elastin_SampleFeed {
 public:
  virtual ~Elastin_SampleFeed() {};

  // A SampleFeed has one duty- to return samples when requested-- 
  // Notice that we require random access to samples-- return sample at
  // position 'pos', with 'cnt' length
  // 
  // GetSamples should store the sample data in the provided 'bufs'
  // and return zero if successful.
  //
  // Bufs is an array of buffers, one per channel. The bufs array and sample
  // buffers are provided. You need only fill them.
  virtual int GetSamples (el_nframes_t pos, el_nframes_t cnt,
			  el_sample_t **bufs) = 0;

  // Return total length of sample
  virtual el_nframes_t GetLength () = 0;

  // Return total number of audio channels
  virtual int GetNumChannels () = 0;
};

typedef enum {
  EL_StaticRun,
  EL_StaticRun_EarlyFade,
  EL_StaticRun_LoopPt,
  EL_Run,
  EL_Run_LoopPt,
  EL_Fade,
} Elastin_SynthStatus;

typedef enum {
  CF_Back,
  CF_Forward,
  CF_LoopPt,
} Elastin_CrossfadeType;

// Elastin data associated with a sample
// This data is generated by class Elastin in an analysis pass,
// and used by class Elastin in a synthesis pass
class Elastin_Data {
  friend class Elastin;

 public:
  Elastin_Data() : analysisdone(0),
    feed(0), slen(0), firstj(0), lastj(0), loopj(0) {};
  ~Elastin_Data();

 private:

  inline void AddJump (Elastin_Jump *nw) {
    // Singly linked list with first & last pointers--
    // Quick adding at end of list,
    // Forward-only scanning
    if (firstj == 0) {
      firstj = nw;
      lastj = nw;
    } else {      
      lastj->next = nw;
      lastj = nw;
    }
  };

  // Analysis parameters
  char analysisdone; // Nonzero if analysis is complete  
  el_nframes_t minchunk, // Minimum length of chunk
    maxchunk,            // Maximum length of chunk
    cmpchunk;            // Compare (correlate) length for chunk

  // General
  Elastin_SampleFeed *feed; // Sample feed which provides sample data
  int numch;           // Number of sample channels
  el_nframes_t slen;   // Length of sample 'feed'

  Elastin_Jump *firstj,   // (First pointer) in list of jumps in sample
    *lastj,               // (Last pointer) in list of jumps in sample
    *loopj;               // Loop point for sample
};

// Main elastin engine- instantiate for analysis or synthesis of a sample
class Elastin {
 public:
  // Instantiate Elastin analysis/synthesis engine- optionally provide
  // a pre-existing Elastin_Data to attach to. If given, this allows
  // a new Elastin instance to synthesize from an existing pre-analyzed sample
  Elastin(Elastin_Data *d = 0) : 
    timestretch(1.0), rateshift(1.0), ts_pitchshift(0.0), rt_pitchshift(0.0),
    d(d), sbufs(0), curj(0) {};
  ~Elastin();

  // Attach this Elastin instance to a given Elastin_Data,
  // allowing you to synthesize using a pre-analyzed sample
  //
  // (not RT safe- performs allocation and initialization related to 'd')
  inline void Attach (Elastin_Data *d) { 
    this->d = d; 
    Synthesize_Prep();
  };

  // ** Analysis

  // Start analysis, getting sample data from 'feed'
  // 
  // Specify range of size for chunks (minchunk, maxchunk), and
  // correlate length 'cmpchunk' (between minchunk and maxchunk)
  // 
  // Pass the maximum crossfade length you intend to use during synthesis.
  // Once set, synthesis will not start with a crossfade length higher than
  // this.
  //
  // Analyze_Start returns an Elastin_Data instance that is linked to
  // the given sample feed and parameters you have set. You can use this
  // Elastin_Data later to synthesize stretched audio. You must delete
  // the Elastin_Data when you are finished with it.
  //
  // Returns null on error.
  Elastin_Data *Analyze_Start (Elastin_SampleFeed *feed, 
			       el_nframes_t maxcrossfadelen,
			       el_nframes_t minchunk, el_nframes_t maxchunk, 
			       el_nframes_t cmpchunk);

  // Do 'numchunks' of analysis
  // Returns 0 on success
  // Returns -1 if no more chunks can be analyzed
  int Analyze (int numchunks);

  // Stop analysis-- once Analyze returns -1, you may call Analyze_Stop
  int Analyze_Stop (el_nframes_t loopstartpt, el_nframes_t loopendpt);

  // ** Resynthesis

  // Start synthesis, getting sample data from feed
  //
  // Start at position startpos (in sample)
  // Specify length of chunk cross fade
  // Specify whether sample should be continuously looped (loopmode = 1)
  // If loop mode is active, specify a length for loop crossfade
  // Lowering synthmaxscore causes poor chunks to be skipped during synthesis
  //
  // Tempbufs is a set of buffers you provide for intermediate data when
  //   rate shifting. Tempbufslen is the length of the buffers you provide.
  //   Tempbufslen must be sufficient to store all the intermediate samples 
  //   needed in a single Synthesize pass. You may not write to tempbufs at
  //   any time, even between calls Synthesize.
  //   
  //   Tempbufslen must be rateshift * len in length. Rateshift is the maximum
  //   effective rateshift during synthesis (including pitch shifting). Len
  //   is the maximum length to be processed during a single Synthesize() pass.
  //   Generally, a safe temp buffer size is 2*len or 3*len, or up to 20*len
  //   if you are doing DJ scratching or scrubby type effects.
  // 
  //   If no rate shifting or pitch shifting will be done (timestretch only),
  //   then tempbufs may be null.
  // 
  // timestretch is the amount to stretch the sample length (2.0 is twice the
  //   length)
  // rateshift is your user specified rateshift.
  // ts_pitchshift is the amount of pitch shift in cents, 
  //   using the timestretch + rateshift method (sample length doesn't change)
  // rt_pitchshift is the amount of pitch shift in cents,
  //   using the pure rateshift method (sample length does change)
  //
  // You can combine the above parameters in any way you like. The net
  // effect is calculated in Synth_ComputeParams(). 
  //
  // (RT safe)
  int Synthesize_Start (el_nframes_t startpos, 
			el_nframes_t crossfadelen, 
			char loopmode,
			el_nframes_t loop_crossfadelen,
			float synthmaxscore,
			el_sample_t **tempbufs = 0,
			el_nframes_t tempbufslen = 0,
			float timestretch = 1.0,
			float rateshift = 1.0,
			float ts_pitchshift = 0.0,
			float rt_pitchshift = 0.0);
  
  // Do synthesis, returning len frames of audio into the supplied buffers
  // (one per channel).
  //
  // Returns the number of frames actually stored
  // When the return value is less than 'len', the synthesis is complete.
  //
  // (RT safe)
  el_nframes_t Synthesize (el_sample_t **bufs, el_nframes_t len);

  // Stop synthesis
  //
  // (RT safe)
  int Synthesize_Stop ();

  // Returns how far along in the original sample we've synthesized
  inline double GetSynthCount () { return synthcount; };

  // These functions modify synthesis parameters
  // You can call them at any time during synthesis 
  // (but not actually during a call to Synthesize())
  inline void Set_TimeStretch (float timestretch) {
    this->timestretch = timestretch;
    Synth_ComputeParams();
  };
  inline void Set_RateShift (float rateshift) {
    this->rateshift = rateshift;
    Synth_ComputeParams();
  };
  inline void Set_TS_PitchShift (float ts_pitchshift) {
    this->ts_pitchshift = ts_pitchshift;
    Synth_ComputeParams();
  };
  inline void Set_RT_Pitchshift (float rt_pitchshift) {
    this->rt_pitchshift = rt_pitchshift;
    Synth_ComputeParams();
  };
  
 private:

  // Rate shift part of synthesis
  // Rate shift from tempbufs, storing outlen samples into bufs
  el_nframes_t Synthesize_RT (el_sample_t **tempbufs, 
			      el_sample_t **bufs,
			      el_nframes_t outlen,
			      char checkinbounds = 0,
			      el_nframes_t inlen_max = 0);

  // Timestretch part of synthesis
  el_nframes_t Synthesize_TS (el_sample_t **bufs, el_nframes_t len);

  // Correlate cnt samples between idx1 and idx2
  // Accounting for all channels
  inline float Correlate (el_nframes_t idx1, el_nframes_t idx2, 
			  el_nframes_t cnt) {
    register float cor = 0.0;
    register int numch = d->numch;
    for (register int i = 0; i < numch; i++) {
      register el_sample_t *t1 = &(sbufs[i][idx1]),
	*t2 = &(sbufs[i][idx2]);
      for (register el_nframes_t j = 0; j < cnt; j++, t1++, t2++) 
	cor += fabs(*t1 - *t2);
    }

    return cor;
  };

  // Get amplitude envelope between idx1 and idx2
  el_sample_t Analyze_GetAmpEnv (el_nframes_t idx1, 
				 el_nframes_t idx2);

  // Check whether we should crossfade forward from current position-
  // Returns the number of jumps to skip, or zero if no crossfade forward
  // should be done
  int Synth_CheckCFForward ();
  // Returns nonzero if, given current conditions, we should crossfade
  // back
  char Synth_CheckCFBack ();

  // Setup buffers and counts for this chunk, given the previous jump
  void Synth_SetupChunk (Elastin_Jump *prevj);

  // Recompute net rate and timestretch from given parameters 
  // (timestretch, rate shift, and two pitch shifts)
  inline void Synth_ComputeParams () {
    combitimestretch = timestretch * CENTS_TO_FREQ_RATIO(ts_pitchshift);
    combirateshift = rateshift * CENTS_TO_FREQ_RATIO(ts_pitchshift) *
      CENTS_TO_FREQ_RATIO(rt_pitchshift);
  };

  // Prepare for synthesis
  // This function performs memory allocation and general initialization
  // related to synthesis. It is called -after- analyzing or attaching
  // to a pre-analyzed sample, but before calling Synthesize_Start()
  //
  // This allows us to move non-RT code out of real-time threads.
  // Synthesize_Start, Synthesize, and Synthesize_Stop are all RT safe!
  //
  // Returns nonzero on error
  int Synthesize_Prep () {
    if (d == 0 || d->feed == 0)
      return -1;

    if (sbufs != 0)
      Takedown_SampleBufs();
    Setup_SampleBufs();

    return 0;
  };

  // Called to allocate/free sample buffers
  void Setup_SampleBufs();
  void Takedown_SampleBufs();

  // Temporary (user provided) buffers
  // Elastin stores timestretched audio in tempbufs.
  // Elastin then performs rate shifting on tempbufs.
  // The net effect encompasses time, pitch, and rate shifting.
  el_sample_t **tempbufs;
  el_nframes_t tempbufslen;
  double tempbuf_pos;        // Floating position in tempbufs 
                             // (used in rateshift)
  el_nframes_t tempbuf_filled;   // Number of samples already filled in tempbufs

  // Synthesis parameters 
  float timestretch,         // Time stretch factor
    rateshift,               // Rate shift factor
    ts_pitchshift,           // Pitch shift (via timestretch+rate) in cents
    rt_pitchshift,           // Pitch shift (via rate) in cents

    // Calculated net parameters
    combirateshift,          // Combined (net) rate shift to use
    combitimestretch,        // Combined (net) time stretch to use

    synthmaxscore;           // Synth max score-- factor for rejecting
                             // poorly correlated chunks

  char loopmode;             // In loop mode, Elastin synthesizes continuously
                             // through the end of the sample, returning to
                             // the beginning. Analysis determines the most
                             // smooth loop points
  double synthcount;         // How far along in original sample have we
                             // synthesized? If looping, this may be greater
                             // than the original sample length.
                             // This is different  than 'spos', in that spos
                             // is an internal count which varies with jumps
                             // and crossfades, whereas 'synthcount' is a
                             // count designed to provide information to the
                             // caller about the overall position of synthesis
                       
  el_nframes_t crossfadelen, // Crossfade length
    loop_crossfadelen,       // Crossfade length for loop point
    chunkpos,                // Position of nearest chunk boundary
    chunkidx,                // Sample index into current chunk
    chunklen,                // Length of current chunk
    runremaining;            // Length remaining to synthesize in current run
  double timedelta;          // How many samples off target are we for length?
  Elastin_SynthStatus ss;    // Current status
  Elastin_CrossfadeType cft; // Current crossfade type
  int cfcnt;                 // Count of how many consecutive crossfades
                             // we've done
  char forcedstatic;         // Nonzero if we force this chunk to play in a
                             // static way.
  
  // Crossfade parameters
  el_nframes_t fadepos1, fadepos2;
  el_sample_t fadevol1, fadevol2, fadeinc;

  // General
  Elastin_Data *d;     // Data for the sample we are using

  el_sample_t **sbufs; // Sample buffers (one per channel) 
                       // (these buffers contain a fragment of sample data)
  el_nframes_t spos;   // Current analysis/synthesis position in sample 'feed'
  Elastin_Jump *curj;  // (Current pointer) in list of jumps in sample
};

#endif
