using Godot;

namespace Rokojori
{
  public enum BiquadType
  {
    LowPass,
    BandPass,
    HighPass,
    Peak,
    LowShelf,
    HighShelf,
    Notch,
    AllPass
  }

  public class Biquad
  {

    public float b0 = 0;
    public float b1 = 0;
    public float b2 = 0;
    public float a1 = 0;
    public float a2 = 0;

    public float x1 = 0;
    public float x2 = 0;
    public float y1 = 0;
    public float y2 = 0;

    float process( float inputSample )
    {
      var result =  this.b0 * inputSample + 
                    this.b1 * this.x1 + 
                    this.b2 * this.x2 -
                    this.a1 * this.y1 - 
                    this.a2 * this.y2;

      this.x2 = this.x1;
      this.x1 = inputSample;

      this.y2 = this.y1;
      this.y1 = result;

      return result;
    }

    
    public static Biquad Create( BiquadType filterType, float gainDB, float frequency, float bandwidth, float sampleRate )
    {
      var A = 0f; 
      var omega = 0f;
      var sin = 0f;
      var cos = 0f;
      var alpha = 0f;
      var beta = 0f;

      var a0 = 0f;
      var a1 = 0f;
      var a2 = 0f;
      var b0 = 0f;
      var b1 = 0f;
      var b2 = 0f;

      var ln2 = 0.69314718055994530942f;

      A = Mathf.Pow( 10f, gainDB / 40f );
      omega = 2f * Mathf.Pi * frequency / sampleRate;
      sin = Mathf.Sin( omega );
      cos = Mathf.Cos( omega );
      alpha = ( sin * Mathf.Sinh( ln2 / 2f * bandwidth * omega / sin ) );
      beta = Mathf.Sqrt(A + A);

      switch ( filterType ) 
      {        
        case BiquadType.LowPass:
        {
          b0 = (1 - cos) /2;
          b1 = 1 - cos;
          b2 = (1 - cos) /2;
          a0 = 1 + alpha;
          a1 = -2 * cos;
          a2 = 1 - alpha;
        }
        break;

        case BiquadType.HighPass:
        {
          b0 = (1 + cos) /2;
          b1 = -(1 + cos);
          b2 = (1 + cos) /2;
          a0 = 1 + alpha;
          a1 = -2 * cos;
          a2 = 1 - alpha;
        }
        break;

        case BiquadType.BandPass:
        {
          b0 = alpha;
          b1 = 0;
          b2 = -alpha;
          a0 = 1 + alpha;
          a1 = -2 * cos;
          a2 = 1 - alpha;
        }
        break;

        case BiquadType.Notch:
        {
          b0 = 1;
          b1 = -2 * cos;
          b2 = 1;
          a0 = 1 + alpha;
          a1 = -2 * cos;
          a2 = 1 - alpha;
        }
        break;
            
        case BiquadType.Peak:
        {
          b0 = 1 + (alpha * A);
          b1 = -2 * cos;
          b2 = 1 - (alpha * A);
          a0 = 1 + (alpha /A);
          a1 = -2 * cos;
          a2 = 1 - (alpha /A);
        }
        break;

        case BiquadType.LowShelf:
        {
          b0 = A * ((A + 1) - (A - 1) * cos + beta * sin);
          b1 = 2 * A * ((A - 1) - (A + 1) * cos);
          b2 = A * ((A + 1) - (A - 1) * cos - beta * sin);
          a0 = (A + 1) + (A - 1) * cos + beta * sin;
          a1 = -2 * ((A - 1) + (A + 1) * cos);
          a2 = (A + 1) + (A - 1) * cos - beta * sin;
        }
        break;

        case BiquadType.HighShelf:
        {
          b0 = A * ((A + 1) + (A - 1) * cos + beta * sin);
          b1 = -2 * A * ((A - 1) + (A + 1) * cos);
          b2 = A * ((A + 1) + (A - 1) * cos - beta * sin);
          a0 = (A + 1) - (A - 1) * cos + beta * sin;
          a1 = 2 * ((A - 1) - (A + 1) * cos);
          a2 = (A + 1) - (A - 1) * cos - beta * sin;
        }
        break;
        
        case BiquadType.AllPass:
        {
          var allPassAlpha = cos / ( 2.0f * bandwidth );
          b0 = 1.0f - allPassAlpha;
          b1 = -2.0f * cos;
          b2 = 1.0f + allPassAlpha;
          a0 = 1.0f + allPassAlpha;
          a1 = -2.0f * cos;
          a2 = 1.0f - allPassAlpha;

        }
        break;

      }

      var filter = new Biquad();

      filter.b0 = b0 / a0;
      filter.b1 = b1 / a0;
      filter.b2 = b2 / a0;
      filter.a1 = a1 / a0;
      filter.a2 = a2 / a0;

      filter.x1 = filter.x2 = 0;
      filter.y1 = filter.y2 = 0;

      return filter;
    }

    public static float filter( float inputSample, float[] coefficients, float[] buffer )
    {      
      var result =  coefficients[ 0 ] * inputSample + 
                    coefficients[ 1 ] * buffer[ 0 ] + 
                    coefficients[ 2 ] * buffer[ 1 ] -
                    coefficients[ 3 ] * buffer[ 2 ] - 
                    coefficients[ 4 ] * buffer[ 3 ];

      buffer[ 1 ] = buffer[ 0 ];
      buffer[ 0 ] = inputSample;

      buffer[ 3 ] = buffer[ 2 ];
      buffer[ 2 ] = result;      

      return result;
    }
  }
}