/********************************************************************
 *                                                                  *
 * 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 XIPHOPHORUS Company http://www.xiph.org/                  *
 *                                                                  *
 ********************************************************************

 function: channel mapping 0 implementation
 last mod: $Id: mapping0.c,v 1.43 2001/12/20 01:00:27 segher Exp $

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

#ifdef MAKEABS
#include <stdlib.h>
#include <stdio.h>
#endif
#include <string.h>
#include "registry.h"
#include "misc.h"
#include "mdct.h"
#include "os.h"
#include "window.h"

/* simplistic, wasteful way of doing this (unique lookup for each
   mode/submapping); there should be a central repository for
   identical lookups.  That will require minor work, so I'm putting it
   off as low priority.

   Why a lookup for each backend in a given mode?  Because the
   blocksize is set by the mode, and low backend lookups may require
   parameters from other areas of the mode/mapping */

static void mapping0_free_info(vorbis_info_mapping *i){
  vorbis_info_mapping0 *info=(vorbis_info_mapping0 *)i;
  if(info){
    _ogg_free(info->pcmbundle);
    _ogg_free(info->zerobundle);
    _ogg_free(info->nonzero);
    _ogg_free(info->floormemo);
    _ogg_free(info);
  }
}

/* also responsible for range checking */
static vorbis_info_mapping *mapping0_unpack(vorbis_info *vi,oggpack_buffer *opb){
  int i, b;
  vorbis_info_mapping0 *info=_ogg_calloc(1,sizeof(*info));
  codec_setup_info     *ci=vi->codec_setup;
  if(!info) return NULL;
  memset(info,0,sizeof(*info));
  if(vi->channels<=0)goto err_out;

  if((info->pcmbundle  = _ogg_malloc(sizeof(*info->pcmbundle)*vi->channels)) == NULL)
    goto err_out;
  if((info->zerobundle = _ogg_malloc(sizeof(*info->zerobundle)*vi->channels)) == NULL)
    goto err_out;
  if((info->nonzero    = _ogg_malloc(sizeof(*info->nonzero)*vi->channels)) == NULL)
    goto err_out;
  if((info->floormemo  = _ogg_malloc(sizeof(*info->floormemo)*vi->channels)) == NULL)
    goto err_out;

  b=oggpack_read(opb,1);
  if(b<0)goto err_out;
  if(b){
    info->submaps=oggpack_read(opb,4)+1;
    if(info->submaps<=0)goto err_out;
  }else
    info->submaps=1;

  b=oggpack_read(opb,1);
  if(b<0)goto err_out;
  if(b){
    info->coupling_steps=oggpack_read(opb,8)+1;
    if(info->coupling_steps<=0)goto err_out;

    for(i=0;i<info->coupling_steps;i++){
      /* vi->channels > 0 is enforced in the caller */
      int testM=info->coupling_mag[i]=
        oggpack_read(opb,ilog2(vi->channels));
      int testA=info->coupling_ang[i]=
        oggpack_read(opb,ilog2(vi->channels));

      if(testM<0 ||
	 testA<0 ||
	 testM==testA ||
	 testM>=vi->channels ||
	 testA>=vi->channels) goto err_out;
    }

  }

  if(oggpack_read(opb,2)!=0)goto err_out; /* 2,3:reserved */

  if(info->submaps>1){
    for(i=0;i<vi->channels;i++){
      info->chmuxlist[i]=oggpack_read(opb,4);
      if(info->chmuxlist[i]>=info->submaps || info->chmuxlist[i]<0)goto err_out;
    }
  }
  for(i=0;i<info->submaps;i++){
    oggpack_read(opb,8); /* time submap unused */
    info->floorsubmap[i]=oggpack_read(opb,8);
    if(info->floorsubmap[i]>=ci->floors || info->floorsubmap[i]<0)goto err_out;
    info->residuesubmap[i]=oggpack_read(opb,8);
    if(info->residuesubmap[i]>=ci->residues || info->residuesubmap[i]<0)goto err_out;
  }

  return info;

 err_out:
  mapping0_free_info(info);
  return(NULL);
}

