/*
 *  tracker/VRand.h
 *
 *  Copyright 2009 Peter Barth
 *
 *  This file is part of Milkytracker.
 *
 *  Milkytracker is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  Milkytracker is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with Milkytracker.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

//  Author: Andrew Simper of Vellocet
//          andy@vellocet.com
//
// Purpose: c++ class to generate white, pink and brown noise
//
// Sources:
// This is a c++ implementation of put together from the 
// code provided by the following people mainly from 
// the music-dsp mailing list:  Allan Herriman, James McCartney,
// Phil Burk and Paul Kellet and the web page by Robin Whittle:
// http://www.firstpr.com.au/dsp/pink-noise/
//
// 13/8/06 - Fixed white() function to work correctly with gcc (Christopher
//           O'Neill)
#ifndef __VRAND_H_
#define __VRAND_H_

#ifndef _WIN32_WCE
#include <time.h>
#else
#include <windows.h>
#endif

#include "BasicTypes.h"

// Usage:
// ------
// VRande rand;
// rand.seed();
// float w = rand.white(); // returns white noise +- 0.5
// float p = rand.pink();  // returns pink noise  +- 0.5
// float b = rand.brown(); // returns brown noise +- 0.5
class VRand
{
public:
  enum
  {
    NumPinkBins  = 16,
    NumPinkBins1 = NumPinkBins-1
  };

  VRand()
  {
    m_white = 0;
    m_count = 1;
    m_brown = 0.0f;
    m_pink  = 0;
    for (int i=0; i<NumPinkBins; i++)
    {
      m_pinkStore[i] = 0.0f;
    }
  };

  void seed(unsigned long seed=0)
  {
    if (seed == 0) 
#ifndef _WIN32_WCE
		m_seed = (unsigned long)time(NULL);
#else
		m_seed = GetTickCount();
#endif
    else           m_seed = seed;
  };

  // returns psuedo random white noise number
  // in the range -scale to scale
  //
  inline float white(float scale=0.5f)
  {
     m_seed   = (m_seed * 196314165) + 907633515;
     m_white  = m_seed >> 9; 
     m_white |= 0x40000000; 
	 return (m_fwhite-3.0f)*scale; 
  };

  int inline CTZ(int num)
  {
    int i=0;
    while (((num>>i)&1)==0 && i<(signed)sizeof(int)) i++;
    return i;
  }

  // returns pink noise random number in the range -0.5 to 0.5
  //
  inline float pink(void)
  {
    float prevr;
    float r;
    unsigned long k;
    k = CTZ(m_count);
    k = k & NumPinkBins1; 

    // get previous value of this octave 
    prevr = m_pinkStore[k]; 

    while (true)
    {
      r = white();

      // store new value 
      m_pinkStore[k] = r;

      r -= prevr;

      // update total 
      m_pink += r; 

      if (m_pink <-4.0f || m_pink > 4.0f) m_pink -= r;
      else break;
    }

    // update counter 
    m_count++; 

    return (white() + m_pink)*0.125f; 
  }

  // returns brown noise random number in the range -0.5 to 0.5
  //
  inline float brown(void)
  {
    while (true)
    {
      float  r = white();
      m_brown += r;
      if (m_brown<-8.0f || m_brown>8.0f) m_brown -= r;
      else break;
    }
    return m_brown*0.0625f;
  }

private:
  pp_uint32  m_seed;
  pp_uint32  m_count;
  union {
       pp_uint32  m_white;
	float m_fwhite;
  };
  float          m_pink;
  float          m_brown;
  float          m_pinkStore[NumPinkBins];
};




// This is a little helper section for fast table
// lookup pink noise to use in denormalising
// and filter excitation
//
// use:
// long i=0;
// float noise = pinkNoise(i);
// 
// this leaves a pink noise sample in "noise"
// and increments i so the next call gives a
// new pink noise sample


#define PinkNoiseBins (32768)
#define PinkNoiseWrap (32767)
extern float VPinkNoise[PinkNoiseBins];
#define pinkNoise(c) VPinkNoise[c&PinkNoiseWrap]; c++;

class VPinkNoiseGlobal
{
public:
  VPinkNoiseGlobal(void)
  {
    VRand rand;
    rand.seed();
    double inaudible = 0.000000059604644775390625; //pow(2.0, -24.0);
    for (int i=0; i<PinkNoiseBins; i++)
    {
      VPinkNoise[i] = (float)(inaudible*rand.pink());
    }
  }
};

#endif
