#import <objc/Storage.h>
#import <appkit/nextstd.h>
#import <musickit/musickit.h>
#import <soundkit/Sound.h>
#import <sound/convertsound.h>

typedef short HWORD;
#include "../sms.h"
#import "HybridInstrument.h"
#import "hybrid.h"

/*
 * function to move the samples to the right in the analysis buffer
 *
 */
void moveSamples (float *pFBuffer, int iBufferSize, int iHopSize)
{
	memcpy ((char *) pFBuffer, (char *) (pFBuffer+iHopSize), 
		sizeof(float) * (iBufferSize - iHopSize));
	memset ((char *) (pFBuffer+iBufferSize-iHopSize), 0, 
		sizeof(float) * iHopSize);
}

/*
 * function to fill the analysis window with sound samples 
 */
void fillWindow (short *pSoundData, int iSample, int iHopSize, short *pIWindow, 
                 int iWindowSize)
{
	short * pITemp2, *pITemp1, i;
	memcpy ((char *) pIWindow, (char *) (pIWindow+iHopSize), 
	        sizeof(short) * (iWindowSize - iHopSize));
	memset ((char *) (pIWindow+iWindowSize-iHopSize), 0, 
	         sizeof(short) * iHopSize);
	/* avoid zeroes */
	pITemp2 = pIWindow + iWindowSize - iHopSize; 
	pITemp1 = pSoundData + iSample; 
	for (i=0;i<iHopSize;++i)
	{
		*pITemp2++ = !(*pITemp1)? ((random()&2) - 1) :*pITemp1;
		pITemp1++;
	}
}


@implementation HybridInstrument 
/* See HybridInstrument.h for instance variables */

static int iFile1Par = 0,iTimeOffset1Par = 0, iFile2Par = 0, 
	iTimeOffset2Par = 0, iDur2Par = 0, iFrameRatePar = 0, iOverlapping1Par = 0,
	iOverlapping2Par = 0, iGain0Par = 0, iGain1Par = 0, iGainEnvPar = 0,
	iNCoefficients0Par = 0, iNCoefficients1Par = 0, iNCoefficientsEnvPar = 0,
	iMagBalance0Par = 0, iMagBalance1Par = 0, iMagBalanceEnvPar = 0,
	iTimeStretch0Par = 0, iTimeStretch1Par = 0, iTimeStretchEnvPar = 0,
	iFilterTypePar = 0, iSmoothOrderPar = 0, iCompression0EnvPar = 0,
	iCompression1EnvPar = 0, iCompressionIntEnvPar = 0;

-init
{
	[super init];
	iCurOutSample = -1;
	idSndObj1 = nil;
	idSndObj2 = nil;
	fFrameRate = 100;
	iOverlapping1 = 4;
	iOverlapping2 = 4;
	fDur1 = 0;
	fDur2 = 0;
	fTimeOffset1 = 0;
	fTimeOffset2 = 0;
	fGain0 = 0;
	fGain1 = 1; 
	idGainEnv = nil;
	iNCoefficients0 = -1;
	iNCoefficients1 = -1;
	idNCoefficientsEnv = nil;
	fMagBalance0 = 0;
	fMagBalance1 = 1;
	idMagBalanceEnv = nil;
	fTimeStretch0 = 1;
	fTimeStretch1 = 0;
	idTimeStretchEnv = nil;
	iFilterType = 1;
	iSmoothOrder = 0;
	idCompression0Env = nil;
	idCompression1Env = nil;
	idCompressionIntEnv = nil;
	bNormal = 1;
	    
	iFile1Par = [Note parName:"sndFile1"];
	iFile2Par = [Note parName:"sndFile2"];
	iFrameRatePar = [Note parName:"frameRate"];
	iOverlapping1Par = [Note parName:"overlapping1"];
	iOverlapping2Par = [Note parName:"overlapping2"];
	iTimeOffset1Par = [Note parName:"timeOffset1"];
	iTimeOffset2Par = [Note parName:"timeOffset2"];
	iDur2Par = [Note parName:"dur2"];
	iGain0Par = [Note parName:"gain0"];
	iGain1Par = [Note parName:"gain1"];
	iGainEnvPar = [Note parName:"gainEnv"];
	iNCoefficients0Par = [Note parName:"nCoefficients0"];
	iNCoefficients1Par = [Note parName:"nCoefficients1"];
	iNCoefficientsEnvPar = [Note parName:"nCoefficientsEnv"];
	iMagBalance0Par = [Note parName:"magBalance0"];
	iMagBalance1Par = [Note parName:"magBalance1"];
	iMagBalanceEnvPar = [Note parName:"magBalanceEnv"];
	iTimeStretch0Par = [Note parName:"timeStretch0"];
	iTimeStretch1Par = [Note parName:"timeStretch1"];
	iTimeStretchEnvPar = [Note parName:"timeStretchEnv"];
	iFilterTypePar = [Note parName:"filterType"];
	iSmoothOrderPar = [Note parName:"smoothOrder"];
	iCompression0EnvPar = [Note parName:"compression0Env"];
	iCompression1EnvPar = [Note parName:"compression1Env"];
	iCompressionIntEnvPar = [Note parName:"compressionIntEnv"];

	iSamplingRate = 22050;
	[self addNoteReceiver:[[NoteReceiver alloc] init]]; 
	return self;
}

