/*
 * DSP_WAFO2_WIN32: DSP Blockset S-function implementing
 *                  wave audio file output
 *
 * Original implementation by:
 *      Steve Mitchell
 *      Department of Electrical Engineering
 *      Cornell University
 *      Ithaca, NY  14853
 *
 * Code adapted and used by permission from the
 * Cornell Laboratory of Ornithology at Cornell University.
 *
 * Initial DSP Blockset version: D. Orofino
 * Copyright 1995-2000 The MathWorks, Inc.
 * $Revision: 1.8 $ $Date: 2000/07/11 19:29:10 $
 */

#include <windows.h>
#include <mmsystem.h>
#include <stdio.h>

#include "dsp_sim.h"

/* Simulink block parameters */
enum {
    FILENAME_ARGC,
    BITS_PER_SAMPLE_ARGC,	/* 8 or 16 */
    MIN_SAMPS_TO_WRITE_ARGC,
    NUM_ARGS
};

#define FILENAME_ARG(S)           ssGetSFcnParam(S, FILENAME_ARGC)
#define BITS_PER_SAMPLE_ARG(S)    ssGetSFcnParam(S, BITS_PER_SAMPLE_ARGC)
#define MIN_SAMPS_TO_WRITE_ARG(S) ssGetSFcnParam(S, MIN_SAMPS_TO_WRITE_ARGC)

#define INPORT 0

#define BITS_PER_SAMPLE(S) (unsigned short)(*mxGetPr(BITS_PER_SAMPLE_ARG(S)))

/* Structure for local block-instance resource-handling */
typedef struct
{
    boolean_T buffersAllocated;
    boolean_T wavFileOpen;
    boolean_T dataCacheConstructed;
} AllocatedResources;

typedef struct {
    AllocatedResources res;
} SFcnDWorkCache;

#define NUM_WAFO2_RESOURCES 3

enum {
    ALLOCATED_RESOURCE_DWORK_CACHE,
    NUM_DWORK
};

enum {
	kIN_WAVE_CHUNK,	    /* Boolean -- in wave chunk	  */
	kIN_DATA_CHUNK,     /* Boolean -- in data chunk	  */
        kSAMPLES_PER_FRAME, /* Samples per frame in input */
        kNUM_CHANNELS,	    /* 1 = mono, 2 = stereo	  */
        kNUMIWORKITEMS
};

#define IN_WAVE_CHUNK	(ssGetIWorkValue(S, kIN_WAVE_CHUNK))
#define IN_DATA_CHUNK	(ssGetIWorkValue(S, kIN_DATA_CHUNK))
#define NUM_SAMPLES	(ssGetIWorkValue(S, kSAMPLES_PER_FRAME))
#define NUM_CHANNELS	(ssGetIWorkValue(S, kNUM_CHANNELS))

enum {
	kDATA_CACHE,			/* Work space for integer samples	*/
	kFILE_DESC,			/* File descriptor			*/
	kFILENAME,			/* C-string file name			*/
	kDATA_CK_INFO,			/* Data chunk info record		*/
	kWAVE_CK_INFO,			/* Wave chunk info record		*/
	kNUMPWORKITEMS
};

#define FILE_DESC		((HMMIO)     (ssGetPWorkValue(S, kFILE_DESC)))
#define FILENAME		((char *)    (ssGetPWorkValue(S, kFILENAME)))
#define DATA_CK_INFO		((MMCKINFO *)(ssGetPWorkValue(S, kDATA_CK_INFO)))
#define WAVE_CK_INFO		((MMCKINFO *)(ssGetPWorkValue(S, kWAVE_CK_INFO)))

#define BYTES_PER_SAMPLE	(BITS_PER_SAMPLE(S)  / 8)
#define SAMPLE_RATE		(NUM_SAMPLES / ssGetSampleTime(S,INPORT))

#define MIN_SAMPS_TO_WRITE(S)    (int_T)*mxGetPr(MIN_SAMPS_TO_WRITE_ARG(S))
#define TOTAL_SAMPS_IN_CACHE(S)  (MAX((MIN_SAMPS_TO_WRITE(S) * NUM_CHANNELS) , ssGetInputPortWidth(S,INPORT)))
#define TOTAL_BYTES_IN_CACHE(S)  (TOTAL_SAMPS_IN_CACHE(S) * BYTES_PER_SAMPLE)

#define MMERROR			(mmResult != MMSYSERR_NOERROR)

