//**************************************************************************
//*                     This file is part of the                           *
//*                      Mpxplay - audio player.                           *
//*                  The source code of Mpxplay is                         *
//*        (C) copyright 1998-2006 by PDSoft (Attila Padar)                *
//*                    http://mpxplay.cjb.net                              *
//*                  email: mpxplay@freemail.hu                            *
//**************************************************************************
//*  This program is distributed in the hope that it will be useful,       *
//*  but WITHOUT ANY WARRANTY; without even the implied warranty of        *
//*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.                  *
//*  Please contact with the author (with me) if you want to use           *
//*  or modify this source.                                                *
//**************************************************************************
//function: CDW file handling (Audio-CD ripper)

#include "in_file.h"

#include "newfunc\newfunc.h"
#include <i86.h>
#include <string.h>

#ifdef __DOS__
 #define CDW_USE_ASM 1
#else
 #undef MPXPLAY_LINK_INFILE_CDW
#endif

#define CD_MAX_DRIVES      8
#define CD_MAX_TRACKS    100
#define CD_FRAME_SIZE   2352
#define CD_READ_SECTORS   24
#define CD_READBUF_SIZE  (CD_READ_SECTORS*CD_FRAME_SIZE)
#define CD_SYNC_SECTORS  8
#define CD_SYNC_SIZE     CD_FRAME_SIZE
#define CD_ERROR_LIMIT   32
#define CD_MAX_RETRIES   6

//#define CD_INFO_DEVICE_READY 0x100
#define CD_INFO_TRACK_DATA    0x40

#define CD_OPEN_DOOR  0
#define CD_CLOSE_DOOR 5

#define CDW_INIT_READY_CONTROL     1
#define CDW_INIT_FAILED_CONTROL    2
#define CDW_INIT_READY_READBUFFER  4

#define CDW_CONTROLFLAG_JITTER  1

#define CDW_DECODERFLAG_SEEK  1  // at jitter correction

#define CDW_FREQNUM    44100
#define CDW_CHANNUM        2
#define CDW_BITNUM        16

unsigned int cdw_is_drive_cd(int drive);

#ifdef MPXPLAY_LINK_INFILE_CDW

//static field -> we handle 1 track only at once, we cannot read 2 tracks paralell
static struct cdw_decoderdata_s{
 int  drivenumber_r;
 int  tracknumber;
 long track_beginpos;
 long track_endpos;
 long track_currpos;
 long track_len;
 char *readbuffer;
 long readbuf_leftbytes;
 int  cd_jitter;
 int  flags;
 unsigned int checknum;        // used to know the latest opened file/track
}cdw_decoder_datas;

typedef struct cdw_lowlevel_s{
 unsigned int checknum;        //
 struct cdw_decoderdata_s *cdwi;
}cdw_lowlevel_s;

/*typedef struct cd_driveinfo_s{
 unsigned int  drivenumber_cd;
 unsigned int  driveletter_dos;
 unsigned int  infobits;
 unsigned int  track_first;
 unsigned int  track_last;
 unsigned int  track_curr;
 unsigned long frameloc_last;
 unsigned long frameloc_curr;
 unsigned long cd_alltrack_startloc[CD_MAX_TRACKS];
 unsigned int  cd_alltrack_infobits[CD_MAX_TRACKS];
}cd_driveinfo_s;*/

//static cd_driveinfo_s *cd_driveinfos[CD_MAX_DRIVES];

static int cdw_extract_tracknumber_from_filename(char *,int *);
static unsigned long cdw_get_tracklen(int,int);
static unsigned int cdrom_test(void);

static int  cd_get_track_loc(int,int,long *,long *);
static int  cd_get_alltracks(int);
static long cdw_start_read(struct cdw_decoderdata_s *cdwi,int,int);

static unsigned int cdw_init_readbuf(struct cdw_decoderdata_s *cdwi);
static int  cd_drives;
static int  cd_drivenum_t;
static long cd_firsttrack,cd_lasttrack;
static long cd_lastframeloc,cd_alltrack_startloc[100];

static dosmem_t dm_cd_data;
static short segment_cdw;
extern unsigned int cdw_controlflags;
#endif // MPXPLAY_LINK_INFILE_CDW

#ifdef __DOS__
static unsigned int cdw_init_control(void);
static int   cdw_initstatus,cd_firstdrive;
static char *dosmemput1,*dosmemput2;
static short segment1,segment2;
static dosmem_t dm_cd_control,dm_cd_info;
#endif

