
/*
**
**  $VER: dispatch.c 2.1 (17.2.98)
**  mpegaudio.datatype 2.1
**
**  Dispatch routine for a DataTypes class
**
**  Written 1996-1998 by Roland 'Gizzy' Mainz
**  Original example source from David N. Junod
**
*/

/* main includes */
#include "classbase.h"
#include "classdata.h"

/*****************************************************************************/

/* local prototypes */
static DISPATCHERFLAGS ULONG            Dispatch( REGA0 struct IClass *, REGA2 Object *, REGA1 Msg );
static                 BOOL             LoadSample( struct ClassBase *, Object * );

#ifndef NO_ENCODER
static                 ULONG            SaveMPEGAudio( struct ClassBase *, Object *, struct dtWrite * );
#endif /* NO_ENCODER */

static DISPATCHERFLAGS ULONG            bitstream_hook( REGA0 struct Hook *, REGA2 APTR , REGA1 MPEGA_ACCESS * );


/*****************************************************************************/

/* Create "mpegaudio.datatype" BOOPSI class */
struct IClass *initClass( struct ClassBase *cb )
{
    struct IClass *cl;

    /* Create our class... */
    if( cl = MakeClass( MPEGAUDIODTCLASS, SOUNDDTCLASS, NULL, (ULONG)sizeof( struct MPEGAudioInstData ), 0UL ) )
    {
#define DTSTACKSIZE (16384UL)
      cl -> cl_Dispatcher . h_Entry    = (HOOKFUNC)StackSwapDispatch; /* see stackswap.c */
      cl -> cl_Dispatcher . h_SubEntry = (HOOKFUNC)Dispatch;          /* see stackswap.c */
      cl -> cl_Dispatcher . h_Data     = (APTR)DTSTACKSIZE;           /* see stackswap.c */
      cl -> cl_UserData                = (ULONG)cb;                   /* class library base as expected by datatypes.library */

      AddClass( cl );
    }

    return( cl );
}

/*****************************************************************************/