static void CloseFile (SimStruct *S)
{
    if (FILE_DESC != NULL) {
        mmioClose(FILE_DESC, 0);
    }
    ssSetPWorkValue(S, kFILE_DESC, NULL);
    ssSetIWorkValue(S, kIN_WAVE_CHUNK, 0);
    ssSetIWorkValue(S, kIN_DATA_CHUNK, 0);
}


static void FreeBuffer(SimStruct *S)
{
    {
        char *filename = FILENAME;
        if (filename != NULL) {
            free(filename);
        }
        ssSetPWorkValue(S, kFILENAME,    NULL);
    }
    {
	MMCKINFO *dataCkInfo = DATA_CK_INFO;
	if (dataCkInfo != NULL) free (dataCkInfo);
	ssSetPWorkValue(S, kDATA_CK_INFO, NULL);
	ssSetIWorkValue(S, kIN_DATA_CHUNK, 0);
    }
    {
	MMCKINFO *waveCkInfo = WAVE_CK_INFO;
	if (waveCkInfo != NULL) free (waveCkInfo);
	ssSetPWorkValue(S, kWAVE_CK_INFO, NULL);
	ssSetIWorkValue(S, kIN_WAVE_CHUNK, 0);
    }
}


/*
 * ===================================================
 * BEGIN - Data Cache functions
 * ===================================================
 */

typedef struct {

    byte_T *data;           /* Pointer to allocated memory       */
    int_T   SizeInSamples;  /* Total number of samples in cache  */
    int_T   SampleCount;    /* Bytes per sample                  */
    int_T   BytesPerSample; /* Total number of bytes in cache    */

} DataCache;


/* Function: FreeDataCache ====================================================
 * Abstract:
 *		Free data buffer and then free data cache structure.
 */
static void FreeDataCache(SimStruct *S)
{
    DataCache *data_cache = (DataCache*)(ssGetPWorkValue (S, kDATA_CACHE));
    if (data_cache != NULL) {
        if (data_cache->data != NULL) free(data_cache->data);
        free(data_cache);
        ssSetPWorkValue(S, kDATA_CACHE, NULL);
    }
}


/* Function: CleanUp ===============================================
 * Abstract:
 *		Free memory and close files
 */
static void CleanUp(SimStruct *S)
{
    CloseFile(S);
    FreeBuffer(S);
    FreeDataCache(S);
}


/* Function: ConstructDataCache ================================================
 * Abstract:
 *		Construct Data Cache
 */
static void ConstructDataCache(SimStruct *S)
{
    /* Allocate data cache and sample buffer */
    DataCache *data_cache = (DataCache *)calloc(1, sizeof(DataCache));
    
    /* We must set the PWork before CleanUp can be called. */
    ssSetPWorkValue(S, kDATA_CACHE, data_cache);  
    
    if (data_cache == NULL) {
        CleanUp(S);
        THROW_ERROR(S, "Failed to allocate data cache.")
    }
    
    data_cache->data = (byte_T *)calloc(TOTAL_BYTES_IN_CACHE(S), sizeof(byte_T));
    
    if (data_cache->data == NULL) {
        CleanUp(S);
        THROW_ERROR(S, "Failed to allocate data cache.")
    }
    
    data_cache->SizeInSamples  = TOTAL_SAMPS_IN_CACHE(S);
    data_cache->SampleCount    = 0;
    data_cache->BytesPerSample = BYTES_PER_SAMPLE;
}

/* Function: WriteSamplesToFile ================================================
 * Abstract:
 *		Write samples to file and empty cache
 */
static void WriteSamplesToFile(SimStruct *S, DataCache *data_cache) { 

    const int_T bufsize = data_cache->SampleCount * BYTES_PER_SAMPLE;

    if (mmioWrite(FILE_DESC, data_cache->data, bufsize) != bufsize) {
	CleanUp(S);
	THROW_ERROR(S,"Error writing file");
    }

    data_cache->SampleCount = 0;
}


/* Function: ReadSamplesFromInput ================================================
 * Abstract:
 *		Read samples from input and set sample count in cache
 */