//-------------------------------------------------------------------------
#ifdef MPXPLAY_LINK_INFILE_CDW
static void cdw_assign_values(struct mpxplay_infile_info_s *miis,unsigned long len)
{
 mpxplay_audio_decoder_info_s *adi=miis->audio_decoder_infos;

 miis->filesize=len;
 adi->freq=CDW_FREQNUM;
 adi->filechannels=adi->outchannels=CDW_CHANNUM;
 adi->bits=CDW_BITNUM;
 miis->timemsec=(float)len*1000.0/(float)(CDW_FREQNUM*CDW_CHANNUM*CDW_BITNUM/8);
 miis->longname="CDWtrack";
}

static int CDW_infile_check(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,char *filename,struct mpxplay_infile_info_s *miis)
{
 int drive,track;
 unsigned long len;

 if(!cdw_init_control())
  return MPXPLAY_ERROR_INFILE_MEMORY;

 drive=0;
 track=cdw_extract_tracknumber_from_filename(filename,&drive);
 if(track>0){
  len=cdw_get_tracklen(drive,track);
  if(len){
   cdw_assign_values(miis,len);
   return MPXPLAY_ERROR_INFILE_OK;
  }
 }
 return MPXPLAY_ERROR_INFILE_CANTOPEN;
}

static int CDW_infile_open(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,char *filename,struct mpxplay_infile_info_s *miis)
{
 struct cdw_decoderdata_s *cdwi=&cdw_decoder_datas;
 int drive,track;
 unsigned long len;

 if(!cdw_init_control())
  return MPXPLAY_ERROR_INFILE_MEMORY;

 drive=0;
 track=cdw_extract_tracknumber_from_filename(filename,&drive);
 if(track>0){
  if(!cdw_init_readbuf(cdwi))
   return MPXPLAY_ERROR_INFILE_MEMORY;
  len=cdw_start_read(cdwi,drive,track);
  if(len){
   cdwi->checknum++;
   if(!fbfs->fopen_read(fbds,filename,0))
    return MPXPLAY_ERROR_INFILE_CANTOPEN;
   cdw_assign_values(miis,len);
   return MPXPLAY_ERROR_INFILE_OK;
  }
 }
 return MPXPLAY_ERROR_INFILE_CANTOPEN;
}

static void CDW_infile_close(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,struct mpxplay_infile_info_s *miis)
{
 fbfs->fclose(fbds); // ???
}

static int CDW_infile_decode(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,struct mpxplay_infile_info_s *miis)
{
 mpxplay_audio_decoder_info_s *adi=miis->audio_decoder_infos;

 adi->pcm_samplenum=fbfs->fread(fbds,adi->pcm_bufptr,adi->pcm_framelen*CDW_CHANNUM*(CDW_BITNUM/8)) / (CDW_BITNUM/8); // /bytespersample (2)
 if(!adi->pcm_samplenum)
  return MPXPLAY_ERROR_INFILE_NODATA;

 return MPXPLAY_ERROR_INFILE_OK;
}

static long CDW_infile_fseek(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,struct mpxplay_infile_info_s *miis,long newmpxframenum)
{
 mpxplay_audio_decoder_info_s *adi=miis->audio_decoder_infos;
 long newfilepos=newmpxframenum*adi->pcm_framelen*CDW_CHANNUM*(CDW_BITNUM/8);
 if(fbfs->fseek(fbds,newfilepos,SEEK_SET)<0)
  return MPXPLAY_ERROR_INFILE_EOF;
 return newmpxframenum;
}

//--------------------------------------------------------------------------
//requires cdw_init_control() !
static int cdw_extract_tracknumber_from_filename(char *filename,int *drive)
{
 int track=0;
 char *ext,*tc;

 ext=pds_strrchr(filename,'.');
 if(!ext)
  return track;

 ext++;

 if(pds_stricmp(ext,"cdw")==0 || pds_stricmp(ext,"*")==0){  // .cdw or .*
  if(!(*drive)){
   if(filename[1]==':')
    *drive=(filename[0]>='a')? (filename[0]-'a'):(filename[0]-'A');
   else
    *drive=cd_firstdrive;
  }
  if(cdw_is_drive_cd(*drive)){
   tc=&ext[-2];
   if(tc[0]=='*')
    track=-1;
   else{
    while(tc[-1]>='0' && tc[-1]<='9')
     tc--;
    track=pds_atol(tc);
   }
  }
 }
 return track;
}

