/*  DAGRABX grabs 16 bit 44.1 KHz CD Audio digitally to a file     */
/*  This version uses XMS as an intermediate buffer to avoid       */
/*  the "glitches" that otherwise occur                            */


#include <stdio.h>
#include <stdlib.h>
#include <dos.h>
#include <string.h>
#include <alloc.h>
#include <xmslib.h>
#include <cdrom.h>

#define FALSE  0
#define TRUE   1

int cdrom_loaded, cdrom_drive;

struct EMMMoveStruct moverec;
unsigned int xmshandle, xmsfree,xmstot;

struct readlong_struc readlong_block;
struct ioctl_struc ioctl_block;
struct diskinfo_struc diskinfo_block;
struct trackinfo_struc trackinfo_block[100];

unsigned long red2hsg(unsigned long redval);

int *buffer;


struct wavhdr_str {
       unsigned char riff[4];
       unsigned long riff_len;
       unsigned char wave[4];
       unsigned char fmt[4];
       unsigned long fmt_len;
       unsigned int  fmt_tag;
       unsigned int channels;
       unsigned long smp_sec;
       unsigned long avg_smp_sec;
       unsigned int blk_align;
       unsigned int bits_per_sample;
       unsigned char data[4];
       unsigned long smpdata_len;
} wavhdr;


main(int argc, char *argv[])
{
       struct dfree diskfree;
       long avail;
       int drive;

       unsigned long start_sect, end_sect, start_sect2;
       unsigned int num_sect, num_sect2, sects_to_grab, sects_to_write,i,j,k,offs;
       unsigned char filename[80];
       FILE *outfp;

       int track, start_min, start_sec, start_frame, end_min, end_sec, end_frame;
       char local_buffer[132];
       char c = ':';

       if(argc > 3) {
               if (! check_cd2f()){
                       printf("MSCDEX NOT LOADED\n");
                       exit(1);
               }

               if(!XMS_Setup()) {
                       printf("No XMS handler installed!\n");
                       exit(1);
               }

               while(get_diskinfo() != STAT_DONE);

               for(i = diskinfo_block.low_track; i <= diskinfo_block.high_track; i++)
                       get_trackinfo(i);

               track = atoi(argv[1]);
               if( (track < 1) || (track > diskinfo_block.high_track)) {
                       printf("Error: Track must be between %d and %d\n",
                                       diskinfo_block.low_track, diskinfo_block.high_track);
                       errexit();
               }

               start_min = 0;
               start_sec = 0;
               start_frame = 0;
               end_min = 0;
               end_sec = 0;
               end_frame = 0;

               strcpy(local_buffer, argv[2]);
               start_min = atoi(strtok(local_buffer, ":"));
               start_sec = atoi(strtok(NULL, ":"));
               start_frame = atoi(&local_buffer[strrchr(argv[2],c)+1-local_buffer]);

               strcpy(local_buffer, argv[3]);
               end_min = atoi(strtok(local_buffer, ":"));
               end_sec = atoi(strtok(NULL, ":"));
               end_frame = atoi(&local_buffer[strrchr(argv[3],c)+1-local_buffer]);

               start_sect = red2hsg(trackinfo_block[track].start_point)+(start_min*60L+((long)start_sec))*75L+((long)start_frame) -150L;
               end_sect = red2hsg(trackinfo_block[track].start_point)+(end_min*60L+((long)end_sec))*75L+((long)end_frame) -150L;
               num_sect = (unsigned int)(end_sect-start_sect);
               num_sect2 = num_sect;
               start_sect2 = start_sect;

               printf("Start sector     : %ld\n", start_sect);
               printf("End   sector     : %ld\n", end_sect);
               printf("Number of sectors: %d\n", num_sect);
               printf("CDDA data size   : %ld bytes\n", ((unsigned long)num_sect)*2352L);

               if(argc > 4) {
                       if((buffer = malloc(59000)) == NULL){
                               printf("Malloc error!\n");
                               errexit();
                       }

                       XMS_FreeMem(&xmsfree,&xmstot);
                       XMS_AllocEMB(xmsfree,&xmshandle);

                       if(xmsfree < ((unsigned long)num_sect)*2352L/1024L) {
                               printf("Not enough XMS memory!\n");
                               errexit();
                       }

                       drive = getdisk();
                       getdfree(drive+1,&diskfree);
                       avail =  (long) diskfree.df_avail *
                                (long) diskfree.df_bsec *
                                (long) diskfree.df_sclus;
                       if(avail < ((unsigned long)num_sect)*2352L) {
                               printf("Not enough disk space!\n");
                               errexit();
                       }

                       strcpy(filename,argv[4]);
                       outfp = fopen(filename,"wb");

                       if(strstr(strupr(filename), ".WAV"))
                       {
                               strncpy(wavhdr.riff, "RIFF", 4);
                               strncpy(wavhdr.wave, "WAVE", 4);
                               strncpy(wavhdr.fmt, "fmt ", 4);
                               wavhdr.fmt_len = 0x10L;
                               wavhdr.fmt_tag = 0x01;
                               wavhdr.channels = 0x02;
                               wavhdr.smp_sec = 44100L;
                               wavhdr.avg_smp_sec = 44100L;
                               wavhdr.blk_align = 0x02;
                               wavhdr.bits_per_sample = 16;
                               strncpy(wavhdr.data, "data", 4);

                               wavhdr.smpdata_len = ((unsigned long)num_sect)*2352L;
                               wavhdr.riff_len = wavhdr.smpdata_len + 36L;

                               fwrite(&wavhdr, sizeof(wavhdr), 1,outfp);
                       }

                       moverec.TLen = 58800L;          /*  2352 bytes * 25 sectors */
                       moverec.SHand = 0;
                       moverec.SOff = (unsigned long)buffer;
                       moverec.DHand = xmshandle;
                       moverec.DOff =  0L;

                       do{
                               sects_to_grab = 25;
                               if(num_sect2 < 25) {
                                       sects_to_grab = num_sect2;
                                       moverec.TLen = (unsigned long)(sects_to_grab*2352);
                               }

                               read_long(start_sect2, sects_to_grab);

                               while(readlong_block.req_hdr.status & STAT_HAS_AN_ERROR)
                                       read_long(start_sect2, sects_to_grab);

                               XMS_MoveEMB(&moverec);

                               start_sect2 += 25;
                               num_sect2 -= 25;
                               moverec.DOff += 58800L;

                       } while ((int)num_sect2 > 0);

                       moverec.TLen = 58800;
                       moverec.SHand = xmshandle;
                       moverec.SOff = 0L;
                       moverec.DHand = 0;
                       moverec.DOff = (unsigned long)buffer;

                       do {
                               sects_to_write = 25;
                               if(num_sect < 25) {
                                       sects_to_write = num_sect;
                                       moverec.TLen = (unsigned long)(sects_to_write*2352);
                               }

                               XMS_MoveEMB(&moverec);
                               fwrite(buffer,2,1176*sects_to_write,outfp);

                               moverec.SOff += 58800L;
                               num_sect -= 25;
                       } while ((int)num_sect > 0);

                       fclose(outfp);
                       free(buffer);
                       XMS_FreeEMB(xmshandle);
               }
               else
               {
                       printf("\nPlaying only...\n");
                       play(start_sect, (unsigned long)num_sect);
               }
       }
       else {
               printf("DAGRAB CD-DA Audio grabber v0.1\n");
               printf("usage: dagrab <track> <start MM:SS:FF> <end MM:SS:FF> [<filename>]\n");
       }
}