static void ReadSamplesFromInput(SimStruct *S, DataCache *data_cache) {
    
    const long  numChannels  = NUM_CHANNELS;
    const long  numSamples   = NUM_SAMPLES;
    real_T     *uptr         = (real_T *)ssGetInputPortRealSignal(S, INPORT);
    
    const int_T  offset       = data_cache->SampleCount * BYTES_PER_SAMPLE;
    byte_T      *sampleBuffer = data_cache->data + offset;
    
    data_cache->SampleCount += numChannels * numSamples;
    
    /* Convert input to integer samples amd fill cache from input */
    switch (BITS_PER_SAMPLE(S)) {
    case 8:
        {
            unsigned char *buf = (unsigned char *)sampleBuffer;
            int_T i;
            for (i=0; i < numSamples; i++) {
                int_T channel;
                for (channel=0; channel < numChannels; channel++) {
                    buf[channel + i * numChannels] = 
                        (unsigned char)MAX(0, MIN(255,
                        floor(uptr[numSamples * channel + i] * 128 + 128)));
                }
            }
        }
        break;
        
    case 16:
        {
            short *buf = (short *)sampleBuffer;
            int_T channel;
            for (channel=0; channel < numChannels; channel++) {
                int_T i;
                for (i=0; i < numSamples; i++) {
#if 0
                    buf[channel + i * numChannels] = 
                        (short)MAX(-32768, MIN(32767, floor(uptr[numSamples * channel + i] * 32768)));
#else
                    real_T u = floor(*uptr * 32768);  uptr++;
                    buf[channel + i * numChannels] = 
                        (short)MAX(-32768, MIN(32767, u));
#endif
                }
            }
        }
        break;
    }
}



static void InitPointers(SimStruct *S)
{
    ssSetPWorkValue(S, kDATA_CACHE,   NULL);
    ssSetPWorkValue(S, kFILE_DESC,    NULL);
    ssSetPWorkValue(S, kFILENAME,     NULL);

    ssSetPWorkValue(S, kDATA_CK_INFO, NULL);
    ssSetPWorkValue(S, kWAVE_CK_INFO, NULL);

    ssSetIWorkValue(S, kIN_WAVE_CHUNK, 0);
    ssSetIWorkValue(S, kIN_DATA_CHUNK, 0);
}


static void MakeBuffer(SimStruct *S)
{
    char *filename;
    long filename_len;

    /* Allocate file name */
    filename_len = mxGetNumberOfElements(FILENAME_ARG(S)) + 1;
    filename = (char *)calloc(filename_len, 1);
    if (filename == NULL) {
	CleanUp(S);
	THROW_ERROR(S, "Error allocating memory")
    }
    ssSetPWorkValue (S, kFILENAME, filename);

    /* Initialize filename */
    if (mxGetString(FILENAME_ARG(S), filename, filename_len)) {
	CleanUp(S);
	THROW_ERROR(S, "File name must be a string")
    }
    
    /* Allocate data chunk info */
    {
	MMCKINFO *dataCkInfo = (MMCKINFO *)calloc(1, sizeof (MMCKINFO));
	if (dataCkInfo == NULL) {
	    CleanUp(S);
	    THROW_ERROR(S, "Error allocating memory")
	}
	ssSetPWorkValue(S, kDATA_CK_INFO, dataCkInfo);
    }

    /* Allocate wave chunk info */
    {
	MMCKINFO *waveCkInfo = (MMCKINFO *)calloc(1, sizeof (MMCKINFO));
	if (waveCkInfo == NULL) {
	    CleanUp(S);
	    THROW_ERROR(S, "Error allocating memory")
	}
	ssSetPWorkValue (S, kWAVE_CK_INFO, waveCkInfo);
    }
}


static void MakePCMWaveFormat(SimStruct *S, LPPCMWAVEFORMAT lpwf)
{
    lpwf->wf.wFormatTag		= WAVE_FORMAT_PCM;
    lpwf->wf.nChannels		= (short)NUM_CHANNELS;
    lpwf->wf.nSamplesPerSec	= (long)floor(0.5 + SAMPLE_RATE);
    lpwf->wBitsPerSample	= BITS_PER_SAMPLE(S);
    lpwf->wf.nBlockAlign	= lpwf->wf.nChannels * (lpwf->wBitsPerSample / 8);
    lpwf->wf.nAvgBytesPerSec	= lpwf->wf.nSamplesPerSec * lpwf->wf.nBlockAlign;
}