/* class dispatcher */
static
DISPATCHERFLAGS
ULONG Dispatch( REGA0 struct IClass *cl, REGA2 Object *o, REGA1 Msg msg )
{
    struct ClassBase     *cb = (struct ClassBase *)(cl -> cl_UserData);
    ULONG                 retval = 0UL;

    switch( msg -> MethodID )
    {
/****** mpegaudio.datatype/OM_NEW ********************************************
*
*    NAME
*        OM_NEW -- Create a mpegaudio.datatype object.
*
*    FUNCTION
*        The OM_NEW method is used to create an instance of the
*        mpegaudio.datatype class. This method is passed to the superclass
*        first. After this, mpegaudio.datatype loads the sample using
*        "mpega.library"'s decoder into memory.
*
*    ATTRIBUTES
*        The following attributes can be specified at creation time.
*
*        DTA_SourceType (ULONG) -- Determinates the type of DTA_Handle
*            attribute. Only DTST_FILE is supported.
*            If any other type was set in a given DTA_SourceType,
*            OM_NEW will be rejected.
*            Defaults to DTST_FILE.
*
*        DTA_Handle -- For DTST_FILE, a BPTR to a lock is expected by
*            datatypesclass, which will convert this into a filehandle.
*            A DTST_RAM (create empty object) source type requires a NULL
*            handle.
*
*    NOTE
*        If the datatype was compiled with the NO_ENCODER flag set,
*        DTA_SourceType == DTST_RAM causes OM_NEW to reject the method.
*
*    RESULT
*        If the object was created a pointer to the object is returned,
*        otherwise NULL is returned.
*
******************************************************************************
*
*/
      case OM_NEW:
      {
          struct TagItem *ti;

          /* We only support DTST_FILE or DTST_RAM as source type */
          if( ti = FindTagItem( DTA_SourceType, (((struct opSet *)msg) -> ops_AttrList) ) )
          {
            if( ((ti -> ti_Data) != DTST_FILE)
#ifndef NO_ENCODER
&& ((ti -> ti_Data) != DTST_RAM)
#endif /* !NO_ENCODER */
)
            {
              SetIoErr( ERROR_OBJECT_WRONG_TYPE );

              break;
            }
          }

          /* This must not be a subclass of mpegaudio.datatype
           * (not implemented yet)
           */
          if( o == (Object *)cl )
          {
            if( retval = DoSuperMethodA( cl, o, msg ) )
            {
              /* Load sample... */
              if( !LoadSample( cb, (Object *)retval ) )
              {
                /* Something went fatally wrong, dispose object */
                CoerceMethod( cl, (Object *)retval, OM_DISPOSE );
                retval = 0UL;
              }
            }
          }
          else
          {
            /* Subclasses of mpegaudio.datatype are not implemented */
            SetIoErr( ERROR_NOT_IMPLEMENTED );
          }
      }
          break;

/****** mpegaudio.datatype/OM_DISPOSE ****************************************
*
*    NAME
*        OM_DISPOSE -- Delete object
*
*    FUNCTION
*        Frees the contents of the mpegvideo.datatype instance data
*        and passes then the msg to the superclass.
*
*    RESULT
*        Returns 0 evertimes.
*
******************************************************************************
*
*/
      case OM_DISPOSE:
      {
          LONG saved_ioerr = IoErr(); /* save I/O error */

          /* Free ourselves */
          DoSuperMethodA( cl, o, msg );

          /* Restore I/O error */
          SetIoErr( saved_ioerr );
      }
          break;

      case OM_UPDATE:
      {
          if( DoMethod( o, ICM_CHECKLOOP ) )
          {
            break;
          }
      }
      case OM_SET:
      {
          /* Pass the attributes to the sound class and force a refresh if we need it */
          if( retval = DoSuperMethodA( cl, o, msg ) )
          {
/* The following check statement isn't needed because OM_NEW does not allow subclasses of mpegaudio.datatype,
 * therefore, we're always the top instance...
 */
#ifdef COMMENTED_OUT
            /* Top instance ? */
            if( OCLASS( o ) == cl )
#endif /* COMMENTED_OUT */
            {
              struct RastPort *rp;

              /* Get a pointer to the rastport */
              if( rp = ObtainGIRPort( (((struct opSet *)msg) -> ops_GInfo) ) )
              {
                struct gpRender gpr;

                /* Force a redraw */
                gpr . MethodID   = GM_RENDER;
                gpr . gpr_GInfo  = ((struct opSet *)msg) -> ops_GInfo;
                gpr . gpr_RPort  = rp;
                gpr . gpr_Redraw = GREDRAW_UPDATE;

                DoMethodA( o, (Msg)(&gpr) );

                /* Release the temporary rastport */
                ReleaseGIRPort( rp );

                /* We did a refresh... */
                retval = 0UL;
              }
            }
          }
      }
          break;

/****** mpegaudio.datatype/DTM_WRITE **********************************************
*
*    NAME
*        DTM_WRITE -- Save data
*
*    FUNCTION
*        This method saves the object's contents to disk.
*
*        If dtw_Mode is DTWM_IFF, the method is passed unchanged to the
*        superclass, sound.datatype, which writes an IFF 8SVX sample.
*
*        If dtw_mode is DTWM_RAW, the object writes a MPEG audio stream to
*        the filehandle given.
*        (If the class library was compiled with the NO_ENCODER switch
*        (not the default), result == 0 and resul2 == ERROR_NOT_IMPLEMENTED
*        are returned).
*
*    TAGS
*        None defined.
*
*    RESULT
*        Returns 0 for failure (IoErr() returns result2), non-zero
*        for success.
*
******************************************************************************
*
*/
      case DTM_WRITE:
      {
          struct dtWrite *dtw;

          dtw = (struct dtWrite *)msg;

          /* Local data format requested ? */
          if( (dtw -> dtw_Mode) == DTWM_RAW )
          {
/* Enable the followng code if you don't have an encoder implemented... */
#ifdef NO_ENCODER
            SetIoErr( ERROR_NOT_IMPLEMENTED );
            retval = 0UL;
#else
            retval = SaveMPEGAudio( cb, o, dtw );
#endif /* NO_ENCODER */
          }
          else
          {
            /* Pass msg to superclass (which writes an IFF 8SVX sample)... */
            retval = DoSuperMethodA( cl, o, msg );
          }
      }
          break;

      /* Let the superclass handle everything else */
      default:
      {
          retval = DoSuperMethodA( cl, o, msg );
      }
          break;
    }

    return( retval );
}