check_cd2f()
{
       union REGS regs;

       regs.x.ax = INIT_MP_INT;
       int86(MULTIPLEX_INT,&regs,&regs);
       cdrom_loaded = regs.x.bx;
       cdrom_drive = regs.x.cx;
}

get_diskinfo()
{
       union REGS regs;
       struct SREGS sregs;

       diskinfo_block.cntrl_code = GET_AUDIO_DISKINFO;

       ioctl_block.req_hdr.param_length = 13;
       ioctl_block.req_hdr.sub_unit     = 0;
       ioctl_block.req_hdr.command_code = READ_IOCTL_COMMAND;
       ioctl_block.req_hdr.status      = 0;
       ioctl_block.med_descr = 0;
       ioctl_block.transf_addr =  (unsigned long)&diskinfo_block;
       ioctl_block.num_bytes = 7;
       ioctl_block.start_sect = 0;
       ioctl_block.vol_id = 0;

       regs.x.ax = CDREQ_MP_INT;
       regs.x.bx = FP_OFF(&ioctl_block);
       regs.x.cx = cdrom_drive;
       sregs.es  = FP_SEG(&ioctl_block);

       int86x(MULTIPLEX_INT,&regs,&regs,&sregs);

       return(ioctl_block.req_hdr.status);
}