static void cdw_create_filename(struct playlist_entry_info *pei,char **filenamesfieldp,int drive,int track)
{
 pei->filename=*filenamesfieldp;
 *filenamesfieldp+=sprintf(*filenamesfieldp,"%c:\\TRACK%2.2d.CDW",drive+65,track)+1;
}

static unsigned long cdw_get_tracklen(int drive,int track)
{
 long loc_begin,loc_end,loclen=0;
 if(cd_get_track_loc(drive,track,&loc_begin,&loc_end)){
  loclen=loc_end-loc_begin;
  loclen*=CD_FRAME_SIZE;
 }
 return loclen;
}

#endif //MPXPLAY_LINK_INFILE_CDW

struct playlist_entry_info *cdw_get_allfiles(struct playlist_entry_info *pei,char **filenamesfieldp,int drive,char *filemask)
{
#ifdef MPXPLAY_LINK_INFILE_CDW
 int cddrive,cdfirsttrack,cdlasttrack,track,trackselect;

 if(!cdw_init_control())
  return pei;

 cddrive=drive;
 trackselect=cdw_extract_tracknumber_from_filename(filemask,&cddrive);
 if(trackselect==0)
  return pei;

 if(!cd_get_alltracks(cddrive))
  return pei;

 if(trackselect>0)
  cdfirsttrack=cdlasttrack=trackselect;
 else{
  cdfirsttrack=cd_firsttrack;
  cdlasttrack=cd_lasttrack;
 }
 for(track=cdfirsttrack;track<=cdlasttrack;track++){
  if(cdw_get_tracklen(cddrive,track)){
   cdw_create_filename(pei,filenamesfieldp,cddrive,track);
   pei++;
  }
 }
#endif //MPXPLAY_LINK_INFILE_CDW
 return pei;
}

//--------------------------------------------------------------------------
// drive handler init - control buffer (DOS-mem) allocation
#ifdef __DOS__
static unsigned int cdw_init_control(void)
{
 if(cdw_initstatus&CDW_INIT_FAILED_CONTROL)
  return 0;

 if(!(cdw_initstatus&CDW_INIT_READY_CONTROL)){
  funcbit_enable(cdw_initstatus,CDW_INIT_FAILED_CONTROL);

  if(!pds_dpmi_dos_allocmem(&dm_cd_control,256))
   return 0;
  if(!pds_dpmi_dos_allocmem(&dm_cd_info,256))
   return 0;

  // if we don't do this, we get : Memory allocation error, system halted...
  dm_cd_control.linearptr+=16;
  dm_cd_control.segment++;
  dm_cd_info.linearptr+=16;
  dm_cd_info.segment++;

  dosmemput1=dm_cd_control.linearptr;
  dosmemput2=dm_cd_info.linearptr;
  segment1=dm_cd_control.segment;
  segment2=dm_cd_info.segment;

  funcbit_disable(cdw_initstatus,CDW_INIT_FAILED_CONTROL);
  funcbit_enable(cdw_initstatus,CDW_INIT_READY_CONTROL);
 }
 return 1;
}
#endif

#ifdef MPXPLAY_LINK_INFILE_CDW

static unsigned int cdw_init_readbuf(struct cdw_decoderdata_s *cdwi)
{
 if(!(cdw_initstatus&CDW_INIT_READY_READBUFFER)){
  if(!cdw_init_control())
   return 0;
  if(!pds_dpmi_dos_allocmem(&dm_cd_data,CD_READBUF_SIZE+CD_SYNC_SIZE+32))
   return 0;

  dm_cd_data.linearptr+=16;
  dm_cd_data.segment++;

  cdwi->readbuffer=dm_cd_data.linearptr;
  segment_cdw=dm_cd_data.segment;
  funcbit_enable(cdw_initstatus,CDW_INIT_READY_READBUFFER);
 }
 return 1;
}

static void CDW_infile_preinit(void)
{
 if(!cdrom_test())
  funcbit_enable(cdw_initstatus,CDW_INIT_FAILED_CONTROL);
}

static void CDW_infile_deinit(void)
{
 pds_dpmi_dos_freemem(&dm_cd_control);
 pds_dpmi_dos_freemem(&dm_cd_info);
 pds_dpmi_dos_freemem(&dm_cd_data);
}

#endif // MPXPLAY_LINK_INFILE_CDW

//------------------------------------------------------------------------
#if defined(CDW_USE_ASM) && defined(__WATCOMC__)

