/*
  DCCCONV 2.0 (C) 1994-2003 Jac Goudsmit (Thanks to Kees Haasnoot and
  Franc Zijderveld)
  This program is freeware. You are allowed to use it in any way you wish, 
  as long as the above copyright statement (and this notice) is retained.

  This command-line program converts a PASC file (as generated by the
  PHILIPS DCC-Studio program) so that it can be played with all popular
  MPEG1 layer I decoders.

  What is the problem?

  After much investigation, I have finally found out what the difference
  between MPEG1 layer I (MP1) and PASC (Precision Adaptive Subband Coding,
  the format used by Philips Digital Compact Cassette - DCC) is: NOTHING!

  The problem is in the decoders that most people use, that are not fully
  compatible with one little thing in the MP1 standard (ISO 11172-3, which
  also describes the better-known MP3 standard). Thanks to Kees Haasnoot,
  Franc Zijderveld (Philips Digital Systems Laboratories) and an anonymous
  source who let me have a quick glance at the ISO 11172-3 document, I now
  know this.

  When a 44.1kHz audio stream is converted to a 384kbps MP1 frame, the
  result is an average frame size of 417.959183 bytes. The MP1 standard
  however requires frames to have lengths of multiples of 4 bytes. Every
  DCC recorder knows how to intermingle frames of 416 bytes with frames
  of 420 bytes to make an average of 384000 bits per second. In order to
  make the servo system (which controls the speed of the tape during 
  playback) and other systems inside DCC recorders as simple as possible,
  PASC always uses 420 bytes on the tape, and on the harddisk if you
  use the DCC-Studio program. 

  An MPEG header consists of the following bits:
  sync word: always 1111 1111 1111
  ID: always 1 for MPEG audio
  layer: 11 for layer I
  protection bits: 1 for no CRC
  bitrate: 1100 for 384kbps
  sample frequency: 00 for 44.1kHz
  PADDING BIT: 1 if the frame "contains an additional slot to adjust the
    mean bitrate to the sampling frequency".
  private bit: 0 (reserved)

  In hexadecimal bytes, this is represented as FF FF FF C0 or FF FF FF C2.
  
  So, PASC uses the padding bit (and adds 32 bits with value 0) at the end
  of each frame, whenever only 416 bytes are needed to store the MPEG1 layer
  I information. This is perfectly legal according to ISO11172-3.

  Of course, this padding is only necessary because the storage medium is
  tape instead of disk. In a disk file, the extra 4 bytes are a waste of
  space. So, most MP1 encoders don't use padding and most decoders simply
  ignore the padding bits because the programmers were too lazy or didn't
  understand what is meant by "contains an additional slot to adjust the
  mean bitrate to the sampling frequency". This wouldn't be such a big 
  deal if those decoders would also simply ignore the extra bits on the 
  padded frames, but they don't, get out of sync and stop playing, or
  just play garbage.

  This program will read a PASC-encoded file and will remove all padding
  from all the frames in the file. It also removes the 2-byte header at
  the start of the file which the DCC-Studio puts there, and which has no 
  apparent function. (If MPEG decoders would ignore any data until they
  would find the sync word, this would also not be necessary - but they
  don't). The program copies all the other data as-is. Therefore there
  is no loss whatsoever from using this program.

  The use of 420-byte tape frames makes it MUCH easier for programmers to
  write a program like DCC-Studio to edit the PASC data on harddisk, 
  because each frame of data is exactly n * 420 bytes from the start of 
  the file (actually 2 + n * 420 bytes because of the 2-byte file header).
  The .TRK files that DCC-Studio uses, use frame numbers instead of 
  byte-offsets. Maybe one day I will write a Windows program that 
  interprets the .TRK files, but for now you will have to use the "Save
  Audio as One File" option in DCC-Studio.

  Acknowledgements:
  - The person at Philips who I got in contact with around 1994, who 
    explained that PASC was basically MPEG, sorry I lost your name
  - Maarten Eijkhout for keeping the first (crude) version of this program
    on his website, then taking it off and not replying emails. (Stupidly,
    I lost the original source code so this is a complete rewrite)
  - C.A.G. (Kees) Haasnoot for biting into the problem once again and 
    sending me source code for a fix in Turbo Pascal, and offering to put
    it online on the DCC-FAQ website.
  - Franc Zijderveld at Philips Digital System Laboratories, Eindhoven,
    The Netherlands, for answering Kees' questions.
  - The anonymous person(s) who put the ISO11172-3 document online in a
    dark corner of the internet (Google for it yourself), which finally
    solved the last piece of the puzzle.

  The reasons I'm not using Kees Haasnoot's code are:
  - It was written in Turbo Pascal and I don't have a compiler for it
  - It needed interactive input for the file names instead of using command
    line input.
  - The program didn't really interpret the bit fields as this program does
    (it just checked for C2 or C0), and didn't reset the padding bit (which
    I know now actually makes the bitstream NON-compliant with MP1). 
  - The program handled one frame at a time: not very efficient, and error
    handling was far from perfect. 
  Nevertheless, your input was very much appreciated, Kees!

  The following program is ANSI C. It has been tested under Visual C++ 1.5
  (16-bit DOS application) and Visual C++ 6.0 (32-bit Windows console app).
  Other compilers should work equally well.
  Note that because of the buffering that the C runtime library may do on
  fread/fwrite, it's possible that the program seems to "hang" sometimes
  when the buffers get flushed. Be patient, all will go back to normal after
  a while. The program was tested with several files that contained more
  than an hour's worth of PASC (more than you can get on one side of a tape!)
  and the results were played from front to back on a pair of headphones
  to check the result.

  For more information on DCC, go to the DCC FAQ at 
  http://www.xs4all.nl/~jacg/dcc-faq.html
*/

