// ----
// ---- file   : StADSRPlayer.cpp
// ---- author : Bastian Spiegel <bs@tkscript.de>
// ---- legal  : (c) 2009-2019 by Bastian Spiegel. 
// ----          Distributed under terms of the GNU LESSER GENERAL PUBLIC LICENSE (LGPL). See 
// ----          http://www.gnu.org/licenses/licenses.html#LGPL or COPYING for further information.
// ----
// ---- info   : This is part of the "syntracker" midi sequencer.
// ----
// ---- created: 23Sep2009
// ----          
// ---- changed: 21Jan2010, 14Feb2010, 29Jun2010, 01Aug2010, 15Dec2018, 28Dec2018, 17Jan2019
// ----          11May2019, 12May2019
// ----          
// ----
// ----

#include "tksampler.h"


StADSRPlayer::StADSRPlayer(void) {
   adsr        = NULL;
   current_env = NULL;
   orig_st_env = NULL;
   orig_env    = NULL;
}

StADSRPlayer::~StADSRPlayer() {
   YAC_DELETE_SAFE(current_env);
}

// // YAC_FloatArray *StADSRPlayer::getEnv(sUI _envIdx) {
// //    switch(_envIdx)
// //    {
// //       default:
// //          return NULL;

// //       case 0:
// //          return adsr->env_attack;

// //       case 1:
// //          return adsr->env_sustain;

// //       case 2:
// //          return adsr->env_release;
// //    }
// // }

void StADSRPlayer::visitEnv(StEnvelope *_stEnv) {
   // yac_host->printf("xxx StADSRPlayer::visitEnv: stEnv=%p\n", _stEnv);
   if(NULL != _stEnv)
   {
      TKS_Envelope *env = _stEnv->data;

      // yac_host->printf("xxx StADSRPlayer::visitEnv: env=%p\n", env);

      if(NULL != env)
      {
         orig_st_env = _stEnv;
         orig_env    = env;

         // (note) duplicate env (except for data) so each instance gets its own state
         current_env->own_data      = 0;
         current_env->elements      = env->elements;
         current_env->num_elements  = env->num_elements;
         current_env->max_elements  = env->max_elements;
         current_env->current_index = env->current_index;
         current_env->abs_time      = env->abs_time; 
         current_env->delta_time    = env->delta_time; 
         current_env->env_fun       = env->env_fun;
         current_env->b_shreset     = env->b_shreset;
         current_env_intensity      = _stEnv->intensity;

         // yac_host->printf("xxx StADSRPlayer::visitEnv: 1 num_elements=%u\n", current_env->num_elements);
         // current_env->yacArrayGetEnvValue(); // (note) do NOT call ->get(), it does not work in VS2017 and will set elements to NULL instead (!)
         // yac_host->printf("xxx StADSRPlayer::visitEnv: 2 num_elements=%u\n", current_env->num_elements);

         // Timescaling / env time advance (updated again in tick() so UI editing has an immediate effect)
         current_env_timescale      = adsr->timescale * _stEnv->timescale;
         current_env->time_advance  = env->time_advance * current_env_timescale;
         
         // Calc total env time
         //  (TODO) move this to TKS_Envelope class ?
         sUI i;
         current_env_totaltime = 0.0f;
         for(i=0; i<current_env->num_elements; i+=2)
         {
            sF32 dt = current_env->elements[i + 0];
            current_env_totaltime += dt;
         }
         // yac_host->printf("xxx StADSRPlayer: 1 this=%p current_env=%p current_env->num_elements=%u totaltime=%f\n", this, current_env, current_env->num_elements, current_env_totaltime);

         if(current_env_totaltime > 0)
         {
            // Ok, envelope started
            return;
         }
      }
   }

   // Cannot start envelope
   current_env->own_data     = 0;
   current_env->elements     = NULL;
   current_env->num_elements = 0;
   current_env->max_elements = 0;
   current_env_totaltime     = 0.0f;
   current_env_timescale     = 1.0;
   current_env_intensity     = 0.0f;

   // yac_host->printf("xxx StADSRPlayer: failed to start envelope current_env=%p\n", current_env);
}