#ifdef MPXPLAY_LINK_INFILE_CDW
unsigned int asm_cdrom_test(void);

static unsigned int cdrom_test(void)
{
#pragma aux asm_cdrom_test=\
 "mov eax,0x1500"\
 "xor ebx,ebx"\
 "xor ecx,ecx"\
 "int 0x2f"\
 "mov eax,ebx"\
 "test eax,eax"\
 "jz nocd"\
 "cmp ecx,0"\
 "jbe nocd"\
  "mov dword ptr cd_drives,eax"\
  "mov dword ptr cd_firstdrive,ecx"\
 "nocd:"\
 value[eax] modify[ebx ecx edx edi esi];
 return asm_cdrom_test();
}

#endif // MPXPLAY_LINK_INFILE_CDW

unsigned int asm_is_drive_cd(int);

unsigned int cdw_is_drive_cd(int drive)
{
#pragma aux asm_is_drive_cd=\
 "cmp cd_firstdrive,0"\
 "jbe cd_no"\
 "mov ecx,eax"\
 "mov eax,0x150b"\
 "xor ebx,ebx"\
 "int 0x2f"\
 "cmp bx,0xadad"\
 "jne cd_no"\
 "test ax,ax"\
 "jnz cd_ok"\
 "cd_no:"\
  "xor eax,eax"\
  "jmp cd_end"\
 "cd_ok:mov eax,1"\
 "cd_end:"\
 parm[eax] value[eax] modify[ebx ecx edx edi esi];
 return asm_is_drive_cd(drive);
}

#else // CDW_USE_ASM

#ifdef MPXPLAY_LINK_INFILE_CDW

static unsigned int cdrom_test(void)
{
#ifdef __DOS__
 union REGS regs;
 pds_memset(&regs,0,sizeof(union REGS));
 regs.w.ax=0x1500;
 int386(0x2f,&regs,&regs);
 if(regs.w.bx && (regs.w.cx>0)){
  cd_drives=regs.w.bx;
  cd_firstdrive=regs.w.cx;
 }
 return ((unsigned int)regs.w.bx);
#else
 return 0;
#endif
}
#endif //MPXPLAY_LINK_INFILE_CDW

unsigned int cdw_is_drive_cd(int drive)
{
#ifdef __DOS__
 union REGS regs;
 if(cd_firstdrive<=0)
  return 0;
 pds_memset(&regs,0,sizeof(union REGS));
 regs.w.ax=0x150b;
 regs.w.cx=drive;
 int386(0x2f,&regs,&regs);
 if((regs.w.bx==0xadad) && regs.w.ax) // ??? correct?
  return 1;
#endif
 return 0;
}

#endif // CDW_USE_ASM

//--------------------------------------------------------------------------
static struct IOCTLI
{
 unsigned char len;
 unsigned char subunit;
 unsigned char command;
 unsigned short status;
 unsigned char reserved[8];
 unsigned char mediadesc;
 unsigned short transseg;
 unsigned short transoff;
 unsigned short numbytes;
 unsigned short startsec;
 unsigned long unused;
}cdinfo_device;

static struct statusinfo{
 unsigned char control;
 unsigned long status;
}cdinfo_status;

#ifdef MPXPLAY_LINK_INFILE_CDW
static struct readinfo
{
 unsigned char len;
 unsigned char subunit;
 unsigned char command;
 unsigned short status;
 unsigned char reserved[8];
 unsigned char mode;
 unsigned short transseg;
 unsigned short transoff;
 unsigned long loc;
 unsigned short secnum;
 unsigned char readmode;
 unsigned char skip[2];
}cdinfo_read;

static struct diskinfo
{
 unsigned char control;
 unsigned char lowest;
 unsigned char highest;
 unsigned long total;
}cdinfo_disk;

static struct trackinfo
{
 unsigned char control;
 unsigned char track;
 unsigned long loc;
 unsigned char info;
}cdinfo_track;
#endif // MPXPLAY_LINK_INFILE_CDW