#include <stdio.h>
#include <malloc.h>
#include <string.h>


#ifdef _DOS
  /* poor 16-bit Visual C++ 1.5 can't use arrays that are more than 32K
     77*420 = 32340 bytes */
  #define BLOCKSATONCE (77)
#else
  #define BLOCKSATONCE (100)
#endif

void processfile(FILE *fin, FILE*fout)
{
  unsigned char buf[BLOCKSATONCE * 420];
  unsigned long offset = 2;
  unsigned long oldoffset;
  unsigned long outdone = 0;
  size_t numtowrite = 0;
  size_t numread = BLOCKSATONCE;

  do
  {
    unsigned long u;
    unsigned char *s = buf;
    unsigned char *t = buf;
    
    numread = fread(buf, 420, BLOCKSATONCE, fin);

    oldoffset = offset;

    for (u = 0; u < numread; u++)
    {
      unsigned sz = 420;

      /* Even though the padding bit and the extra 4 bytes at the end of the
         block are compliant to the MPEG1 layer 1 standard (ISO 11172), 
         most decoders don't understand them so they are removed here. */
      if ((s[2] & 2) == 0) /* Padding bit in MPEG1 header */
      {
        s[2] &= ~2; /* Reset padding bit */
        sz -= 4;
      }

      if (t != s)
      {
        memcpy(t, s, sz);
      }

      s += 420;
      t += sz;

      offset += 420;
    }

    numtowrite = t - buf;

    u = oldoffset/1024;
    printf("%luK\x08", u);
    do
    {
      printf("\x08");
    } while ((u/=10)!=0);
    
    if (numtowrite)
    {
      unsigned long oldoutdone = outdone;
      outdone += fwrite(buf, 1, numtowrite, fout);

      if (oldoutdone == outdone)
      {
        break;
      }
    }
  } while (numread == BLOCKSATONCE);

  printf("%lu input bytes, %lu output bytes.\n", offset, outdone);
}

int main(int argc, char *argv[])
{
  int result = 0;

  fprintf(stderr, 
    "DCC to MP1 converter 2.00 (C) 1994-2003 Jac Goudsmit\n\n");

  if (argc < 2)
  {
    fprintf(stderr, 
      "Syntax: dccconv inputfile [inputfile...]\n"
      "\n"
      "Target files have their name changed to *.mp1 and are stored in the same\n"
      "folder as the source file\n");

    result = 1;
  }
  else
  {
    int i;

    for (i = 1; i < argc; i++)
    {
      FILE *fin;

      fin = fopen(argv[i], "rb");
      if (fin)
      {
        unsigned char header[2];

        if ( (fread(header, 1, 2, fin) == 2)
          && (header[0] == 0x2C) 
          && (header[1] == 0))
        {
          FILE *fout;
          char *fname;
          char *p = NULL;
          char *t, *s;

          fname = malloc(strlen(argv[i]) + 4);
          if (fname)
          {
            for (t = fname, s = argv[i]; *s; t++, s++)
            {
              if (*s == '.')
              {
                p = t;
              }
              *t = *s;
            }

            if (!p)
            {
              p = t;
            }

            strcpy(p, ".mp1");

            fout = fopen(fname, "wb");
            if (fout)
            {
              fprintf(stderr, "%s -> %s: ", argv[i], fname);

              processfile(fin, fout);
              if (ferror(fin))
              {
                fprintf(stderr, "Error reading input, partial file processed");
                result = 2;
              }
              if (ferror(fout))
              {
                fprintf(stderr, "Error writing output, partial file processed");
                result = 2;
              }
              fprintf(stderr, "\n");

              fclose(fout);
            }
            else
            {
              fprintf(stderr, "%s: error opening file for output\n", fname);
              result = 2;
            }
            free(fname);
          }
          else
          {
            fprintf(stderr, "Error allocating memory for output filename for %s\n", argv[i]);
            result = 2;
          }
        }
        else
        {
          fprintf(stderr, "File %s does not seem to be a PASC file (Starts with \\x%02X \\x%02X\n instead of 2C 00)\n", argv[i], header[0], header[1]);
          result = 2;
        }

        fclose(fin);
      }
      else
      {
        fprintf(stderr, "%s: error opening file for input.\n", argv[i]);
        result = 2;
      }
    }
  }

  return result;
}