-setSamplingRate:(double)aSrate normal:(char)bNorm stream:(NXStream *)aStream
/* Invoked once before performance from hybridMk.m. */
{
	if ([self inPerformance])
		return nil;
	iSamplingRate = (int)aSrate;
	pStream = aStream;
	bNormal = bNorm;
	return self;
}

-firstNote:aNote 
/* You never invoke this method; it's invoked automatically just before the
receiver writes its first Note.  It opens stream to the receiver's filename
(if set) and then sends initializeFile to the receiver.*/
{
	SNDAlloc (&pOutSoundStruct,
	          0,   /* data size (we'll set this later) */
	          SND_FORMAT_LINEAR_16,
	          iSamplingRate,
	          1,
	          0 /* info string space to allocate (for 128 bytes) */  );
	/* Write header */
	NXWrite(pStream,(char *)pOutSoundStruct, sizeof(*pOutSoundStruct));
	pTempStream = NXOpenMemory(NULL, 0L, NX_READWRITE); 

	return self;
}

-_mixToOutput:(int *)pData length:(int)iLength sample:(int)iFirstSample
{
    int iLastSample = iFirstSample + iLength;
    int i, iLastCommonSample = 0, iLoc = 0, nSamps = 0;
		
    if (iFirstSample > (iCurOutSample + 1))
    {
      int *pBuffer;
      nSamps = iFirstSample - iCurOutSample;
      pBuffer = (int *) calloc (nSamps, sizeof(int));
      NXSeek(pTempStream, ((iCurOutSample + 1) * sizeof(int)), NX_FROMSTART);
      NXWrite(pTempStream,(char *)pBuffer, nSamps * sizeof(int));
      iCurOutSample += nSamps;
      free (pBuffer);
    }
    else if (iCurOutSample >= iFirstSample)
    {
      int *pBuffer;
      NXSeek (pTempStream, (iFirstSample * sizeof (int)), NX_FROMSTART);
      iLastCommonSample = MIN (iLastSample, iCurOutSample);
      nSamps = MIN (iLength , 1 + iLastCommonSample - iFirstSample);
      pBuffer = (int *) calloc (nSamps, sizeof(int));
      NXRead (pTempStream, (char *)pBuffer, sizeof(int) * nSamps);
      for (i = 0; i < nSamps; i++)
        pBuffer[i] += pData[i];
      NXSeek(pTempStream, (iFirstSample * sizeof (int)), NX_FROMSTART);
      NXWrite (pTempStream, (char *)pBuffer, sizeof(int) * nSamps);    
      iLoc = nSamps - 1;
      free (pBuffer);
    }
    
    if (iLastSample > (iCurOutSample + 1))
    {
      nSamps = iLastSample - (iCurOutSample + 1);
      NXSeek(pTempStream, ((iCurOutSample + 1) * sizeof(int)), NX_FROMSTART);
      NXWrite(pTempStream,(char *)(pData+iLoc), nSamps * sizeof(int));
      iCurOutSample += nSamps;
    }
    NXFlush(pTempStream);
    return self;
}


