home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 10 Tools / 10-Tools.zip / cset21v6.zip / MMPM2TK / TK / ADMCT / ADMCLOAD.C < prev    next >
C/C++ Source or Header  |  1993-04-12  |  21KB  |  660 lines

  1. /********************* START OF SPECIFICATIONS *********************
  2. *
  3. * SUBROUTINE NAME: MCILOAD.C
  4. *
  5. * DESCRIPTIVE NAME: Audio MCD Load Element Routine.
  6. *
  7. *
  8. *
  9. *              Copyright (c) IBM Corporation  1991, 1993
  10. *                        All Rights Reserved
  11. *
  12. * FUNCTION:Load an Waveform Element.
  13. *
  14. * NOTES:
  15. *
  16. * ENTRY POINTS:
  17. *
  18. * INPUT: MCI_LOAD message.
  19. *
  20. * EXIT-NORMAL: MCIERR_SUCCESS.
  21. *
  22. * EXIT_ERROR:  Error Code.
  23. *
  24. * EFFECTS:
  25. *
  26. * INTERNAL REFERENCES:    CreateNAssocStream ().
  27. *                         DestroyStream().
  28. *                         SetAudioDevice().
  29. *                         VSDInstToWaveSetParms().
  30. *                         OpenFile().
  31. *
  32. * EXTERNAL REFERENCES:    SpiStopStream  ().
  33. *                         SpiAssociate   ().
  34. *                         SpiDisableEvent().
  35. *                         SpiSeekStream  ().
  36. *
  37. *********************** END OF SPECIFICATIONS **********************/
  38. #define INCL_BASE
  39. #define INCL_DOSMODULEMGR
  40. #define INCL_DOSSEMAPHORES
  41.  
  42. #include <os2.h>
  43. #include <string.h>
  44. #include <os2medef.h>                   // MME includes files.
  45. #include <audio.h>                      // Audio Device defines
  46. #include <ssm.h>                        // SSM spi includes.
  47. #include <meerror.h>                    // MM Error Messages.
  48. #include <mmioos2.h>                    // MMIO Include.
  49. #include <mcios2.h>                     // MM System Include.
  50. #include <mmdrvos2.h>                   // Mci Driver Include.
  51. #include <mcipriv.h>                    // MCI Connection stuff
  52. #include <mcd.h>                        // VSDIDriverInterface.
  53. #include <hhpheap.h>                    // Heap Manager Definitions
  54. #include <qos.h>
  55. #include <audiomcd.h>                   // Component Definitions.
  56. #include "admcfunc.h"                   // Function Prototypes
  57. #include <sw.h>
  58.  
  59. /********************* START OF SPECIFICATIONS *********************
  60. *
  61. * SUBROUTINE NAME: MCILOAD.C
  62. *
  63. * DESCRIPTIVE NAME: Audio MCD Load Element Routine.
  64. *
  65. * FUNCTION:Load an Waveform Element.
  66. *
  67. * NOTES: The following concepts are illustrated in this file.
  68. *  A. How to check flags on a load.
  69. *  B. How to stop any commands which are in process.
  70. *  C. Why cuepoints/positionadvises need to be turned off on a MCI_LOAD.
  71. *  D. Handling OPEN_MMIO on an MCI_LOAD.
  72. *  E. Why reassociation of a stream on MCI_LOAD is desirable and
  73. *     when it is appropriate.
  74. *
  75. * ENTRY POINTS:
  76. *
  77. * INPUT: MCI_LOAD message.
  78. *
  79. * EXIT-NORMAL: Return Code 0.
  80. *
  81. * EXIT_ERROR:  Error Code.
  82. *
  83. * EFFECTS:
  84. *
  85. * INTERNAL REFERENCES:DestroyStream(), ReadRIFFWaveHeaderInfo()
  86. *                     SetAmpDefaults(), SetWaveDeviceDefaults().
  87. *                     SetAudioDevice().
  88. *
  89. * EXTERNAL REFERENCES:
  90. *
  91. *********************** END OF SPECIFICATIONS **********************/
  92.  
  93.  
  94. RC MCILoad ( FUNCTION_PARM_BLOCK *pFuncBlock)
  95. {
  96.  
  97.   ULONG                ulrc;                // MME Propogated Error RC
  98.   ULONG                ulHoldError;         /* Hold status of errors */
  99.   ULONG                ulParam1;            // Incoming MCI Flags
  100.   ULONG                ulAbortNotify = FALSE;// indicates whether to abort a previous
  101.   ULONG                ulOldMode;           // indicates if stream must be destroyed
  102.                                             // or reassociated
  103.   ULONG                ulOldDataType;       // ditto--see comments below.
  104.                                             // operation
  105.   ULONG                ulOldBPS;
  106.   ULONG                ulOldSampRate;
  107.   ULONG                ulOldChannels;
  108.  
  109.   ULONG                ulLoadFlags;         // Incoming Flags Mask
  110.   ULONG                ulSetAll;            //  Reinit AudioIF
  111.  
  112.   BOOL                 fMustReinit = FALSE;
  113.   INSTANCE             *ulpInstance;        // Local Instance//
  114.  
  115.   PMCI_LOAD_PARMS      pLoadParms;         // App MCI Data Struct
  116.   MCI_WAVE_SET_PARMS   SetParms;            // App MCI Data Struct
  117.  
  118.   MCI_AMP_INSTANCE     BackupAmp;           // Hold old amp values
  119.  
  120.   extern HHUGEHEAP     heap;                // Global MCD Heap
  121.   extern HID           hidASource;          // hid's for FSSH
  122.   extern HID           hidATarget;          // "     "      "
  123.  
  124.   MTIME_EVCB           *pMCuePtEVCB;
  125.   MTIME_EVCB           *pTempCuePtEVCB;
  126.  
  127.  
  128.  
  129.   /**************************************
  130.   * Dereference Pointers
  131.   **************************************/
  132.   ulpInstance = (INSTANCE *)pFuncBlock->ulpInstance;
  133.   ulParam1 = pFuncBlock->ulParam1;
  134.   ulParam1 &= ~MCI_OPEN_ELEMENT;
  135.   ulLoadFlags = ulParam1;
  136.  
  137.   /*---------------------------------------------------------
  138.   * Make a copy of the amp/mixer instance in case any errors
  139.   * happened.
  140.   *---------------------------------------------------------*/
  141.  
  142.   memmove( &BackupAmp, &MIX, sizeof( MCI_AMP_INSTANCE ) );
  143.  
  144.  
  145.   /**************************************
  146.   * Mask UnWanted Bits
  147.   **************************************/
  148.   ulLoadFlags &= ~(MCI_NOTIFY + MCI_WAIT + MCI_OPEN_MMIO + MCI_READONLY );
  149.  
  150.  
  151.   ulSetAll = MCI_WAVE_SET_BITSPERSAMPLE | MCI_WAVE_SET_FORMATTAG     |
  152.              MCI_WAVE_SET_CHANNELS      | MCI_WAVE_SET_SAMPLESPERSEC;
  153.  
  154.  
  155.   if ( ulLoadFlags > 0 )
  156.      {
  157.  
  158.      if ( ulParam1 & MCI_OPEN_PLAYLIST)
  159.         {
  160.         return ( MCIERR_UNSUPPORTED_FLAG );
  161.         }
  162.  
  163.      return ( MCIERR_INVALID_FLAG );
  164.      }
  165.  
  166.  
  167.  
  168.   /****************************************
  169.   * Check For Valid Parameters and memory
  170.   ****************************************/
  171.  
  172.   pLoadParms = (PMCI_LOAD_PARMS) pFuncBlock->ulParam2;
  173.  
  174.   ulrc = CheckMem ( (PVOID)pLoadParms,
  175.                     sizeof (MCI_LOAD_PARMS),
  176.                     PAG_READ);
  177.  
  178.   if (ulrc != MCIERR_SUCCESS)
  179.           return ( MCIERR_MISSING_PARAMETER );
  180.  
  181.  
  182.   /*-------------------------------------------
  183.   * If the caller used a readonly flag and
  184.   * there was no filename, this is an
  185.   * invalid combination.
  186.   *------------------------------------------*/
  187.  
  188.   if ( ulParam1 & MCI_READONLY &&
  189.        !pLoadParms->pszElementName )
  190.      {
  191.      return ( MCIERR_MISSING_PARAMETER );
  192.      }
  193.  
  194.   /******************************************
  195.   * If these variables change during the
  196.   * load, then the stream must be destroyed
  197.   * rather than reassociated since SSM's
  198.   * buffer sizes can easily change.
  199.   *******************************************/
  200.  
  201.   ulOldMode     = AMPMIX.ulOperation;
  202.   ulOldBPS      = AMPMIX.lBitsPerSRate;
  203.   ulOldSampRate = AMPMIX.lSRate;
  204.   ulOldDataType = AMPMIX.sMode;
  205.   ulOldChannels = AMPMIX.sChannels;
  206.  
  207.  
  208.   /****************************************************
  209.   * Get the semaphore which allows us to abort pending
  210.   * plays and records.
  211.   ****************************************************/
  212.  
  213.   GetNotifyAbortAccess ( ulpInstance, &ulAbortNotify );
  214.  
  215.   /****************************************************
  216.   * Interrupt any plays/records/saves which are pending
  217.   * so the load can continue
  218.   ****************************************************/
  219.  
  220.   ulrc = LoadAbortNotifies( ulpInstance, pFuncBlock, ulAbortNotify );
  221.  
  222.   if ( ulrc )
  223.      {
  224.      return ( ulrc );
  225.      }
  226.  
  227.   ulpInstance->ulOldStreamPos  = 0;
  228.  
  229.  
  230.   /********************************************
  231.   * If the user has enabled cuepoints discard
  232.   * them since this is a new file.  If we do not
  233.   * do this, then cuepoints could pop up in the
  234.   * new file which were set in the old file.
  235.   ********************************************/
  236.  
  237.   if ((ulpInstance->usCuePt == TRUE) || (ulpInstance->usCuePt == EVENT_ENABLED))
  238.      {
  239.      pMCuePtEVCB = CUEPOINT;
  240.  
  241.      while ( pMCuePtEVCB )
  242.         {
  243.         if (ulpInstance->ulCreateFlag == PREROLL_STATE)
  244.             {
  245.             SpiDisableEvent (pMCuePtEVCB->HCuePtHndl );
  246.             }
  247.         pTempCuePtEVCB = pMCuePtEVCB;
  248.         pMCuePtEVCB = pMCuePtEVCB->pNextEVCB;
  249.         CleanUp( pTempCuePtEVCB );
  250.         } /* while there are cue points to remove */
  251.  
  252.      CUEPOINT = NULL;
  253.  
  254.      } /* if we have cue points in use */
  255.  
  256.  
  257.   /****************************************************
  258.   * Disable Position Advise (i.e. the user may have
  259.   * turned on position advise in the previous stream
  260.   * and since we can possibly reusing the stream, turn
  261.   * off position advise.
  262.   ****************************************************/
  263.  
  264. // why is there two states for this flag--why not just one?
  265.  
  266.   if ( (ulpInstance->usPosAdvise == TRUE) ||
  267.        (ulpInstance->usPosAdvise == EVENT_ENABLED))
  268.       {
  269.       if (ulpInstance->ulCreateFlag == PREROLL_STATE)
  270.  
  271.           SpiDisableEvent (ulpInstance->StreamInfo.hPosEvent);
  272.  
  273.       ulpInstance->usPosAdvise = FALSE;
  274.       }
  275.  
  276.   /* Remove any undo nodes if they were allocated */
  277.  
  278.   RemoveUndoNodes( ulpInstance );
  279.  
  280.   /************************************************
  281.   * If a file was previously opened then close it
  282.   * (however, if it was opened with the OPEN_MMIO
  283.   * flag, then don't close it.  That is the
  284.   * applications responsibility).
  285.   ************************************************/
  286.  
  287.   ulrc = CloseFile( ulpInstance );
  288.  
  289.  
  290.   /**************************************************
  291.   * Check for valid element names -- ONLY if the user
  292.   * passed in a filename, not a file handle
  293.   **************************************************/
  294.  
  295.   if ( !( ulParam1 & MCI_OPEN_MMIO )  )
  296.      {
  297.  
  298.      ulrc = CheckForValidElement( ulpInstance,
  299.                                   pLoadParms->pszElementName,
  300.                                   ulParam1 );
  301.  
  302.      if ( ulrc )
  303.         {
  304.         ulpInstance->fFileExists = FALSE;
  305.         return ( ulrc );
  306.         }
  307.  
  308.      } /* if the user did not pass in a file handle instead of a file name */
  309.  
  310.  
  311.  
  312.  
  313.   /******************************************************
  314.   * If hmmio is passed in just update instance copy of
  315.   * hmmio, reset filexists flag and return success
  316.   *****************************************************/
  317.  
  318.   ulrc = OpenHandle( ulpInstance,
  319.                      ulParam1,
  320.                      (HMMIO) pLoadParms->pszElementName);
  321.  
  322.   if ( ulrc )
  323.      {
  324.      /* Set a flag to indicate that there no longer is a valid file loaded */
  325.  
  326.      ulpInstance->fFileExists = TRUE;
  327.      return ( ulrc );
  328.      }
  329.  
  330.   /**************************************************************
  331.   * Temporary File creation Flags.  If the io proc cannot
  332.   * perform temporary changes or the user specifically requested
  333.   * that the file was opened read only then do not try to
  334.   * perform temporary changes on the file (both of those actions
  335.   * require the ability to write).
  336.   ***************************************************************/
  337.  
  338.   if ( ulParam1 & MCI_READONLY )
  339.      {
  340.      ulpInstance->ulOpenTemp = MCI_FALSE;
  341.      }
  342.   else
  343.      {
  344.      ulpInstance->ulOpenTemp = MCI_TRUE;
  345.      }
  346.  
  347.  
  348.   ulpInstance->ulUsingTemp = MCI_FALSE;
  349.  
  350.  
  351.   if ( !( ulParam1 & MCI_OPEN_MMIO ) )
  352.  
  353.       {
  354.       ulrc = ProcessElement( ulpInstance, ulParam1, MCI_LOAD );
  355.  
  356.       if ( ulrc )
  357.          {
  358.          /* If processelements fails, it sets fFileExists to false */
  359.  
  360.          return ( ulrc );
  361.          }
  362.  
  363.       } /* Element Specified on Load */
  364.  
  365.  
  366.   /*******************************************
  367.   * Copy audio device attributes into MCI
  368.   * Wave Set structure
  369.   ********************************************/
  370.  
  371.   VSDInstToWaveSetParms ( &SetParms, ulpInstance);
  372.  
  373.   /**********************************************
  374.   * Set the Audio device attributes.
  375.   * ulSetAll is a flag set to waveaudio extensions
  376.   *************************************************/
  377.  
  378.   /**************************************************
  379.   * If the audio card switched modes (i.e. from play
  380.   * to record) because of the file load, then the
  381.   * stream must be destroyed and there is no
  382.   **************************************************/
  383.  
  384.   if ( ulOldMode     != AMPMIX.ulOperation     ||
  385.        ulOldDataType != ( ULONG ) AMPMIX.sMode ||
  386.        ulOldBPS      != AMPMIX.lBitsPerSRate   ||
  387.        ulOldSampRate != AMPMIX.lSRate          ||
  388.        ulOldChannels != AMPMIX.sChannels )
  389.      {
  390.      fMustReinit = TRUE;
  391.      ulrc = SetAudioDevice (ulpInstance, &SetParms, ulSetAll);
  392.    
  393.      if (ulrc)
  394.    
  395.         {
  396.         
  397.     //#ifdef PTRFIX
  398.     //     ulHoldError = ulrc;
  399.     //
  400.     //     AMPMIX.lSRate = AMPMIX.ulBestFitRate;
  401.     //     AMPMIX.sChannels = ( SHORT ) AMPMIX.ulBestFitChan;
  402.     //     AMPMIX.sMode = ( SHORT ) AMPMIX.ulBestFitTag;
  403.     //     AMPMIX.lBitsPerSRate = AMPMIX.ulBestFitBPS;
  404.     //
  405.     //
  406.     //     /*******************************************
  407.     //     * Copy audio device attributes into MCI
  408.     //     * Wave Set structure
  409.     //     ********************************************/
  410.     //
  411.     //     VSDInstToWaveSetParms ( &SetParms, ulpInstance);
  412.     //
  413.     //     /**********************************************
  414.     //     * Set the Audio device attributes.
  415.     //     * ulSetAll is a flag set to waveaudio extensions
  416.     //     *************************************************/
  417.     //
  418.     //     ulrc = SetAudioDevice (ulpInstance, &SetParms, ulSetAll);
  419.     //
  420.     //     if ( ulrc )
  421.     //        {
  422.      //#endif
  423.    
  424.    
  425.            /* Dont leave the file open */
  426.       
  427.            CloseFile( ulpInstance );
  428.       
  429.            /* Set a flag to indicate that there no longer is a valid file loaded */
  430.       
  431.            ulpInstance->fFileExists = FALSE;
  432.       
  433.            /* Ensure that our instance remains the same as before the load attempt */
  434.       
  435.            memmove( &MIX, &BackupAmp, sizeof ( MCI_AMP_INSTANCE ) );
  436.            return ( ulrc );
  437.    
  438.     //#ifdef PTRFIX
  439.     //        }
  440.     //#endif
  441.     //     /*-----------------------------------
  442.     //     * If we will be requesting realtime
  443.     //     * translation do not allow recording
  444.     //     *------------------------------------*/
  445.     //
  446.     //     ulpInstance->ulCapabilities &= ~( CAN_RECORD + CAN_SAVE );
  447.     //     ulpInstance->ulRealTimeTranslation = MMIO_REALTIME;
  448.     //
  449.         }
  450.  //#ifdef PTRFIX
  451.  //  else
  452.  //     {
  453.  //     ulpInstance->ulRealTimeTranslation = MMIO_NONREALTIME;
  454.  //     }
  455.  //#endif
  456.  
  457.      }
  458.   else
  459.      {
  460.      fMustReinit = FALSE;
  461.      }
  462.  
  463.   /*************************************************************
  464.   * If a stream has previously been created, it will be simpler
  465.   * and quicker to just reassociate the stream handlers.
  466.   * reassociation requires that the stream be stoped however
  467.   * before continuing.
  468.   *************************************************************/
  469.  
  470.   if (ulpInstance->ulCreateFlag == PREROLL_STATE)
  471.      {
  472.      /**************************************************
  473.      * If the audio card switched modes (i.e. from play
  474.      * to record) because of the file load, then the
  475.      * stream must be destroyed and there is no
  476.      * way to reassociate it.  In addition, reassociation
  477.      * is only possible if the data type, bits/sample
  478.      * sampling rate and channels remain the same.  If
  479.      * you have a non-audio streaming MCD, then reassociation
  480.      * is only possible when the data type and subtype
  481.      * remain the same.
  482.      **************************************************/
  483.  
  484.      if ( fMustReinit )
  485.  
  486.          {
  487.          /**********************************************
  488.          * Destroy The Stream -- no hope to reassociate
  489.          ***********************************************/
  490.          DestroyStream ( &ulpInstance->StreamInfo.hStream);
  491.  
  492.          /*************************************
  493.          * Set Stream Creation Flag-- so that
  494.          * following routines know to create
  495.          * the stream
  496.          **************************************/
  497.  
  498.          ulpInstance->ulCreateFlag = CREATE_STATE;
  499.  
  500.          }
  501.      else
  502.          {
  503.          /*************************************
  504.          * Place the stream in a stopped state
  505.          * which is required for reassociation.
  506.          *************************************/
  507.  
  508.          ulrc = SpiStopStream (ulpInstance->StreamInfo.hStream, SPI_STOP_DISCARD );
  509.  
  510.          if (!ulrc)
  511.             {
  512.             DosWaitEventSem (ulpInstance->hEventSem, (ULONG) -1 );
  513.             }
  514.          } /* the audio card did not switch modes */
  515.  
  516.      }  /* Stream in PreRoll (started) State */
  517.  
  518.  
  519.  
  520.   /***********************************************************
  521.   * perform this after the open because open file will modify
  522.   * the state of the following flags
  523.   ***********************************************************/
  524.  
  525.   if ( ulParam1 & MCI_READONLY )
  526.      {
  527.      ulpInstance->ulCapabilities &= ~( CAN_SAVE | CAN_RECORD );
  528.      }
  529.  
  530.  
  531.   /*****************************************************
  532.   * Currently return true if a playlst strm was created
  533.   *****************************************************/
  534.   if (ulpInstance->usPlayLstStrm == TRUE)
  535.       {
  536.       ulpInstance->StreamInfo.hidASource = hidASource;
  537.       ulpInstance->StreamInfo.hidATarget = hidATarget;
  538.  
  539.       ulpInstance->usPlayLstStrm = FALSE;
  540.  
  541.       }  /* Trash the old Stream Handler Handles */
  542.  
  543.   /*************************************************
  544.   * Reassociate The Stream Handlers with the new
  545.   * stream object if the stream has been created
  546.   * in the correct direction already.
  547.   *************************************************/
  548.   if (ulpInstance->ulCreateFlag == PREROLL_STATE)
  549.       {
  550.       /*********************************************
  551.       * Fill in Associate Control Block Info for
  552.       * file system stream handler (FSSH).  FSSH will
  553.       * use the mmio handle we are associating to
  554.       * stream information.
  555.       *********************************************/
  556.       ulpInstance->StreamInfo.acbmmio.ulObjType = ACBTYPE_MMIO;
  557.       ulpInstance->StreamInfo.acbmmio.ulACBLen = sizeof (ACB_MMIO);
  558.       ulpInstance->StreamInfo.acbmmio.hmmio = ulpInstance->hmmio;
  559.  
  560.       /***********************************************
  561.       * Associate FileSystem as source if Playing. Note
  562.       * the association is always done with the file
  563.       * system stream handler since it is invovled with
  564.       * mmio operations.  If you try this with the
  565.       * audio stream handler, you will get invalid
  566.       * handle back.
  567.       ***********************************************/
  568.  
  569.       if (AMPMIX.ulOperation == OPERATION_PLAY)
  570.          {
  571.          ulrc = SpiAssociate ( ulpInstance->StreamInfo.hStream,
  572.                                ulpInstance->StreamInfo.hidASource,
  573.                                (PVOID) &ulpInstance->StreamInfo.acbmmio );
  574.          }
  575.       /***********************************************
  576.       * Associate FileSystem as target if recording
  577.       ***********************************************/
  578.  
  579.       else
  580.          {
  581.          ulrc = SpiAssociate ( ulpInstance->StreamInfo.hStream,
  582.                                ulpInstance->StreamInfo.hidATarget,
  583.                                (PVOID) &ulpInstance->StreamInfo.acbmmio );
  584.  
  585.          } /* else we are in record mode */
  586.  
  587.       /* If the SpiAssociate failed, return an error */
  588.  
  589.       if ( ulrc )
  590.          {
  591.          return ( ulrc );
  592.          }
  593.  
  594.          /******************************************************************
  595.          * We need to seek to 0 to reset the target stream handlers position
  596.          * since an associate DOES NOT reset the current stream's time.
  597.          *******************************************************************/
  598.  
  599.          ulrc = SpiSeekStream ( ulpInstance->StreamInfo.hStream,
  600.                                 SPI_SEEK_ABSOLUTE,
  601.                                 0L );
  602.  
  603.          if (ulrc)
  604.             {
  605.             return (ulrc);
  606.             }
  607.  
  608.       } /* Preoll State Flag */
  609.  
  610.   /****************************************************
  611.   * If the user did not pass in the read only flag then
  612.   * tell the io proc that we have opened to to use temp
  613.   * files (if it can).  Temp files will allow the user
  614.   * to abort changes.
  615.   *****************************************************/
  616.  
  617.   if ( ulpInstance->ulOpenTemp )
  618.     {
  619.  
  620.     ulrc = SetupTempFiles( ulpInstance, ulParam1 );
  621.  
  622.     if ( ulrc )
  623.        {
  624.        return ( ulrc );
  625.        }
  626.     }
  627.  
  628.  
  629.   /****************************************************
  630.   * Inform the io proc whether or not the data should
  631.   * be translated real-time or not.
  632.   *****************************************************/
  633.  
  634. //  ulrc = DataTranslation( ulpInstance );
  635. //
  636. //  if ( ulrc )
  637. //     {
  638. //     return ( ulrc );
  639. //     }
  640.  
  641.  
  642.   /********************************************
  643.   * We will say that the stream is in a stopped
  644.   * state so that subsequent operations know
  645.   * that it will be safe to do operations on
  646.   * the stream--such as a cue or a seek.
  647.   ********************************************/
  648.  
  649.   STRMSTATE = MCI_STOP;
  650.  
  651.   return (ulrc);
  652.  
  653. } /* MCILoad */
  654.  
  655.  
  656.  
  657.  
  658.  
  659.  
  660.