/********************************************************************
 *                                                                  *
 * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE.   *
 * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS     *
 * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE *
 * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING.       *
 *                                                                  *
 * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002             *
 * by the Xiph.Org Foundation http://www.xiph.org/                  *
 *                                                                  *
 ********************************************************************/

package org.xiph.libvorbis;

import static org.xiph.libvorbis.vorbis_constants.integer_constants.*;

class envelope_lookup {

	int ch;
	int winlength;
	int searchstep;
	float minenergy;

	mdct_lookup mdct;
	float[] mdct_win;

	envelope_band[] band;		// VE_BANDS;
	envelope_filter_state[] filter;	// *filter;
	int stretch;

	int[] mark;

	int storage;
	int current;
	int curmark;
	int cursor;


	public envelope_lookup( int _ch, int _winlength, int _searchstep, float _minenergy, mdct_lookup _mdct, float[] _mdct_win,
				envelope_band[] _band, envelope_filter_state[] _filter, int _stretch, int[] _mark, int _storage, int _current, int _curmark, int _cursor ) {

		ch = _ch;
		winlength = _winlength;
		searchstep = _searchstep;
		minenergy = _minenergy;

		mdct = _mdct;

		mdct_win = new float[ _mdct_win.length ];
		System.arraycopy( _mdct_win, 0, mdct_win, 0, _mdct_win.length );

		band = new envelope_band[ VE_BANDS ];
		System.arraycopy( _band, 0, band, 0, _band.length );

		filter = _filter;
		stretch = _stretch;

		mark = new int[ _mark.length ];
		System.arraycopy( _mark, 0, mark, 0, _mark.length );

		storage = _storage;
		current = _current;
		curmark = _curmark;
		cursor = _cursor;
	}

	public envelope_lookup( envelope_lookup src ) {

		this( src.ch, src.winlength, src.searchstep, src.minenergy, src.mdct, src.mdct_win, src.band, src.filter, src.stretch, src.mark, src.storage, src.current, src.curmark, src.cursor );
	}

	public envelope_lookup( vorbis_info _vi ) {

		ch = _vi.channels;
		winlength = 128;
		searchstep = 64; // not random
		minenergy = _vi.codec_setup.psy_g_param.preecho_minenergy;

		int n = winlength;

		storage = 128;
		cursor = _vi.codec_setup.blocksizes[1]/2;

		// mdct_win = _ogg_calloc(n,sizeof(*mdct_win));
		mdct_win = new float[ n ];
		mdct = new mdct_lookup();
		mdct.mdct_init( n );

		for( int i=0; i<n; i++ ) {
			mdct_win[i] = new Double( Math.sin( i/(n-1.)*M_PI ) ).floatValue();
			mdct_win[i] *= mdct_win[i];
		}

		band = new envelope_band[ VE_BANDS ];

		for (int i=0; i<VE_BANDS; i++)
			band[i] = new envelope_band();

		band[0].begin=2;  band[0].end=4;
		band[1].begin=4;  band[1].end=5;
		band[2].begin=6;  band[2].end=6;
		band[3].begin=9;  band[3].end=8;
		band[4].begin=13;  band[4].end=8;
		band[5].begin=17;  band[5].end=8;
		band[6].begin=22;  band[6].end=8;

		for( int j=0; j<VE_BANDS; j++) {

			n = band[j].end;

			// band[j].window = _ogg_malloc(n*sizeof(*band[0].window));
			band[j].window = new float[ n ];

			for ( int i=0; i<n; i++ ) {
				band[j].window[i] = new Double( Math.sin( (i+.5)/n*M_PI ) ).floatValue();
				band[j].total += band[j].window[i];
			}
			band[j].total = 1.0f / band[j].total;
		}
  
		// filter=_ogg_calloc(VE_BANDS*ch,sizeof(*filter));
		// mark=_ogg_calloc(storage,sizeof(*mark));

		filter = new envelope_filter_state[ VE_BANDS*ch ];
		for ( int i=0; i < VE_BANDS*ch; i++ )
			filter[i] = new envelope_filter_state();
		
		mark = new int[ storage ];
	}
	