/* This is invoked when performance is over. */
-afterPerformance 
{
	if (!pOutSoundStruct)  /* Did we never received any notes? */
		return self;
	pOutSoundStruct->dataSize = iCurOutSample * sizeof(short);
	NXSeek(pStream, 0L, NX_FROMSTART);
	NXWrite(pStream,(char *)pOutSoundStruct, sizeof(*pOutSoundStruct));
	[self normalAmp];
	NXFlush(pStream);
	NXCloseMemory(pTempStream, NX_FREEBUFFER);
	pOutSoundStruct->dataSize = 0;
	SNDFree(pOutSoundStruct);
	pOutSoundStruct = NULL;
	NXSeek(pStream,0L,NX_FROMEND);
	NXFlush(pStream);
	return self;
}

/* Invoked from afterPerformance, normalizes the amplitude of the resulting
   time signal-sound */ 
-normalAmp
{
	int *plBuffer;
	short *psBuffer;
	int nSamps, iMaxAmp = 0L, nBytes;
	register int iSamps;
	double fNormFactor; 

	NXSeek(pTempStream, 0L, NX_FROMEND);
	nSamps = MIN ((NXTell(pTempStream) - sizeof(*pOutSoundStruct)) /
	                sizeof(int), 
	               MAX_BUFF) ;
	NXSeek(pTempStream, 0L, NX_FROMSTART);
	NXSeek(pStream, sizeof(*pOutSoundStruct), NX_FROMSTART);

	if ((plBuffer = (int *) calloc (nSamps, sizeof(int))) == NULL)
	{
		fprintf(stderr,"Error: out of mem normalAmp1.\n");
		return self;
	}
    
	if ((psBuffer = (short *) calloc (nSamps, sizeof(short))) == NULL)
	{
		fprintf(stderr,"Error: out of mem normalAmp2.\n");
		return self;
	}

	if (bNormal)
	{
		while (nBytes = NXRead (pTempStream, plBuffer, sizeof(int) * nSamps))
			for (iSamps = 0; iSamps < nSamps; ++iSamps)
     			iMaxAmp = MAX(abs(plBuffer[iSamps]), iMaxAmp); 				
			
		fNormFactor = 32700. / iMaxAmp;
		NXSeek(pTempStream, 0L, NX_FROMSTART);

		while (nBytes = NXRead (pTempStream, plBuffer, sizeof(int) * nSamps))
		{
			nSamps = MIN (nBytes >> 2,nSamps);
			for (iSamps = 0; iSamps < nSamps; ++iSamps)
            	psBuffer[iSamps] = plBuffer[iSamps] * fNormFactor;
			NXWrite(pStream, psBuffer, nSamps * sizeof(short));			
		}
	}
	else while (nBytes = NXRead (pTempStream, plBuffer, sizeof(int) * nSamps))
	{
		nSamps = MIN (nBytes >> 2,nSamps);
		for (iSamps = 0; iSamps < nSamps; ++iSamps)
			psBuffer[iSamps] = (short) plBuffer[iSamps];
		NXWrite(pStream, psBuffer, nSamps * sizeof(short));
	}
		
	free (plBuffer);
	free (psBuffer); 
	return self; 
}



