// Copyright 2014 Emilie Gillet.
//
// Author: Emilie Gillet (emilie.o.gillet@gmail.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// 
// See http://creativecommons.org/licenses/MIT/ for more information.
//
// -----------------------------------------------------------------------------
//
// Circular buffer storing audio samples.

#ifndef CLOUDS_DSP_AUDIO_BUFFER_H_
#define CLOUDS_DSP_AUDIO_BUFFER_H_

#include <algorithm>

#include "stmlib/stmlib.h"

#include "stmlib/dsp/dsp.h"
#include "stmlib/utils/dsp.h"

#include "clouds/dsp/mu_law.h"

const int32_t kCrossFadeSize = 256;
const int32_t kInterpolationTail = 8;

namespace clouds {

enum Resolution {
  RESOLUTION_16_BIT,
  RESOLUTION_8_BIT,
  RESOLUTION_8_BIT_DITHERED,
  RESOLUTION_8_BIT_MU_LAW,
};

enum InterpolationMethod {
  INTERPOLATION_ZOH,
  INTERPOLATION_LINEAR,
  INTERPOLATION_HERMITE
};

template<Resolution resolution>
class AudioBuffer {
 public:
  AudioBuffer() { }
  ~AudioBuffer() { }
  
  void Init(
      void* buffer,
      int32_t size,
      int16_t* tail_buffer) {
    s16_ = static_cast<int16_t*>(buffer);
    s8_ = static_cast<int8_t*>(buffer);
    size_ = size - kInterpolationTail;
    write_head_ = 0;
    quantization_error_ = 0.0f;
    crossfade_counter_ = 0;
    if (resolution == RESOLUTION_16_BIT) {
      std::fill(&s16_[0], &s16_[size], 0);
    } else {
      std::fill(
          &s8_[0],
          &s8_[size],
          resolution == RESOLUTION_8_BIT_MU_LAW ? 127 : 0);
    }
    tail_ = tail_buffer;
  }
  
  inline void Resync(int32_t head) {
    write_head_ = head;
    crossfade_counter_ = 0;
  }
  
  inline void Write(float in) {
    if (resolution == RESOLUTION_16_BIT) {
      s16_[write_head_] = stmlib::Clip16(
            static_cast<int32_t>(in * 32768.0f));
    } else if (resolution == RESOLUTION_8_BIT_DITHERED) {
      float sample = in * 127.0f;
      sample += quantization_error_;
      int32_t quantized = static_cast<int32_t>(sample);
      if (quantized < -127) quantized = -127;
      else if (quantized > 127) quantized = 127;
      quantization_error_ = sample - static_cast<float>(in);
      s8_[write_head_] = quantized;
    } else if (resolution == RESOLUTION_8_BIT_MU_LAW) {
      int16_t sample = stmlib::Clip16(static_cast<int32_t>(in * 32768.0f));
      s8_[write_head_] = Lin2MuLaw(sample);
    } else {
      s8_[write_head_] = static_cast<int8_t>(
          stmlib::Clip16(in * 32768.0f) >> 8);
    }
    
    if (resolution == RESOLUTION_16_BIT) {
      if (write_head_ < kInterpolationTail) {
        s16_[write_head_ + size_] = s16_[write_head_];
      }
    } else {
      if (write_head_ < kInterpolationTail) {
        s8_[write_head_ + size_] = s8_[write_head_];
      }
    }
    ++write_head_;
    if (write_head_ >= size_) {
      write_head_ = 0;
    }
  }
  
  inline void WriteFade(
      const float* in,
      int32_t size,
      int32_t stride,
      bool write) {
    if (!write) {
      // Continue recording samples to have something to crossfade with
      // when recording resumes.
      if (crossfade_counter_ < kCrossFadeSize) {
        while (size--) {
          if (crossfade_counter_ < kCrossFadeSize) {
            tail_[crossfade_counter_++] = stmlib::Clip16(
                static_cast<int32_t>(*in * 32767.0f));
            in += stride;
          }
        }
      }
    } else if (write && !crossfade_counter_ && 
        resolution == RESOLUTION_16_BIT &&
        write_head_ >= kInterpolationTail && write_head_ < (size_ - size)) {
      // Fast write routine for the most common case.
      while (size--) {
        s16_[write_head_] = stmlib::Clip16(
            static_cast<int32_t>(*in * 32767.0f));
        ++write_head_;
        in += stride;
      }
    } else {
      while (size--) {
        float sample = *in;
        if (crossfade_counter_) {
          --crossfade_counter_;
          float tail_sample = tail_[kCrossFadeSize - crossfade_counter_];
          float gain = crossfade_counter_ * (1.0f / float(kCrossFadeSize));
          sample += (tail_sample / 32768.0f - sample) * gain;
        }
        Write(sample);
        in += stride;
      }
    }
  }
  