static void WriteWavHeader(SimStruct *S)
{
    MMRESULT		mmResult;
    MMCKINFO		mmFMTCkInfo;
    PCMWAVEFORMAT	pcmWaveFormat;
    long		m;

    const long length     = 0;  /* This will be corrected when ascending from the chunk */
    long       dataLength = length * NUM_CHANNELS * sizeof (short);

    /* Create 'WAVE' chunk */
    WAVE_CK_INFO->fccType = mmioFOURCC('W', 'A', 'V', 'E'); 
    /* 'WAVE' + 'fmt ' + size + 'data' + size = 20 */
    WAVE_CK_INFO->cksize = 20 + sizeof (PCMWAVEFORMAT) + dataLength; 
    mmResult = mmioCreateChunk(FILE_DESC, WAVE_CK_INFO, MMIO_CREATERIFF);
    if (MMERROR) {
	CleanUp(S);
	THROW_ERROR(S, "Error writing file header")
    }
    ssSetIWorkValue(S, kIN_WAVE_CHUNK, 1);

    /* Create 'fmt ' chunk */
    mmFMTCkInfo.ckid = mmioFOURCC('f', 'm', 't', ' '); 
    mmFMTCkInfo.cksize = sizeof(PCMWAVEFORMAT); 
    mmResult = mmioCreateChunk(FILE_DESC, &mmFMTCkInfo, 0);
    if (MMERROR) {
	CleanUp(S);
	THROW_ERROR(S, "Error writing file header")
    }
    MakePCMWaveFormat(S, &pcmWaveFormat);

    /* Write format chunk */
    m = mmioWrite (FILE_DESC, (char*) &pcmWaveFormat, sizeof (PCMWAVEFORMAT));
    if (m != sizeof(PCMWAVEFORMAT)) {
	CleanUp(S);
	THROW_ERROR(S, "Error writing file header")
    }

    /* End format chunk */
    mmResult = mmioAscend (FILE_DESC, &mmFMTCkInfo, 0);
    if (MMERROR) {
	CleanUp(S);
	THROW_ERROR(S, "Error writing file header")
    }

    /* Create 'data' chunk */
    DATA_CK_INFO->ckid = mmioFOURCC('d', 'a', 't', 'a'); 
    DATA_CK_INFO->cksize = dataLength; 
    mmResult = mmioCreateChunk(FILE_DESC, DATA_CK_INFO, 0);
    if (MMERROR) {
	CleanUp(S);
	THROW_ERROR(S, "Error writing file header")
    }

    ssSetIWorkValue(S, kIN_DATA_CHUNK, 1);
}


static void OpenWavFile (SimStruct *S)
{
    HMMIO	fp;
    MMRESULT	mmResult;

    /* Check file name */
    if (strlen(FILENAME) > 127) {
	CleanUp(S);
	THROW_ERROR(S, "File name too long")
    }

    if (strchr (FILENAME, '+') != NULL) {
	CleanUp(S);
	THROW_ERROR(S, "File name must not contain a \"+\" character")
    }

    fp = mmioOpen(FILENAME, NULL, MMIO_WRITE | MMIO_CREATE | MMIO_ALLOCBUF);
    if (fp == NULL) {
	CleanUp(S);
	THROW_ERROR(S, "Error opening file");
    }
    ssSetPWorkValue(S, kFILE_DESC, fp);

    /* Set internal buffer size 
     *
     * Note -- for faster access, supply a custom I/O buffer 
     * and access with mmioAdvance () */
        
    mmResult = mmioSetBuffer (FILE_DESC, NULL, TOTAL_BYTES_IN_CACHE(S), 0);
    if (MMERROR) {
        CleanUp (S);
        THROW_ERROR(S, "Error opening file")
    }
}


/* Function: setFrameSizAndChans ===============================================
 * Abstract:
 *     Determines both the frame size and number of channels
 *     based on the input port matrix dimensions, and stores
 *     the values in the IWork area.
 */
static void setFrameSizAndChans(SimStruct *S)
{
    /* Rules for interpreting input dimensions:
     *
     *    frame input: rows=frame data, cols=#chans
     *      non-frame: matrix -> treat like frame input
     *                 vector -> one channel, any orientation
     */
    const boolean_T isFrame = (ssGetInputPortFrameData(S, INPORT) == FRAME_YES);
    int spf;
    int nChans;

    const int_T numDims = ssGetInputPortNumDimensions(S, 0);
    const int_T *dims   = ssGetInputPortDimensions(S, 0);
    const int_T m       = dims[0];
    const int_T n       = (numDims == 2) ? dims[1] : 1;

    if (isFrame) {
        spf    = m;
        nChans = n;
    } else {
        spf    = 1;
        nChans = ssGetInputPortWidth(S, INPORT);
    }

    ssSetIWorkValue(S, kSAMPLES_PER_FRAME, spf);
    ssSetIWorkValue(S, kNUM_CHANNELS,      nChans);
}