-_readParams:aNote
{
	char *pChFile1 = [aNote parAsStringNoCopy:iFile1Par];
	char *pChFile2 = [aNote parAsStringNoCopy:iFile2Par];

	if (!pChFile1 || !strlen(pChFile1))  /* Parameter not present? */
	{
		fprintf(stderr,"No first input sound file specified.\n");
		return nil;
	}
	if (!pChFile2 || !strlen(pChFile2))  /* Parameter not present? */
	{
		fprintf(stderr,"No second input sound file specified.\n");
		return nil;
	}

	idSndObj1 = [[Sound alloc] initFromSoundfile:pChFile1]; 
	if (!idSndObj1) 
	{
		fprintf(stderr,"Can't find file %s.\n", pChFile1);
		return nil;
	}
	else if  (([idSndObj1 dataFormat] != SND_FORMAT_LINEAR_16) ||
	          ([idSndObj1 channelCount] != 1) ||
	          ([idSndObj1 samplingRate] != iSamplingRate) )
	{
		[idSndObj1 convertToFormat:SND_FORMAT_LINEAR_16
		           samplingRate:(double)iSamplingRate
		           channelCount:1];
		if (([idSndObj1 dataFormat] != SND_FORMAT_LINEAR_16) ||
		    ([idSndObj1 channelCount] != 1) ||
		    ([idSndObj1 samplingRate] != iSamplingRate) ) 
		{
			fprintf(stderr,"Error: input files must be in 16-bit linear, and \
				              mono.\n");
			return nil;
		}
	}
	idSndObj2 = [[Sound alloc] initFromSoundfile:pChFile2]; 
	if (!idSndObj2) 
	{
		fprintf(stderr,"Can't find file %s.\n", pChFile2);
		return nil;
	}
	else if  (([idSndObj2 dataFormat] != SND_FORMAT_LINEAR_16) ||
	      	  ([idSndObj2 channelCount] != 1) ||
            ([idSndObj2 samplingRate] != iSamplingRate) )  
	{
		[idSndObj2 convertToFormat:SND_FORMAT_LINEAR_16
			         samplingRate:(double)iSamplingRate
			         channelCount:1];
		if (([idSndObj2 dataFormat]  != SND_FORMAT_LINEAR_16) ||
			   ([idSndObj2 channelCount] != 1) ||
			   ([idSndObj1 samplingRate] != iSamplingRate) )  
 
		{
			fprintf(stderr,"Error: input files must be in 16-bit linear, and \
				              mono.\n");
			return nil;
		}
	}
    
	if ([aNote isParPresent:iFrameRatePar])
		fFrameRate = [aNote parAsDouble:iFrameRatePar];
	if ([aNote isParPresent:iOverlapping1Par]) 
		iOverlapping1 = [aNote parAsInt:iOverlapping1Par];
	if ([aNote isParPresent:iOverlapping2Par]) 
		iOverlapping2 = [aNote parAsInt:iOverlapping2Par];
	if ([aNote isParPresent:iTimeOffset1Par]) 
		fTimeOffset1 = [aNote parAsDouble:iTimeOffset1Par];
	if ([aNote isParPresent:iTimeOffset2Par]) 
		fTimeOffset2 = [aNote parAsDouble:iTimeOffset2Par];
	fDur1 = [aNote dur];
	if ([aNote isParPresent:iDur2Par]) 
		fDur2 = [aNote parAsDouble:iDur2Par];
	if ([aNote isParPresent:iGain0Par]) 
		fGain0 = [aNote parAsDouble:iGain0Par];
	if ([aNote isParPresent:iGain1Par]) 
		fGain1 = [aNote parAsDouble:iGain1Par];
	if ([aNote isParPresent:iGainEnvPar]) 
		idGainEnv = (Envelope *)[aNote parAsEnvelope:iGainEnvPar];
	if ([aNote isParPresent:iNCoefficients0Par]) 
		iNCoefficients0 = [aNote parAsInt:iNCoefficients0Par];
	if ([aNote isParPresent:iNCoefficients1Par]) 
		iNCoefficients1 = [aNote parAsInt:iNCoefficients1Par];
	if ([aNote isParPresent:iNCoefficientsEnvPar]) 
		idNCoefficientsEnv = 
			(Envelope *)[aNote parAsEnvelope:iNCoefficientsEnvPar];
	if ([aNote isParPresent:iMagBalance0Par]) 
		fMagBalance0 = [aNote parAsDouble:iMagBalance0Par];
	if ([aNote isParPresent:iMagBalance1Par]) 
		fMagBalance1 = [aNote parAsDouble:iMagBalance1Par];
	if ([aNote isParPresent:iMagBalanceEnvPar]) 
		idMagBalanceEnv = (Envelope *)[aNote parAsEnvelope:iMagBalanceEnvPar];
	if ([aNote isParPresent:iTimeStretch0Par]) 
		fTimeStretch0 = [aNote parAsDouble:iTimeStretch0Par];
	if ([aNote isParPresent:iTimeStretch1Par]) 
		fTimeStretch1 = [aNote parAsDouble:iTimeStretch1Par];
	if ([aNote isParPresent:iTimeStretchEnvPar]) 
		idTimeStretchEnv = 
			(Envelope *)[aNote parAsEnvelope:iTimeStretchEnvPar];
	if ([aNote isParPresent:iFilterTypePar]) 
		iFilterType = [aNote parAsInt:iFilterTypePar];
	if ([aNote isParPresent:iSmoothOrderPar]) 
		iSmoothOrder = [aNote parAsInt:iSmoothOrderPar];
	if ([aNote isParPresent:iCompression0EnvPar]) 
		idCompression0Env = 
			(Envelope *)[aNote parAsEnvelope:iCompression0EnvPar];
	if ([aNote isParPresent:iCompression1EnvPar]) 
		idCompression1Env = 
			(Envelope *)[aNote parAsEnvelope:iCompression1EnvPar];
	if ([aNote isParPresent:iCompressionIntEnvPar]) 
		idCompressionIntEnv = 
			(Envelope *)[aNote parAsEnvelope:iCompressionIntEnvPar];

	return self;
}