void StADSRPlayer::startADSR(StADSR *_adsr, sF32 _defaultLevel, sBool _bResetMod, sBool _bRelease) {
   adsr = _adsr;
   if(NULL != adsr)
   {
      time          = 0.0f;
      env_index     = ENV_ATTACK;
      b_finished    = YAC_FALSE;
      b_sustain_finished = YAC_FALSE;

      b_skip_sustain = _bRelease;  // when restarting envelope after note off

      {
         default_level = _defaultLevel;
         attack_level  = _defaultLevel; // reset last seen levels
         sustain_level = _defaultLevel;
         final_level   = 0.0f;
      }

      if(_bResetMod)
      {
         mod_speed     = 1.0f;
         mod_level     = 1.0f;
         mod_intensity = 1.0f;
         mod_aspeed    = 1.0f;
         mod_sspeed    = 1.0f;
         mod_rspeed    = 1.0f;

         mmdst_speed     = 1.0f;
         mmdst_level     = 1.0f;
         mmdst_intensity = 1.0f;
         mmdst_aspeed    = 1.0f;
         mmdst_sspeed    = 1.0f;
         mmdst_rspeed    = 1.0f;
      }

      // Lazy create temporary envelope 
      if(NULL == current_env)
      {
         current_env = (TKS_Envelope*) yac_host->yacNew(NULL, "Envelope");
      }

      // Start attack envelope
      visitEnv(adsr->env_attack);

      // yac_host->printf("xxx startADSR: attack this=%p current_env=%p elements=%p num_elements=%u\n", this, current_env, current_env->elements, current_env->num_elements);

      // Skip attack envelope if it does not exist
      if(0 == current_env->num_elements)
      {
         if(b_skip_sustain)
         {
            // Start release envelope
            noteOff();
         }
         else
         {
            // No AD envelope, proceed directly to sustain
            env_index = ENV_SUSTAIN;
            visitEnv(adsr->env_sustain);
         }

         // yac_host->printf("xxx startADSR: skip ENV_ATTACK\n");

         // // if(0 == current_env->num_elements)
         // // {
         // //    // No S envelope
         // // }
      }

      // yac_host->printf("xxx startADSR: leave this=%p current_env=%p elements=%p num_elements=%u\n", this, current_env, current_env->elements, current_env->num_elements);
   }
}

void StADSRPlayer::stopADSR(void) {
   adsr = NULL;
}

void StADSRPlayer::noteOff(void) {
   ////printf("xxx StADSRPlayer::noteOff: b_finished=%d\n", b_finished);
   if(!b_finished)
   {
      if(NULL != adsr)
      {
         // Start release envelope
         if(YAC_MAYBE != adsr->b_sustain_loop)  // infinite sustain loop ?
         {
            env_index = ENV_RELEASE;
            time      = 0.0f;

            visitEnv(adsr->env_release);
            current_env->yacEnvSetTime(0.0f);

            if(0 == current_env->num_elements)
            {
               b_finished = 1;
            }
         }
         else if(ENV_SUSTAIN != env_index)
         {
            // Infinite sustain loop
            env_index = ENV_SUSTAIN;
            time      = 0.0f;

            visitEnv(adsr->env_sustain);
            current_env->yacEnvSetTime(0.0f);

            if(0 == current_env->num_elements)
            {
               b_finished = 1;
            }
         }

         // yac_host->printf("xxx StADSRPlayer::noteOff: current_env->num_elements=%d\n",current_env->num_elements);
         // yac_host->printf("xxx StADSRPlayer::noteOff: end of S env. sustain_level=%f\n", sustain_level);
      }  
   }
}
 