static void cd_device_command(int drive,int command,char *info2,int inf2size)
{
#ifdef __DOS__
 struct rminfo RMI;
 pds_dpmi_rmi_clear(&RMI);
 cdinfo_device.len=26;
 cdinfo_device.subunit=0;
 cdinfo_device.command=command;
 cdinfo_device.status=0x0000;
 cdinfo_device.mediadesc=0;
 cdinfo_device.transseg=segment2;
 cdinfo_device.transoff=0;
 cdinfo_device.numbytes=inf2size;//7;
 cdinfo_device.startsec=0;
 cdinfo_device.unused=0;
 pds_memcpy(dosmemput1,(char *)(&cdinfo_device),sizeof(struct IOCTLI));
 pds_memcpy(dosmemput2,info2,inf2size);

 RMI.EAX=0x00001510;
 RMI.ECX=drive;
 RMI.ES=segment1;
 pds_dpmi_realmodeint_call(0x2f,&RMI);
 cdinfo_device.status=*((unsigned short *)&dosmemput1[3]); // !!!
 pds_memcpy(info2,dosmemput2,inf2size); // ??? Watcom or 32-bit sux
#else
 pds_memset(info2,0,inf2size);
#endif
}

static unsigned int cd_device_info(int drive,char *info2,int inf2size)
{
 int counter=3;
 do{
  cd_device_command(drive,3,info2,inf2size);
  cdinfo_device.status&=0x100;
 }while(!cdinfo_device.status && (counter--));
 return ((unsigned int)cdinfo_device.status);
}

#ifdef MPXPLAY_LINK_INFILE_CDW

static unsigned short cd_read_sectors(unsigned short drive,unsigned long loc,unsigned short secnum)
{
 struct rminfo RMI;
 pds_dpmi_rmi_clear(&RMI);
 cdinfo_read.len=sizeof(struct readinfo);
 cdinfo_read.subunit=0;
 cdinfo_read.command=128;
 cdinfo_read.transseg=segment_cdw;
 cdinfo_read.transoff=secnum;
 cdinfo_read.mode=0;
 cdinfo_read.loc=loc;
 cdinfo_read.secnum=1;
 cdinfo_read.readmode=0;
 cdinfo_read.skip[0]=0;
 cdinfo_read.skip[1]=0;
 pds_memcpy((char *)dosmemput1,(char *)(&cdinfo_read),sizeof(struct readinfo));
 RMI.EAX=0x00001510;
 RMI.ECX=drive;
 RMI.ES=segment1;
 pds_dpmi_realmodeint_call(0x2f,&RMI);
 cdinfo_read.status=*((unsigned short *)&dosmemput1[3]); // !!!
 if(cdinfo_read.status&0x100)
  return 1;
 return 0;
}

//---------------------------------------------------------------------------
static unsigned long Red2Sierra(unsigned long locstr)
{
 unsigned long min,sec,frame;

 min  =(locstr>>16) & 0xff;
 sec  =(locstr>> 8) & 0xff;
 frame= locstr      & 0xff;
 return min*75*60+sec*75+frame-150;
}

static unsigned int cd_get_maxtrack(int drive,long *firsttrack,long *lasttrack,long *lastframeloc)
{
 unsigned long cd_totaltimestr;
 cdinfo_disk.control=10;
 if(!cd_device_info(drive,(char *)&cdinfo_disk,sizeof(struct diskinfo)))
  return 0;
 *firsttrack=cdinfo_disk.lowest;
 *lasttrack =cdinfo_disk.highest;
 if(*firsttrack<=0 || *lasttrack<=0)
  return 0;
 cd_totaltimestr=*((unsigned long *)&dosmemput2[3]); // !!!
 *lastframeloc=Red2Sierra(cd_totaltimestr)-1;
 return 1;
}

static long cd_get_track_startloc_fromdrive(int drive,int track,int check_datatrack)
{
 long track_startloc;
 cdinfo_track.control=11;
 cdinfo_track.track=track;
 if(!cd_device_info(drive,(char *)&cdinfo_track,sizeof(struct trackinfo)))
  return -1;
 cdinfo_track.loc=*((unsigned long *)&dosmemput2[2]); // !!!
 cdinfo_track.info=dosmemput2[6];

 if(check_datatrack && (cdinfo_track.info&CD_INFO_TRACK_DATA))
  track_startloc=-1;
 else
  track_startloc=Red2Sierra(cdinfo_track.loc);
 return track_startloc;
}

static int cd_get_alltracks(int drive)
{
 unsigned int track;

 if(!cd_get_maxtrack(drive,&cd_firsttrack,&cd_lasttrack,&cd_lastframeloc))
  return 0;
 cd_drivenum_t=drive;

 for(track=cd_firsttrack;track<=cd_lasttrack;track++)
  cd_alltrack_startloc[track]=cd_get_track_startloc_fromdrive(drive,track,1);
 return 1;
}