static
BOOL LoadSample( struct ClassBase *cb, Object *o )
{
    struct MPEGAudioInstData *maid    = (struct MPEGAudioInstData *)INST_DATA( (cb -> cb_Lib . cl_Class), o );
    LONG                      error   = 0L;
    BOOL                      success = FALSE;
    BPTR                      fh;               /* stream handle                                */
    ULONG                     sourcetype;       /* type of stream (either DTST_FILE or DTST_RAM */
    struct VoiceHeader       *vh;               /* obj's voice header                           */

    /* Get file handle, handle type and VoiceHeader */
    if( GetDTAttrs( o, DTA_SourceType,    (&sourcetype),
                       DTA_Handle,        (&fh),
                       SDTA_VoiceHeader,  (&vh),
                       TAG_DONE ) == 3UL )
    {
      switch( sourcetype )
      {
        case DTST_FILE:
        {
            /* We got all what we want (a filehandle) */
        }
            break;

#ifndef NO_ENCODER
        case DTST_RAM:
        {
            /* Do nothing... */
        }
            break;
#endif /* !NO_ENCODER */

        default:
        {
            /* unsupported source type */
            error = ERROR_NOT_IMPLEMENTED;
        }
            break;
      }

      /* Any error ? */
      if( error == 0L )
      {
        if( fh )
        {
          BYTE         *sample     = NULL;
          LONG          pcm_pos    = 0L;   /* position in buffer */
          struct Hook   accesshook = { 0 };

          MPEGA_CTRL mpa_ctrl =
          {
             NULL,          // Bitstream access hook, see below (set to (&accesshook))
             // Layers I & II settings (mono, stereo)
             { TRUE, { 2, 0, 20000 }, { 2, 0, 20000 } },
             // Layer III settings (mono, stereo)
             { TRUE, { 2, 0, 20000 }, { 2, 0, 20000 } },
             0,             // Don't check mpeg validity at start (needed for mux stream)
             2048           // Stream Buffer size
          };

          ULONG samplesize = 0UL;

          /* init hook */
          accesshook . h_Entry    = (HOOKFUNC)bitstream_hook;
          accesshook . h_SubEntry = (HOOKFUNC)cb; /* misused here as 2nd data field... */
          accesshook . h_Data     = (APTR)fh;
          mpa_ctrl . bs_access    = (&accesshook);

          if( maid -> maid_MPAS = MPEGA_open( NULL, (&mpa_ctrl) ) )
          {
            WORD  buffer[ MPEGA_PCM_SIZE + 32 ],
                 *pcm[ 2 ];
            LONG  pcm_count;

            pcm[ 0 ] = pcm[ 1 ] = buffer;

            /* Decoder frames into a continous ram buffer; if the buffer gets to small, enlarge it... */
            while( (pcm_count = MPEGA_decode_frame( (maid -> maid_MPAS), pcm )) >= 0 )
            {
              BYTE  *newsample;
              ULONG  next  = pcm_pos + pcm_count + 32;

              if( samplesize <= next )
              {
                next += MPEGA_PCM_SIZE * 256UL;

                if( newsample = (BYTE *)AllocVec( (next + 256), MEMF_PUBLIC ) )
                {
                  /* "sample" and "newsample" are long-word aligned, and "pcm_pos" can be padded without trouble,
                   * therefore I can use CopyMemQuick here...
                   */
                  CopyMemQuick( sample, newsample, ((pcm_pos + 3UL) & ~3UL) );
                  FreeVec( sample );
                  sample     = newsample;
                  samplesize = next;
                }
                else
                {
                  /* no buffer memory */
                  pcm_count = MPEGA_ERR_MEM;
                  break;
                }
              }

#ifdef COMNTED_OUT
              /* KISS PCM->sample conversion function */
              {
                LONG   i;

                for( i = 0L ; i < pcm_count ; i++ )
                {
                  sample[ i + pcm_pos ] = (buffer[ i ] >> 8);
                }
              }
#else
              /* This replacement for the loop above is much faster because
               * the ratio "loop-check vs. PCM->sample-conversion" is ~~ 1:8
               * instead of 1:1 (like above)...
               */
              {
                register ULONG blocks = (pcm_count & ~(0x07UL)) >> 3UL,
                               cut    = pcm_count &  (0x07UL),
                               i;
                register BYTE *sptr   = sample + pcm_pos;
                register WORD *bptr   = buffer;

                /* Copy sample blocks... */
                for( i = 0UL ; i < blocks ; i++ )
                {
                  *sptr++ = (*bptr++ >> 8);
                  *sptr++ = (*bptr++ >> 8);
                  *sptr++ = (*bptr++ >> 8);
                  *sptr++ = (*bptr++ >> 8);
                  *sptr++ = (*bptr++ >> 8);
                  *sptr++ = (*bptr++ >> 8);
                  *sptr++ = (*bptr++ >> 8);
                  *sptr++ = (*bptr++ >> 8);
                }

                /* ... then the remaining samples. */
                for( i = 0UL ; i < cut ; i++ )
                {
                  *sptr++ = (*bptr++ >> 8);
                }
              }
#endif /* MMENTED_OUT */

              pcm_pos += pcm_count;
            }

            /* dispatch error code... */
            switch( pcm_count )
            {
              case MPEGA_ERR_NONE:                                   break;
              case MPEGA_ERR_EOF:                                    break;
              case MPEGA_ERR_BADFRAME: error = DTERROR_INVALID_DATA; break;
              case MPEGA_ERR_MEM:      error = ERROR_NO_FREE_STORE;  break;
              case MPEGA_ERR_NO_SYNC:  error = DTERROR_INVALID_DATA; break;
            }

            /* No error ? */
            if( !error )
            {
              ULONG clock  = ((SysBase -> ex_EClockFrequency) * 5UL); /* amiga clock */
              ULONG period = clock / (maid -> maid_MPAS -> dec_frequency);

              /* Set up voice header */
              vh -> vh_OneShotHiSamples     = clock / period;
              vh -> vh_RepeatHiSamples      = 0UL;
              vh -> vh_SamplesPerHiCycle    = 0UL;
              vh -> vh_SamplesPerSec        = clock / period;
              vh -> vh_Octaves              = 1UL;
              vh -> vh_Compression          = CMP_NONE;
              vh -> vh_Volume               = 0x10000UL; /* maximum volume */

              /* Set misc attributes
               * In fact, SDTA_Period and SDTA_Volume are calculated from SDTA_VoiceHeader info,
               * but we set it here EXPLICITLY that noone can say we didn't pass this to sound.datatype...
               */
              SetDTAttrs( o, NULL, NULL, SDTA_Sample,        sample,
                                         SDTA_SampleLength,  pcm_pos,
                                         SDTA_Period,        period,
                                         TAG_DONE );

              sample = NULL; /* The object now owns the sample data */

              success = TRUE;
            }

            MPEGA_close( (maid -> maid_MPAS) );
          }

          FreeVec( sample );
        }
        else
        {
          /* no filehandle */
          error = ERROR_NO_FREE_STORE;
        }
      }
    }
    else
    {
      /* can't get required attributes from superclass */
      error = ERROR_OBJECT_WRONG_TYPE;
    }

    SetIoErr( error );

    return( success );
}