# if defined(MATLAB_MEX_FILE)
# define MDL_CHECK_PARAMETERS
static void mdlCheckParameters (SimStruct *S)
{
    if(!mxIsChar(FILENAME_ARG(S)))
        THROW_ERROR(S, "File Name must be a string");

    if(!IS_SCALAR(BITS_PER_SAMPLE_ARG(S))) 
        THROW_ERROR(S, "Sample Width  must be a scalar");

    switch (BITS_PER_SAMPLE(S)) {
	case 8:
	case 16:
	    break;
	default:
	    THROW_ERROR(S, "Number of bits per sample must be 8 or 16");
    }

    if(OK_TO_CHECK_VAR(S, MIN_SAMPS_TO_WRITE_ARG(S))) {
        if(!IS_SCALAR(MIN_SAMPS_TO_WRITE_ARG(S)))
            THROW_ERROR(S, "Minimum samples to write must be a scalar");
    }
}
# endif


static void mdlInitializeSizes (SimStruct *S)
{
    ssSetNumSFcnParams(S, NUM_ARGS);

#if defined(MATLAB_MEX_FILE)
    if(ssGetNumSFcnParams(S) != ssGetSFcnParamsCount(S)) return;
    mdlCheckParameters(S);
    RETURN_IF_ERROR(S);
#endif

    ssSetSFcnParamNotTunable(S, FILENAME_ARGC);
    ssSetSFcnParamNotTunable(S, BITS_PER_SAMPLE_ARGC);
    ssSetSFcnParamNotTunable(S, MIN_SAMPS_TO_WRITE_ARGC);

    if (!ssSetNumOutputPorts(S, 0)) return;

    if (!ssSetNumInputPorts(         S, 1)) return;
    if (!ssSetInputPortDimensionInfo(S, INPORT, DYNAMIC_DIMENSION)) return;
    ssSetInputPortFrameData(         S, INPORT, FRAME_INHERITED);
    ssSetInputPortRequiredContiguous(S, INPORT, 1);
    
    ssSetInputPortDirectFeedThrough(S, 0, 1);

    ssSetNumSampleTimes(S, 1);
    ssSetNumIWork(      S, kNUMIWORKITEMS);
    ssSetNumPWork(      S, kNUMPWORKITEMS);

    if(!ssSetNumDWork(      S, NUM_DWORK)) return;
    ssSetDWorkWidth(        S, ALLOCATED_RESOURCE_DWORK_CACHE, NUM_WAFO2_RESOURCES);
    ssSetDWorkName(         S, ALLOCATED_RESOURCE_DWORK_CACHE, "allocRsrcsCache");
    ssSetDWorkDataType(     S, ALLOCATED_RESOURCE_DWORK_CACHE, SS_BOOLEAN);
    ssSetDWorkComplexSignal(S, ALLOCATED_RESOURCE_DWORK_CACHE, COMPLEX_NO);

    ssSetOptions(S, SS_OPTION_CALL_TERMINATE_ON_EXIT);

    /* IMPORTANT NOTE! mdlTerminate can be called at any point now... */
}


static void mdlInitializeSampleTimes (SimStruct *S)
{
    ssSetSampleTime (S, 0, INHERITED_SAMPLE_TIME);
    ssSetOffsetTime (S, 0, FIXED_IN_MINOR_STEP_OFFSET);
}


#define MDL_START
static void mdlStart (SimStruct *S)
{
    SFcnDWorkCache *cache = (SFcnDWorkCache *)ssGetDWork(S, ALLOCATED_RESOURCE_DWORK_CACHE);

    /* Initialize the resource cache for case of an early mdlTerminate call */
    cache->res.buffersAllocated     = false;
    cache->res.wavFileOpen          = false;
    cache->res.dataCacheConstructed = false;

#ifdef MATLAB_MEX_FILE
    /* Check that input rate is not continuous: */
    if (ssGetSampleTime(S, INPORT) == CONTINUOUS_SAMPLE_TIME) {
        THROW_ERROR(S,"Input to block must have a discrete sample time.");
    }
#endif

    /* Determine number of channels */
    setFrameSizAndChans(S);
    if(NUM_CHANNELS > 2) {
        THROW_ERROR(S,"Number of channels must be either 1-(mono) or 2-(stereo) ");
    }

    /* Instance-specific work-vector pointers */
    InitPointers(S);

    /*
    ** Allocate resources necessary for using this S-function,
    ** and set appropriate flags associated with each resource.
    */

    /* Allocate buffers, initialize filename & waveheader */
    MakeBuffer(S);
    RETURN_IF_ERROR(S);
    cache->res.buffersAllocated = true;
    
    /* Open file */
    OpenWavFile(S);
    RETURN_IF_ERROR(S);
    cache->res.wavFileOpen = true;
    
    /* Write file header */
    WriteWavHeader(S);
    RETURN_IF_ERROR(S);
    
    /* Local data cache allocation */
    ConstructDataCache(S);
    RETURN_IF_ERROR(S);
    cache->res.dataCacheConstructed = true;
}