  inline void Write(const float* in, int32_t size, int32_t stride) {
    if (resolution == RESOLUTION_16_BIT
        && write_head_ >= kInterpolationTail && write_head_ < (size_ - size)) {
      // Fast write routine for the most common case.
      while (size--) {
        s16_[write_head_] = stmlib::Clip16(
            static_cast<int32_t>(*in * 32768.0f));
        ++write_head_;
        in += stride;
      }
    } else {
      while (size--) {
        Write(*in);
        in += stride;
      }
    }
  }
  
  template<InterpolationMethod method>
  inline float Read(int32_t integral, uint16_t fractional) const {
    if (method == INTERPOLATION_ZOH) {
      return ReadZOH(integral, fractional);
    } else if (method == INTERPOLATION_LINEAR) {
      return ReadLinear(integral, fractional);
    } else if (method == INTERPOLATION_HERMITE) {
      return ReadHermite(integral, fractional);
    }
  }
  
  inline float ReadZOH(int32_t integral, uint16_t fractional) const {
    if (integral >= size_) {
      integral -= size_;
    }
    
    float x0, scale;
    if (resolution == RESOLUTION_16_BIT) {
      x0 = s16_[integral];
      scale = 1.0f / 32768.0f;
    } else if (resolution == RESOLUTION_8_BIT_MU_LAW) {
      x0 = MuLaw2Lin(s8_[integral]);
      scale = 1.0f / 32768.0f;
    } else {
      x0 = s8_[integral];
      scale = 1.0f / 128.0f;
    }
    return x0 * scale;
  }
  
  inline float ReadLinear(int32_t integral, uint16_t fractional) const {
    if (integral >= size_) {
      integral -= size_;
    }
    
    // assert(integral >= 0 && integral < size_);
    
    float x0, x1, scale;
    float t = static_cast<float>(fractional) / 65536.0f;
    if (resolution == RESOLUTION_16_BIT) {
      x0 = s16_[integral];
      x1 = s16_[integral + 1];
      scale = 1.0f / 32768.0f;
    } else if (resolution == RESOLUTION_8_BIT_MU_LAW) {
      x0 = MuLaw2Lin(s8_[integral]);
      x1 = MuLaw2Lin(s8_[integral + 1]);
      scale = 1.0f / 32768.0f;
    } else {
      x0 = s8_[integral];
      x1 = s8_[integral + 1];
      scale = 1.0f / 128.0f;
    }
    return (x0 + (x1 - x0) * t) * scale;
  }
  
  inline float ReadHermite(int32_t integral, uint16_t fractional) const {
    if (integral >= size_) {
      integral -= size_;
    }
    
    // assert(integral >= 0 && integral < size_);
    
    float xm1, x0, x1, x2, scale;
    float t = static_cast<float>(fractional) / 65536.0f;
    
    if (resolution == RESOLUTION_16_BIT) {
      xm1 = s16_[integral];
      x0 = s16_[integral + 1];
      x1 = s16_[integral + 2];
      x2 = s16_[integral + 3];
      scale = 1.0f / 32768.0f;
    } else if (resolution == RESOLUTION_8_BIT_MU_LAW) {
      xm1 = MuLaw2Lin(s8_[integral]);
      x0 = MuLaw2Lin(s8_[integral + 1]);
      x1 = MuLaw2Lin(s8_[integral + 2]);
      x2 = MuLaw2Lin(s8_[integral + 3]);
      scale = 1.0f / 32768.0f;
    } else {
      xm1 = s8_[integral];
      x0 = s8_[integral + 1];
      x1 = s8_[integral + 2];
      x2 = s8_[integral + 3];
      scale = 1.0f / 128.0f;
    }
    
    // Laurent de Soras's Hermite interpolator.
    const float c = (x1 - xm1) * 0.5f;
    const float v = x0 - x1;
    const float w = c + v;
    const float a = w + v + (x2 - x0) * 0.5f;
    const float b_neg = w + a;
    return ((((a * t) - b_neg) * t + c) * t + x0) * scale;
  }
  
  inline int32_t size() const { return size_; }
  inline int32_t head() const { return write_head_; }
  
 private:
  int16_t* s16_;
  int8_t* s8_;
  
  float quantization_error_;
  
  int16_t tail_ptr_;

  int32_t size_;
  int32_t write_head_;
  
  int16_t* tail_;
  int32_t crossfade_counter_;
  
  DISALLOW_COPY_AND_ASSIGN(AudioBuffer);
};

}  // namespace clouds

#endif  // CLOUDS_DSP_AUDIO_BUFFER_H_