static int cd_get_track_loc(int drive,int track,long *track_startloc,long *track_endloc)
{
 long firsttrack,lasttrack,lastframeloc;

 if(cd_drivenum_t==drive){
  if(track>=cd_firsttrack && track<=cd_lasttrack){
   *track_startloc=cd_alltrack_startloc[track];
   if((track+1)<=cd_lasttrack)
    *track_endloc=cd_alltrack_startloc[track+1];
   else
    *track_endloc=cd_lastframeloc;
  }else
   return 0;
 }else{
  if(!cd_get_maxtrack(drive,&firsttrack,&lasttrack,&lastframeloc))
   return 0;
  if(track>=firsttrack && track<=lasttrack){
   *track_startloc=cd_get_track_startloc_fromdrive(drive,track,1);
   if((track+1)<=lasttrack)
    *track_endloc=cd_get_track_startloc_fromdrive(drive,track+1,0);
   else
    *track_endloc=lastframeloc;
  }else
   return 0;
 }
 if(*track_startloc>=0 && *track_endloc>*track_startloc)
  return 1;
 return 0;
}

//------------------------------------------------------------------------
static void *cdw_open(char *filename)
{
 struct cdw_lowlevel_s *cdwl;
 cdwl=calloc(1,sizeof(struct cdw_lowlevel_s));
 if(!cdwl)
  return cdwl;
 cdwl->cdwi=&cdw_decoder_datas;
 cdwl->checknum=cdwl->cdwi->checknum;
 return cdwl;
}

static void cdw_close(void *lld)
{
 struct cdw_lowlevel_s *cdwl=lld;
 if(cdwl)
  free(cdwl);
}

static long cdw_start_read(struct cdw_decoderdata_s *cdwi,int drive,int track)
{
 long loc,diff,sectors;

 if(!cd_get_track_loc(drive,track,&cdwi->track_beginpos,&cdwi->track_endpos))
  return 0;

 cdwi->tracknumber=track;
 cdwi->track_len=cdwi->track_endpos-cdwi->track_beginpos;
 loc=cdwi->track_beginpos;
 diff=cdwi->track_beginpos-cdwi->track_currpos;
 if(diff<0)
  diff=-diff;
 if((cdwi->drivenumber_r!=drive) || (diff>100)){ // drive has changed or not-next-track
  cdwi->drivenumber_r=drive;
  cdwi->track_currpos=loc;
  if(loc>=CD_READ_SECTORS){
   loc-=CD_READ_SECTORS;
   sectors=CD_READ_SECTORS;
  }else{
   sectors=loc;
   loc=0;
  }
  cdwi->readbuf_leftbytes=0;
  funcbit_enable(cdwi->flags,CDW_DECODERFLAG_SEEK);
  if(!cd_read_sectors(drive,loc,sectors))
   return 0;
 }
 return (cdwi->track_len*CD_FRAME_SIZE);
}

