home *** CD-ROM | disk | FTP | other *** search
/ Nebula 2 / Nebula Two.iso / Apps / SoundApps / TimeWarp / Source / Controller.m < prev    next >
Encoding:
Text File  |  1995-06-12  |  9.7 KB  |  398 lines

  1. /* Contoller.h
  2.  * The Controller is provides all of the User Interface as well as the
  3.  * computational "guts" of the TimeWarp application.
  4.  *
  5.  * You may freely copy, distribute, and reuse the code in this example.
  6.  * NeXT disclaims any warranty of any kind, expressed or  implied, as to its
  7.  * fitness for any particular use.
  8.  *
  9.  * Written by: Robert Poor
  10.  * Created: Sep/92
  11.  */
  12.  
  13. #import "Controller.h"
  14. #import "CompletionView.h"
  15. #import "errors.h"
  16. #import <appkit/Cell.h>
  17. #import <appkit/OpenPanel.h>
  18. #import <appkit/Window.h>
  19. #import <math.h>
  20. #import <stdio.h>
  21. #import <sys/param.h>
  22. #import <appkit/Application.h>    // for NX_BASETHRESHOLD
  23. #import <appkit/Matrix.h>
  24.  
  25. @interface Controller(ControllerPrivate)
  26. - _setSoundFile:(char *)filename;
  27. - _updateStatus:sender;
  28. - _setSpeed:(float)linValue;
  29. @end
  30.  
  31. /*
  32.  * _updateStatus is called via a timed entry once every second, which in
  33.  * turn simply calls the _updateStatus: method in Controller.  Note that
  34.  * the "data" argument is bound to the Controller instance.  (See the call
  35.  * to DPSAddTimedEntry in appDidInit: to see how this is managed.)
  36.  */
  37. void static _updateStatus (DPSTimedEntry te, double timeNow, void *data)
  38. {
  39.   [(id)data _updateStatus:(id)data];
  40. }
  41.  
  42. @implementation Controller
  43.  
  44. - init
  45. {
  46.   [super init];
  47.   dacPlayer = [[DACPlayer alloc] init];
  48.   [dacPlayer setDelegate:self];
  49.   return self;
  50. }
  51.  
  52. - free
  53. {
  54.   DPSRemoveTimedEntry(updateTE);
  55.   [dacPlayer free];
  56.   return [super free];
  57. }
  58.  
  59. - appDidInit:sender
  60. {
  61.   [completionView setTextField:completionField];
  62.   /*
  63.    * by passing "self" as the third arg to DPSAddTimedEntry, we get a
  64.    * handle by which to call back into ourselves from the _updateStatus
  65.    * function.
  66.    */
  67.   updateTE = DPSAddTimedEntry(UPDATE_RATE,
  68.     &_updateStatus,
  69.     self,
  70.     NX_BASETHRESHOLD);
  71.   [self _setSpeed:1.0];
  72.   return self;
  73. }
  74.  
  75. - openSoundFile:sender
  76. {
  77.   const char *const *files;
  78.   static const char *const fileType[2] = {"snd", NULL};
  79.   id openPanel;
  80.   char fullName[MAXPATHLEN+1];
  81.  
  82.   openPanel = [[OpenPanel new] allowMultipleFiles:NO];
  83.  
  84.   /* run the open panel, filtering for out type of document */
  85.   if ([openPanel runModalForTypes:fileType]) {
  86.     /* open all the files returned by the open panel */
  87.     for (files = [openPanel filenames]; files && *files; files++) {
  88.       /* for the one selected filename... */
  89.       sprintf(fullName,"%s/%s",[openPanel directory],*files);
  90.       [self _setSoundFile:fullName];
  91.     }
  92.   }
  93.   return self;
  94. }
  95.  
  96. - closeSoundFile:sender
  97. {
  98.   return [self _setSoundFile:NULL];
  99. }
  100.  
  101. - _setSoundFile:(char *)aFilename
  102. {
  103.   int r;
  104.  
  105.   [self stop:self];
  106.  
  107.   if (aFilename) {
  108.     SNDSoundStruct *newSound;
  109.     r = SNDReadSoundfile(aFilename, &newSound);
  110.     if (!checkSNDError(self, r, "Couldn't read input sound file")) {
  111.       return nil;
  112.     }
  113.     /* aFilename references a valid sound file */
  114.     if (srcSound) {
  115.       SNDFree(srcSound);
  116.     }
  117.     srcSound = newSound;
  118.     [window setTitleAsFilename:aFilename];
  119.     srcBase = src = (short *)((void *)srcSound + srcSound->dataLocation);
  120.     srcEnd = &srcBase[srcSound->dataSize / sizeof(short)];
  121.   } else {
  122.     /* Didn't get a valid sound file */
  123.     if (srcSound) {
  124.       SNDFree(srcSound);
  125.       srcSound = NULL;
  126.     }
  127.     [window setTitleAsFilename:"No Sound File Open"];
  128.     /* hack to keep _updateStatus: happy */
  129.     srcBase = src = (short *)0;
  130.     srcEnd = &src[1];
  131.   }
  132.  
  133.   /* and note the new filename */
  134.   filename = aFilename;
  135.  
  136.   return self;
  137. }
  138.  
  139. - play:sender
  140. {
  141.   if (!filename) {
  142.     [self openSoundFile:sender];
  143.   }
  144.   if (filename) {
  145.     // [startButton setEnabled:NO];
  146.     [dacPlayer run];
  147.   }
  148.   stoppedManually = NO;
  149.   return self;
  150. }
  151.  
  152. - stop:sender
  153. {
  154.   stoppedManually = YES;
  155.   [dacPlayer stop];
  156.   return self;
  157. }
  158.  
  159. - pause:sender
  160. {
  161.   if (!filename) {
  162.     [self openSoundFile:sender];
  163.   }
  164.   [dacPlayer pause];
  165.   return self;
  166. }
  167.  
  168. - setSpeedLinear:sender
  169. {
  170.   return [self _setSpeed:[sender floatValue]];
  171. }
  172.  
  173. - setSpeedLogarithmic:sender
  174. /*
  175.  * set the speed logarithmically.  The speed will be set to 1/SPEED_RANGE to
  176.  * SPEED_RANGE as [sender floatValue] ranges from 0 to 1.
  177.  */
  178. {
  179.   float logValue, linValue;
  180.  
  181.   logValue = [sender floatValue];
  182.   linValue = pow((SPEED_RANGE*SPEED_RANGE),logValue)/SPEED_RANGE;
  183.   /*
  184.    * A purist might complain that _setSpeed will simply undo all the
  185.    * hard work in computing linValue from logValue.  Tough.
  186.    */
  187.   return [self _setSpeed:linValue];
  188. }
  189.  
  190. - _setSpeed:(float)linValue
  191. {
  192.   float logValue;
  193.  
  194.   if (linValue > SPEED_RANGE) linValue = SPEED_RANGE;
  195.   else if (linValue < 1.0/SPEED_RANGE) linValue = 1.0/SPEED_RANGE;
  196.   
  197.   fixRate = linValue * FIXPOINT_UNITY;
  198.  
  199.   logValue = log(linValue * SPEED_RANGE)/log(SPEED_RANGE*SPEED_RANGE);
  200.   [speedField setFloatValue:linValue];
  201.   [speedSlider setFloatValue:logValue];
  202.   return self;
  203. }
  204.  
  205. /***
  206.  *** Some non-UI methods
  207.  ***/
  208.  
  209. - _updateStatus:sender
  210. {
  211.   char buf[1000];
  212.   Pla_state_t state;
  213.   
  214.   if (!filename) {
  215.     sprintf(buf,"No sound file open.");
  216.   } else {
  217.     state = [dacPlayer playerState];
  218.     switch (state) {
  219.     case PLA_STOPPED:
  220.       sprintf(buf,"Stopped");
  221.       break;
  222.     case PLA_PAUSED:
  223.       sprintf(buf,"Paused");
  224.       break;
  225.     case PLA_RUNNING:
  226.       sprintf(buf,"Running");
  227.       break;
  228.     case PLA_STOPPING:
  229.       sprintf(buf,"Stopping...");
  230.       break;
  231.     default:
  232.       sprintf(buf,"I'm confused!");
  233.       break;
  234.     }
  235.   }
  236.   [statusField setStringValue:buf];
  237.   [queuedField setIntValue:[dacPlayer framesQueued]];
  238.   [playedField setIntValue:[dacPlayer framesPlayed]];
  239.  
  240.   if (srcSound) {
  241.     double completed;
  242.     completed = (double)(src - srcBase)/(double)(srcEnd - srcBase);
  243.     [completionView setDoubleValue:completed];
  244.   } else {
  245.     [completionView setDoubleValue:0.0];
  246.   }
  247.   return self;
  248. }
  249.  
  250. /***
  251.  *** Delegate Methods called from the DACPlayer object
  252.  ***/
  253.  
  254. - willPlay :player
  255. /*
  256.  * Called just before the playing starts.  First we set up some
  257.  * configuration parameters in the DACPlayer (region size, sampling
  258.  * rate, etc).  We then cache some pointers into the sound data.
  259.  */
  260. {
  261.   [dacPlayer setSamplingRate:srcSound->samplingRate];
  262.   if (srcSound) {
  263.     src = srcBase;
  264.     residue = 0;
  265.   }
  266.   return self;
  267. }
  268.  
  269. - didPlay :player
  270. /*
  271.  * Called after the DAC resources have been freed.
  272.  */
  273. {
  274.   /* 
  275.    * The _updateStatus: method looks at the value of src to see how far
  276.    * through the source sound we've played.  Reset it now back to the
  277.    * start of the sound.  (This is really only for cosmetics.)
  278.    */
  279.   src = srcBase;
  280.   return self;
  281. }
  282.  
  283. - playData :(DACPlayer *)player :(char *)region :(int)nbytes
  284. /*
  285.  * This is the delegate method called from the DACPlayer.  In this method,
  286.  * we copy samples from the source sound into the buffer, resampling (ala
  287.  * linear interpolation) according to the current fixRate parameter.  When
  288.  * all the sound samples have been processed, we call [dacPlayer finish] to
  289.  * tell it to finish playing any queued samples.
  290.  */
  291. {
  292.   short *dst, *dstEnd, *tsrc, *tsrcEnd;
  293.   int endMargin;
  294.   fixpoint_t tfixRate, tresidue;
  295.  
  296.   if (!srcSound) {
  297.     [self stop:self];
  298.     return nil;
  299.   }
  300.   
  301.   dst = (short *)region;
  302.   dstEnd = (short *)(®ion[nbytes]);
  303.  
  304.   /* cache some instance variable locally (generates better code) */
  305.   tsrc = src;
  306.   tsrcEnd = srcEnd;
  307.   tfixRate = fixRate;        /* rate at which we advance through src */
  308.   tresidue = residue;        /* current offset between s[0] and s[1] */
  309.  
  310.   /*
  311.    * endMargin is the number of sample frames we might advance at each step.
  312.    * We set it to (effectively) CEILING(fixRate) and we use it in calculating
  313.    * how far we can go in the src buffer.
  314.    */
  315.   endMargin = tfixRate >> LOG2_FIXPOINT_UNITY;
  316.   if ((endMargin << LOG2_FIXPOINT_UNITY) != tfixRate) {
  317.     endMargin += 1;
  318.   }
  319.   
  320.   if (srcSound->channelCount == 1) {        /* mono src -> stereo dst */
  321.     tsrcEnd = tsrcEnd - endMargin - 1;
  322.     while ((dst < dstEnd) && (tsrc < tsrcEnd)) {
  323.       short s0, s1, samp;
  324.  
  325.       tresidue += tfixRate;
  326.       tsrc += (tresidue >> LOG2_FIXPOINT_UNITY);
  327.       tresidue = tresidue & (FIXPOINT_UNITY-1);
  328.       s0 = tsrc[0];
  329.       s1 = tsrc[1];
  330.       /* at this point:
  331.        * s0 is the "low" sample
  332.        * s1 is the "high" sample
  333.        * tresidue is between 0 and (fixpoint) 1.
  334.        * do a linear interpolation between s0 and s1
  335.        */
  336.       samp = s0 + ((tresidue * (s1 - s0)) >> LOG2_FIXPOINT_UNITY);
  337.       *dst++ = samp;
  338.       *dst++ = samp;
  339.     }
  340.   } else {                    /* stereo src -> stereo dst */
  341.     tsrcEnd = tsrcEnd - endMargin - 3;
  342.     while ((dst < dstEnd) && (tsrc < tsrcEnd)) {
  343.       short s0, s1, samp;
  344.  
  345.       tresidue += tfixRate;
  346.       tsrc += (tresidue >> LOG2_FIXPOINT_UNITY) * 2;
  347.       tresidue = tresidue & (FIXPOINT_UNITY-1);
  348.       s0 = tsrc[0];
  349.       s1 = tsrc[2];
  350.       /* at this point:
  351.        * s0 is the "low" sample for left channel
  352.        * s1 is the "high" sample for left channel
  353.        * tresidue is between 0 and (fixpoint) 1.
  354.        * do a linear interpolation between s0 and s1
  355.        */
  356.       samp = s0 + ((tresidue * (s1 - s0)) >> LOG2_FIXPOINT_UNITY);
  357.       *dst++ = samp;        /* left channel */
  358.       /* and now for the right channel */
  359.       s0 = tsrc[1];
  360.       s1 = tsrc[3];
  361.       samp = s0 + ((tresidue * (s1 - s0)) >> LOG2_FIXPOINT_UNITY);
  362.       *dst++ = samp;
  363.     }
  364.   }
  365.  
  366.   /* decache instance variables that have changed */
  367.   src = tsrc;
  368.   residue = tresidue;        /* current offset between s[0] and s[1] */
  369.  
  370.   /* zero out any remaining part of the buffer */
  371.   while (dst < dstEnd) {
  372.     *dst++ = 0;
  373.   }
  374.  
  375.   /* stop the music when we've played the whole file */
  376.   if (tsrc >= tsrcEnd) {
  377.     [dacPlayer finish];
  378.   }
  379.  
  380.   return self;
  381. }
  382.  
  383. - didChangeState:player from:(Pla_state_t)old to:(Pla_state_t)new
  384. {
  385.   [self _updateStatus:self];
  386.   /*
  387.    * If the sound has ended naturally (without us hitting the stop button)
  388.    * and the repeat button is on, then play the sound again.
  389.    */
  390.   if ((new == PLA_STOPPED) &&
  391.       (stoppedManually == NO) &&
  392.       [repeatButton state]) {
  393.     [self play:self];
  394.   }
  395.   return self;  
  396. }
  397.  
  398. @end