1#include <math.h>
2#include <vorbis/codec.h>
3#include <vorbis/vorbisenc.h>
4#include <tcl.h>
5#include "snack.h"
6#include <stdlib.h>
7#include <time.h>
8
9#if defined(__WIN32__)
10#  include <io.h>
11#  include <fcntl.h>
12#  define WIN32_LEAN_AND_MEAN
13#  include <windows.h>
14#  undef WIN32_LEAN_AND_MEAN
15#  define EXPORT(a,b) __declspec(dllexport) a b
16BOOL APIENTRY
17DllMain(HINSTANCE hInst, DWORD reason, LPVOID reserved)
18{
19  return TRUE;
20}
21#else
22#  define EXPORT(a,b) a b
23#endif
24
25/* vorbisfile.h */
26
27/********************************************************************
28 *                                                                  *
29 * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE.   *
30 * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS     *
31 * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE *
32 * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING.       *
33 *                                                                  *
34 * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2001             *
35 * by the XIPHOPHORUS Company http://www.xiph.org/                  *
36 *                                                                  *
37 ********************************************************************
38
39 function: stdio-based convenience library for opening/seeking/decoding
40 last mod: $Id: vorbisfile.h,v 1.17 2002/03/07 03:41:03 xiphmont Exp $
41
42 ********************************************************************/
43
44#ifndef _OV_FILE_H_
45#define _OV_FILE_H_
46
47#ifdef __cplusplus
48extern "C"
49{
50#endif /* __cplusplus */
51
52#include <stdio.h>
53#include "vorbis/codec.h"
54
55/* The function prototypes for the callbacks are basically the same as for
56 * the stdio functions fread, fseek, fclose, ftell.
57 * The one difference is that the FILE * arguments have been replaced with
58 * a void * - this is to be used as a pointer to whatever internal data these
59 * functions might need. In the stdio case, it's just a FILE * cast to a void *
60 *
61 * If you use other functions, check the docs for these functions and return
62 * the right values. For seek_func(), you *MUST* return -1 if the stream is
63 * unseekable
64 */
65typedef struct {
66  size_t (*read_func)  (void *ptr, size_t size, size_t nmemb, void *datasource);
67  int    (*seek_func)  (void *datasource, ogg_int64_t offset, int whence);
68  int    (*close_func) (void *datasource);
69  long   (*tell_func)  (void *datasource);
70} ov_callbacks;
71
72#define  NOTOPEN   0
73#define  PARTOPEN  1
74#define  OPENED    2
75#define  STREAMSET 3
76#define  INITSET   4
77
78typedef struct OggVorbis_File {
79  Tcl_Channel      datasource; /* Pointer to a FILE *, etc. */
80  int              seekable;
81  ogg_int64_t      offset;
82  ogg_int64_t      end;
83  ogg_sync_state   oy;
84
85  /* If the FILE handle isn't seekable (eg, a pipe), only the current
86     stream appears */
87  int              links;
88  ogg_int64_t     *offsets;
89  ogg_int64_t     *dataoffsets;
90  long            *serialnos;
91  ogg_int64_t     *pcmlengths; /* overloaded to maintain binary
92				  compatability; x2 size, stores both
93				  beginning and end values */
94  vorbis_info     *vi;
95  vorbis_comment  *vc;
96
97  /* Decoding working state local storage */
98  ogg_int64_t      pcm_offset;
99  int              ready_state;
100  long             current_serialno;
101  int              current_link;
102
103  double           bittrack;
104  double           samptrack;
105
106  ogg_stream_state os; /* take physical pages, weld into a logical
107                          stream of packets */
108  vorbis_dsp_state vd; /* central working state for the packet->PCM decoder */
109  vorbis_block     vb; /* local working space for packet->PCM decode */
110
111  ov_callbacks callbacks;
112
113  int maxbitrate;
114  int minbitrate;
115  int nombitrate;
116  double quality;
117  Tcl_Obj *commList;
118  Tcl_Obj *vendor;
119
120} OggVorbis_File;
121
122  /*
123extern int ov_clear(OggVorbis_File *vf);
124extern int ov_open(FILE *f,OggVorbis_File *vf,char *initial,long ibytes);
125extern int ov_open_callbacks(void *datasource, OggVorbis_File *vf,
126		char *initial, long ibytes, ov_callbacks callbacks);
127  */
128extern int ov_clear(Tcl_Interp *interp, OggVorbis_File *vf);
129extern int ov_open(Tcl_Interp *interp, Tcl_Channel *f,OggVorbis_File *vf,char *initial,long ibytes);
130extern int ov_open_callbacks(Tcl_Interp *interp, Tcl_Channel *datasource, OggVorbis_File *vf,
131		char *initial, long ibytes, ov_callbacks callbacks);
132
133extern int ov_test(FILE *f,OggVorbis_File *vf,char *initial,long ibytes);
134extern int ov_test_callbacks(void *datasource, OggVorbis_File *vf,
135		char *initial, long ibytes, ov_callbacks callbacks);
136extern int ov_test_open(OggVorbis_File *vf);
137
138extern long ov_bitrate(OggVorbis_File *vf,int i);
139extern long ov_bitrate_instant(OggVorbis_File *vf);
140extern long ov_streams(OggVorbis_File *vf);
141extern long ov_seekable(OggVorbis_File *vf);
142extern long ov_serialnumber(OggVorbis_File *vf,int i);
143
144extern ogg_int64_t ov_raw_total(OggVorbis_File *vf,int i);
145extern ogg_int64_t ov_pcm_total(OggVorbis_File *vf,int i);
146extern double ov_time_total(OggVorbis_File *vf,int i);
147
148extern int ov_raw_seek(OggVorbis_File *vf,ogg_int64_t pos);
149extern int ov_pcm_seek(OggVorbis_File *vf,ogg_int64_t pos);
150extern int ov_pcm_seek_page(OggVorbis_File *vf,ogg_int64_t pos);
151extern int ov_time_seek(OggVorbis_File *vf,double pos);
152extern int ov_time_seek_page(OggVorbis_File *vf,double pos);
153
154extern ogg_int64_t ov_raw_tell(OggVorbis_File *vf);
155extern ogg_int64_t ov_pcm_tell(OggVorbis_File *vf);
156extern double ov_time_tell(OggVorbis_File *vf);
157
158extern vorbis_info *ov_info(OggVorbis_File *vf,int link);
159extern vorbis_comment *ov_comment(OggVorbis_File *vf,int link);
160
161extern long ov_read_float(OggVorbis_File *vf,float ***pcm_channels,int samples,
162			  int *bitstream);
163extern long ov_read(OggVorbis_File *vf,char *buffer,int length,
164		    int bigendianp,int word,int sgned,int *bitstream);
165
166#ifdef __cplusplus
167}
168#endif /* __cplusplus */
169
170#endif
171
172
173/* vorbisfile.c */
174
175/********************************************************************
176 *                                                                  *
177 * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE.   *
178 * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS     *
179 * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE *
180 * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING.       *
181 *                                                                  *
182 * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002             *
183 * by the XIPHOPHORUS Company http://www.xiph.org/                  *
184 *                                                                  *
185 ********************************************************************
186
187 function: stdio-based convenience library for opening/seeking/decoding
188 last mod: $Id: vorbisfile.c,v 1.62 2002/07/06 04:20:03 msmith Exp $
189
190 ********************************************************************/
191
192#include <stdlib.h>
193#include <stdio.h>
194#include <errno.h>
195#include <string.h>
196#include <math.h>
197
198#include "vorbis/codec.h"
199#include "vorbis/vorbisfile.h"
200
201/* misc.h */
202#ifndef _V_RANDOM_H_
203#define _V_RANDOM_H_
204#include "vorbis/codec.h"
205
206extern int analysis_noisy;
207
208extern void *_vorbis_block_alloc(vorbis_block *vb,long bytes);
209extern void _vorbis_block_ripcord(vorbis_block *vb);
210extern void _analysis_output(char *base,int i,float *v,int n,int bark,int dB,
211			     ogg_int64_t off);
212
213#ifdef DEBUG_MALLOC
214
215#define _VDBG_GRAPHFILE "malloc.m"
216extern void *_VDBG_malloc(void *ptr,long bytes,char *file,long line);
217extern void _VDBG_free(void *ptr,char *file,long line);
218
219#ifndef MISC_C
220#undef _ogg_malloc
221#undef _ogg_calloc
222#undef _ogg_realloc
223#undef _ogg_free
224
225#define _ogg_malloc(x) _VDBG_malloc(NULL,(x),__FILE__,__LINE__)
226#define _ogg_calloc(x,y) _VDBG_malloc(NULL,(x)*(y),__FILE__,__LINE__)
227#define _ogg_realloc(x,y) _VDBG_malloc((x),(y),__FILE__,__LINE__)
228#define _ogg_free(x) _VDBG_free((x),__FILE__,__LINE__)
229#endif
230#endif
231
232#endif
233
234/* os.h */
235#include <ogg/os_types.h>
236
237#ifndef _V_IFDEFJAIL_H_
238#  define _V_IFDEFJAIL_H_
239
240#  ifdef __GNUC__
241#    define STIN static __inline__
242#  elif _WIN32
243#    define STIN static __inline
244#else
245#  define STIN static
246#endif
247
248#ifndef M_PI
249#  define M_PI (3.1415926536f)
250#endif
251
252#ifdef _WIN32
253#  include <malloc.h>
254#  define rint(x)   (floor((x)+0.5f))
255#  define NO_FLOAT_MATH_LIB
256#  define FAST_HYPOT(a, b) sqrt((a)*(a) + (b)*(b))
257#endif
258
259#ifndef FAST_HYPOT
260#  define FAST_HYPOT hypot
261#endif
262
263#endif
264
265#ifdef HAVE_ALLOCA_H
266#  include <alloca.h>
267#endif
268
269#ifdef USE_MEMORY_H
270#  include <memory.h>
271#endif
272
273#ifndef min
274#  define min(x,y)  ((x)>(y)?(y):(x))
275#endif
276
277#ifndef max
278#  define max(x,y)  ((x)<(y)?(y):(x))
279#endif
280
281#if defined(__i386__) && defined(__GNUC__) && !defined(__BEOS__)
282#  define VORBIS_FPU_CONTROL
283/* both GCC and MSVC are kinda stupid about rounding/casting to int.
284   Because of encapsulation constraints (GCC can't see inside the asm
285   block and so we end up doing stupid things like a store/load that
286   is collectively a noop), we do it this way */
287
288/* we must set up the fpu before this works!! */
289
290typedef ogg_int16_t vorbis_fpu_control;
291
292static inline void vorbis_fpu_setround(vorbis_fpu_control *fpu){
293  ogg_int16_t ret;
294  ogg_int16_t temp;
295  __asm__ __volatile__("fnstcw %0\n\t"
296	  "movw %0,%%dx\n\t"
297	  "orw $62463,%%dx\n\t"
298	  "movw %%dx,%1\n\t"
299	  "fldcw %1\n\t":"=m"(ret):"m"(temp): "dx");
300  *fpu=ret;
301}
302
303static inline void vorbis_fpu_restore(vorbis_fpu_control fpu){
304  __asm__ __volatile__("fldcw %0":: "m"(fpu));
305}
306
307/* assumes the FPU is in round mode! */
308static inline int vorbis_ftoi(double f){  /* yes, double!  Otherwise,
309                                             we get extra fst/fld to
310                                             truncate precision */
311  int i;
312  __asm__("fistl %0": "=m"(i) : "t"(f));
313  return(i);
314}
315#endif
316
317
318#if defined(_WIN32) && !defined(__GNUC__) && !defined(__BORLANDC__)
319#  define VORBIS_FPU_CONTROL
320
321typedef ogg_int16_t vorbis_fpu_control;
322
323static __inline int vorbis_ftoi(double f){
324	int i;
325	__asm{
326		fld f
327		fistp i
328	}
329	return i;
330}
331
332static __inline void vorbis_fpu_setround(vorbis_fpu_control *fpu){
333}
334
335static __inline void vorbis_fpu_restore(vorbis_fpu_control fpu){
336}
337
338#endif
339
340
341#ifndef VORBIS_FPU_CONTROL
342
343typedef int vorbis_fpu_control;
344
345static int vorbis_ftoi(double f){
346  return (int)(f+.5);
347}
348
349/* We don't have special code for this compiler/arch, so do it the slow way */
350#  define vorbis_fpu_setround(vorbis_fpu_control) {}
351#  define vorbis_fpu_restore(vorbis_fpu_control) {}
352
353#endif
354
355
356/* A 'chained bitstream' is a Vorbis bitstream that contains more than
357   one logical bitstream arranged end to end (the only form of Ogg
358   multiplexing allowed in a Vorbis bitstream; grouping [parallel
359   multiplexing] is not allowed in Vorbis) */
360
361/* A Vorbis file can be played beginning to end (streamed) without
362   worrying ahead of time about chaining (see decoder_example.c).  If
363   we have the whole file, however, and want random access
364   (seeking/scrubbing) or desire to know the total length/time of a
365   file, we need to account for the possibility of chaining. */
366
367/* We can handle things a number of ways; we can determine the entire
368   bitstream structure right off the bat, or find pieces on demand.
369   This example determines and caches structure for the entire
370   bitstream, but builds a virtual decoder on the fly when moving
371   between links in the chain. */
372
373/* There are also different ways to implement seeking.  Enough
374   information exists in an Ogg bitstream to seek to
375   sample-granularity positions in the output.  Or, one can seek by
376   picking some portion of the stream roughly in the desired area if
377   we only want coarse navigation through the stream. */
378
379/*************************************************************************
380 * Many, many internal helpers.  The intention is not to be confusing;
381 * rampant duplication and monolithic function implementation would be
382 * harder to understand anyway.  The high level functions are last.  Begin
383 * grokking near the end of the file */
384
385/* read a little more data from the file/pipe into the ogg_sync framer
386*/
387#define CHUNKSIZE 8500 /* a shade over 8k; anyone using pages well
388                          over 8k gets what they deserve */
389static long _get_data(OggVorbis_File *vf){
390  errno=0;
391  if(vf->datasource){
392    char *buffer=ogg_sync_buffer(&vf->oy,CHUNKSIZE);
393   /*long bytes=(vf->callbacks.read_func)(buffer,1,CHUNKSIZE,vf->datasource);*/
394    long bytes=Tcl_Read(vf->datasource,buffer,CHUNKSIZE);
395    if(bytes>0)ogg_sync_wrote(&vf->oy,bytes);
396    if(bytes==0 && errno)return(-1);
397    return(bytes);
398  }else
399    return(0);
400}
401
402/* save a tiny smidge of verbosity to make the code more readable */
403static void _seek_helper(OggVorbis_File *vf,ogg_int64_t offset){
404  if(vf->datasource){
405    TCL_SEEK(vf->datasource, offset, SEEK_SET);
406    /*(vf->callbacks.seek_func)(vf->datasource, offset, SEEK_SET);*/
407    vf->offset=offset;
408    ogg_sync_reset(&vf->oy);
409  }else{
410    /* shouldn't happen unless someone writes a broken callback */
411    return;
412  }
413}
414
415/* The read/seek functions track absolute position within the stream */
416
417/* from the head of the stream, get the next page.  boundary specifies
418   if the function is allowed to fetch more data from the stream (and
419   how much) or only use internally buffered data.
420
421   boundary: -1) unbounded search
422              0) read no additional data; use cached only
423	      n) search for a new page beginning for n bytes
424
425   return:   <0) did not find a page (OV_FALSE, OV_EOF, OV_EREAD)
426              n) found a page at absolute offset n */
427
428static ogg_int64_t _get_next_page(OggVorbis_File *vf,ogg_page *og,
429				  ogg_int64_t boundary){
430  if(boundary>0)boundary+=vf->offset;
431  while(1){
432    long more;
433
434    if(boundary>0 && vf->offset>=boundary)return(OV_FALSE);
435    more=ogg_sync_pageseek(&vf->oy,og);
436
437    if(more<0){
438      /* skipped n bytes */
439      vf->offset-=more;
440    }else{
441      if(more==0){
442	/* send more paramedics */
443	if(!boundary)return(OV_FALSE);
444	{
445	  long ret=_get_data(vf);
446	  if(ret==0)return(OV_EOF);
447	  if(ret<0)return(OV_EREAD);
448	}
449      }else{
450	/* got a page.  Return the offset at the page beginning,
451           advance the internal offset past the page end */
452	ogg_int64_t ret=vf->offset;
453	vf->offset+=more;
454	return(ret);
455
456      }
457    }
458  }
459}
460
461/* find the latest page beginning before the current stream cursor
462   position. Much dirtier than the above as Ogg doesn't have any
463   backward search linkage.  no 'readp' as it will certainly have to
464   read. */
465/* returns offset or OV_EREAD, OV_FAULT */
466static ogg_int64_t _get_prev_page(OggVorbis_File *vf,ogg_page *og){
467  ogg_int64_t begin=vf->offset;
468  ogg_int64_t end=begin;
469  ogg_int64_t ret;
470  ogg_int64_t offset=-1;
471
472  while(offset==-1){
473    begin-=CHUNKSIZE;
474    if(begin<0)
475      begin=0;
476    _seek_helper(vf,begin);
477    while(vf->offset<end){
478      ret=_get_next_page(vf,og,end-vf->offset);
479      if(ret==OV_EREAD)return(OV_EREAD);
480      if(ret<0){
481	break;
482      }else{
483	offset=ret;
484      }
485    }
486  }
487
488  /* we have the offset.  Actually snork and hold the page now */
489  _seek_helper(vf,offset);
490  ret=_get_next_page(vf,og,CHUNKSIZE);
491  if(ret<0)
492    /* this shouldn't be possible */
493    return(OV_EFAULT);
494
495  return(offset);
496}
497
498/* finds each bitstream link one at a time using a bisection search
499   (has to begin by knowing the offset of the lb's initial page).
500   Recurses for each link so it can alloc the link storage after
501   finding them all, then unroll and fill the cache at the same time */
502static int _bisect_forward_serialno(OggVorbis_File *vf,
503				    ogg_int64_t begin,
504				    ogg_int64_t searched,
505				    ogg_int64_t end,
506				    long currentno,
507				    long m){
508  ogg_int64_t endsearched=end;
509  ogg_int64_t next=end;
510  ogg_page og;
511  ogg_int64_t ret;
512
513  /* the below guards against garbage seperating the last and
514     first pages of two links. */
515  while(searched<endsearched){
516    ogg_int64_t bisect;
517
518    if(endsearched-searched<CHUNKSIZE){
519      bisect=searched;
520    }else{
521      bisect=(searched+endsearched)/2;
522    }
523
524    _seek_helper(vf,bisect);
525    ret=_get_next_page(vf,&og,-1);
526    if(ret==OV_EREAD)return(OV_EREAD);
527    if(ret<0 || ogg_page_serialno(&og)!=currentno){
528      endsearched=bisect;
529      if(ret>=0)next=ret;
530    }else{
531      searched=ret+og.header_len+og.body_len;
532    }
533  }
534
535  _seek_helper(vf,next);
536  ret=_get_next_page(vf,&og,-1);
537  if(ret==OV_EREAD)return(OV_EREAD);
538
539  if(searched>=end || ret<0){
540    vf->links=m+1;
541    vf->offsets=_ogg_malloc((vf->links+1)*sizeof(*vf->offsets));
542    vf->serialnos=_ogg_malloc(vf->links*sizeof(*vf->serialnos));
543    vf->offsets[m+1]=searched;
544  }else{
545    ret=_bisect_forward_serialno(vf,next,vf->offset,
546				 end,ogg_page_serialno(&og),m+1);
547    if(ret==OV_EREAD)return(OV_EREAD);
548  }
549
550  vf->offsets[m]=begin;
551  vf->serialnos[m]=currentno;
552  return(0);
553}
554
555/* uses the local ogg_stream storage in vf; this is important for
556   non-streaming input sources */
557static int _fetch_headers(OggVorbis_File *vf,vorbis_info *vi,vorbis_comment *vc,
558			  long *serialno,ogg_page *og_ptr){
559  ogg_page og;
560  ogg_packet op;
561  int i,ret;
562
563  if(!og_ptr){
564    ogg_int64_t llret=_get_next_page(vf,&og,CHUNKSIZE);
565    if(llret==OV_EREAD)return(OV_EREAD);
566    if(llret<0)return OV_ENOTVORBIS;
567    og_ptr=&og;
568  }
569
570  ogg_stream_reset_serialno(&vf->os,ogg_page_serialno(og_ptr));
571  if(serialno)*serialno=vf->os.serialno;
572  vf->ready_state=STREAMSET;
573
574  /* extract the initial header from the first page and verify that the
575     Ogg bitstream is in fact Vorbis data */
576
577  vorbis_info_init(vi);
578  vorbis_comment_init(vc);
579
580  i=0;
581  while(i<3){
582    ogg_stream_pagein(&vf->os,og_ptr);
583    while(i<3){
584      int result=ogg_stream_packetout(&vf->os,&op);
585      if(result==0)break;
586      if(result==-1){
587	ret=OV_EBADHEADER;
588	goto bail_header;
589      }
590      if((ret=vorbis_synthesis_headerin(vi,vc,&op))){
591	goto bail_header;
592      }
593      i++;
594    }
595    if(i<3)
596      if(_get_next_page(vf,og_ptr,CHUNKSIZE)<0){
597	ret=OV_EBADHEADER;
598	goto bail_header;
599      }
600  }
601  return 0;
602
603 bail_header:
604  vorbis_info_clear(vi);
605  vorbis_comment_clear(vc);
606  vf->ready_state=OPENED;
607
608  return ret;
609}
610
611/* last step of the OggVorbis_File initialization; get all the
612   vorbis_info structs and PCM positions.  Only called by the seekable
613   initialization (local stream storage is hacked slightly; pay
614   attention to how that's done) */
615
616/* this is void and does not propogate errors up because we want to be
617   able to open and use damaged bitstreams as well as we can.  Just
618   watch out for missing information for links in the OggVorbis_File
619   struct */
620static void _prefetch_all_headers(OggVorbis_File *vf, ogg_int64_t dataoffset){
621  ogg_page og;
622  int i;
623  ogg_int64_t ret;
624
625  vf->vi=_ogg_realloc(vf->vi,vf->links*sizeof(*vf->vi));
626  vf->vc=_ogg_realloc(vf->vc,vf->links*sizeof(*vf->vc));
627  vf->dataoffsets=_ogg_malloc(vf->links*sizeof(*vf->dataoffsets));
628  vf->pcmlengths=_ogg_malloc(vf->links*2*sizeof(*vf->pcmlengths));
629
630  for(i=0;i<vf->links;i++){
631    if(i==0){
632      /* we already grabbed the initial header earlier.  Just set the offset */
633      vf->dataoffsets[i]=dataoffset;
634      _seek_helper(vf,dataoffset);
635
636    }else{
637
638      /* seek to the location of the initial header */
639
640      _seek_helper(vf,vf->offsets[i]);
641      if(_fetch_headers(vf,vf->vi+i,vf->vc+i,NULL,NULL)<0){
642    	vf->dataoffsets[i]=-1;
643      }else{
644	vf->dataoffsets[i]=vf->offset;
645      }
646    }
647
648    /* fetch beginning PCM offset */
649
650    if(vf->dataoffsets[i]!=-1){
651      ogg_int64_t accumulated=0;
652      long        lastblock=-1;
653      int         result;
654
655      ogg_stream_reset_serialno(&vf->os,vf->serialnos[i]);
656
657      while(1){
658	ogg_packet op;
659
660	ret=_get_next_page(vf,&og,-1);
661	if(ret<0)
662	  /* this should not be possible unless the file is
663             truncated/mangled */
664	  break;
665
666	if(ogg_page_serialno(&og)!=vf->serialnos[i])
667	  break;
668
669	/* count blocksizes of all frames in the page */
670	ogg_stream_pagein(&vf->os,&og);
671	while((result=ogg_stream_packetout(&vf->os,&op))){
672	  if(result>0){ /* ignore holes */
673	    long thisblock=vorbis_packet_blocksize(vf->vi+i,&op);
674	    if(lastblock!=-1)
675	      accumulated+=(lastblock+thisblock)>>2;
676	    lastblock=thisblock;
677	  }
678	}
679
680	if(ogg_page_granulepos(&og)!=-1){
681	  /* pcm offset of last packet on the first audio page */
682	  accumulated= ogg_page_granulepos(&og)-accumulated;
683	  break;
684	}
685      }
686
687      /* less than zero?  This is a stream with samples trimmed off
688         the beginning, a normal occurrence; set the offset to zero */
689      if(accumulated<0)accumulated=0;
690
691      vf->pcmlengths[i*2]=accumulated;
692    }
693
694    /* get the PCM length of this link. To do this,
695       get the last page of the stream */
696    {
697      ogg_int64_t end=vf->offsets[i+1];
698      _seek_helper(vf,end);
699
700      while(1){
701	ret=_get_prev_page(vf,&og);
702	if(ret<0){
703	  /* this should not be possible */
704	  vorbis_info_clear(vf->vi+i);
705	  vorbis_comment_clear(vf->vc+i);
706	  break;
707	}
708	if(ogg_page_granulepos(&og)!=-1){
709	  vf->pcmlengths[i*2+1]=ogg_page_granulepos(&og)-vf->pcmlengths[i*2];
710	  break;
711	}
712	vf->offset=ret;
713      }
714    }
715  }
716}
717
718static void _make_decode_ready(OggVorbis_File *vf){
719  if(vf->ready_state!=STREAMSET)return;
720  if(vf->seekable){
721    vorbis_synthesis_init(&vf->vd,vf->vi+vf->current_link);
722  }else{
723    vorbis_synthesis_init(&vf->vd,vf->vi);
724  }
725  vorbis_block_init(&vf->vd,&vf->vb);
726  vf->ready_state=INITSET;
727  return;
728}
729
730static int _open_seekable2(OggVorbis_File *vf){
731  long serialno=vf->current_serialno;
732  ogg_int64_t dataoffset=vf->offset, end;
733  ogg_page og;
734
735  /* we're partially open and have a first link header state in
736     storage in vf */
737  /* we can seek, so set out learning all about this file */
738  /*(vf->callbacks.seek_func)(vf->datasource,0,SEEK_END);*/
739  TCL_SEEK(vf->datasource, 0, SEEK_END);
740  /*  vf->offset=vf->end=(vf->callbacks.tell_func)(vf->datasource);*/
741  vf->offset=vf->end=TCL_TELL(vf->datasource);
742
743  /* We get the offset for the last page of the physical bitstream.
744     Most OggVorbis files will contain a single logical bitstream */
745  end=_get_prev_page(vf,&og);
746  if(end<0)return(end);
747
748  /* more than one logical bitstream? */
749  if(ogg_page_serialno(&og)!=serialno){
750
751    /* Chained bitstream. Bisect-search each logical bitstream
752       section.  Do so based on serial number only */
753    if(_bisect_forward_serialno(vf,0,0,end+1,serialno,0)<0)return(OV_EREAD);
754
755  }else{
756
757    /* Only one logical bitstream */
758    if(_bisect_forward_serialno(vf,0,end,end+1,serialno,0))return(OV_EREAD);
759
760  }
761
762  /* the initial header memory is referenced by vf after; don't free it */
763  _prefetch_all_headers(vf,dataoffset);
764  return(ov_raw_seek(vf,0));
765}
766
767/* clear out the current logical bitstream decoder */
768static void _decode_clear(OggVorbis_File *vf){
769  vorbis_dsp_clear(&vf->vd);
770  vorbis_block_clear(&vf->vb);
771  vf->ready_state=OPENED;
772
773  vf->bittrack=0.f;
774  vf->samptrack=0.f;
775}
776
777/* fetch and process a packet.  Handles the case where we're at a
778   bitstream boundary and dumps the decoding machine.  If the decoding
779   machine is unloaded, it loads it.  It also keeps pcm_offset up to
780   date (seek and read both use this.  seek uses a special hack with
781   readp).
782
783   return: <0) error, OV_HOLE (lost packet) or OV_EOF
784            0) need more data (only if readp==0)
785	    1) got a packet
786*/
787
788static int _fetch_and_process_packet(OggVorbis_File *vf,
789				     ogg_packet *op_in,
790				     int readp){
791  ogg_page og;
792
793  /* handle one packet.  Try to fetch it from current stream state */
794  /* extract packets from page */
795  while(1){
796
797    /* process a packet if we can.  If the machine isn't loaded,
798       neither is a page */
799    if(vf->ready_state==INITSET){
800      while(1) {
801      	ogg_packet op;
802      	ogg_packet *op_ptr=(op_in?op_in:&op);
803	int result=ogg_stream_packetout(&vf->os,op_ptr);
804	ogg_int64_t granulepos;
805
806	op_in=NULL;
807	if(result==-1)return(OV_HOLE); /* hole in the data. */
808	if(result>0){
809	  /* got a packet.  process it */
810	  granulepos=op_ptr->granulepos;
811	  if(!vorbis_synthesis(&vf->vb,op_ptr)){ /* lazy check for lazy
812						    header handling.  The
813						    header packets aren't
814						    audio, so if/when we
815						    submit them,
816						    vorbis_synthesis will
817						    reject them */
818
819	    /* suck in the synthesis data and track bitrate */
820	    {
821	      int oldsamples=vorbis_synthesis_pcmout(&vf->vd,NULL);
822	      /* for proper use of libvorbis within libvorbisfile,
823                 oldsamples will always be zero. */
824	      if(oldsamples)return(OV_EFAULT);
825
826	      vorbis_synthesis_blockin(&vf->vd,&vf->vb);
827	      vf->samptrack+=vorbis_synthesis_pcmout(&vf->vd,NULL)-oldsamples;
828	      vf->bittrack+=op_ptr->bytes*8;
829	    }
830
831	    /* update the pcm offset. */
832	    if(granulepos!=-1 && !op_ptr->e_o_s){
833	      int link=(vf->seekable?vf->current_link:0);
834	      int i,samples;
835
836	      /* this packet has a pcm_offset on it (the last packet
837	         completed on a page carries the offset) After processing
838	         (above), we know the pcm position of the *last* sample
839	         ready to be returned. Find the offset of the *first*
840
841	         As an aside, this trick is inaccurate if we begin
842	         reading anew right at the last page; the end-of-stream
843	         granulepos declares the last frame in the stream, and the
844	         last packet of the last page may be a partial frame.
845	         So, we need a previous granulepos from an in-sequence page
846	         to have a reference point.  Thus the !op_ptr->e_o_s clause
847	         above */
848
849	      if(vf->seekable && link>0)
850		granulepos-=vf->pcmlengths[link*2];
851	      if(granulepos<0)granulepos=0; /* actually, this
852					       shouldn't be possible
853					       here unless the stream
854					       is very broken */
855
856	      samples=vorbis_synthesis_pcmout(&vf->vd,NULL);
857
858	      granulepos-=samples;
859	      for(i=0;i<link;i++)
860	        granulepos+=vf->pcmlengths[i*2+1];
861	      vf->pcm_offset=granulepos;
862	    }
863	    return(1);
864	  }
865	}
866	else
867	  break;
868      }
869    }
870
871    if(vf->ready_state>=OPENED){
872      if(!readp)return(0);
873      if(_get_next_page(vf,&og,-1)<0)return(OV_EOF); /* eof.
874							leave unitialized */
875      /* bitrate tracking; add the header's bytes here, the body bytes
876	 are done by packet above */
877      vf->bittrack+=og.header_len*8;
878
879      /* has our decoding just traversed a bitstream boundary? */
880      if(vf->ready_state==INITSET){
881	if(vf->current_serialno!=ogg_page_serialno(&og)){
882	  _decode_clear(vf);
883
884	  if(!vf->seekable){
885	    vorbis_info_clear(vf->vi);
886	    vorbis_comment_clear(vf->vc);
887	  }
888	}
889      }
890    }
891
892    /* Do we need to load a new machine before submitting the page? */
893    /* This is different in the seekable and non-seekable cases.
894
895       In the seekable case, we already have all the header
896       information loaded and cached; we just initialize the machine
897       with it and continue on our merry way.
898
899       In the non-seekable (streaming) case, we'll only be at a
900       boundary if we just left the previous logical bitstream and
901       we're now nominally at the header of the next bitstream
902    */
903
904    if(vf->ready_state!=INITSET){
905      int link;
906
907      if(vf->ready_state<STREAMSET){
908	if(vf->seekable){
909	  vf->current_serialno=ogg_page_serialno(&og);
910
911	  /* match the serialno to bitstream section.  We use this rather than
912	     offset positions to avoid problems near logical bitstream
913	     boundaries */
914	  for(link=0;link<vf->links;link++)
915	    if(vf->serialnos[link]==vf->current_serialno)break;
916	  if(link==vf->links)return(OV_EBADLINK); /* sign of a bogus
917						     stream.  error out,
918						     leave machine
919						     uninitialized */
920
921	  vf->current_link=link;
922
923	  ogg_stream_reset_serialno(&vf->os,vf->current_serialno);
924	  vf->ready_state=STREAMSET;
925
926	}else{
927	  /* we're streaming */
928	  /* fetch the three header packets, build the info struct */
929
930	  int ret=_fetch_headers(vf,vf->vi,vf->vc,&vf->current_serialno,&og);
931	  if(ret)return(ret);
932	  vf->current_link++;
933	  link=0;
934	}
935      }
936
937      _make_decode_ready(vf);
938    }
939    ogg_stream_pagein(&vf->os,&og);
940  }
941}
942
943/* if, eg, 64 bit stdio is configured by default, this will build with
944   fseek64 */
945static int _fseek64_wrap(FILE *f,ogg_int64_t off,int whence){
946  if(f==NULL)return(-1);
947  return fseek(f,off,whence);
948}
949
950static int _ov_open1(Tcl_Interp *interp,Tcl_Channel *f,OggVorbis_File *vf,char *initial,
951		     long ibytes, ov_callbacks callbacks){
952  /*int offsettest=(f?callbacks.seek_func(f,0,SEEK_CUR):-1);*/
953  int offsettest=(f?TCL_SEEK(*f,0,SEEK_CUR):-1);
954  int ret;
955
956  memset(vf,0,sizeof(*vf)-28);
957  vf->datasource=*f;
958  vf->callbacks = callbacks;
959
960  /* init the framing state */
961  ogg_sync_init(&vf->oy);
962
963  /* perhaps some data was previously read into a buffer for testing
964     against other stream types.  Allow initialization from this
965     previously read data (as we may be reading from a non-seekable
966     stream) */
967  if(initial){
968    char *buffer=ogg_sync_buffer(&vf->oy,ibytes);
969    memcpy(buffer,initial,ibytes);
970    ogg_sync_wrote(&vf->oy,ibytes);
971  }
972
973  /* can we seek? Stevens suggests the seek test was portable */
974  if(offsettest!=-1)vf->seekable=1;
975
976  /* No seeking yet; Set up a 'single' (current) logical bitstream
977     entry for partial open */
978  vf->links=1;
979  vf->vi=_ogg_calloc(vf->links,sizeof(*vf->vi));
980  vf->vc=_ogg_calloc(vf->links,sizeof(*vf->vc));
981  ogg_stream_init(&vf->os,-1); /* fill in the serialno later */
982
983  /* Try to fetch the headers, maintaining all the storage */
984  if((ret=_fetch_headers(vf,vf->vi,vf->vc,&vf->current_serialno,NULL))<0){
985    vf->datasource=NULL;
986    /*    ov_clear(vf);*/
987    ov_clear(interp,vf);
988  }else if(vf->ready_state < PARTOPEN)
989    vf->ready_state=PARTOPEN;
990  return(ret);
991}
992
993static int _ov_open2(Tcl_Interp *interp,OggVorbis_File *vf){
994  if(vf->ready_state < OPENED)
995    vf->ready_state=OPENED;
996  if(vf->seekable){
997    int ret=_open_seekable2(vf);
998    if(ret){
999      vf->datasource=NULL;
1000      /*ov_clear(vf);*/
1001      ov_clear(interp,vf);
1002    }
1003    return(ret);
1004  }
1005  return 0;
1006}
1007
1008
1009/* clear out the OggVorbis_File struct */
1010/*int ov_clear(OggVorbis_File *vf){*/
1011int ov_clear(Tcl_Interp *interp,OggVorbis_File *vf){
1012  if(vf){
1013    vorbis_block_clear(&vf->vb);
1014    vorbis_dsp_clear(&vf->vd);
1015    ogg_stream_clear(&vf->os);
1016
1017    if(vf->vi && vf->links){
1018      int i;
1019      for(i=0;i<vf->links;i++){
1020	vorbis_info_clear(vf->vi+i);
1021	vorbis_comment_clear(vf->vc+i);
1022      }
1023      _ogg_free(vf->vi);
1024      _ogg_free(vf->vc);
1025    }
1026    if(vf->dataoffsets)_ogg_free(vf->dataoffsets);
1027    if(vf->pcmlengths)_ogg_free(vf->pcmlengths);
1028    if(vf->serialnos)_ogg_free(vf->serialnos);
1029    if(vf->offsets)_ogg_free(vf->offsets);
1030    ogg_sync_clear(&vf->oy);
1031    /*    if(vf->datasource)(vf->callbacks.close_func)(vf->datasource);*/
1032    if(vf->datasource)Tcl_Close(interp,vf->datasource);
1033    memset(vf,0,sizeof(*vf)-28);
1034  }
1035#ifdef DEBUG_LEAKS
1036  _VDBG_dump();
1037#endif
1038  return(0);
1039}
1040
1041/* inspects the OggVorbis file and finds/documents all the logical
1042   bitstreams contained in it.  Tries to be tolerant of logical
1043   bitstream sections that are truncated/woogie.
1044
1045   return: -1) error
1046            0) OK
1047*/
1048
1049/*int ov_open_callbacks(void *f,OggVorbis_File *vf,char *initial,long ibytes,
1050  ov_callbacks callbacks){*/
1051int ov_open_callbacks(Tcl_Interp *interp,Tcl_Channel *f,OggVorbis_File *vf,char *initial,long ibytes,
1052    ov_callbacks callbacks){
1053  int ret=_ov_open1(interp, f,vf,initial,ibytes,callbacks);
1054  if(ret)return ret;
1055  return _ov_open2(interp, vf);
1056}
1057
1058/*int ov_open(FILE *f,OggVorbis_File *vf,char *initial,long ibytes){*/
1059int ov_open(Tcl_Interp *interp,Tcl_Channel *f,OggVorbis_File *vf,char *initial,long ibytes){
1060  ov_callbacks callbacks = {
1061    (size_t (*)(void *, size_t, size_t, void *))  fread,
1062    (int (*)(void *, ogg_int64_t, int))              _fseek64_wrap,
1063    (int (*)(void *))                             fclose,
1064    (long (*)(void *))                            ftell
1065  };
1066
1067  return ov_open_callbacks(interp, (void *)f, vf, initial, ibytes, callbacks);
1068}
1069
1070/* Only partially open the vorbis file; test for Vorbisness, and load
1071   the headers for the first chain.  Do not seek (although test for
1072   seekability).  Use ov_test_open to finish opening the file, else
1073   ov_clear to close/free it. Same return codes as open. */
1074/*
1075int ov_test_callbacks(void *f,OggVorbis_File *vf,char *initial,long ibytes,
1076    ov_callbacks callbacks)
1077{
1078  return _ov_open1(f,vf,initial,ibytes,callbacks);
1079}
1080
1081int ov_test(FILE *f,OggVorbis_File *vf,char *initial,long ibytes){
1082  ov_callbacks callbacks = {
1083    (size_t (*)(void *, size_t, size_t, void *))  fread,
1084    (int (*)(void *, ogg_int64_t, int))              _fseek64_wrap,
1085    (int (*)(void *))                             fclose,
1086    (long (*)(void *))                            ftell
1087  };
1088
1089  return ov_test_callbacks((void *)f, vf, initial, ibytes, callbacks);
1090}
1091
1092int ov_test_open(OggVorbis_File *vf){
1093  if(vf->ready_state!=PARTOPEN)return(OV_EINVAL);
1094  return _ov_open2(vf);
1095}
1096*/
1097/* How many logical bitstreams in this physical bitstream? */
1098long ov_streams(OggVorbis_File *vf){
1099  return vf->links;
1100}
1101
1102/* Is the FILE * associated with vf seekable? */
1103long ov_seekable(OggVorbis_File *vf){
1104  return vf->seekable;
1105}
1106
1107/* returns the bitrate for a given logical bitstream or the entire
1108   physical bitstream.  If the file is open for random access, it will
1109   find the *actual* average bitrate.  If the file is streaming, it
1110   returns the nominal bitrate (if set) else the average of the
1111   upper/lower bounds (if set) else -1 (unset).
1112
1113   If you want the actual bitrate field settings, get them from the
1114   vorbis_info structs */
1115
1116long ov_bitrate(OggVorbis_File *vf,int i){
1117  if(vf->ready_state<OPENED)return(OV_EINVAL);
1118  if(i>=vf->links)return(OV_EINVAL);
1119  if(!vf->seekable && i!=0)return(ov_bitrate(vf,0));
1120  if(i<0){
1121    ogg_int64_t bits=0;
1122    int i;
1123    for(i=0;i<vf->links;i++)
1124      bits+=(vf->offsets[i+1]-vf->dataoffsets[i])*8;
1125    return(rint(bits/ov_time_total(vf,-1)));
1126  }else{
1127    if(vf->seekable){
1128      /* return the actual bitrate */
1129      return(rint((vf->offsets[i+1]-vf->dataoffsets[i])*8/ov_time_total(vf,i)));
1130    }else{
1131      /* return nominal if set */
1132      if(vf->vi[i].bitrate_nominal>0){
1133	return vf->vi[i].bitrate_nominal;
1134      }else{
1135	if(vf->vi[i].bitrate_upper>0){
1136	  if(vf->vi[i].bitrate_lower>0){
1137	    return (vf->vi[i].bitrate_upper+vf->vi[i].bitrate_lower)/2;
1138	  }else{
1139	    return vf->vi[i].bitrate_upper;
1140	  }
1141	}
1142	return(OV_FALSE);
1143      }
1144    }
1145  }
1146}
1147
1148/* returns the actual bitrate since last call.  returns -1 if no
1149   additional data to offer since last call (or at beginning of stream),
1150   EINVAL if stream is only partially open
1151*/
1152long ov_bitrate_instant(OggVorbis_File *vf){
1153  int link=(vf->seekable?vf->current_link:0);
1154  long ret;
1155  if(vf->ready_state<OPENED)return(OV_EINVAL);
1156  if(vf->samptrack==0)return(OV_FALSE);
1157  ret=vf->bittrack/vf->samptrack*vf->vi[link].rate+.5;
1158  vf->bittrack=0.f;
1159  vf->samptrack=0.f;
1160  return(ret);
1161}
1162
1163/* Guess */
1164long ov_serialnumber(OggVorbis_File *vf,int i){
1165  if(i>=vf->links)return(ov_serialnumber(vf,vf->links-1));
1166  if(!vf->seekable && i>=0)return(ov_serialnumber(vf,-1));
1167  if(i<0){
1168    return(vf->current_serialno);
1169  }else{
1170    return(vf->serialnos[i]);
1171  }
1172}
1173
1174/* returns: total raw (compressed) length of content if i==-1
1175            raw (compressed) length of that logical bitstream for i==0 to n
1176	    OV_EINVAL if the stream is not seekable (we can't know the length)
1177	    or if stream is only partially open
1178*/
1179ogg_int64_t ov_raw_total(OggVorbis_File *vf,int i){
1180  if(vf->ready_state<OPENED)return(OV_EINVAL);
1181  if(!vf->seekable || i>=vf->links)return(OV_EINVAL);
1182  if(i<0){
1183    ogg_int64_t acc=0;
1184    int i;
1185    for(i=0;i<vf->links;i++)
1186      acc+=ov_raw_total(vf,i);
1187    return(acc);
1188  }else{
1189    return(vf->offsets[i+1]-vf->offsets[i]);
1190  }
1191}
1192
1193/* returns: total PCM length (samples) of content if i==-1 PCM length
1194	    (samples) of that logical bitstream for i==0 to n
1195	    OV_EINVAL if the stream is not seekable (we can't know the
1196	    length) or only partially open
1197*/
1198ogg_int64_t ov_pcm_total(OggVorbis_File *vf,int i){
1199  if(vf->ready_state<OPENED)return(OV_EINVAL);
1200  if(!vf->seekable || i>=vf->links)return(OV_EINVAL);
1201  if(i<0){
1202    ogg_int64_t acc=0;
1203    int i;
1204    for(i=0;i<vf->links;i++)
1205      acc+=ov_pcm_total(vf,i);
1206    return(acc);
1207  }else{
1208    return(vf->pcmlengths[i*2+1]);
1209  }
1210}
1211
1212/* returns: total seconds of content if i==-1
1213            seconds in that logical bitstream for i==0 to n
1214	    OV_EINVAL if the stream is not seekable (we can't know the
1215	    length) or only partially open
1216*/
1217double ov_time_total(OggVorbis_File *vf,int i){
1218  if(vf->ready_state<OPENED)return(OV_EINVAL);
1219  if(!vf->seekable || i>=vf->links)return(OV_EINVAL);
1220  if(i<0){
1221    double acc=0;
1222    int i;
1223    for(i=0;i<vf->links;i++)
1224      acc+=ov_time_total(vf,i);
1225    return(acc);
1226  }else{
1227    return((double)(vf->pcmlengths[i*2+1])/vf->vi[i].rate);
1228  }
1229}
1230
1231/* seek to an offset relative to the *compressed* data. This also
1232   scans packets to update the PCM cursor. It will cross a logical
1233   bitstream boundary, but only if it can't get any packets out of the
1234   tail of the bitstream we seek to (so no surprises).
1235
1236   returns zero on success, nonzero on failure */
1237
1238int ov_raw_seek(OggVorbis_File *vf,ogg_int64_t pos){
1239  ogg_stream_state work_os;
1240
1241  if(vf->ready_state<OPENED)return(OV_EINVAL);
1242  if(!vf->seekable)
1243    return(OV_ENOSEEK); /* don't dump machine if we can't seek */
1244
1245  if(pos<0 || pos>vf->end)return(OV_EINVAL);
1246
1247  /* clear out decoding machine state */
1248  vf->pcm_offset=-1;
1249  _decode_clear(vf);
1250
1251  _seek_helper(vf,pos);
1252
1253  /* we need to make sure the pcm_offset is set, but we don't want to
1254     advance the raw cursor past good packets just to get to the first
1255     with a granulepos.  That's not equivalent behavior to beginning
1256     decoding as immediately after the seek position as possible.
1257
1258     So, a hack.  We use two stream states; a local scratch state and
1259     a the shared vf->os stream state.  We use the local state to
1260     scan, and the shared state as a buffer for later decode.
1261
1262     Unfortuantely, on the last page we still advance to last packet
1263     because the granulepos on the last page is not necessarily on a
1264     packet boundary, and we need to make sure the granpos is
1265     correct.
1266  */
1267
1268  {
1269    ogg_page og;
1270    ogg_packet op;
1271    int lastblock=0;
1272    int accblock=0;
1273    int thisblock;
1274    int eosflag;
1275
1276    ogg_stream_init(&work_os,-1); /* get the memory ready */
1277
1278    while(1){
1279      if(vf->ready_state==STREAMSET){
1280	/* snarf/scan a packet if we can */
1281	int result=ogg_stream_packetout(&work_os,&op);
1282
1283	if(result>0){
1284
1285	  if(vf->vi[vf->current_link].codec_setup)
1286	    thisblock=vorbis_packet_blocksize(vf->vi+vf->current_link,&op);
1287	  if(eosflag)
1288	    ogg_stream_packetout(&vf->os,NULL);
1289	  else
1290	    if(lastblock)accblock+=(lastblock+thisblock)>>2;
1291
1292	  if(op.granulepos!=-1){
1293	    int i,link=vf->current_link;
1294	    ogg_int64_t granulepos=op.granulepos-vf->pcmlengths[link*2];
1295	    if(granulepos<0)granulepos=0;
1296
1297	    for(i=0;i<link;i++)
1298	      granulepos+=vf->pcmlengths[i*2+1];
1299	    vf->pcm_offset=granulepos-accblock;
1300	    break;
1301	  }
1302	  lastblock=thisblock;
1303	  continue;
1304	}
1305      }
1306
1307      if(!lastblock){
1308	if(_get_next_page(vf,&og,-1)<0){
1309	  vf->pcm_offset=ov_pcm_total(vf,-1);
1310	  break;
1311	}
1312      }else{
1313	/* huh?  Bogus stream with packets but no granulepos */
1314	vf->pcm_offset=-1;
1315	break;
1316      }
1317
1318      /* has our decoding just traversed a bitstream boundary? */
1319      if(vf->ready_state==STREAMSET)
1320	if(vf->current_serialno!=ogg_page_serialno(&og)){
1321	_decode_clear(vf); /* clear out stream state */
1322	ogg_stream_clear(&work_os);
1323      }
1324
1325      if(vf->ready_state<STREAMSET){
1326	int link;
1327
1328	vf->current_serialno=ogg_page_serialno(&og);
1329	for(link=0;link<vf->links;link++)
1330	  if(vf->serialnos[link]==vf->current_serialno)break;
1331	if(link==vf->links)goto seek_error; /* sign of a bogus stream.
1332					       error out, leave
1333					       machine uninitialized */
1334	vf->current_link=link;
1335
1336	ogg_stream_reset_serialno(&vf->os,vf->current_serialno);
1337	ogg_stream_reset_serialno(&work_os,vf->current_serialno);
1338	vf->ready_state=STREAMSET;
1339
1340      }
1341
1342      ogg_stream_pagein(&vf->os,&og);
1343      ogg_stream_pagein(&work_os,&og);
1344      eosflag=ogg_page_eos(&og);
1345    }
1346  }
1347
1348  ogg_stream_clear(&work_os);
1349  return(0);
1350
1351 seek_error:
1352  /* dump the machine so we're in a known state */
1353  vf->pcm_offset=-1;
1354  ogg_stream_clear(&work_os);
1355  _decode_clear(vf);
1356  return OV_EBADLINK;
1357}
1358
1359/* Page granularity seek (faster than sample granularity because we
1360   don't do the last bit of decode to find a specific sample).
1361
1362   Seek to the last [granule marked] page preceeding the specified pos
1363   location, such that decoding past the returned point will quickly
1364   arrive at the requested position. */
1365int ov_pcm_seek_page(OggVorbis_File *vf,ogg_int64_t pos){
1366  int link=-1;
1367  ogg_int64_t result=0;
1368  ogg_int64_t total=ov_pcm_total(vf,-1);
1369
1370  if(vf->ready_state<OPENED)return(OV_EINVAL);
1371  if(!vf->seekable)return(OV_ENOSEEK);
1372
1373  if(pos<0 || pos>total)return(OV_EINVAL);
1374
1375  /* which bitstream section does this pcm offset occur in? */
1376  for(link=vf->links-1;link>=0;link--){
1377    total-=vf->pcmlengths[link*2+1];
1378    if(pos>=total)break;
1379  }
1380
1381  /* search within the logical bitstream for the page with the highest
1382     pcm_pos preceeding (or equal to) pos.  There is a danger here;
1383     missing pages or incorrect frame number information in the
1384     bitstream could make our task impossible.  Account for that (it
1385     would be an error condition) */
1386
1387  /* new search algorithm by HB (Nicholas Vinen) */
1388  {
1389    ogg_int64_t end=vf->offsets[link+1];
1390    ogg_int64_t begin=vf->offsets[link];
1391    ogg_int64_t begintime = vf->pcmlengths[link*2];
1392    ogg_int64_t endtime = vf->pcmlengths[link*2+1]+begintime;
1393    ogg_int64_t target=pos-total+begintime;
1394    ogg_int64_t best=begin;
1395
1396    ogg_page og;
1397    while(begin<end){
1398      ogg_int64_t bisect;
1399
1400      if(end-begin<CHUNKSIZE){
1401	bisect=begin;
1402      }else{
1403	/* take a (pretty decent) guess. */
1404	bisect=begin +
1405	  (target-begintime)*(end-begin)/(endtime-begintime) - CHUNKSIZE;
1406	if(bisect<=begin)
1407	  bisect=begin+1;
1408      }
1409
1410      _seek_helper(vf,bisect);
1411
1412      while(begin<end){
1413	result=_get_next_page(vf,&og,end-vf->offset);
1414	if(result==OV_EREAD) goto seek_error;
1415	if(result<0){
1416	  if(bisect<=begin+1)
1417	    end=begin; /* found it */
1418	  else{
1419	    if(bisect==0) goto seek_error;
1420	    bisect-=CHUNKSIZE;
1421	    if(bisect<=begin)bisect=begin+1;
1422	    _seek_helper(vf,bisect);
1423	  }
1424	}else{
1425	  ogg_int64_t granulepos=ogg_page_granulepos(&og);
1426	  if(granulepos==-1)continue;
1427	  if(granulepos<target){
1428	    best=result;  /* raw offset of packet with granulepos */
1429	    begin=vf->offset; /* raw offset of next page */
1430	    begintime=granulepos;
1431
1432	    if(target-begintime>44100)break;
1433	    bisect=begin; /* *not* begin + 1 */
1434	  }else{
1435	    if(bisect<=begin+1)
1436	      end=begin;  /* found it */
1437	    else{
1438	      if(end==vf->offset){ /* we're pretty close - we'd be stuck in */
1439		end=result;
1440		bisect-=CHUNKSIZE; /* an endless loop otherwise. */
1441		if(bisect<=begin)bisect=begin+1;
1442		_seek_helper(vf,bisect);
1443	      }else{
1444		end=result;
1445		endtime=granulepos;
1446		break;
1447	      }
1448	    }
1449	  }
1450	}
1451      }
1452    }
1453
1454    /* found our page. seek to it, update pcm offset. Easier case than
1455       raw_seek, don't keep packets preceeding granulepos. */
1456    {
1457      ogg_page og;
1458      ogg_packet op;
1459      /* clear out decoding machine state */
1460      _decode_clear(vf);
1461      /* seek */
1462      _seek_helper(vf,best);
1463
1464      if(_get_next_page(vf,&og,-1)<0)return(OV_EOF); /* shouldn't happen */
1465      vf->current_serialno=ogg_page_serialno(&og);
1466      vf->current_link=link;
1467
1468      ogg_stream_reset_serialno(&vf->os,vf->current_serialno);
1469      vf->ready_state=STREAMSET;
1470      ogg_stream_pagein(&vf->os,&og);
1471
1472      /* pull out all but last packet; the one with granulepos */
1473      while(1){
1474	result=ogg_stream_packetpeek(&vf->os,&op);
1475	if(result==0){
1476	  /* !!! the packet finishing this page originated on a
1477             preceeding page. Keep fetching previous pages until we
1478             get one with a granulepos or without the 'continued' flag
1479             set.  Then just use raw_seek for simplicity. */
1480
1481	  _decode_clear(vf);
1482	  _seek_helper(vf,best);
1483
1484	  while(1){
1485	    result=_get_prev_page(vf,&og);
1486	    if(result<0) goto seek_error;
1487	    if(ogg_page_granulepos(&og)>-1 ||
1488	       !ogg_page_continued(&og)){
1489	      return ov_raw_seek(vf,result);
1490	    }
1491	    vf->offset=result;
1492	  }
1493	}
1494	if(result<0){
1495      result = OV_EBADPACKET;
1496      goto seek_error;
1497    }
1498	if(op.granulepos!=-1){
1499	  vf->pcm_offset=op.granulepos-vf->pcmlengths[vf->current_link*2];
1500	  if(vf->pcm_offset<0)vf->pcm_offset=0;
1501	  vf->pcm_offset+=total;
1502	  break;
1503	}else
1504	  result=ogg_stream_packetout(&vf->os,NULL);
1505      }
1506    }
1507  }
1508
1509  /* verify result */
1510  if(vf->pcm_offset>pos || pos>ov_pcm_total(vf,-1)){
1511    result=OV_EFAULT;
1512    goto seek_error;
1513  }
1514  return(0);
1515
1516 seek_error:
1517  /* dump machine so we're in a known state */
1518  vf->pcm_offset=-1;
1519  _decode_clear(vf);
1520  return (int)result;
1521}
1522
1523/* seek to a sample offset relative to the decompressed pcm stream
1524   returns zero on success, nonzero on failure */
1525
1526int ov_pcm_seek(OggVorbis_File *vf,ogg_int64_t pos){
1527  int thisblock,lastblock=0;
1528  int ret=ov_pcm_seek_page(vf,pos);
1529  if(ret<0)return(ret);
1530  _make_decode_ready(vf);
1531
1532  /* discard leading packets we don't need for the lapping of the
1533     position we want; don't decode them */
1534
1535  while(1){
1536    ogg_packet op;
1537    ogg_page og;
1538
1539    int ret=ogg_stream_packetpeek(&vf->os,&op);
1540    if(ret>0){
1541      thisblock=vorbis_packet_blocksize(vf->vi+vf->current_link,&op);
1542      if(thisblock<0)thisblock=0; /* non audio packet */
1543      if(lastblock)vf->pcm_offset+=(lastblock+thisblock)>>2;
1544
1545      if(vf->pcm_offset+((thisblock+
1546			  vorbis_info_blocksize(vf->vi,1))>>2)>=pos)break;
1547
1548      /* remove the packet from packet queue and track its granulepos */
1549      ogg_stream_packetout(&vf->os,NULL);
1550      vorbis_synthesis_trackonly(&vf->vb,&op);  /* set up a vb with
1551                                                   only tracking, no
1552                                                   pcm_decode */
1553      vorbis_synthesis_blockin(&vf->vd,&vf->vb);
1554
1555      /* end of logical stream case is hard, especially with exact
1556	 length positioning. */
1557
1558      if(op.granulepos>-1){
1559	int i;
1560	/* always believe the stream markers */
1561	vf->pcm_offset=op.granulepos-vf->pcmlengths[vf->current_link*2];
1562	if(vf->pcm_offset<0)vf->pcm_offset=0;
1563	for(i=0;i<vf->current_link;i++)
1564	  vf->pcm_offset+=vf->pcmlengths[i*2+1];
1565      }
1566
1567      lastblock=thisblock;
1568
1569    }else{
1570      if(ret<0 && ret!=OV_HOLE)break;
1571
1572      /* suck in a new page */
1573      if(_get_next_page(vf,&og,-1)<0)break;
1574      if(vf->current_serialno!=ogg_page_serialno(&og))_decode_clear(vf);
1575
1576      if(vf->ready_state<STREAMSET){
1577	int link;
1578
1579	vf->current_serialno=ogg_page_serialno(&og);
1580	for(link=0;link<vf->links;link++)
1581	  if(vf->serialnos[link]==vf->current_serialno)break;
1582	if(link==vf->links)return(OV_EBADLINK);
1583	vf->current_link=link;
1584
1585	ogg_stream_reset_serialno(&vf->os,vf->current_serialno);
1586	vf->ready_state=STREAMSET;
1587	_make_decode_ready(vf);
1588	lastblock=0;
1589      }
1590
1591      ogg_stream_pagein(&vf->os,&og);
1592    }
1593  }
1594
1595  /* discard samples until we reach the desired position. Crossing a
1596     logical bitstream boundary with abandon is OK. */
1597  while(vf->pcm_offset<pos){
1598    float **pcm;
1599    ogg_int64_t target=pos-vf->pcm_offset;
1600    long samples=vorbis_synthesis_pcmout(&vf->vd,&pcm);
1601
1602    if(samples>target)samples=target;
1603    vorbis_synthesis_read(&vf->vd,samples);
1604    vf->pcm_offset+=samples;
1605
1606    if(samples<target)
1607      if(_fetch_and_process_packet(vf,NULL,1)<=0)
1608	vf->pcm_offset=ov_pcm_total(vf,-1); /* eof */
1609  }
1610  return 0;
1611}
1612
1613/* seek to a playback time relative to the decompressed pcm stream
1614   returns zero on success, nonzero on failure */
1615int ov_time_seek(OggVorbis_File *vf,double seconds){
1616  /* translate time to PCM position and call ov_pcm_seek */
1617
1618  int link=-1;
1619  ogg_int64_t pcm_total=ov_pcm_total(vf,-1);
1620  double time_total=ov_time_total(vf,-1);
1621
1622  if(vf->ready_state<OPENED)return(OV_EINVAL);
1623  if(!vf->seekable)return(OV_ENOSEEK);
1624  if(seconds<0 || seconds>time_total)return(OV_EINVAL);
1625
1626  /* which bitstream section does this time offset occur in? */
1627  for(link=vf->links-1;link>=0;link--){
1628    pcm_total-=vf->pcmlengths[link*2+1];
1629    time_total-=ov_time_total(vf,link);
1630    if(seconds>=time_total)break;
1631  }
1632
1633  /* enough information to convert time offset to pcm offset */
1634  {
1635    ogg_int64_t target=pcm_total+(seconds-time_total)*vf->vi[link].rate;
1636    return(ov_pcm_seek(vf,target));
1637  }
1638}
1639
1640/* page-granularity version of ov_time_seek
1641   returns zero on success, nonzero on failure */
1642int ov_time_seek_page(OggVorbis_File *vf,double seconds){
1643  /* translate time to PCM position and call ov_pcm_seek */
1644
1645  int link=-1;
1646  ogg_int64_t pcm_total=ov_pcm_total(vf,-1);
1647  double time_total=ov_time_total(vf,-1);
1648
1649  if(vf->ready_state<OPENED)return(OV_EINVAL);
1650  if(!vf->seekable)return(OV_ENOSEEK);
1651  if(seconds<0 || seconds>time_total)return(OV_EINVAL);
1652
1653  /* which bitstream section does this time offset occur in? */
1654  for(link=vf->links-1;link>=0;link--){
1655    pcm_total-=vf->pcmlengths[link*2+1];
1656    time_total-=ov_time_total(vf,link);
1657    if(seconds>=time_total)break;
1658  }
1659
1660  /* enough information to convert time offset to pcm offset */
1661  {
1662    ogg_int64_t target=pcm_total+(seconds-time_total)*vf->vi[link].rate;
1663    return(ov_pcm_seek_page(vf,target));
1664  }
1665}
1666
1667/* tell the current stream offset cursor.  Note that seek followed by
1668   tell will likely not give the set offset due to caching */
1669ogg_int64_t ov_raw_tell(OggVorbis_File *vf){
1670  if(vf->ready_state<OPENED)return(OV_EINVAL);
1671  return(vf->offset);
1672}
1673
1674/* return PCM offset (sample) of next PCM sample to be read */
1675ogg_int64_t ov_pcm_tell(OggVorbis_File *vf){
1676  if(vf->ready_state<OPENED)return(OV_EINVAL);
1677  return(vf->pcm_offset);
1678}
1679
1680/* return time offset (seconds) of next PCM sample to be read */
1681double ov_time_tell(OggVorbis_File *vf){
1682  /* translate time to PCM position and call ov_pcm_seek */
1683
1684  int link=-1;
1685  ogg_int64_t pcm_total=0;
1686  double time_total=0.f;
1687
1688  if(vf->ready_state<OPENED)return(OV_EINVAL);
1689  if(vf->seekable){
1690    pcm_total=ov_pcm_total(vf,-1);
1691    time_total=ov_time_total(vf,-1);
1692
1693    /* which bitstream section does this time offset occur in? */
1694    for(link=vf->links-1;link>=0;link--){
1695      pcm_total-=vf->pcmlengths[link*2+1];
1696      time_total-=ov_time_total(vf,link);
1697      if(vf->pcm_offset>=pcm_total)break;
1698    }
1699  }
1700
1701  return((double)time_total+(double)(vf->pcm_offset-pcm_total)/vf->vi[link].rate);
1702}
1703
1704/*  link:   -1) return the vorbis_info struct for the bitstream section
1705                currently being decoded
1706           0-n) to request information for a specific bitstream section
1707
1708    In the case of a non-seekable bitstream, any call returns the
1709    current bitstream.  NULL in the case that the machine is not
1710    initialized */
1711
1712vorbis_info *ov_info(OggVorbis_File *vf,int link){
1713  if(vf->seekable){
1714    if(link<0)
1715      if(vf->ready_state>=STREAMSET)
1716	return vf->vi+vf->current_link;
1717      else
1718      return vf->vi;
1719    else
1720      if(link>=vf->links)
1721	return NULL;
1722      else
1723	return vf->vi+link;
1724  }else{
1725    return vf->vi;
1726  }
1727}
1728
1729/* grr, strong typing, grr, no templates/inheritence, grr */
1730vorbis_comment *ov_comment(OggVorbis_File *vf,int link){
1731  if(vf->seekable){
1732    if(link<0)
1733      if(vf->ready_state>=STREAMSET)
1734	return vf->vc+vf->current_link;
1735      else
1736	return vf->vc;
1737    else
1738      if(link>=vf->links)
1739	return NULL;
1740      else
1741	return vf->vc+link;
1742  }else{
1743    return vf->vc;
1744  }
1745}
1746
1747static int host_is_big_endian() {
1748  ogg_int32_t pattern = 0xfeedface; /* deadbeef */
1749  unsigned char *bytewise = (unsigned char *)&pattern;
1750  if (bytewise[0] == 0xfe) return 1;
1751  return 0;
1752}
1753
1754/* up to this point, everything could more or less hide the multiple
1755   logical bitstream nature of chaining from the toplevel application
1756   if the toplevel application didn't particularly care.  However, at
1757   the point that we actually read audio back, the multiple-section
1758   nature must surface: Multiple bitstream sections do not necessarily
1759   have to have the same number of channels or sampling rate.
1760
1761   ov_read returns the sequential logical bitstream number currently
1762   being decoded along with the PCM data in order that the toplevel
1763   application can take action on channel/sample rate changes.  This
1764   number will be incremented even for streamed (non-seekable) streams
1765   (for seekable streams, it represents the actual logical bitstream
1766   index within the physical bitstream.  Note that the accessor
1767   functions above are aware of this dichotomy).
1768
1769   input values: buffer) a buffer to hold packed PCM data for return
1770		 length) the byte length requested to be placed into buffer
1771		 bigendianp) should the data be packed LSB first (0) or
1772		             MSB first (1)
1773		 word) word size for output.  currently 1 (byte) or
1774		       2 (16 bit short)
1775
1776   return values: <0) error/hole in data (OV_HOLE), partial open (OV_EINVAL)
1777                   0) EOF
1778		   n) number of bytes of PCM actually returned.  The
1779		   below works on a packet-by-packet basis, so the
1780		   return length is not related to the 'length' passed
1781		   in, just guaranteed to fit.
1782
1783	    *section) set to the logical bitstream number */
1784
1785long ov_read(OggVorbis_File *vf,char *buffer,int length,
1786		    int bigendianp,int word,int sgned,int *bitstream){
1787  int i,j;
1788  int host_endian = host_is_big_endian();
1789
1790  float **pcm;
1791  long samples;
1792
1793  if(vf->ready_state<OPENED)return(OV_EINVAL);
1794
1795  while(1){
1796    if(vf->ready_state>=STREAMSET){
1797      samples=vorbis_synthesis_pcmout(&vf->vd,&pcm);
1798      if(samples)break;
1799    }
1800
1801    /* suck in another packet */
1802    {
1803      int ret=_fetch_and_process_packet(vf,NULL,1);
1804      if(ret==OV_EOF)return(0);
1805      if(ret<=0)return(ret);
1806    }
1807
1808  }
1809
1810  if(samples>0){
1811
1812    /* yay! proceed to pack data into the byte buffer */
1813
1814    long channels=ov_info(vf,-1)->channels;
1815    long bytespersample=word * channels;
1816    vorbis_fpu_control fpu;
1817    if(samples>length/bytespersample)samples=length/bytespersample;
1818
1819    if(samples <= 0)
1820      return OV_EINVAL;
1821
1822    /* a tight loop to pack each size */
1823    {
1824      int val;
1825      if(word==1){
1826	int off=(sgned?0:128);
1827	vorbis_fpu_setround(&fpu);
1828	for(j=0;j<samples;j++)
1829	  for(i=0;i<channels;i++){
1830	    val=vorbis_ftoi(pcm[i][j]*128.f);
1831	    if(val>127)val=127;
1832	    else if(val<-128)val=-128;
1833	    *buffer++=val+off;
1834	  }
1835	vorbis_fpu_restore(fpu);
1836      }else{
1837	int off=(sgned?0:32768);
1838
1839	if(host_endian==bigendianp){
1840	  if(sgned){
1841
1842	    vorbis_fpu_setround(&fpu);
1843	    for(i=0;i<channels;i++) { /* It's faster in this order */
1844	      float *src=pcm[i];
1845	      short *dest=((short *)buffer)+i;
1846	      for(j=0;j<samples;j++) {
1847		val=vorbis_ftoi(src[j]*32768.f);
1848		if(val>32767)val=32767;
1849		else if(val<-32768)val=-32768;
1850		*dest=val;
1851		dest+=channels;
1852	      }
1853	    }
1854	    vorbis_fpu_restore(fpu);
1855
1856	  }else{
1857
1858	    vorbis_fpu_setround(&fpu);
1859	    for(i=0;i<channels;i++) {
1860	      float *src=pcm[i];
1861	      short *dest=((short *)buffer)+i;
1862	      for(j=0;j<samples;j++) {
1863		val=vorbis_ftoi(src[j]*32768.f);
1864		if(val>32767)val=32767;
1865		else if(val<-32768)val=-32768;
1866		*dest=val+off;
1867		dest+=channels;
1868	      }
1869	    }
1870	    vorbis_fpu_restore(fpu);
1871
1872	  }
1873	}else if(bigendianp){
1874
1875	  vorbis_fpu_setround(&fpu);
1876	  for(j=0;j<samples;j++)
1877	    for(i=0;i<channels;i++){
1878	      val=vorbis_ftoi(pcm[i][j]*32768.f);
1879	      if(val>32767)val=32767;
1880	      else if(val<-32768)val=-32768;
1881	      val+=off;
1882	      *buffer++=(val>>8);
1883	      *buffer++=(val&0xff);
1884	    }
1885	  vorbis_fpu_restore(fpu);
1886
1887	}else{
1888	  int val;
1889	  vorbis_fpu_setround(&fpu);
1890	  for(j=0;j<samples;j++)
1891	    for(i=0;i<channels;i++){
1892	      val=vorbis_ftoi(pcm[i][j]*32768.f);
1893	      if(val>32767)val=32767;
1894	      else if(val<-32768)val=-32768;
1895	      val+=off;
1896	      *buffer++=(val&0xff);
1897	      *buffer++=(val>>8);
1898	  	}
1899	  vorbis_fpu_restore(fpu);
1900
1901	}
1902      }
1903    }
1904
1905    vorbis_synthesis_read(&vf->vd,samples);
1906    vf->pcm_offset+=samples;
1907    if(bitstream)*bitstream=vf->current_link;
1908    return(samples*bytespersample);
1909  }else{
1910    return(samples);
1911  }
1912}
1913
1914/* input values: pcm_channels) a float vector per channel of output
1915		 length) the sample length being read by the app
1916
1917   return values: <0) error/hole in data (OV_HOLE), partial open (OV_EINVAL)
1918                   0) EOF
1919		   n) number of samples of PCM actually returned.  The
1920		   below works on a packet-by-packet basis, so the
1921		   return length is not related to the 'length' passed
1922		   in, just guaranteed to fit.
1923
1924	    *section) set to the logical bitstream number */
1925
1926
1927
1928long ov_read_float(OggVorbis_File *vf,float ***pcm_channels,int length,
1929		   int *bitstream){
1930
1931  if(vf->ready_state<OPENED)return(OV_EINVAL);
1932
1933  while(1){
1934    if(vf->ready_state>=STREAMSET){
1935      float **pcm;
1936      long samples=vorbis_synthesis_pcmout(&vf->vd,&pcm);
1937      if(samples){
1938	if(pcm_channels)*pcm_channels=pcm;
1939	if(samples>length)samples=length;
1940	vorbis_synthesis_read(&vf->vd,samples);
1941	vf->pcm_offset+=samples;
1942	if(bitstream)*bitstream=vf->current_link;
1943	return samples;
1944
1945      }
1946    }
1947
1948    /* suck in another packet */
1949    {
1950      int ret=_fetch_and_process_packet(vf,NULL,1);
1951      if(ret==OV_EOF)return(0);
1952      if(ret<=0)return(ret);
1953    }
1954
1955  }
1956}
1957
1958/* end vorbisfile.c */
1959
1960
1961#define OGG_PATTERN "OggS"
1962#define OGG_STRING "OGG"
1963
1964static char *
1965GuessOggFile(char *buf, int len)
1966{
1967  if (len < (int) strlen(OGG_PATTERN)) return(QUE_STRING);
1968  if (strncasecmp(OGG_PATTERN, buf, strlen(OGG_PATTERN)) == 0) {
1969    return(OGG_STRING);
1970  }
1971  return(NULL);
1972}
1973
1974char *
1975ExtOggFile(char *s)
1976{
1977  int l1 = strlen(".ogg");
1978  int l2 = strlen(s);
1979
1980  if (strncasecmp(".ogg", &s[l2 - l1], l1) == 0) {
1981    return(OGG_STRING);
1982  }
1983  return(NULL);
1984}
1985
1986static int started = 0;
1987
1988#define READBUFSIZE 1024
1989
1990static ogg_stream_state os; /* take physical pages, weld into a logical
1991	  		       stream of packets */
1992static ogg_page         og; /* one Ogg bitstream page. Vorbis packets are
1993			       inside */
1994static ogg_packet       op; /* one raw packet of data for decode */
1995
1996static vorbis_info      vi; /* struct that stores all the static vorbis
1997			       bitstream settings */
1998static vorbis_comment   vc; /* struct that stores all the user comments */
1999
2000static vorbis_dsp_state vd; /* central working state for the
2001			       packet->PCM decoder */
2002static vorbis_block     vb; /* local working space for packet->PCM decode */
2003
2004#define SNACK_OGG_INT 19
2005
2006static int
2007OpenOggFile(Sound *s, Tcl_Interp *interp, Tcl_Channel *ch, char *mode)
2008{
2009  if (s->debug > 2) Snack_WriteLog("    Enter OpenOggFile\n");
2010
2011  if ((*ch = Tcl_OpenFileChannel(interp, s->fcname, mode, 420)) == 0) {
2012    return TCL_ERROR;
2013  }
2014  if (*ch == NULL) {
2015    Tcl_AppendResult(interp, "Ogg: unable to open file: ",
2016		     Snack_GetSoundFilename(s), NULL);
2017    return TCL_ERROR;
2018  }
2019  Tcl_SetChannelOption(interp, *ch, "-translation", "binary");
2020#ifdef TCL_81_API
2021  Tcl_SetChannelOption(interp, *ch, "-encoding", "binary");
2022#endif
2023
2024  if (s->extHead2 != NULL && s->extHead2Type != SNACK_OGG_INT) {
2025    Snack_FileFormat *ff;
2026
2027    for (ff = Snack_GetFileFormats(); ff != NULL; ff = ff->nextPtr) {
2028      if (strcmp(s->fileType, ff->name) == 0) {
2029	if (ff->freeHeaderProc != NULL) {
2030	  (ff->freeHeaderProc)(s);
2031	}
2032      }
2033    }
2034  }
2035
2036  if (s->extHead2 == NULL) {
2037    s->extHead2 = (char *) ckalloc(sizeof(OggVorbis_File));
2038    s->extHead2Type = SNACK_OGG_INT;
2039    ((OggVorbis_File *)s->extHead2)->nombitrate = 128000;
2040    ((OggVorbis_File *)s->extHead2)->maxbitrate = -1;
2041    ((OggVorbis_File *)s->extHead2)->minbitrate = -1;
2042    ((OggVorbis_File *)s->extHead2)->quality = -1.0;
2043    ((OggVorbis_File *)s->extHead2)->commList = NULL;
2044    ((OggVorbis_File *)s->extHead2)->vendor = NULL;
2045  }
2046
2047  if (strcmp(mode,"r") == 0) {
2048    if(ov_open(interp, ch, (OggVorbis_File *)s->extHead2, NULL, 0) < 0) {
2049      Tcl_AppendResult(interp, "Input does not appear to be an Ogg bitstream",
2050		       NULL);
2051      return TCL_ERROR;
2052    }
2053  }
2054
2055  if (s->debug > 2) Snack_WriteLog("    Exit OpenOggFile\n");
2056
2057  return TCL_OK;
2058}
2059
2060static int
2061CloseOggFile(Sound *s, Tcl_Interp *interp, Tcl_Channel *ch)
2062{
2063  if (s->debug > 2) Snack_WriteLog("    Enter CloseOggFile\n");
2064
2065  if (started == 0) {
2066    ov_clear(interp, (OggVorbis_File *)s->extHead2);
2067    *ch = NULL;
2068  } else {
2069
2070    /* Tell the library we're at end of stream */
2071    vorbis_analysis_wrote(&vd, 0);
2072
2073    while (vorbis_analysis_blockout(&vd, &vb) == 1) {
2074      vorbis_analysis(&vb,&op);
2075      ogg_stream_packetin(&os,&op);
2076
2077      while (1) {
2078	int result = ogg_stream_pageout(&os, &og);
2079	if (result == 0) break;
2080	if (Tcl_Write(*ch, (char *) og.header, og.header_len) == -1)
2081	  return TCL_ERROR;
2082	if (Tcl_Write(*ch, (char *) og.body, og.body_len) == -1)
2083	  return TCL_ERROR;
2084
2085	if (ogg_page_eos(&og)) break;
2086      }
2087    }
2088
2089    /* clean up, vorbis_info_clear() must be called last */
2090
2091    ogg_stream_clear(&os);
2092    vorbis_block_clear(&vb);
2093    vorbis_dsp_clear(&vd);
2094    vorbis_comment_clear(&vc);
2095    vorbis_info_clear(&vi);
2096
2097    if (ch != NULL) {
2098      Tcl_Close(interp, *ch);
2099    }
2100    started = 0;
2101  }
2102
2103  if (s->debug > 2) Snack_WriteLog("    Exit CloseOggFile\n");
2104
2105  return TCL_OK;
2106}
2107
2108float pcmout[READBUFSIZE];
2109
2110static int
2111ReadOggSamples(Sound *s, Tcl_Interp *interp, Tcl_Channel ch, char *ibuf,
2112		  float *obuf, int len)
2113{
2114  int nread = 0, bigendian = Snack_PlatformIsLittleEndian() ? 0 : 1;
2115  float *f  = obuf;
2116  int n, i, dummy;
2117
2118  if (s->debug > 2) Snack_WriteLog("    Enter ReadOggSamples\n");
2119
2120  while (nread < len) {
2121    int size = min(sizeof(pcmout), (len - nread) * s->sampsize);
2122    n = ov_read((OggVorbis_File *)s->extHead2, (char *)pcmout, size,
2123		bigendian, 2, 1, &dummy);
2124    if (n < 0) {
2125      return -1;
2126    } else if (n == 0) {
2127      return(nread);
2128    } else {
2129      short *r = (short *) pcmout;
2130      for (i = 0; i < n / s->sampsize; i++) {
2131	*f++ = (float) *r++;
2132      }
2133      nread += (n / s->sampsize);
2134    }
2135  }
2136
2137  if (s->debug > 2) Snack_WriteLogInt("    Exit ReadOggSamples", nread);
2138
2139  return(nread);
2140}
2141
2142static int
2143SeekOggFile(Sound *s, Tcl_Interp *interp, Tcl_Channel ch, int pos)
2144{
2145  if (pos == 0) return 0; /* ov_time_seek() does not like seeking to 0 */
2146
2147  if (ov_pcm_seek((OggVorbis_File *)s->extHead2, (ogg_int64_t) pos)) {
2148    return(-1);
2149  } else {
2150    return(pos);
2151  }
2152}
2153
2154static int
2155GetOggHeader(Sound *s, Tcl_Interp *interp, Tcl_Channel ch, Tcl_Obj *obj,
2156	     char *buf)
2157{
2158  int i;
2159  vorbis_info *vi;
2160  vorbis_comment *vc;
2161
2162  if (s->debug > 2) Snack_WriteLog("    Enter GetOggHeader\n");
2163
2164  /* For the case when Tcl_Open has been done somewhere else */
2165
2166  if (s->extHead2 != NULL && s->extHead2Type != SNACK_OGG_INT) {
2167    Snack_FileFormat *ff;
2168
2169    for (ff = Snack_GetFileFormats(); ff != NULL; ff = ff->nextPtr) {
2170      if (strcmp(s->fileType, ff->name) == 0) {
2171	if (ff->freeHeaderProc != NULL) {
2172	  (ff->freeHeaderProc)(s);
2173	}
2174      }
2175    }
2176  }
2177
2178  if (s->extHead2 == NULL) {
2179    s->extHead2 = (char *) ckalloc(sizeof(OggVorbis_File));
2180    s->extHead2Type = SNACK_OGG_INT;
2181    ((OggVorbis_File *)s->extHead2)->maxbitrate = -1;
2182    ((OggVorbis_File *)s->extHead2)->minbitrate = -1;
2183    ((OggVorbis_File *)s->extHead2)->quality = -1.0;
2184
2185    if (ov_open(interp, &s->rwchan, (OggVorbis_File *)s->extHead2,
2186		(char *)s->tmpbuf, s->firstNRead) < 0) {
2187      Tcl_AppendResult(interp, "Input does not appear to be an Ogg bitstream",
2188		       NULL);
2189      return TCL_ERROR;
2190    }
2191  }
2192
2193  vi = ov_info((OggVorbis_File *)s->extHead2,-1);
2194
2195  Snack_SetSampleRate(s, vi->rate);
2196  Snack_SetNumChannels(s, vi->channels);
2197  Snack_SetSampleEncoding(s, LIN16);
2198  Snack_SetBytesPerSample(s, 2);
2199  Snack_SetHeaderSize(s, 0);
2200  Snack_SetLength(s, (long)ov_pcm_total((OggVorbis_File *)s->extHead2, -1));
2201  ((OggVorbis_File *)s->extHead2)->nombitrate =
2202    ov_bitrate((OggVorbis_File *)s->extHead2, -1);
2203  vc = ov_comment((OggVorbis_File *)s->extHead2, -1);
2204  ((OggVorbis_File *)s->extHead2)->commList = Tcl_NewListObj(0, NULL);
2205  Tcl_IncrRefCount(((OggVorbis_File *)s->extHead2)->commList);
2206  for (i = 0; i < vc->comments; i++) {
2207    Tcl_Obj *newObj = Tcl_NewStringObj(vc->user_comments[i], -1);
2208    Tcl_IncrRefCount(newObj);
2209    Tcl_ListObjAppendElement(interp, ((OggVorbis_File *)s->extHead2)->commList,
2210			     newObj);
2211  }
2212  ((OggVorbis_File *)s->extHead2)->vendor = Tcl_NewStringObj(vc->vendor, -1);
2213
2214  if (s->debug > 2) Snack_WriteLog("    Exit GetOggHeader\n");
2215
2216  return TCL_OK;
2217}
2218
2219static int
2220PutOggHeader(Sound *s, Tcl_Interp *interp, Tcl_Channel ch, Tcl_Obj *obj,
2221	     int objc, Tcl_Obj *CONST objv[], int len)
2222{
2223  int arg, n = 0, ret;
2224  OggVorbis_File *of = (OggVorbis_File *)s->extHead2;
2225  Tcl_Obj **listObj;
2226  static char *subOptionStrings[] = {
2227    "-comment", "-maxbitrate", "-minbitrate", "-nominalbitrate",
2228    "-quality", NULL
2229  };
2230  enum subOptions {
2231    COMMENT, MAX, MIN, NOMINAL, QUALITY
2232  };
2233
2234  if (s->debug > 2) Snack_WriteLog("    Enter PutOggHeader\n");
2235
2236  for (arg = 0; arg < objc; arg+=2) {
2237    int index;
2238
2239    if (Tcl_GetIndexFromObj(interp, objv[arg], subOptionStrings,
2240			    "option", 0, &index) != TCL_OK) {
2241      return TCL_ERROR;
2242    }
2243
2244    if (arg + 1 == objc) {
2245      Tcl_AppendResult(interp, "No argument given for ",
2246		       subOptionStrings[index], " option", (char *) NULL);
2247      return TCL_ERROR;
2248    }
2249
2250    switch ((enum subOptions) index) {
2251    case COMMENT:
2252      {
2253	if (Tcl_ListObjGetElements(interp, objv[arg+1], &n, &listObj) !=
2254	    TCL_OK) {
2255	  return TCL_ERROR;
2256	}
2257	break;
2258      }
2259    case MAX:
2260      {
2261	if (Tcl_GetIntFromObj(interp, objv[arg+1], &of->maxbitrate) != TCL_OK)
2262	  return TCL_ERROR;
2263	break;
2264      }
2265    case MIN:
2266      {
2267	if (Tcl_GetIntFromObj(interp, objv[arg+1], &of->minbitrate) != TCL_OK)
2268	  return TCL_ERROR;
2269	break;
2270      }
2271    case NOMINAL:
2272      {
2273	if (Tcl_GetIntFromObj(interp, objv[arg+1], &of->nombitrate) != TCL_OK)
2274	  return TCL_ERROR;
2275	break;
2276      }
2277    case QUALITY:
2278      {
2279	if (Tcl_GetDoubleFromObj(interp, objv[arg+1], &of->quality) != TCL_OK)
2280	  return TCL_ERROR;
2281	break;
2282      }
2283    }
2284  }
2285
2286  /* For the case when Tcl_Open has been done somewhere else */
2287
2288  if (started == 0) {
2289    ogg_packet header;
2290    ogg_packet header_comm;
2291    ogg_packet header_code;
2292
2293    if (s->extHead2 != NULL && s->extHead2Type != SNACK_OGG_INT) {
2294      Snack_FileFormat *ff;
2295
2296      for (ff = Snack_GetFileFormats(); ff != NULL; ff = ff->nextPtr) {
2297	if (strcmp(s->fileType, ff->name) == 0) {
2298	  if (ff->freeHeaderProc != NULL) {
2299	    (ff->freeHeaderProc)(s);
2300	  }
2301	}
2302      }
2303    }
2304
2305    if (s->extHead2 == NULL) {
2306      s->extHead2 = (char *) ckalloc(sizeof(OggVorbis_File));
2307      s->extHead2Type = SNACK_OGG_INT;
2308      ((OggVorbis_File *)s->extHead2)->nombitrate = 128000;
2309      ((OggVorbis_File *)s->extHead2)->maxbitrate = -1;
2310      ((OggVorbis_File *)s->extHead2)->minbitrate = -1;
2311      ((OggVorbis_File *)s->extHead2)->quality = -1.0;
2312      ((OggVorbis_File *)s->extHead2)->commList = NULL;
2313      ((OggVorbis_File *)s->extHead2)->vendor = NULL;
2314      of = (OggVorbis_File *)s->extHead2;
2315    }
2316
2317    started = 1;
2318    vorbis_info_init(&vi);
2319    if (((OggVorbis_File *)s->extHead2)->quality == -1.0) {
2320      ret = vorbis_encode_init(&vi, s->nchannels, s->samprate, of->maxbitrate,
2321			       of->nombitrate, of->minbitrate);
2322    } else {
2323      ret = vorbis_encode_init_vbr(&vi, s->nchannels, s->samprate,
2324				   of->quality);
2325    }
2326
2327    if (ret) {
2328      Tcl_AppendResult(interp, "vorbis_encode_init failed", (char *) NULL);
2329      return TCL_ERROR;
2330    }
2331    if (of->commList != NULL && n == 0) {
2332      Tcl_ListObjGetElements(interp, of->commList, &n, &listObj);
2333    }
2334
2335    if (n > 0) {
2336      int i;
2337
2338      vorbis_comment_init(&vc);
2339      for (i = 0; i < n; i++) {
2340	vorbis_comment_add(&vc, Tcl_GetStringFromObj(listObj[i], NULL));
2341      }
2342    }
2343
2344    vorbis_analysis_init(&vd, &vi);
2345    vorbis_block_init(&vd, &vb);
2346
2347    srand(time(NULL));
2348    ogg_stream_init(&os, rand());
2349
2350    vorbis_analysis_headerout(&vd, &vc, &header, &header_comm, &header_code);
2351    ogg_stream_packetin(&os, &header);
2352    ogg_stream_packetin(&os, &header_comm);
2353    ogg_stream_packetin(&os, &header_code);
2354
2355    while (ogg_stream_flush(&os, &og) != 0) {
2356      if (Tcl_Write(ch, (char *) og.header, og.header_len) == -1)
2357	return TCL_ERROR;
2358      if (Tcl_Write(ch, (char *) og.body, og.body_len) == -1)
2359	return TCL_ERROR;
2360    }
2361  }
2362  s->headSize = 0;
2363
2364  if (s->debug > 2) Snack_WriteLog("    Exit PutOggHeader\n");
2365
2366  return TCL_OK;
2367}
2368
2369static int
2370WriteOggSamples(Sound *s, Tcl_Channel ch, Tcl_Obj *obj, int start, int length)
2371{
2372  int eos = 0, pos = start, end = start + length;
2373  long i, j, k;
2374
2375  if (s->debug > 2) Snack_WriteLogInt("    Enter WriteOggSamples", length);
2376
2377  while (pos < end) {
2378    float **buffer = vorbis_analysis_buffer(&vd, READBUFSIZE);
2379
2380    /* uninterleave samples */
2381    Snack_GetSoundData(s, pos, pcmout, READBUFSIZE);
2382    for (i = 0, k = 0; i < READBUFSIZE / s->nchannels; i++) {
2383      for (j = 0; j < s->nchannels; j++, k++) {
2384	if (s->readStatus == READ) {
2385	  buffer[j][i] = FSAMPLE(s, pos) / 32768.0f;
2386	} else {
2387	  buffer[j][i] = pcmout[k] / 32768.0f;
2388	}
2389	pos++;
2390	if (pos > end && j == s->nchannels-1) break;
2391      }
2392      if (pos > end && j == s->nchannels-1) break;
2393    }
2394
2395    /* tell the library how much we actually submitted */
2396    vorbis_analysis_wrote(&vd, i);
2397  }
2398
2399  while(vorbis_analysis_blockout(&vd, &vb)==1){
2400    vorbis_analysis(&vb,NULL);
2401    vorbis_bitrate_addblock(&vb);
2402
2403    while(vorbis_bitrate_flushpacket(&vd,&op)) {
2404      ogg_stream_packetin(&os,&op);
2405
2406      while(!eos){
2407	int result = ogg_stream_pageout(&os, &og);
2408	if (result == 0) break;
2409	if (Tcl_Write(ch, (char *) og.header, og.header_len) == -1)
2410	  return TCL_ERROR;
2411	if (Tcl_Write(ch, (char *) og.body, og.body_len) == -1)
2412	  return TCL_ERROR;
2413
2414	if (ogg_page_eos(&og)) eos=1;
2415      }
2416    }
2417  }
2418
2419  if (s->debug > 2) Snack_WriteLog("    Exit WriteOggSamples\n");
2420
2421  return(length);
2422}
2423
2424void
2425FreeOggHeader(Sound *s)
2426{
2427  if (s->debug > 2) Snack_WriteLog("    Enter FreeOggHeader\n");
2428
2429  if (s->extHead2 != NULL) {
2430    /* To be cleared commList */
2431    ckfree((char *)s->extHead2);
2432    s->extHead2 = NULL;
2433    s->extHead2Type = 0;
2434  }
2435
2436  if (s->debug > 2) Snack_WriteLog("    Exit FreeOggHeader\n");
2437}
2438
2439int
2440ConfigOgg(Sound *s, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
2441{
2442  int arg, index;
2443  OggVorbis_File *of = (OggVorbis_File *)s->extHead2;
2444  static char *optionStrings[] = {
2445    "-comment", "-vendor", "-maxbitrate", "-minbitrate", "-nominalbitrate",
2446    "-quality", NULL
2447  };
2448  enum options {
2449    COMMENT, VENDOR, MAX, MIN, NOMINAL, QUALITY
2450  };
2451
2452  if (s->debug > 2) Snack_WriteLog("    Enter ConfigOgg\n");
2453
2454  if (s->extHead2 != NULL && s->extHead2Type != SNACK_OGG_INT) {
2455    Snack_FileFormat *ff;
2456
2457    for (ff = Snack_GetFileFormats(); ff != NULL; ff = ff->nextPtr) {
2458      if (strcmp(s->fileType, ff->name) == 0) {
2459	if (ff->freeHeaderProc != NULL) {
2460	  (ff->freeHeaderProc)(s);
2461	}
2462      }
2463    }
2464  }
2465
2466  if (s->extHead2 == NULL) {
2467    s->extHead2 = (char *) ckalloc(sizeof(OggVorbis_File));
2468    s->extHead2Type = SNACK_OGG_INT;
2469    ((OggVorbis_File *)s->extHead2)->nombitrate = 128000;
2470    ((OggVorbis_File *)s->extHead2)->maxbitrate = -1;
2471    ((OggVorbis_File *)s->extHead2)->minbitrate = -1;
2472    ((OggVorbis_File *)s->extHead2)->quality = -1.0;
2473    ((OggVorbis_File *)s->extHead2)->commList = NULL;
2474    ((OggVorbis_File *)s->extHead2)->vendor = NULL;
2475    of = (OggVorbis_File *)s->extHead2;
2476  }
2477
2478  if (objc < 3) return 0;
2479
2480
2481  if (objc == 3) { /* get option */
2482    if (Tcl_GetIndexFromObj(interp, objv[2], optionStrings, "option", 0,
2483			    &index) != TCL_OK) {
2484      Tcl_AppendResult(interp, ", or\n", NULL);
2485      return 0;
2486    }
2487
2488    switch ((enum options) index) {
2489    case COMMENT:
2490      {
2491	Tcl_SetObjResult(interp, of->commList);
2492	break;
2493      }
2494    case VENDOR:
2495      {
2496	Tcl_SetObjResult(interp, of->vendor);
2497	break;
2498      }
2499    case MAX:
2500      {
2501	Tcl_SetObjResult(interp, Tcl_NewIntObj(of->maxbitrate));
2502	break;
2503      }
2504    case MIN:
2505      {
2506	Tcl_SetObjResult(interp, Tcl_NewIntObj(of->minbitrate));
2507	break;
2508      }
2509    case NOMINAL:
2510      {
2511	Tcl_SetObjResult(interp, Tcl_NewIntObj(of->nombitrate));
2512	break;
2513      }
2514    case QUALITY:
2515      {
2516	Tcl_SetObjResult(interp, Tcl_NewDoubleObj(of->quality));
2517	break;
2518      }
2519    }
2520  } else { /* set option */
2521    for (arg = 2; arg < objc; arg+=2) {
2522      int index;
2523
2524      if (Tcl_GetIndexFromObj(interp, objv[arg], optionStrings, "option", 0,
2525			      &index) != TCL_OK) {
2526	return 0;
2527      }
2528
2529      if (arg + 1 == objc) {
2530	Tcl_AppendResult(interp, "No argument given for ",
2531			 optionStrings[index], " option\n", (char *) NULL);
2532	return 0;
2533      }
2534
2535      switch ((enum options) index) {
2536      case COMMENT:
2537	{
2538	  int i, n;
2539	  Tcl_Obj **listObj;
2540
2541	  if (Tcl_ListObjGetElements(interp, objv[arg+1], &n, &listObj) !=
2542	      TCL_OK) {
2543	    return 0;
2544	  }
2545	  /* To be cleared commList */
2546	  of->commList = Tcl_NewListObj(0, NULL);
2547	  for (i = 0; i < n; i++) {
2548	    Tcl_ListObjAppendElement(interp, of->commList, listObj[i]);
2549	  }
2550	  break;
2551	}
2552      case MAX:
2553	{
2554	  if (Tcl_GetIntFromObj(interp,objv[arg+1], &of->maxbitrate) != TCL_OK)
2555	    return 0;
2556	  break;
2557	}
2558      case MIN:
2559	{
2560	  if (Tcl_GetIntFromObj(interp,objv[arg+1], &of->minbitrate) != TCL_OK)
2561	    return 0;
2562	  break;
2563	}
2564      case NOMINAL:
2565	{
2566	  if (Tcl_GetIntFromObj(interp,objv[arg+1], &of->nombitrate) != TCL_OK)
2567	    return 0;
2568	  break;
2569	}
2570      case QUALITY:
2571	{
2572	  if (Tcl_GetDoubleFromObj(interp, objv[arg+1], &of->quality) !=TCL_OK)
2573	    return 0;
2574	  break;
2575	}
2576      }
2577    }
2578  }
2579
2580  if (s->debug > 2) Snack_WriteLog("    Exit ConfigOgg\n");
2581
2582  return 1;
2583}
2584
2585#define OGGFILE_VERSION "1.3"
2586
2587Snack_FileFormat snackOggFormat = {
2588  OGG_STRING,
2589  GuessOggFile,
2590  GetOggHeader,
2591  ExtOggFile,
2592  PutOggHeader,
2593  OpenOggFile,
2594  CloseOggFile,
2595  ReadOggSamples,
2596  WriteOggSamples,
2597  SeekOggFile,
2598  FreeOggHeader,
2599  ConfigOgg,
2600  (Snack_FileFormat *) NULL
2601};
2602
2603/* Called by "load libsnackogg" */
2604EXPORT(int, Snackogg_Init) _ANSI_ARGS_((Tcl_Interp *interp))
2605{
2606  int res;
2607
2608#ifdef USE_TCL_STUBS
2609  if (Tcl_InitStubs(interp, "8", 0) == NULL) {
2610    return TCL_ERROR;
2611  }
2612#endif
2613
2614#ifdef USE_SNACK_STUBS
2615  if (Snack_InitStubs(interp, "2", 0) == NULL) {
2616    return TCL_ERROR;
2617  }
2618#endif
2619
2620  res = Tcl_PkgProvide(interp, "snackogg", OGGFILE_VERSION);
2621
2622  if (res != TCL_OK) return res;
2623
2624  Tcl_SetVar(interp, "snack::snackogg", OGGFILE_VERSION, TCL_GLOBAL_ONLY);
2625
2626  Snack_CreateFileFormat(&snackOggFormat);
2627
2628  return TCL_OK;
2629}
2630
2631EXPORT(int, Snackogg_SafeInit)(Tcl_Interp *interp)
2632{
2633  return Snackogg_Init(interp);
2634}
2635