sF32 StADSRPlayer::tick(void) {
   sF32 r = 0.0f;

   // if(adsr->b_abs_levels)
   //    yac_host->printf("xxx StADSRPlayer: b_finished=%d adsr=%p idx=%d time=%f current_env_totaltime=%f\n", b_finished, adsr, env_index, time, current_env_totaltime);

   if(NULL != adsr)
   {
      if(!b_finished)
      {
         // Timescaling / env time advance
         //  (note) updated again so UI editing has an immediate effect
         current_env_timescale      = adsr->timescale * orig_st_env->timescale;
         current_env->time_advance  = orig_env->time_advance * current_env_timescale;
         sF32 stageTimescale = 1.0f;

         sF32 cIntensity = current_env_intensity * mod_intensity * mmdst_intensity;

         // Envelope finished ?
         if(time >= current_env_totaltime)
         {
            time -= current_env_totaltime;
            switch(env_index)
            {
               default:
               case ENV_ATTACK:
               {
                  // End of AD envelope, begin sustain
                  // Determine exact attack_level at end of AD env
                  current_env->yacEnvSetTime(current_env_totaltime);
                  attack_level = current_env->yacEnvGetValue() * cIntensity + (default_level * (1.0f - cIntensity));
                  // yac_host->printf("xxx end of AD env. attack_level=%f\n", attack_level);

                  if(b_skip_sustain)
                  {
                     // Hold last value
                     b_sustain_finished = YAC_TRUE;
                     sustain_level = attack_level;

                     // Start release env (if any)
                     noteOff();
                  }
                  else
                  {
                     // Begin sustain envelope
                     visitEnv(adsr->env_sustain);
                     env_index++;
                     current_env->yacEnvSetTime((sF32)time);
                  }
               }
               break;

               case ENV_SUSTAIN:
               {
                  // End of sustain envelope
                  if(!b_sustain_finished)
                  {
                     if(YAC_FALSE != adsr->b_sustain_loop)
                     {
                        // Loop sustain envelope
                        // yac_host->printf("xxx loop sustain env\n");
                        current_env->yacEnvSetTime((sF32)time);
                     }
                     else
                     {
                        // Hold last value
                        b_sustain_finished = YAC_TRUE;
                     }
                  }
               }
               break;

               case ENV_RELEASE:
               {
                  // End of release envelope
                  // yac_host->printf("xxx end of R envelope\n");
                  b_finished = 1;
               }
               break;
            }
         }

         if(!b_finished)
         {
            switch(env_index)
            {
               default:
               case ENV_ATTACK:
               {
                  // Process attack/decay envelope
                  if(current_env->num_elements)
                  {
                     // yac_host->printf("xxx AD env get 1 current_env=%p elements=%p &elements=%p\n", current_env, current_env->elements, &current_env->elements);
                     sF32 envLvl = current_env->yacEnvGetValue();
                     // yac_host->printf("xxx AD env get 2 current_env=%p elements=%p\n", current_env, current_env->elements);
                     attack_level = envLvl * cIntensity + (default_level * (1.0f - cIntensity));
                     // yac_host->printf("xxx StADSRPlayer: AD time=%f attack_level=%f envLvl=%f speed=%f mod_speed=%f\n", time, attack_level, envLvl, current_env->time_advance, mod_speed);
                     // // // current_env->tickPrecise(mod_speed * mod_aspeed);
                     current_env->yacEnvTickPrecise(mod_speed * mod_aspeed * mmdst_speed * mmdst_aspeed);
                     // yac_host->printf("xxx AD env get 3 current_env=%p elements=%p\n", current_env, current_env->elements);
                     stageTimescale = mod_aspeed * mmdst_aspeed;
                  }
                  else
                  {
                     // No AD envelope (xxx ever reached??)
                     attack_level = default_level;
                     // yac_host->printf("xxx NO AD ENV!!! this=%p current_env=%p elements=%p num_elements=%u\n", this, current_env, current_env->elements, current_env->num_elements);
                  }

                  r = attack_level;
                  sustain_level = attack_level; // in case sustain is entirely skipped (noteoff)
               }
               break;

               case ENV_SUSTAIN:
               {
                  // Process sustain envelope
                  if(!b_sustain_finished)
                  {
                     if(current_env->num_elements)
                     {
                        // Have sustain envelope
                        sustain_level = current_env->yacEnvGetValue() * cIntensity + (default_level * (1.0f - cIntensity));

                        switch(adsr->stage_op)
                        {
                           default:
                           case STADSR_STAGEOP_MUL:
                              sustain_level = attack_level * sustain_level;
                              break;

                           case STADSR_STAGEOP_MUL_INTENSITY:
                              sustain_level = (1.0f - cIntensity) * attack_level + sustain_level;
                              break;

                           case STADSR_STAGEOP_ADD:
                              sustain_level = attack_level + sustain_level;
                              break;
                        }
                       
                        // if(adsr->b_abs_levels)
                        // {
                        //    yac_host->printf("xxx StADSRPlayer: sustainenv.abstime=%f sustain_level=%f attack_level=%f\n", current_env->abs_time, sustain_level, attack_level);
                        //    yac_host->printf("xxx StADSRPlayer: mod_speed=%f mod_sspeed=%f\n", mod_speed, mod_sspeed);
                        // }
                  
                        current_env->yacEnvTickPrecise(mod_speed * mod_sspeed * mmdst_speed * mmdst_sspeed);
                        stageTimescale = mod_sspeed * mmdst_sspeed;
                     }
                     else
                     {
                        sustain_level = attack_level;
                     }
                  }

                  r = sustain_level;
               }
               break;

               case ENV_RELEASE:
               {
                  // Process release envelope
                  if(current_env->num_elements)
                  {
                     // Have release envelope
                     // // // r = current_env->get();
                     r = current_env->yacEnvGetValue();
                     ////printf("xxx release cenv val=%f\n", r);
                     r = r * cIntensity + (default_level * (1.0f - cIntensity));

                     switch(adsr->stage_op)
                     {
                        default:
                        case STADSR_STAGEOP_MUL:
                           r = r * sustain_level;
                           break;

                        case STADSR_STAGEOP_MUL_INTENSITY:
                           r = r + (1.0f - cIntensity) * sustain_level;
                           break;

                        case STADSR_STAGEOP_ADD:
                           r = r + sustain_level;
                           break;
                     }

                     // printf("xxx env_release: attack_level=%f sustain_level=%f\n", attack_level, sustain_level);
                     // printf("xxx env_release: op=%d r=%f attack_level=%f sustain_level=%f\n", adsr->stage_op, r, attack_level, sustain_level);
                     // yac_host->printf("xxx StADSRPlayer: env_release: time=%f mod_speed=%f mod_rspeed=%f cIntensity=%f r=%f\n", time, mod_speed, mod_rspeed, cIntensity, r);
                     // // // current_env->tickPrecise(mod_speed * mod_rspeed);
                     current_env->yacEnvTickPrecise(mod_speed * mod_rspeed * mmdst_speed * mmdst_rspeed);
                     stageTimescale = mod_rspeed * mmdst_rspeed;
                  }
                  else
                  {
                     // No release envelope
                     b_finished = 1;
                     // yac_host->printf("xxx StADSRPlayer: No release envelope\n");
                     r = 0.0f;
                  }
               }
               break;
            }
        
            time += current_env_timescale * mod_speed * mmdst_speed * stageTimescale;

            final_level = r;
         } // if !b_finished
         else
         {
            r = final_level;
         }
      } // if !b_finished
      else
      {
         r = final_level;
      }

      r *= mod_level * mmdst_level * adsr->level;

      // if( (r < -1.0f) || (r > 1.0f))
      // {
      //    printf("xxx adsr ill r=%f\n", r);
      // }

      //printf("r = %f this=0x%p\n", r, this);

      ////yac_host->printf("StADSRPlayer::tick: time=%f/%f spd=%f env_index=%i r=%f\n", time, current_env_totaltime, current_env_timescale, env_index, r);
      ////yac_host->printf("StADSRPlayer::tick: r=%f\n", r);
   } // if adsr
   return r;
}

sBool StADSRPlayer::isFinished(void) {
   return b_finished;
}
