/* Contoller.h
 * The Controller is provides all of the User Interface as well as the
 * computational "guts" of the TimeWarp application.
 *
 * You may freely copy, distribute, and reuse the code in this example.
 * NeXT disclaims any warranty of any kind, expressed or  implied, as to its
 * fitness for any particular use.
 *
 * Written by: Robert Poor
 * Created: Sep/92
 */

#import "Controller.h"
#import "CompletionView.h"
#import "errors.h"
#import <appkit/Cell.h>
#import <appkit/OpenPanel.h>
#import <appkit/Window.h>
#import <math.h>
#import <stdio.h>
#import <sys/param.h>
#import <appkit/Application.h>	// for NX_BASETHRESHOLD
#import <appkit/Matrix.h>

@interface Controller(ControllerPrivate)
- _setSoundFile:(char *)filename;
- _updateStatus:sender;
- _setSpeed:(float)linValue;
@end

/*
 * _updateStatus is called via a timed entry once every second, which in
 * turn simply calls the _updateStatus: method in Controller.  Note that
 * the "data" argument is bound to the Controller instance.  (See the call
 * to DPSAddTimedEntry in appDidInit: to see how this is managed.)
 */
void static _updateStatus (DPSTimedEntry te, double timeNow, void *data)
{
  [(id)data _updateStatus:(id)data];
}

@implementation Controller

- init
{
  [super init];
  dacPlayer = [[DACPlayer alloc] init];
  [dacPlayer setDelegate:self];
  return self;
}

- free
{
  DPSRemoveTimedEntry(updateTE);
  [dacPlayer free];
  return [super free];
}

- appDidInit:sender
{
  [completionView setTextField:completionField];
  /*
   * by passing "self" as the third arg to DPSAddTimedEntry, we get a
   * handle by which to call back into ourselves from the _updateStatus
   * function.
   */
  updateTE = DPSAddTimedEntry(UPDATE_RATE,
	&_updateStatus,
	self,
	NX_BASETHRESHOLD);
  [self _setSpeed:1.0];
  return self;
}

- openSoundFile:sender
{
  const char *const *files;
  static const char *const fileType[2] = {"snd", NULL};
  id openPanel;
  char fullName[MAXPATHLEN+1];

  openPanel = [[OpenPanel new] allowMultipleFiles:NO];

  /* run the open panel, filtering for out type of document */
  if ([openPanel runModalForTypes:fileType]) {
    /* open all the files returned by the open panel */
    for (files = [openPanel filenames]; files && *files; files++) {
      /* for the one selected filename... */
      sprintf(fullName,"%s/%s",[openPanel directory],*files);
      [self _setSoundFile:fullName];
    }
  }
  return self;
}

- closeSoundFile:sender
{
  return [self _setSoundFile:NULL];
}

- _setSoundFile:(char *)aFilename
{
  int r;

  [self stop:self];

  if (aFilename) {
    SNDSoundStruct *newSound;
    r = SNDReadSoundfile(aFilename, &newSound);
    if (!checkSNDError(self, r, "Couldn't read input sound file")) {
      return nil;
    }
    /* aFilename references a valid sound file */
    if (srcSound) {
      SNDFree(srcSound);
    }
    srcSound = newSound;
    [window setTitleAsFilename:aFilename];
    srcBase = src = (short *)((void *)srcSound + srcSound->dataLocation);
    srcEnd = &srcBase[srcSound->dataSize / sizeof(short)];
  } else {
    /* Didn't get a valid sound file */
    if (srcSound) {
      SNDFree(srcSound);
      srcSound = NULL;
    }
    [window setTitleAsFilename:"No Sound File Open"];
    /* hack to keep _updateStatus: happy */
    srcBase = src = (short *)0;
    srcEnd = &src[1];
  }

  /* and note the new filename */
  filename = aFilename;

  return self;
}

- play:sender
{
  if (!filename) {
    [self openSoundFile:sender];
  }
  if (filename) {
    // [startButton setEnabled:NO];
    [dacPlayer run];
  }
  stoppedManually = NO;
  return self;
}

- stop:sender
{
  stoppedManually = YES;
  [dacPlayer stop];
  return self;
}

- pause:sender
{
  if (!filename) {
    [self openSoundFile:sender];
  }
  [dacPlayer pause];
  return self;
}

- setSpeedLinear:sender
{
  return [self _setSpeed:[sender floatValue]];
}

- setSpeedLogarithmic:sender
/*
 * set the speed logarithmically.  The speed will be set to 1/SPEED_RANGE to
 * SPEED_RANGE as [sender floatValue] ranges from 0 to 1.
 */
{
  float logValue, linValue;

  logValue = [sender floatValue];
  linValue = pow((SPEED_RANGE*SPEED_RANGE),logValue)/SPEED_RANGE;
  /*
   * A purist might complain that _setSpeed will simply undo all the
   * hard work in computing linValue from logValue.  Tough.
   */
  return [self _setSpeed:linValue];
}

- _setSpeed:(float)linValue
{
  float logValue;

  if (linValue > SPEED_RANGE) linValue = SPEED_RANGE;
  else if (linValue < 1.0/SPEED_RANGE) linValue = 1.0/SPEED_RANGE;
  
  fixRate = linValue * FIXPOINT_UNITY;

  logValue = log(linValue * SPEED_RANGE)/log(SPEED_RANGE*SPEED_RANGE);
  [speedField setFloatValue:linValue];
  [speedSlider setFloatValue:logValue];
  return self;
}

/***
 *** Some non-UI methods
 ***/

