Finished LPC and LSP code. Adding cut down version of full FFT from
[platform/upstream/libvorbis.git] / lib / block.c
1 /********************************************************************
2  *                                                                  *
3  * THIS FILE IS PART OF THE Ogg Vorbis SOFTWARE CODEC SOURCE CODE.  *
4  * USE, DISTRIBUTION AND REPRODUCTION OF THIS SOURCE IS GOVERNED BY *
5  * THE GNU PUBLIC LICENSE 2, WHICH IS INCLUDED WITH THIS SOURCE.    *
6  * PLEASE READ THESE TERMS DISTRIBUTING.                            *
7  *                                                                  *
8  * THE OggSQUISH SOURCE CODE IS (C) COPYRIGHT 1994-1999             *
9  * by 1999 Monty <monty@xiph.org> and The XIPHOPHORUS Company       *
10  * http://www.xiph.org/                                             *
11  *                                                                  *
12  ********************************************************************
13
14  function: PCM data vector blocking, windowing and dis/reassembly
15  author: Monty <xiphmont@mit.edu>
16  modifications by: Monty
17  last modification date: Jul 29 1999
18
19  Handle windowing, overlap-add, etc of the PCM vectors.  This is made
20  more amusing by Vorbis' current two allowed block sizes (512 and 2048
21  elements/channel).
22  
23  Vorbis manipulates the dynamic range of the incoming PCM data
24  envelope to minimise time-domain energy leakage from percussive and
25  plosive waveforms being quantized in the MDCT domain.
26
27  ********************************************************************/
28
29 #include <stdlib.h>
30 #include <string.h>
31 #include "codec.h"
32 #include "window.h"
33 #include "envelope.h"
34 #include "mdct.h"
35
36 /* pcm accumulator examples (not exhaustive):
37
38  <-------------- lW ---------------->
39                    <--------------- W ---------------->
40 :            .....|.....       _______________         |
41 :        .'''     |     '''_---      |       |\        |
42 :.....'''         |_____--- '''......|       | \_______|
43 :.................|__________________|_______|__|______|
44                   |<------ Sl ------>|      > Sr <     |endW
45                   |beginSl           |endSl  |  |endSr   
46                   |beginW            |endlW  |beginSr
47
48
49                       |< lW >|       
50                    <--------------- W ---------------->
51                   |   |  ..  ______________            |
52                   |   | '  `/        |     ---_        |
53                   |___.'___/`.       |         ---_____| 
54                   |_______|__|_______|_________________|
55                   |      >|Sl|<      |<------ Sr ----->|endW
56                   |       |  |endSl  |beginSr          |endSr
57                   |beginW |  |endlW                     
58                   mult[0] |beginSl                     mult[n]
59
60  <-------------- lW ----------------->
61                           |<--W-->|                               
62 :            ..............  ___  |   |                    
63 :        .'''             |`/   \ |   |                       
64 :.....'''                 |/`....\|...|                    
65 :.........................|___|___|___|                  
66                           |Sl |Sr |endW    
67                           |   |   |endSr
68                           |   |beginSr
69                           |   |endSl
70                           |beginSl
71                           |beginW
72 */
73
74 static int _vds_shared_init(vorbis_dsp_state *v,vorbis_info *vi){
75   memset(v,0,sizeof(vorbis_dsp_state));
76   v->samples_per_envelope_step=vi->envelopesa;
77   v->block_size[0]=vi->smallblock;
78   v->block_size[1]=vi->largeblock;
79   
80   v->window[0][0][0]=_vorbis_window(v->block_size[0],
81                                    v->block_size[0]/2,v->block_size[0]/2);
82   v->window[0][0][1]=v->window[0][0][0];
83   v->window[0][1][0]=v->window[0][0][0];
84   v->window[0][1][1]=v->window[0][0][0];
85
86   v->window[1][0][0]=_vorbis_window(v->block_size[1],
87                                    v->block_size[0]/2,v->block_size[0]/2);
88   v->window[1][0][1]=_vorbis_window(v->block_size[1],
89                                    v->block_size[0]/2,v->block_size[1]/2);
90   v->window[1][1][0]=_vorbis_window(v->block_size[1],
91                                    v->block_size[1]/2,v->block_size[0]/2);
92   v->window[1][1][1]=_vorbis_window(v->block_size[1],
93                                     v->block_size[1]/2,v->block_size[1]/2);
94
95   /* initialize the storage vectors to a decent size greater than the
96      minimum */
97   
98   v->pcm_storage=8192; /* we'll assume later that we have
99                           a minimum of twice the blocksize of
100                           accumulated samples in analysis */
101   v->pcm_channels=v->vi.channels=vi->channels;
102   v->pcm=malloc(vi->channels*sizeof(double *));
103   v->pcmret=malloc(vi->channels*sizeof(double *));
104   {
105     int i;
106     for(i=0;i<vi->channels;i++)
107       v->pcm[i]=calloc(v->pcm_storage,sizeof(double));
108   }
109
110   /* Initialize the envelope multiplier storage */
111
112   if(vi->envelopech){
113     v->envelope_storage=v->pcm_storage/v->samples_per_envelope_step;
114     v->envelope_channels=vi->envelopech;
115     v->multipliers=calloc(v->envelope_channels,sizeof(int *));
116     {
117       int i;
118       for(i=0;i<v->envelope_channels;i++){
119         v->multipliers[i]=calloc(v->envelope_storage,sizeof(int));
120       }
121     }
122   }
123
124   /* all 1 (large block) or 0 (small block) */
125   /* explicitly set for the sake of clarity */
126   v->lW=0; /* previous window size */
127   v->W=0;  /* current window size */
128
129   /* all vector indexes; multiples of samples_per_envelope_step */
130   v->centerW=v->block_size[1]/2;
131
132   v->pcm_current=v->centerW;
133   v->envelope_current=v->centerW/v->samples_per_envelope_step;
134   return(0);
135 }
136
137 /* arbitrary settings and spec-mandated numbers get filled in here */
138 int vorbis_analysis_init(vorbis_dsp_state *v,vorbis_info *vi){
139   vi->smallblock=512;
140   vi->largeblock=2048;
141   vi->envelopesa=64;
142   vi->envelopech=vi->channels;
143
144   _vds_shared_init(v,vi);
145
146   return(0);
147 }
148
149 void vorbis_analysis_clear(vorbis_dsp_state *v){
150   int i,j,k;
151   if(v){
152
153     if(v->window[0][0][0])free(v->window[0][0][0]);
154     for(j=0;j<2;j++)
155       for(k=0;k<2;k++)
156         if(v->window[1][j][k])free(v->window[1][j][k]);
157     if(v->pcm){
158       for(i=0;i<v->pcm_channels;i++)
159         if(v->pcm[i])free(v->pcm[i]);
160       free(v->pcm);
161       free(v->pcmret);
162     }
163     if(v->multipliers){
164       for(i=0;i<v->envelope_channels;i++)
165         if(v->multipliers[i])free(v->multipliers[i]);
166       free(v->multipliers);
167     }
168     memset(v,0,sizeof(vorbis_dsp_state));
169   }
170 }
171
172 double **vorbis_analysis_buffer(vorbis_dsp_state *v, int vals){
173   int i;
174
175   /* Do we have enough storage space for the requested buffer? If not,
176      expand the PCM (and envelope) storage */
177     
178   if(v->pcm_current+vals>=v->pcm_storage){
179     v->pcm_storage=v->pcm_current+vals*2;
180     v->envelope_storage=v->pcm_storage/v->samples_per_envelope_step;
181    
182     for(i=0;i<v->pcm_channels;i++){
183       v->pcm[i]=realloc(v->pcm[i],v->pcm_storage*sizeof(double));
184     }
185     for(i=0;i<v->envelope_channels;i++){
186       v->multipliers[i]=realloc(v->multipliers[i],
187                                 v->envelope_storage*sizeof(double));
188     }
189   }
190
191   for(i=0;i<v->pcm_channels;i++)
192     v->pcmret[i]=v->pcm[i]+v->pcm_current;
193     
194   return(v->pcmret);
195 }
196
197 /* call with val<=0 to set eof */
198
199 int vorbis_analysis_wrote(vorbis_dsp_state *v, int vals){
200   if(vals<=0){
201     /* We're encoding the end of the stream.  Just make sure we have
202        [at least] a full block of zeroes at the end. */
203
204     int i;
205     vorbis_analysis_buffer(v,v->block_size[1]*2);
206     v->eofflag=v->pcm_current;
207     v->pcm_current+=v->block_size[1]*2;
208     for(i=0;i<v->pcm_channels;i++)
209       memset(v->pcm[i]+v->eofflag,0,
210              (v->pcm_current-v->eofflag)*sizeof(double));
211   }else{
212     
213     if(v->pcm_current+vals>v->pcm_storage)
214       return(-1);
215
216     v->pcm_current+=vals;
217   }
218   return(0);
219 }
220
221 int vorbis_block_init(vorbis_dsp_state *v, vorbis_block *vb){
222   int i;
223   vb->pcm_storage=v->block_size[1];
224   vb->pcm_channels=v->pcm_channels;
225   vb->mult_storage=v->block_size[1]/v->samples_per_envelope_step;
226   vb->mult_channels=v->envelope_channels;
227   
228   vb->pcm=malloc(vb->pcm_channels*sizeof(double *));
229   for(i=0;i<vb->pcm_channels;i++)
230     vb->pcm[i]=malloc(vb->pcm_storage*sizeof(double));
231   
232   vb->mult=malloc(vb->mult_channels*sizeof(int *));
233   for(i=0;i<vb->mult_channels;i++)
234     vb->mult[i]=malloc(vb->mult_storage*sizeof(int));
235   return(0);
236 }
237
238 int vorbis_block_clear(vorbis_block *vb){
239   int i;
240   if(vb->pcm){
241     for(i=0;i<vb->pcm_channels;i++)
242       free(vb->pcm[i]);
243     free(vb->pcm);
244   }
245   if(vb->mult){
246     for(i=0;i<vb->mult_channels;i++)
247       free(vb->mult[i]);
248     free(vb->mult);
249   }
250   memset(vb,0,sizeof(vorbis_block));
251   return(0);
252 }
253
254 /* do the deltas, envelope shaping, pre-echo and determine the size of
255    the next block on which to continue analysis */
256 int vorbis_analysis_blockout(vorbis_dsp_state *v,vorbis_block *vb){
257   int i,j;
258   long beginW=v->centerW-v->block_size[v->W]/2,centerNext;
259   long beginM=beginW/v->samples_per_envelope_step;
260
261   /* check to see if we're done... */
262   if(v->eofflag==-1)return(0);
263
264   /* if we have any unfilled envelope blocks for which we have PCM
265      data, fill them up in before proceeding. */
266
267   if(v->pcm_current/v->samples_per_envelope_step>v->envelope_current){
268     _ve_envelope_multipliers(v);
269   }
270
271   /* By our invariant, we have lW, W and centerW set.  Search for
272      the next boundary so we can determine nW (the next window size)
273      which lets us compute the shape of the current block's window */
274
275   /* overconserve for now; any block with a non-placeholder multiplier
276      should be minimal size. We can be greedy and only look at nW size */
277   
278   if(v->W)
279     /* this is a long window; we start the search forward of centerW
280        because that's the fastest we could react anyway */
281     i=v->centerW+v->block_size[1]/4-v->block_size[0]/4;
282   else
283     /* short window.  Search from centerW */
284     i=v->centerW;
285   i/=v->samples_per_envelope_step;
286
287   for(;i<v->envelope_current;i++){
288     for(j=0;j<v->envelope_channels;j++)
289       if(v->multipliers[j][i])break;
290     if(j<v->envelope_channels)break;
291   }
292   
293   if(i<v->envelope_current){
294     /* Ooo, we hit a multiplier. Is it beyond the boundary to make the
295        upcoming block large ? */
296     long largebound;
297     if(v->W)
298       largebound=v->centerW+v->block_size[1];
299     else
300       largebound=v->centerW+v->block_size[0]/4+v->block_size[1]*3/4;
301     largebound/=v->samples_per_envelope_step;
302
303     if(i>=largebound)
304       v->nW=1;
305     else
306       v->nW=0;
307
308   }else{
309     /* Assume maximum; if the block is incomplete given current
310        buffered data, this will be detected below */
311     v->nW=1;
312   }
313
314   /* Do we actually have enough data *now* for the next block? The
315      reason to check is that if we had no multipliers, that could
316      simply been due to running out of data.  In that case, we don;t
317      know the size of the next block for sure and we need that now to
318      figure out the window shape of this block */
319   
320   centerNext=v->centerW+v->block_size[v->W]/4+v->block_size[v->nW]/4;
321
322   {
323     long blockbound=centerNext+v->block_size[v->nW]/2;
324     if(v->pcm_current<blockbound)return(0); /* not enough data yet */    
325   }
326
327   /* fill in the block */
328   vb->lW=v->lW;
329   vb->W=v->W;
330   vb->nW=v->nW;
331   vb->vd=v;
332
333   vb->pcmend=v->block_size[v->W];
334   vb->multend=vb->pcmend / v->samples_per_envelope_step;
335
336   if(v->pcm_channels!=vb->pcm_channels ||
337      v->block_size[1]!=vb->pcm_storage ||
338      v->envelope_channels!=vb->mult_channels){
339
340     /* Storage not initialized or initilized for some other codec
341        instance with different settings */
342
343     vorbis_block_clear(vb);
344     vorbis_block_init(v,vb);
345   }
346
347   /* copy the vectors */
348   for(i=0;i<v->pcm_channels;i++)
349     memcpy(vb->pcm[i],v->pcm[i]+beginW,v->block_size[v->W]*sizeof(double));
350   for(i=0;i<v->envelope_channels;i++)
351     memcpy(vb->mult[i],v->multipliers[i]+beginM,v->block_size[v->W]/
352            v->samples_per_envelope_step*sizeof(int));
353
354   vb->frameno=v->frame;
355
356   /* handle eof detection: eof==0 means that we've not yet received EOF
357                            eof>0  marks the last 'real' sample in pcm[]
358                            eof<0  'no more to do'; doesn't get here */
359
360   if(v->eofflag){
361     if(v->centerW>=v->eofflag){
362       v->eofflag=-1;
363       vb->eofflag=1;
364     }
365   }
366
367   /* advance storage vectors and clean up */
368   {
369     int new_centerNext=v->block_size[1]/2;
370     int movementW=centerNext-new_centerNext;
371     int movementM=movementW/v->samples_per_envelope_step;
372
373     for(i=0;i<v->pcm_channels;i++)
374       memmove(v->pcm[i],v->pcm[i]+movementW,
375               (v->pcm_current-movementW)*sizeof(double));
376     
377     for(i=0;i<v->envelope_channels;i++){
378       memmove(v->multipliers[i],v->multipliers[i]+movementM,
379               (v->envelope_current-movementM)*sizeof(int));
380     }
381
382     v->pcm_current-=movementW;
383     v->envelope_current-=movementM;
384
385     v->lW=v->W;
386     v->W=v->nW;
387     v->centerW=new_centerNext;
388
389     v->frame++;
390     v->samples+=movementW;
391
392     if(v->eofflag)
393       v->eofflag-=movementW;
394   }
395
396   /* done */
397   return(1);
398 }
399
400 int vorbis_synthesis_init(vorbis_dsp_state *v,vorbis_info *vi){
401   _vds_shared_init(v,vi);
402   /* Adjust centerW to allow an easier mechanism for determining output */
403   v->pcm_returned=v->centerW;
404   v->centerW-= v->block_size[v->W]/4+v->block_size[v->lW]/4;
405   return(0);
406 }
407
408 /* Unike in analysis, the window is only partially applied, and
409    envelope *not* applied. */
410
411 int vorbis_synthesis_blockin(vorbis_dsp_state *v,vorbis_block *vb){
412
413   /* Shift out any PCM that we returned previously */
414
415   if(v->pcm_returned  && v->centerW>v->block_size[1]/2){
416
417     /* don't shift too much; we need to have a minimum PCM buffer of
418        1/2 long block */
419
420     int shift=v->centerW-v->block_size[1]/2;
421     shift=(v->pcm_returned<shift?v->pcm_returned:shift);
422
423     v->pcm_current-=shift;
424     v->centerW-=shift;
425     v->pcm_returned-=shift;
426     
427     if(shift){
428       int i;
429       for(i=0;i<v->pcm_channels;i++)
430         memmove(v->pcm[i],v->pcm[i]+shift,
431                 v->pcm_current*sizeof(double));
432     }
433   }
434
435   {
436     int envperlong=v->block_size[1]/v->samples_per_envelope_step;
437     int envperW=v->block_size[v->W]/v->samples_per_envelope_step;
438     int sizeW=v->block_size[vb->W];
439     int centerW=v->centerW+v->block_size[vb->lW]/4+sizeW/4;
440     int beginW=centerW-sizeW/2;
441     int endW=beginW+sizeW;
442     int beginSl;
443     int endSl;
444     int i,j,k;
445     double *window;
446
447     /* Do we have enough PCM storage for the block? */
448     if(endW>v->pcm_storage){
449       /* expand the PCM storage */
450
451       v->pcm_storage=endW+v->block_size[1];
452    
453       for(i=0;i<v->pcm_channels;i++)
454         v->pcm[i]=realloc(v->pcm[i],v->pcm_storage*sizeof(double)); 
455     }
456
457     /* multiplier storage works differently in decode than it does in
458        encode; we only need to buffer the last 'half largeblock' and
459        we don't need to keep it aligned with the PCM. */
460
461     /* fill in the first half of the block's multipliers */
462     i=v->envelope_current-1;
463     j=envperW/2-1;
464     for(;j>=0;j--)
465       for(k=0;k<v->envelope_channels;k++)
466         vb->mult[k][j]=v->multipliers[k][i];
467       
468     /* shift out unneeded buffered multipliers */
469     {
470       int needed=(envperlong-envperW)/2;
471       if(needed){
472         /* We need to keep some of the buffered ones */
473         for(k=0;k<v->envelope_channels;k++)
474           memmove(v->multipliers[k],
475                   v->multipliers[k]+v->envelope_current-needed,
476                   needed*sizeof(int));
477       }
478       v->envelope_current=needed;
479     }
480
481     /* add block's second half to the multiplier buffer */
482     /* init makes certain we have enough storage; we only buffer a
483        half longblock */
484     
485     for(i=envperW/2;i<envperW;i++){
486       j=v->envelope_current;
487       for(k=0;k<v->envelope_channels;k++)
488         v->multipliers[k][j++]=vb->mult[k][i];
489     }
490     v->envelope_current+=envperW/2;
491
492     /* manufacture/apply multiplier vector */
493
494     _ve_envelope_apply(vb);
495     
496     /* Overlap/add */
497     switch(vb->W){
498     case 0:
499       beginSl=0;
500       endSl=v->block_size[0]/2;
501       break;
502     case 1:
503       beginSl=v->block_size[1]/4-v->block_size[vb->lW]/4;
504       endSl=beginSl+v->block_size[vb->lW]/2;
505       break;
506     }
507
508     window=v->window[vb->W][0][vb->lW]+v->block_size[vb->W]/2;
509
510     for(j=0;j<v->pcm_channels;j++){
511       double *pcm=v->pcm[j]+beginW;
512
513       /* the add section */
514       for(i=beginSl;i<endSl;i++)
515         pcm[i]=pcm[i]*window[i]+vb->pcm[j][i];
516       /* the remaining section */
517       for(;i<sizeW;i++)
518         pcm[i]=vb->pcm[j][i];
519     }
520
521     /* Update, cleanup */
522
523     v->centerW=centerW;
524     v->pcm_current=endW;
525
526     if(vb->eofflag)v->eofflag=1;
527   }
528   return(0);
529 }
530
531 int vorbis_synthesis_pcmout(vorbis_dsp_state *v,double ***pcm){
532   if(v->pcm_returned<v->centerW){
533     int i;
534     for(i=0;i<v->pcm_channels;i++)
535       v->pcmret[i]=v->pcm[i]+v->pcm_returned;
536     *pcm=v->pcmret;
537     return(v->centerW-v->pcm_returned);
538   }
539   return(0);
540 }
541
542 int vorbis_synthesis_read(vorbis_dsp_state *v,int bytes){
543   if(bytes && v->pcm_returned+bytes>v->centerW)return(-1);
544   v->pcm_returned+=bytes;
545   return(0);
546 }
547
548 #ifdef _V_SELFTEST
549 #include <stdio.h>
550 #include <math.h>
551
552 void _ve_envelope_multipliers(vorbis_dsp_state *v){
553   /* set 'random' deltas... */
554   int new_current=v->pcm_current/v->samples_per_envelope_step;
555   int i,j;
556
557   for(i=v->envelope_current;i<new_current;i++){
558     int flag=0;
559     for(j=0;j<v->samples_per_envelope_step;j++)
560       if(v->pcm[0][j+i*v->samples_per_envelope_step]>5)flag=1;
561     
562     for(j=0;j<v->envelope_channels;j++)
563       v->multipliers[j][i]=flag;
564   }
565   v->envelope_current=i;
566 }
567
568 void _ve_envelope_apply(vorbis_block *vb){
569
570 }
571
572 /* basic test of PCM blocking:
573
574    construct a PCM vector and block it using preset sizing in our fake
575    delta/multiplier generation.  Immediately hand the block over to
576    'synthesis' and rebuild it. */
577
578 int main(){
579   int blocksize=1024;
580   int fini=100*1024;
581   vorbis_dsp_state encode,decode;
582   vorbis_info vi;
583   vorbis_block vb;
584   long counterin=0;
585   long countermid=0;
586   long counterout=0;
587   int done=0;
588   char *temp[]={ "Test" ,"the Test band", "test records",NULL };
589   int frame=0;
590
591   MDCT_lookup *ml[2];
592
593   vi.channels=2;
594   vi.rate=44100;
595   vi.version=0;
596   vi.mode=0;
597
598   vi.user_comments=temp;
599   vi.vendor="Xiphophorus";
600
601   vorbis_analysis_init(&encode,&vi);
602   vorbis_synthesis_init(&decode,&vi);
603
604   memset(&vb,0,sizeof(vorbis_block));
605   vorbis_block_init(&encode,&vb);
606
607   ml[0]=MDCT_init(encode.block_size[0]);
608   ml[1]=MDCT_init(encode.block_size[1]);
609
610   /* Submit 100K samples of data reading out blocks... */
611   
612   while(!done){
613     int i;
614     double **buf=vorbis_analysis_buffer(&encode,blocksize);
615     for(i=0;i<blocksize;i++){
616       buf[0][i]=sin((counterin+i)%500/500.*M_PI*2)+2;
617       buf[1][i]=-1;
618
619       if((counterin+i)%15000>13000)buf[0][i]+=10;
620     }
621
622     i=(counterin+blocksize>fini?fini-counterin:blocksize);
623     vorbis_analysis_wrote(&encode,i);
624     counterin+=i;
625
626     while(vorbis_analysis_blockout(&encode,&vb)){
627       double **pcm;
628       int avail;
629
630       /* temp fixup */
631
632       double *window=encode.window[vb.W][vb.lW][vb.nW];
633
634       for(i=0;i<vb.pcm_channels;i++)
635         MDCT(vb.pcm[i],vb.pcm[i],ml[vb.W],window);
636
637       for(i=0;i<vb.pcm_channels;i++)
638         iMDCT(vb.pcm[i],vb.pcm[i],ml[vb.W],window);
639
640
641       {
642         FILE *out;
643         char path[80];
644         int i;
645
646         int avail=encode.block_size[vb.W];
647         int beginW=countermid-avail/2;
648         
649         sprintf(path,"ana%d",vb.frameno);
650         out=fopen(path,"w");
651
652         for(i=0;i<avail;i++)
653           fprintf(out,"%ld %g\n",i+beginW,vb.pcm[0][i]);
654         fprintf(out,"\n");
655         for(i=0;i<avail;i++)
656           fprintf(out,"%ld %g\n",i+beginW,window[i]);
657
658         fclose(out);
659         countermid+=encode.block_size[vb.W]/4+encode.block_size[vb.nW]/4;
660       }
661
662
663       vorbis_synthesis_blockin(&decode,&vb);
664       
665
666       while((avail=vorbis_synthesis_pcmout(&decode,&pcm))){
667         FILE *out;
668         char path[80];
669         int i;
670         
671         sprintf(path,"syn%d",frame);
672         out=fopen(path,"w");
673
674         for(i=0;i<avail;i++)
675           fprintf(out,"%ld %g\n",i+counterout,pcm[0][i]);
676         fprintf(out,"\n");
677         for(i=0;i<avail;i++)
678           fprintf(out,"%ld %g\n",i+counterout,pcm[1][i]);
679
680         fclose(out);
681
682         vorbis_synthesis_read(&decode,avail);
683
684         counterout+=avail;
685         frame++;
686       }
687       
688
689       if(vb.eofflag){
690         done=1;
691         break;
692       }
693     }
694   }
695   return 0;
696 }
697
698 #endif