static long cdw_read(void *lld,char *ptr,unsigned int num)
{
 struct cdw_lowlevel_s *cdwl=lld;
 struct cdw_decoderdata_s *cdwi=cdwl->cdwi;
 int i,j;
 int jitter_dev, cs, jitter1, jitter2, match, tries;
 int min_errors, curr_errors, max_reached, min_error_jitter;
 unsigned long sectors;

 if(cdwl->checknum!=cdwi->checknum) // this is not the latest track-start
  return 0;

 i=0;
 if(cdw_controlflags&CDW_CONTROLFLAG_JITTER){
  do{
   if(cdwi->readbuf_leftbytes>0){
    if(num>cdwi->readbuf_leftbytes)
     j=cdwi->readbuf_leftbytes;
    else
     j=num;
    pds_memcpy(ptr,&cdwi->readbuffer[CD_READBUF_SIZE-cdwi->readbuf_leftbytes],j);
    ptr+=j;
    cdwi->readbuf_leftbytes-=j;
    num-=j;
    i+=j;
   }
   if(cdwi->track_currpos>=(cdwi->track_endpos))
    break;
   if(cdwi->readbuf_leftbytes<=0){
    sectors=cdwi->track_endpos-cdwi->track_currpos + CD_SYNC_SECTORS;
    if(sectors>CD_READ_SECTORS)
     sectors=CD_READ_SECTORS;
    pds_qmemcpyr(&cdwi->readbuffer[CD_READBUF_SIZE], &cdwi->readbuffer[CD_READBUF_SIZE-CD_FRAME_SIZE], (CD_SYNC_SIZE>>2) + 1);
    if((cdwi->track_currpos<CD_SYNC_SECTORS) || (cdwi->flags&CDW_DECODERFLAG_SEEK)){
     if(!cd_read_sectors(cdwi->drivenumber_r,cdwi->track_currpos,(unsigned short)sectors))
      break;
     cdwi->cd_jitter = 0;
     cdwi->readbuf_leftbytes=sectors*CD_FRAME_SIZE;
    }else{
     tries = 0;
     match = 0;
     min_errors = 0;

     while(!match && (tries < CD_MAX_RETRIES)){
      if(!cd_read_sectors(cdwi->drivenumber_r,cdwi->track_currpos - CD_SYNC_SECTORS,(unsigned short)sectors))
       return i;

      tries ++;

      jitter_dev = 0;
      while(1){
       jitter1 = cdwi->cd_jitter + jitter_dev;
       jitter2 = cdwi->cd_jitter - jitter_dev;
       if((jitter2 < 0) && (jitter1 >= CD_FRAME_SIZE * CD_SYNC_SECTORS * 2))
        break;
       if(jitter1 < (CD_FRAME_SIZE * CD_SYNC_SECTORS * 2)) {
        for(cs = 0; cs < CD_SYNC_SIZE; cs++)
         if(cdwi->readbuffer[jitter1 + cs] !=cdwi->readbuffer[CD_READBUF_SIZE + cs])
          break;
        if(cs == CD_SYNC_SIZE) {
         cdwi->cd_jitter = jitter1;
         match = 1;
         break;
        }
       }
       if(jitter2 >= 0) {
        for (cs = 0; cs < CD_SYNC_SIZE; cs++)
         if (cdwi->readbuffer[jitter2 + cs] !=cdwi->readbuffer[CD_READBUF_SIZE + cs])
          break;
        if(cs == CD_SYNC_SIZE) {
         cdwi->cd_jitter = jitter2;
         match = 1;
         break;
        }
       }
       jitter_dev ++;
      }
     }

     if(match == 0) {
      min_errors = CD_ERROR_LIMIT;
      max_reached = 0;
      min_error_jitter = cdwi->cd_jitter;

      jitter1 = 0;
      while(jitter1 < (CD_FRAME_SIZE * CD_SYNC_SECTORS * 2)) {
       curr_errors = 0;
       for(cs = 0; cs < CD_SYNC_SIZE; cs++)
        if(cdwi->readbuffer[jitter1 + cs] !=cdwi->readbuffer[CD_READBUF_SIZE + cs]) {
         curr_errors ++;
         if(curr_errors > CD_ERROR_LIMIT)
          break;
        }
       if(curr_errors < min_errors) {
        min_errors = curr_errors;
        max_reached = cs;
        min_error_jitter = jitter1;
       }else
        if(curr_errors == min_errors) {
         if(cs > max_reached) {
          max_reached = cs;
          min_error_jitter = jitter1;
         }
        }
       jitter1++;
      }
      cdwi->cd_jitter = min_error_jitter;
     }
     cdwi->readbuf_leftbytes=sectors*CD_FRAME_SIZE - (cdwi->cd_jitter + CD_FRAME_SIZE);
    }

    cdwi->track_currpos+= sectors - CD_SYNC_SECTORS;

    // at the end of the track - to get gapless sound
    if(sectors<CD_READ_SECTORS){
     unsigned int target=CD_READBUF_SIZE-cdwi->readbuf_leftbytes;
     unsigned int source=cdwi->cd_jitter+CD_FRAME_SIZE;

     if(target>source) // ??? always have to be
      pds_qmemcpyr(&cdwi->readbuffer[target],&cdwi->readbuffer[source],(cdwi->readbuf_leftbytes>>2)+1);
    }

   }
  }while(num && cdwi->readbuf_leftbytes);
 }else{
  do{
   if(num>cdwi->readbuf_leftbytes)
    j=cdwi->readbuf_leftbytes;
   else
    j=num;
   pds_memcpy(ptr,&cdwi->readbuffer[CD_READBUF_SIZE-cdwi->readbuf_leftbytes],j);
   ptr+=j;
   cdwi->readbuf_leftbytes-=j;
   num-=j;
   i+=j;
   if(cdwi->track_currpos>=cdwi->track_endpos)
    break;
   if(cdwi->readbuf_leftbytes<=0){
    sectors=cdwi->track_endpos-cdwi->track_currpos;
    if(sectors>CD_READ_SECTORS)
     sectors=CD_READ_SECTORS;
    if(!cd_read_sectors(cdwi->drivenumber_r,cdwi->track_currpos,(unsigned short)sectors))
     break;
    cdwi->track_currpos+=sectors;
    cdwi->readbuf_leftbytes=sectors*CD_FRAME_SIZE;
    if(cdwi->readbuf_leftbytes<CD_READBUF_SIZE)
     pds_qmemcpyr(&cdwi->readbuffer[CD_READBUF_SIZE-cdwi->readbuf_leftbytes],cdwi->readbuffer,(cdwi->readbuf_leftbytes>>2)+1);
   }
  }while(num && cdwi->readbuf_leftbytes);
 }

 funcbit_disable(cdwi->flags,CDW_DECODERFLAG_SEEK);
 return i;
}