-_generateCompression:(float *)pFArray size:(int)sizeArray 
                       envelope:(id)idEnvelope
{
  double xfirst, xlast, y, s, fValT = 1, xval, fVal = 0;
  float fXinc;
  int lastPoint = [idEnvelope pointCount] - 1;
  int i;

  [idEnvelope getNth:0 x:&xfirst y:&y smoothing:&s];
  [idEnvelope getNth:lastPoint x:&xlast y:&y smoothing:&s];
   
  fXinc = (float) ((xlast - xfirst) / sizeArray);
  xval = xfirst;
  for (i = 0; i < sizeArray; i++)
  {
    pFArray[i] = [idEnvelope lookupYForX:xval];
    xval += fXinc;  
  }
  return self;
}

-_interpolateArrays:(double)fLocation envelope:(id)idEnvelope
                     array0:(float *) pFBuffer0 array1:(float *) pFBuffer1
                     params:(HYB_PARAMS) params
{
	double xfirst, xlast, y, s, fVal = 0, xval;
	int lastPoint = [idEnvelope pointCount] - 1;

	if (idEnvelope)
	{
		[idEnvelope getNth:0 x:&xfirst y:&y smoothing:&s];
		[idEnvelope getNth:lastPoint x:&xlast y:&y smoothing:&s];
		xval = xfirst + fLocation * (xlast - xfirst);
		fVal = [idEnvelope lookupYForX:xval];
	}
  
	InterpolateArrays (pFBuffer0, params.sizeCompressionEnv, pFBuffer1,
	                   params.sizeCompressionEnv, params.pCompressionEnv,
	                   params.sizeCompressionEnv, fVal);
	return self;
}													

-(float) interpolate:(double)fLocation envelope:(id)idEnvelope 
                      bottom:(double)bottom top:(double)top 
{
    double xfirst, xlast, y, s, fValT = 1, xval, fVal = 0;
    int lastPoint = [idEnvelope pointCount] - 1;

    if (idEnvelope)
    {
      [idEnvelope getNth:0 x:&xfirst y:&y smoothing:&s];
      [idEnvelope getNth:lastPoint x:&xlast y:&y smoothing:&s];
      xval = xfirst + fLocation * (xlast - xfirst);
      fValT = [idEnvelope lookupYForX:xval];
    }
  
    fVal = bottom + fValT * (top - bottom);
    
    return (fVal);
}	