#ifndef NO_ENCODER
/* The MPEG Audio encoder */
static
ULONG SaveMPEGAudio( struct ClassBase *cb, Object *o, struct dtWrite *dtw )
{
    ULONG retval = 0UL;
    LONG  error  = ERROR_NOT_IMPLEMENTED;

#if 0
    /* A NULL file handle is a nop (GMultiView uses this to test if a datatype supports RAW writing) */
    if( dtw -> dtw_FileHandle )
    {
    }
#endif

    /* Store Result2 */
    SetIoErr( error );

    return( retval );
}
#endif /* !NO_ENCODER */


/* mpega.library bitstream hook
 * Required because mpega.library has no way to access an already open filehandle and
 * because we're not allowed to close a filehandle which is owned by "datatypesclass".
 */
static
DISPATCHERFLAGS
ULONG bitstream_hook( REGA0 struct Hook *hook, REGA2 APTR handle, REGA1 MPEGA_ACCESS *access )
{
   struct ClassBase *cb     = (struct ClassBase *)(hook -> h_SubEntry);
   ULONG             retval = 0UL;

   switch( access -> func )
   {
      case MPEGA_BSFUNC_OPEN:
      {
         /* We don't know the total size :-( */
         access -> data . open . stream_size = 0L;

         /* Return handle */
         retval = (ULONG)(hook -> h_Data);
      }
         break;

      case MPEGA_BSFUNC_CLOSE: /* NOP */
         break;

      case MPEGA_BSFUNC_READ:
      {
         /* Check valid handle */
         if( handle )
         {
           retval = Read( (BPTR)handle, (access -> data . read . buffer), (access -> data . read . num_bytes) );
         }
      }
         break;

      case MPEGA_BSFUNC_SEEK:
      {
         if( handle )
         {
           retval = (Seek( (BPTR)handle, (access -> data . seek . abs_byte_seek_pos), OFFSET_BEGINNING ) == -1L);
         }
      }
         break;
   }

   return( retval );
}