- _updateStatus:sender
{
  char buf[1000];
  Pla_state_t state;
  
  if (!filename) {
    sprintf(buf,"No sound file open.");
  } else {
    state = [dacPlayer playerState];
    switch (state) {
    case PLA_STOPPED:
      sprintf(buf,"Stopped");
      break;
    case PLA_PAUSED:
      sprintf(buf,"Paused");
      break;
    case PLA_RUNNING:
      sprintf(buf,"Running");
      break;
    case PLA_STOPPING:
      sprintf(buf,"Stopping...");
      break;
    default:
      sprintf(buf,"I'm confused!");
      break;
    }
  }
  [statusField setStringValue:buf];
  [queuedField setIntValue:[dacPlayer framesQueued]];
  [playedField setIntValue:[dacPlayer framesPlayed]];

  if (srcSound) {
    double completed;
    completed = (double)(src - srcBase)/(double)(srcEnd - srcBase);
    [completionView setDoubleValue:completed];
  } else {
    [completionView setDoubleValue:0.0];
  }
  return self;
}

/***
 *** Delegate Methods called from the DACPlayer object
 ***/

- willPlay :player
/*
 * Called just before the playing starts.  First we set up some
 * configuration parameters in the DACPlayer (region size, sampling
 * rate, etc).  We then cache some pointers into the sound data.
 */
{
  [dacPlayer setSamplingRate:srcSound->samplingRate];
  if (srcSound) {
    src = srcBase;
    residue = 0;
  }
  return self;
}

- didPlay :player
/*
 * Called after the DAC resources have been freed.
 */
{
  /* 
   * The _updateStatus: method looks at the value of src to see how far
   * through the source sound we've played.  Reset it now back to the
   * start of the sound.  (This is really only for cosmetics.)
   */
  src = srcBase;
  return self;
}

- playData :(DACPlayer *)player :(char *)region :(int)nbytes
/*
 * This is the delegate method called from the DACPlayer.  In this method,
 * we copy samples from the source sound into the buffer, resampling (ala
 * linear interpolation) according to the current fixRate parameter.  When
 * all the sound samples have been processed, we call [dacPlayer finish] to
 * tell it to finish playing any queued samples.
 */
{
  short *dst, *dstEnd, *tsrc, *tsrcEnd;
  int endMargin;
  fixpoint_t tfixRate, tresidue;

  if (!srcSound) {
    [self stop:self];
    return nil;
  }
  
  dst = (short *)region;
  dstEnd = (short *)(&region[nbytes]);

  /* cache some instance variable locally (generates better code) */
  tsrc = src;
  tsrcEnd = srcEnd;
  tfixRate = fixRate;		/* rate at which we advance through src */
  tresidue = residue;		/* current offset between s[0] and s[1] */

  /*
   * endMargin is the number of sample frames we might advance at each step.
   * We set it to (effectively) CEILING(fixRate) and we use it in calculating
   * how far we can go in the src buffer.
   */
  endMargin = tfixRate >> LOG2_FIXPOINT_UNITY;
  if ((endMargin << LOG2_FIXPOINT_UNITY) != tfixRate) {
    endMargin += 1;
  }
  
  if (srcSound->channelCount == 1) {		/* mono src -> stereo dst */
    tsrcEnd = tsrcEnd - endMargin - 1;
    while ((dst < dstEnd) && (tsrc < tsrcEnd)) {
      short s0, s1, samp;

      tresidue += tfixRate;
      tsrc += (tresidue >> LOG2_FIXPOINT_UNITY);
      tresidue = tresidue & (FIXPOINT_UNITY-1);
      s0 = tsrc[0];
      s1 = tsrc[1];
      /* at this point:
       * s0 is the "low" sample
       * s1 is the "high" sample
       * tresidue is between 0 and (fixpoint) 1.
       * do a linear interpolation between s0 and s1
       */
      samp = s0 + ((tresidue * (s1 - s0)) >> LOG2_FIXPOINT_UNITY);
      *dst++ = samp;
      *dst++ = samp;
    }
  } else {					/* stereo src -> stereo dst */
    tsrcEnd = tsrcEnd - endMargin - 3;
    while ((dst < dstEnd) && (tsrc < tsrcEnd)) {
      short s0, s1, samp;

      tresidue += tfixRate;
      tsrc += (tresidue >> LOG2_FIXPOINT_UNITY) * 2;
      tresidue = tresidue & (FIXPOINT_UNITY-1);
      s0 = tsrc[0];
      s1 = tsrc[2];
      /* at this point:
       * s0 is the "low" sample for left channel
       * s1 is the "high" sample for left channel
       * tresidue is between 0 and (fixpoint) 1.
       * do a linear interpolation between s0 and s1
       */
      samp = s0 + ((tresidue * (s1 - s0)) >> LOG2_FIXPOINT_UNITY);
      *dst++ = samp;		/* left channel */
      /* and now for the right channel */
      s0 = tsrc[1];
      s1 = tsrc[3];
      samp = s0 + ((tresidue * (s1 - s0)) >> LOG2_FIXPOINT_UNITY);
      *dst++ = samp;
    }
  }

  /* decache instance variables that have changed */
  src = tsrc;
  residue = tresidue;		/* current offset between s[0] and s[1] */

  /* zero out any remaining part of the buffer */
  while (dst < dstEnd) {
    *dst++ = 0;
  }

  /* stop the music when we've played the whole file */
  if (tsrc >= tsrcEnd) {
    [dacPlayer finish];
  }

  return self;
}

- didChangeState:player from:(Pla_state_t)old to:(Pla_state_t)new
{
  [self _updateStatus:self];
  /*
   * If the sound has ended naturally (without us hitting the stop button)
   * and the repeat button is on, then play the sound again.
   */
  if ((new == PLA_STOPPED) &&
      (stoppedManually == NO) &&
      [repeatButton state]) {
    [self play:self];
  }
  return self;  
}

@end