static void mdlOutputs (SimStruct *S, int_T tid)
{
    const int_T  inWidth    = ssGetInputPortWidth(S,INPORT);
    DataCache   *data_cache = (DataCache*)(ssGetPWorkValue (S, kDATA_CACHE));
    
    /* Empty buffer to file if not enough space for next set of inputs. */
    if(data_cache->SampleCount + inWidth > data_cache->SizeInSamples) {

        WriteSamplesToFile(S, data_cache);
    }
    ReadSamplesFromInput(S, data_cache);
}


static void mdlTerminate (SimStruct *S)
{
    SFcnDWorkCache *cache =
            (SFcnDWorkCache *)ssGetDWork(S, ALLOCATED_RESOURCE_DWORK_CACHE);

    if (cache != NULL) {
        /* Note - need to tear each of these down in the order in which they were */
        /* initially allocated, since there may be a premature terminate due to   */
        /* an error out of our control (i.e. initiated by Simulink from another   */
        /* part of a model during init/compile time).  Need to handle all cases!  */
        if (cache->res.dataCacheConstructed) {
            DataCache *data_cache = (DataCache*)(ssGetPWorkValue (S, kDATA_CACHE));
            
            /* If any data is left in the cache then write it to the file. */
            if(data_cache->SampleCount > 0) {
                WriteSamplesToFile(S, data_cache);
            }
            
            /* End data chunk */
            if (IN_DATA_CHUNK && DATA_CK_INFO != NULL && FILE_DESC != NULL) {
                const MMRESULT mmResult = mmioAscend (FILE_DESC, DATA_CK_INFO, 0);
                if (MMERROR) {
                    CleanUp(S);
                    THROW_ERROR(S, "Error writing file")
                }
                ssSetIWorkValue(S, kIN_DATA_CHUNK, 0);
            }
            
            /* End WAVE chunk */
            if (IN_WAVE_CHUNK && WAVE_CK_INFO != NULL && FILE_DESC != NULL) {
                const MMRESULT mmResult = mmioAscend (FILE_DESC, WAVE_CK_INFO, 0);
                if (MMERROR) {
                    CleanUp(S);
                    THROW_ERROR(S, "Error writing file")
                }
                ssSetIWorkValue(S, kIN_WAVE_CHUNK, 0);
            }
            
            CleanUp(S);
        }
        else if (cache->res.wavFileOpen) {
            /* All local resources allocated EXCEPT for "dataCacheConstructed" */
            CloseFile(S);
            FreeBuffer(S);
        }
        else if (cache->res.buffersAllocated) {
            /* Only the local "Buffer" resource was allocated */
            FreeBuffer(S);
        }
    }
}


#ifdef MATLAB_MEX_FILE
#define MDL_SET_INPUT_PORT_DIMENSION_INFO
static void mdlSetInputPortDimensionInfo(SimStruct *S, 
                                      int_T port,
                                      const DimsInfo_T *dimsInfo)
{
    if(!ssSetInputPortDimensionInfo(S, port, dimsInfo)) return;

    if(ssGetInputPortFrameData(S, port) == FRAME_YES) {
        if(dimsInfo->dims[1] > 2) {        
            THROW_ERROR(S, "Input cannot contain more than 2 channels.");   
        }
    } else {
        if(dimsInfo->width > 2) {        
            THROW_ERROR(S, "Input cannot contain more than 2 channels.");   
        }
    }

    ErrorIfInputIsNot1or2D(S, port);
}


# define MDL_SET_OUTPUT_PORT_DIMENSION_INFO
static void mdlSetOutputPortDimensionInfo(SimStruct        *S, 
                                          int_T            port,
                                          const DimsInfo_T *dimsInfo)
{
}
#endif

#include "dsp_trailer.c"

/* [EOF] dsp_wafo2_win32.c */
