#import "MLPluckMaster.h"

#define SAMPLEFREQ    44100
#define MAXEXTRA      8820
#define NEEDED_ZEROES 200

@implementation MLPluckMaster (Algorithm)

- generateTriangle:(double *)buffer length:(int)length
  volume:(double)volume maximumAt:(int)maximum
{
  // check before calling, to be 0 < maximum < length
  int    i;
  double up, down;

  up = volume / maximum;
  down = volume / (length - maximum);
  i = 0;

  while (i < maximum)
    *buffer++ = up * i++;

  while (i < length)
    *buffer++ = down * (length - i++);

  return self;
}

- (int)calculateSamples:(short int **)buf
  withFrequency:(double)frequency
  volume:(double)volume
  duration:(double)duration
  position:(double)position
  attenuation:(double)attenuation
  durationFactor:(double)durationFactor
{
  int         mainSamples, totalSamples;
  short int * data;
  int         i;
  int         zeroes;
  double    * leftString;
  double    * rightString;
  double    * leftPointer;
  double    * rightPointer;
  int         stringLength;
  int         offset;
  double    * rightEnd;
  double    * rightOffsetEnd;
  double    * leftEnd;
  double    * leftOffsetEnd;
  int         wrappedOffset;
  double      sample;
  double      filterValue;

  // between mainSamples and totalSamples, we do a smooth release

  mainSamples = (int) (duration * durationFactor * (double)SAMPLEFREQ);
  totalSamples = mainSamples + MAXEXTRA;

  // output sound buffer allocation

  data = malloc (totalSamples * sizeof (short int));

  // preparing two strings

  stringLength = (SAMPLEFREQ / 2) / frequency;
  offset = stringLength * position;

  // we get a good triangle if offset ranges from 1 to stringLength - 1

  if (offset < 1)
    offset = 1;
  if (offset > stringLength)
    stringLength = offset + 1;

  leftPointer = leftString = malloc (stringLength * sizeof (double));
  rightPointer = rightString = malloc (stringLength * sizeof (double));

  [self generateTriangle:leftString length:stringLength
        volume:volume * 0.5 * 32767.0 // we split the amplitude into two waves
                                      // also, we are going to short int range
        maximumAt:offset];
  bcopy (leftString, rightString, stringLength * sizeof (double));

  // praparing bridge attenuation filter

  filterValue = 0;

  // prepare wrapping

  rightEnd = rightString + stringLength - 1;
  rightOffsetEnd = rightEnd - offset;
  leftEnd = leftString + stringLength - 1;
  leftOffsetEnd = leftEnd - offset;
  wrappedOffset = offset - stringLength;

  // preparing end detection

  zeroes = 0;

  for (i = 0; i < totalSamples; i++) { // break when we have enough zeroes

    // CALCULATE NEW SAMPLE

    // take right offset value

    if (rightPointer > rightOffsetEnd)
      sample = *(rightPointer + wrappedOffset);
    else
      sample = *(rightPointer + offset);

    // add left offset value

    if (leftPointer > leftOffsetEnd)
      sample += *(leftPointer + wrappedOffset);
    else
      sample += *(leftPointer + offset);

    // sample is ready - convert to 16 bit
    data[i] = (short int) sample;

    // decrement right

    if (rightPointer == rightString )
      rightPointer = rightEnd;
    else
      rightPointer --;

    // increment left

    if (++leftPointer > leftEnd)
      leftPointer = leftString;

    // right = - filtered left

    filterValue = attenuation * (filterValue + *leftPointer);
    *rightPointer = -filterValue;

    // left = - (next right)

    if (rightPointer == rightString)
      *leftPointer = -(*rightEnd);
    else
      *leftPointer = -(*(rightPointer - 1));

    // end detection

    if (data[i] == 0) {
      if (zeroes++ > NEEDED_ZEROES)
        break;
    }
    else
      zeroes = 0;

    // switching to the release phase
    if (i == mainSamples)
      attenuation = 0.45; // moving fast towards zero
  }

  free (leftString);
  free (rightString);
  *buf = data;
  return i;
}

@end