-realizeNote:aNote fromNoteReceiver:aNoteReceiver
{
	MKNoteType type;
	/* one of MK_noteDur, MK_noteOn, MK_noteOff, MK_noteUpdate, or MK_mute.  
	   The note type describes the character of the Note, whether it represents
	   an entire musical note (or event), the beginning, middle, or end of a 
	   note, or no note (no sound) */ 
	if (!aNote) /* el parÖmetre d'entrada */
		return self;
	type = [aNote noteType]; /* guardem tipus de la nota */
	if (type == MK_noteDur ||  type == MK_noteUpdate)
		[self _readParams:aNote];
	else
		return self;
    	
	/* if a real note, synthesize */
	if (type == MK_noteDur)
	{  
		short *pSndData1, *pSndData2, *pIWindow1, *pIWindow2; 
		int iFirstSample, iHopSize1, iRecordSize1, nRecords1, nRecords2, 
			iHopSize2, iRecordSize2, iSample1, iSample2, nHops, i, iRec,
			nSamples, iTmp, sizeMag1, sizeMag2, iLastSample1, iLastSample2;
		int *pIBuffer; 
		float *pFBuffer, fStretch = 1, fLocation, *pFCompBuffer0, 
			*pFCompBuffer1;
		HYB_PARAMS params;

		/* Give user feedback: MKGetTime() returns the current time, in 
		   seconds, during a Music Kit performance */
		fprintf(stderr,"%f ",MKGetTime()); 
		fflush(stderr);
	     
		if (fDur1 < 0)
  			fDur1 = [idSndObj1 duration];
		else
			fDur1 = MIN ([idSndObj1 duration], fDur1);
		if (fDur2 <= 0)
			fDur2 = [idSndObj2 duration];
		else
			fDur2 = MIN ([idSndObj2 duration], fDur2);

		/* loop through the input file */
		iFirstSample = MKGetTime() * iSamplingRate;
		iHopSize1 = [idSndObj1 samplingRate] / fFrameRate;
		iRecordSize1 = iHopSize1 * iOverlapping1;
		nRecords2 = nRecords1 = fFrameRate * fDur1;
		iHopSize2 = fDur2 * [idSndObj2 samplingRate] / nRecords2;
		iRecordSize2 = iHopSize1 * iOverlapping2;
		iSample1 = fTimeOffset1 * [idSndObj1 samplingRate];
		iSample2 = fTimeOffset2 * [idSndObj2 samplingRate];
		iLastSample1 = [idSndObj1 dataSize]/sizeof(short);
		iLastSample2 = [idSndObj2 dataSize]/sizeof(short);
		pSndData1 = (short *) [idSndObj1 data];
		pSndData2 = (short *) [idSndObj2 data];

		if ((pFBuffer = (float *) calloc(iRecordSize1, sizeof(float))) == NULL)
			return nil;
		if ((pIWindow1 = (short *) calloc(iRecordSize1, sizeof(short))) == 
			NULL)
			return nil;
		if ((pIWindow2 = (short *) calloc(iRecordSize2, sizeof(short))) == 
			NULL)
			return nil;
		if ((pIBuffer = (int *) calloc(iHopSize1, sizeof(int))) == NULL)
			return nil;

		/* initialize window */
		nHops = ((iOverlapping1 >> 1) - 1);
		for (i = 0; i < nHops; i++)
		{
			if (iSample1 >= iLastSample1 || iSample2 >= iLastSample2)
			{
				fprintf(stderr,"Error: reached end of file.\n");
				return self;
			}
			fillWindow (pSndData1, iSample1, iHopSize1, pIWindow1, 
			            iRecordSize1);
			fillWindow (pSndData2, iSample2, iHopSize2, pIWindow2, 
			            iRecordSize2);
			iSample1 += iHopSize1;
			iSample2 += iHopSize2;
		}

		sizeMag1 = 
			(int)(pow(2.0, 
			         (double) (1+(floor(log((double)iRecordSize1) / 
			          LOG2)))))/2;
		sizeMag2 = 
				(int) (pow(2.0, 
                  (double) (1+(floor(log((double)iRecordSize2) / LOG2)))))/2;

		iTmp = iHopSize2;
		if ((pFCompBuffer0 = (float *) calloc(sizeMag1, sizeof(float))) == 
			NULL)
			return nil;
		if ((pFCompBuffer1 = (float *) calloc(sizeMag1, sizeof(float))) == 
			NULL)
			return nil;

		if (idCompression0Env)
			[self _generateCompression:pFCompBuffer0 size:sizeMag1 
			                           envelope:idCompression0Env];
		if (idCompression1Env)
			[self _generateCompression:pFCompBuffer1 size:sizeMag1 
			                           envelope:idCompression1Env];
											 
		if (idCompression1Env || idCompression0Env)
		{
			if ((params.pCompressionEnv = 
				  (float *) calloc(sizeMag1, sizeof(float))) == NULL)
				return nil;
			params.sizeCompressionEnv = sizeMag1;
			if (!idCompressionIntEnv)
			{
				if (idCompression0Env)
					for (i = 0; i < sizeMag1; i++)
						params.pCompressionEnv[i] = pFCompBuffer0[i];
				else if (idCompression1Env)
					for (i = 0; i < sizeMag1; i++)
						params.pCompressionEnv[i] = pFCompBuffer0[i];
			}
		}
		else
		{
			params.pCompressionEnv = NULL;
			params.sizeCompressionEnv = 0;
		}

		if (iNCoefficients0 == -1)
			iNCoefficients0 = MIN (sizeMag1, sizeMag2);

		if (iNCoefficients1 == -1)
			iNCoefficients1 = MIN (sizeMag1, sizeMag2);

		/* Principi del loop principal de tota la aplicacií */
		for (iRec = 0; iRec < nRecords1; iRec++)
		{
			fLocation = iRec / (double) nRecords1;

			if (idTimeStretchEnv)
				fStretch = [self interpolate:fLocation 
				                             envelope:idTimeStretchEnv
				                             bottom:fTimeStretch0 
				                             top:fTimeStretch1];
			iHopSize2 = fStretch * iTmp;
			nSamples = MIN (iHopSize2, iRecordSize2);
			if (iSample1+iHopSize1 >= iLastSample1 ||
			    iSample2+nSamples >= iLastSample2)
			{
				fprintf(stderr,"Error: reached end of file.\n");
				return self;
			}
 
			fillWindow (pSndData1, iSample1, iHopSize1, pIWindow1, 
			            iRecordSize1);
			fillWindow (pSndData2, iSample2, nSamples, pIWindow2, 
			            iRecordSize2);

			params.nCoefficients = 
				[self interpolate:fLocation envelope:idNCoefficientsEnv
				                  bottom:iNCoefficients0 top:iNCoefficients1];
			if (params.nCoefficients > sizeMag1 ||
			    params.nCoefficients > sizeMag2)
			{
				params.nCoefficients = MIN (sizeMag1, sizeMag2);
				fprintf(stderr,"nCoefficients set to %d\n", 
				        params.nCoefficients);
			}

			params.fGain = 
				[self interpolate:fLocation envelope:idGainEnv
				                  bottom:fGain0 top:fGain1];
			params.fMagBalance = 
				[self interpolate:fLocation envelope:idMagBalanceEnv
				                  bottom:fMagBalance0 top:fMagBalance1];
													
			params.iSmoothOrder = iSmoothOrder;
			
			if (params.pCompressionEnv && idCompressionIntEnv)
				[self _interpolateArrays:fLocation envelope:idCompressionIntEnv
				                        array0:pFCompBuffer0 
				                        array1:pFCompBuffer1
										params:params];
			
			if (iFilterType == 1)
				Hybridize (pIWindow1, iRecordSize1, pIWindow2, iRecordSize2, 
				           pFBuffer, params);
       
			if (iRec >= iOverlapping1>>1)
			{
				for (i = 0; i < iHopSize1; i++)
					pIBuffer[i] = params.fGain * pFBuffer[i] / iOverlapping1;
				[self _mixToOutput:pIBuffer length:iHopSize1 
				                   sample:iFirstSample];
			}

			moveSamples (pFBuffer, iRecordSize1, iHopSize1);
			iFirstSample += iHopSize1;
			iSample1 += iHopSize1;
			iSample2 += iHopSize2;
		}
		freeBuffers();
		[idSndObj1 free];
		[idSndObj2 free];
		free (pFBuffer);
		free (pIWindow1);
		free (pIWindow2);
		free (pIBuffer);
		free (pFCompBuffer0);
		free (pFCompBuffer1);
		if (params.pCompressionEnv)
		  free (params.pCompressionEnv);
	}
	return self;
}

@end