static int mapping0_inverse(vorbis_block* vb, vorbis_info_mapping* l){
	vorbis_dsp_state*   vd = vb->vd;
	vorbis_info*        vi = vd->vi;
	codec_setup_info*   ci = vi->codec_setup;
	private_state*      b = vd->backend_state;
	vorbis_info_mapping0*   info = (vorbis_info_mapping0*) l;
	int i,j;
	int32_t n = vb->pcmend = ci->blocksizes[vb->W];

	/* recover the spectral envelope; store it in the PCM vector for now */
	for (i = 0; i < vi->channels; i++){
		int submap = info->chmuxlist[i];
    	info->floormemo[i] = _floor_P[ci->floor_type[info->floorsubmap[submap]]]->
		  inverse1(vb, b->flr[info->floorsubmap[submap]], i);
		if (info->floormemo[i])
			info->nonzero[i] = 1;
		else
			info->nonzero[i] = 0;
		memset(vb->pcm[i], 0, sizeof(*vb->pcm[i])*n/2);
	}

	/* channel coupling can 'dirty' the nonzero listing */
	for (i = 0; i < info->coupling_steps; i++){
		if (info->nonzero[info->coupling_mag[i]]
		||  info->nonzero[info->coupling_ang[i]]){
			info->nonzero[info->coupling_mag[i]] = 1;
			info->nonzero[info->coupling_ang[i]] = 1;
		}
	}

	/* recover the residue into our working vectors */
	for (i = 0; i < info->submaps; i++){
		int ch_in_bundle = 0;
		for (j = 0; j < vi->channels; j++){
			if (info->chmuxlist[j] == i){
				if (info->nonzero[j])
					info->zerobundle[ch_in_bundle] = 1;
				else
					info->zerobundle[ch_in_bundle] = 0;
				info->pcmbundle[ch_in_bundle++] = vb->pcm[j];
			}
		}

		_residue_P[ci->residue_type[info->residuesubmap[i]]]->
			inverse(vb,b->residue[info->residuesubmap[i]],
					info->pcmbundle,info->zerobundle,ch_in_bundle);
	}

	/* channel coupling */
	for(i = info->coupling_steps - 1; i >= 0; i--){
		xint* pcmM = vb->pcm[info->coupling_mag[i]];
		xint* pcmA = vb->pcm[info->coupling_ang[i]];

		for(j = 0; j < n/2; j++){
			xint mag = pcmM[j];
			xint ang = pcmA[j];

			if(mag > 0)
				if(ang > 0){
					pcmM[j] = mag;
					pcmA[j] = mag - ang;
				}else{
					pcmA[j] = mag;
					pcmM[j] = mag + ang;
				}
			else
				if(ang > 0){
					pcmM[j] = mag;
					pcmA[j] = mag + ang;
				}else{
					pcmA[j] = mag;
					pcmM[j] = mag - ang;
				}
		}
	}

	/* compute and apply spectral envelope */
	for(i = 0; i < vi->channels; i++){
		xint* pcm = vb->pcm[i];
		int submap = info->chmuxlist[i];
		_floor_P[ci->floor_type[info->floorsubmap[submap]]]->
			inverse2(vb, b->flr[info->floorsubmap[submap]],
				info->floormemo[i], pcm);
	}

	/* transform the PCM data; takes PCM vector, vb; modifies PCM vector */
	/* only MDCT right now.... */
	for(i = 0; i < vi->channels; i++){
		xint* pcm = vb->pcm[i];
#ifdef MAKEABS
		for (j = 0; j < n>>1; j++){
			if (ilog(abs(pcm[j])) > 28)
				printf("Big val %x\n", pcm[j]);
		}
#endif
		mdct_backward(b->transform[vb->W][0], pcm, pcm);
	}

	// window the data
	for(i = 0; i < vi->channels; i++){
		xint* pcm = vb->pcm[i];
		if (info->nonzero[i])
			_vorbis_apply_window(pcm,b->window,ci->blocksizes,vb->lW,vb->W,vb->nW);
		else
			map_zero(pcm, n);
	}

	/* all done! */
	return(0);
}

/* export hooks */
const vorbis_func_mapping mapping0_exportbundle ={
&mapping0_unpack,
&mapping0_free_info,
&mapping0_inverse
};