static long cdw_tell(void *lld)
{
 struct cdw_lowlevel_s *cdwl=lld;
 struct cdw_decoderdata_s *cdwi=cdwl->cdwi;
 if(cdwl->checknum!=cdwi->checknum)
  return -1;
 return (cdwi->track_currpos-cdwi->track_beginpos)*CD_FRAME_SIZE;
}

static long cdw_lseek(void *lld,long pos,int fromwhere)
{
 struct cdw_lowlevel_s *cdwl=lld;
 struct cdw_decoderdata_s *cdwi=cdwl->cdwi;
 if(cdwl->checknum!=cdwi->checknum)
  return -1;
 pos-=cdwi->readbuf_leftbytes;
 if(pos<0)
  pos=0;
 else
  pos=pos/CD_FRAME_SIZE;
 cdwi->track_currpos=cdwi->track_beginpos+pos;
 cdwi->readbuf_leftbytes=0; // this clears/resets the cdwi->readbuffer
 if(pos>0)
  funcbit_enable(cdwi->flags,CDW_DECODERFLAG_SEEK);

 return (cdw_tell(cdwl));
}

static long cdw_filelength(void *lld)
{
 struct cdw_lowlevel_s *cdwl=lld;
 struct cdw_decoderdata_s *cdwi=cdwl->cdwi;
 if(cdwl->checknum!=cdwi->checknum)
  return -1;
 return (cdwi->track_len*CD_FRAME_SIZE);
}

static int cdw_eof(void *lld)
{
 struct cdw_lowlevel_s *cdwl=lld;
 struct cdw_decoderdata_s *cdwi=cdwl->cdwi;
 if(cdwl->checknum!=cdwi->checknum)
  return 1;
 return (((cdwi->track_currpos>=cdwi->track_endpos) && !cdwi->readbuf_leftbytes)? 1:0);
}
#endif //MPXPLAY_LINK_INFILE_CDW

//--------------------------------------------------------------------------

void cdw_open_or_close_door(unsigned int drive) // 0 or 1
{
 if(!cdw_init_control())
  return;

 cdinfo_status.control=6;
 if(!cd_device_info(cd_firstdrive+drive,(char *)&cdinfo_status,sizeof(struct statusinfo)))
  return;
 cdinfo_status.status=*((unsigned long *)&dosmemput2[1]); // !!!
 if(cdinfo_status.status&0x1){ // door is open, so close it
  cdinfo_status.control=CD_CLOSE_DOOR;
  cd_device_command(cd_firstdrive+drive,12,(char *)&cdinfo_status,sizeof(struct statusinfo));
 }else{                        // door is closed, so open it
  cdinfo_status.control=CD_OPEN_DOOR;
  cd_device_command(cd_firstdrive+drive,12,(char *)&cdinfo_status,sizeof(struct statusinfo));
 }
}

//--------------------------------------------------------------------------
#ifdef MPXPLAY_LINK_INFILE_CDW

static struct mpxplay_filehand_low_func_s cdw_low_funcs={
 NULL,
 &cdw_open,
 NULL,
 NULL,
 &cdw_close,
 &cdw_read,
 NULL,
 &cdw_lseek,
 &cdw_tell,
 &cdw_filelength,
 &cdw_eof,
 NULL
};

struct mpxplay_infile_func_s IN_CDW_funcs={
 0,
 &CDW_infile_preinit,
 &CDW_infile_deinit,
 NULL,
 &CDW_infile_check,
 &CDW_infile_open,
 &CDW_infile_close,
 &CDW_infile_decode,
 &CDW_infile_fseek,
 NULL,
 NULL,
 NULL,
 &cdw_low_funcs,
 {"CDW",NULL}
};

#endif // MPXPLAY_LINK_INFILE_CDW