	public int _ve_amp( vorbis_info_psy_global gi, float[] data, int offset1, envelope_band[] bands, envelope_filter_state[] filters, int offset2, int pos ) {
		
		int n = winlength;
		int ret=0;
		int i,j;
		float decay;
		
		/* we want to have a 'minimum bar' for energy, else we're just
		 * basing blocks on quantization noise that outweighs the signal
		 * itself (for low power signals) */
		
		float minV = minenergy;
		// float *vec=alloca(n*sizeof(*vec));
		float[] vec = new float[ n ];
		
		/* stretch is used to gradually lengthen the number of windows
		 * considered prevoius-to-potential-trigger */
// local OOP variable rename stretch
		int stretch_local = Math.max( VE_MINSTRETCH, stretch/2 );
		float penalty = gi.stretch_penalty-(stretch/2-VE_MINSTRETCH);
		if ( penalty < 0.f )
			penalty = 0.f;
		if (penalty > gi.stretch_penalty )
			penalty = gi.stretch_penalty;
		
		/*_analysis_output_always("lpcm",seq2,data,n,0,0,totalshift+pos*ve->searchstep);*/
		
		// window and transform
		for ( i=0; i < n; i++ )
			vec[i] = data[offset1+i]*mdct_win[i];
		
		mdct.mdct_forward( vec, vec );
//		mdct.out( "", null );
		
		/*_analysis_output_always("mdct",seq2,vec,n/2,0,1,0); */
		
		/* near-DC spreading function; this has nothing to do with
		 * psychoacoustics, just sidelobe leakage and window size */
		float temp = vec[0]*vec[0]+.7f*vec[1]*vec[1]+.2f*vec[2]*vec[2];
		int ptr = filters[offset2].nearptr;

	    // the accumulation is regularly refreshed from scratch to avoid floating point creep
		if ( ptr == 0 ) {
			decay = filters[offset2].nearDC_acc = filters[offset2].nearDC_partialacc+temp;
			filters[offset2].nearDC_partialacc = temp;
		} else {
			decay = filters[offset2].nearDC_acc += temp;
			filters[offset2].nearDC_partialacc += temp;
		}
		filters[offset2].nearDC_acc -= filters[offset2].nearDC[ptr];
		filters[offset2].nearDC[ptr] = temp;
		
		decay *= (1./(VE_NEARDC+1));
		filters[offset2].nearptr++;
		if ( filters[offset2].nearptr >= VE_NEARDC )
			filters[offset2].nearptr = 0;
		decay = todB(decay)*.5f-15.f;
		
		/* perform spreading and limiting, also smooth the spectrum.  yes, the MDCT results
		 * in all real coefficients, but it still *behaves* like real/imaginary pairs */
		for ( i=0; i < n/2; i+=2 ) {
			float val = vec[i]*vec[i]+vec[i+1]*vec[i+1];
			val = todB(val)*.5f;
			if ( val < decay )
				val=decay;
			if (val < minV)
				val=minV;
			vec[i>>1] = val;
			decay-=8.;
		}
		
		/*_analysis_output_always("spread",seq2++,vec,n/4,0,0,0);*/
		
		// perform preecho/postecho triggering by band
		for ( j=0; j < VE_BANDS; j++ ) {
			
			float acc = 0.f;
			float valmax;
			float valmin;
			
			// accumulate amplitude
			for ( i=0; i < bands[j].end; i++ )
				acc += vec[i+bands[j].begin]*bands[j].window[i];
			
			acc *= bands[j].total;
			
			// convert amplitude to delta

// local OOP variable rename
// protected variable name this
			int p,_this = filters[offset2+j].ampptr;
			float postmax,postmin,premax = -99999.f, premin = 99999.f;
			
			p=_this;
			p--;
			if ( p < 0 )
				p += VE_AMP;
			postmax = Math.max( acc, filters[offset2+j].ampbuf[p] );
			postmin = Math.min( acc, filters[offset2+j].ampbuf[p] );
			
			for ( i=0; i < stretch_local; i++ ) {
				
				p--;
				if ( p < 0)
					p += VE_AMP;
				premax = Math.max( premax, filters[offset2+j].ampbuf[p] );
				premin = Math.min( premin, filters[offset2+j].ampbuf[p] );
			}
			
			valmin = postmin-premin;
			valmax = postmax-premax;
			
			/*filters[j].markers[pos]=valmax;*/
			filters[offset2+j].ampbuf[_this] = acc;
			filters[offset2+j].ampptr++;
			if (filters[offset2+j].ampptr >= VE_AMP )
				filters[offset2+j].ampptr = 0;
			
			// look at min/max, decide trigger
			if ( valmax > gi.preecho_thresh[j]+penalty ) {
				ret|=1;
				ret|=4;
			}
			if ( valmin < gi.postecho_thresh[j]-penalty )
				ret|=2;
		}
		
		return ret;
	}
	
	public void _ve_envelope_shift( int shift ) {
		
		int smallsize = current/searchstep+VE_POST; // adjust for placing marks ahead of ve->current
		int smallshift = shift/searchstep;
		
		// memmove(e->mark,e->mark+smallshift,(smallsize-smallshift)*sizeof(*e->mark));
		System.arraycopy( mark, smallshift, mark, 0, smallsize - smallshift );
		
		current -= shift;
		if ( curmark >= 0 )
			curmark -= shift;
		cursor -= shift;
	}
}