get_trackinfo(unsigned char trk)
{
       union REGS regs;
       struct SREGS sregs;

       trackinfo_block[trk].cntrl_code = GET_AUDIO_TRACKINFO;
       trackinfo_block[trk].track_num = trk;

       ioctl_block.req_hdr.param_length = 13;
       ioctl_block.req_hdr.sub_unit     = 0;
       ioctl_block.req_hdr.command_code = READ_IOCTL_COMMAND;
       ioctl_block.req_hdr.status      = 0;
       ioctl_block.med_descr = 0;
       ioctl_block.transf_addr = (unsigned long)&(trackinfo_block[trk]);
       ioctl_block.num_bytes = 7;
       ioctl_block.start_sect = 0;
       ioctl_block.vol_id = 0;

       regs.x.ax = CDREQ_MP_INT;
       regs.x.bx = FP_OFF(&ioctl_block);
       regs.x.cx = cdrom_drive;
       sregs.es  = FP_SEG(&ioctl_block);

       int86x(MULTIPLEX_INT,&regs,&regs,&sregs);

       return(ioctl_block.req_hdr.status);
}

play(unsigned long ssec,unsigned long numsecs)
{
       union REGS regs;
       struct SREGS sregs;

       struct play_struc play_block;
       struct qinfo_struc qinfo_block;

       play_block.req_hdr.param_length = 13;
       play_block.req_hdr.sub_unit     = 0;
       play_block.req_hdr.command_code = CD_PLAY_AUDIO;
       play_block.req_hdr.status      = 0;

       play_block.address_mode = ADDR_HSG;
       play_block.start_sect = ssec;
       play_block.num_sect = numsecs;

       regs.x.ax = CDREQ_MP_INT;
       regs.x.bx = FP_OFF(&play_block);
       regs.x.cx = cdrom_drive;
       sregs.es  = FP_SEG(&play_block);

       int86x(MULTIPLEX_INT,&regs,&regs,&sregs);

       do {
               qinfo_block.cntrl_code = GET_QCHAN_INFO;

               ioctl_block.req_hdr.param_length = 13;
               ioctl_block.req_hdr.sub_unit     = 0;
               ioctl_block.req_hdr.command_code = READ_IOCTL_COMMAND;
               ioctl_block.req_hdr.status      = 0;
               ioctl_block.med_descr = 0;
               ioctl_block.transf_addr = (unsigned long)&qinfo_block;
               ioctl_block.num_bytes = 11;
               ioctl_block.start_sect = 0;
               ioctl_block.vol_id = 0;

               regs.x.ax = CDREQ_MP_INT;
               regs.x.bx = FP_OFF(&ioctl_block);
               regs.x.cx = cdrom_drive;
               sregs.es  = FP_SEG(&ioctl_block);
               int86x(MULTIPLEX_INT,&regs,&regs,&sregs);

               if(kbhit()){
                       getch();
                       stop();
                       break;
               }
       } while (ioctl_block.req_hdr.status & STAT_BUSY);
}

stop()
{
       union REGS regs;
       struct SREGS sregs;

       struct reqhdr_struc stop_block;

       stop_block.param_length = 13;
       stop_block.sub_unit     = 0;
       stop_block.command_code = CD_STOP_AUDIO;
       stop_block.status      = 0;

       regs.x.ax = CDREQ_MP_INT;
       regs.x.bx = FP_OFF(&stop_block);
       regs.x.cx = cdrom_drive;
       sregs.es  = FP_SEG(&stop_block);

       int86x(MULTIPLEX_INT,&regs,&regs,&sregs);
       return(stop_block.status);
}

read_long(unsigned long ssec,unsigned int numsecs)
{
       union REGS regs;
       struct SREGS sregs;

       readlong_block.req_hdr.param_length = 13;
       readlong_block.req_hdr.sub_unit     = 0;
       readlong_block.req_hdr.command_code = CD_READ_LONG;
       readlong_block.req_hdr.status      = 0;
       readlong_block.address_mode = ADDR_HSG;
       readlong_block.transf_addr = (int far*)buffer;
       readlong_block.num_sect  = numsecs;
       readlong_block.start_sect  = ssec;
       readlong_block.read_mode   = RAW;
       readlong_block.interl_size  = 0;
       readlong_block.interl_skip = 0;

       regs.x.ax = CDREQ_MP_INT;
       regs.x.bx = FP_OFF(&readlong_block);
       regs.x.cx = cdrom_drive;
       sregs.es  = FP_SEG(&readlong_block);

       int86x(MULTIPLEX_INT,&regs,&regs,&sregs);

       return(readlong_block.req_hdr.status);
}

unsigned long red2hsg(unsigned long redval)
{
       unsigned long mins;
       unsigned long secs;
       unsigned long plus_frames;

       plus_frames = redval & 0x000000ffL;

       secs = ((redval & 0x0000ff00L) >> 8);

       mins = ((redval & 0x00ff0000L) >> 16);

       return((mins*60+secs)*75+plus_frames);
}


errexit()
{
       XMS_FreeEMB(xmshandle);
       exit(1);
}