home *** CD-ROM | disk | FTP | other *** search
/ The C Users' Group Library 1994 August / wc-cdrom-cusersgrouplibrary-1994-08.iso / listings / v_02_12 / 2n12040a < prev    next >
Text File  |  1991-07-15  |  8KB  |  308 lines

  1. /*
  2.  * Listing 1: TOSPKR.C
  3.  * Play an audio file file out the PC speaker.
  4.  * Written for Turbo C 2.0, by Bob Bybee, 7/91
  5.  *
  6.  * usage: tospkr <filename> [<bits>]
  7.  *
  8.  *   <filename> is the audio file to be played.
  9.  *
  10.  *   <bits> is the number of speaker bits per sample.
  11.  *          if not entered, it will be calculated.
  12.  */
  13. #include <stdio.h>
  14. #include <stdlib.h>
  15. #include <mem.h>
  16. #include <bios.h>
  17. #include <alloc.h>
  18. #include <dos.h>
  19. #include <conio.h>
  20.  
  21. #ifndef __LARGE__
  22. #error must use LARGE memory model!
  23. #endif
  24.  
  25. /* audio sample rate, per second */
  26. #define SAMPRATE 8000
  27.  
  28. /*
  29.  * 80x86 opcodes for the instruction sequences we
  30.  * will need, in order to drive the PC speaker.
  31.  *
  32.  *  On entry, to set up the registers:
  33.  *      BA 61 00   mov     dx,61h
  34.  *      B5 4A      mov     ch,4ah
  35.  *      B1 48      mov     cl,48h
  36.  *
  37.  * (Note: the 4a/48 values, at offset 4/6, should be
  38.  * changed to agree with the current value of port 61H.)
  39.  *
  40.  * To set the speaker bit high:
  41.  *      8A C5      mov     al,ch
  42.  *      EE         out     dx,al
  43.  *
  44.  * To set it low:
  45.  *      8A C1      mov     al,cl
  46.  *      EE         out     dx,al
  47.  *
  48.  * A "far return" to end the routine:
  49.  *      CB         retf
  50.  */
  51. unsigned char ops_begin[] =
  52.     {0xba, 0x61, 0x00, 0xb5, 0x4a, 0xb1, 0x48};
  53. unsigned char ops_high[]  = {0x8a, 0xc5, 0xee};
  54. unsigned char ops_low[]   = {0x8a, 0xc1, 0xee};
  55. unsigned char ops_end[]   = {0xcb};
  56.  
  57. /* input buffer size, and pointer to it */
  58. #define INBUFSIZE 65000U
  59. unsigned char *inbuf;
  60.  
  61. /* array of audio segment lengths in file */
  62. #define MAX_SEGS 200
  63. unsigned int seg_length[MAX_SEGS];
  64. unsigned int num_segs = 0;
  65. char got_lengths = 0;
  66.  
  67. /* PFV is a pointer to a function taking
  68.  * no args, and returning void */
  69. typedef void (*PFV)( void );
  70. PFV playfuncs[256];
  71.  
  72. unsigned int bits_per_sample;
  73.  
  74. /* prototypes */
  75. void patch_opcodes( void );
  76. int best_rate( void );
  77. int test_time( int n_bits );
  78. void generate_codes( void );
  79.  
  80.  
  81. void main( int ac, char **av )
  82.     {
  83.     FILE *fp;
  84.     unsigned int nbytes, len, seg, n_to_read;
  85.     unsigned char *p;
  86.  
  87.     --ac;
  88.     ++av;
  89.     if (ac < 1)
  90.         {
  91.         printf("usage: tospkr <voicefile> [<bits>]\n");
  92.         exit(1);
  93.         }
  94.  
  95.     /* open the input file */
  96.     if ((fp = fopen(*av, "rb")) == NULL)
  97.         {
  98.         printf("Can't open voice file: %s\n", *av);
  99.         exit(1);
  100.         }
  101.  
  102.     /* allocate memory for the input buffer */
  103.     if ((inbuf = calloc(1, INBUFSIZE)) == NULL)
  104.         {
  105.         printf("Can't get memory for input buffer!\n");
  106.         exit(1);
  107.         }
  108.  
  109.     /* get the current value of port 61H */
  110.     patch_opcodes();
  111.  
  112.     if (ac >= 2 && (bits_per_sample = atoi(av[1])) > 0)
  113.         ;   /* got our sample rate on the command line */
  114.     else
  115.         bits_per_sample = best_rate();
  116.  
  117.     /* Create the 256 executable functions */
  118.     generate_codes();
  119.  
  120.     /* See if the file is prefixed with a list of audio
  121.      * segment lengths.  If so, read it into the
  122.      * seg_length[] array.  It contains unsigned ints,
  123.      * zero-terminated.
  124.      */
  125.     fread(inbuf, 4, 1, fp);
  126.     if (memcmp(inbuf, "LIST", 4) == 0)
  127.         {
  128.         got_lengths = 1;
  129.         do  {
  130.             fread(&len, 2, 1, fp);
  131.             seg_length[num_segs++] = len;
  132.             } while (len != 0);
  133.         }
  134.     else
  135.         ;   /* We could rewind here, but
  136.              * 4 bytes isn't worth it.
  137.              */
  138.  
  139.     /* Read in the file and play it thru the speaker.
  140.      * On each read, get the next segment's worth (if a
  141.      * list of segments exists), or as much as we can.
  142.      */
  143.     for (seg = 0; ; ++seg)
  144.         {
  145.         n_to_read =
  146.             got_lengths ? seg_length[seg] : INBUFSIZE;
  147.         if (n_to_read == 0 ||
  148.             (nbytes = fread(inbuf, 1, n_to_read, fp)) <= 0)
  149.                 break;
  150.  
  151.         p = inbuf;
  152.         disable();
  153.  
  154.         /* For each byte, call one of 256 functions which
  155.          * were created in the playfuncs[] array.
  156.          */
  157.         while (nbytes-- > 0)
  158.             (*playfuncs[*p++])();
  159.  
  160.         enable();
  161.         if (kbhit())    /* if a key was hit, */
  162.             {
  163.             getch();    /* eat the key, */
  164.             break;      /* and quit. */
  165.             }
  166.         }
  167.  
  168.     fclose(fp);
  169.     exit(0);
  170.     }
  171.  
  172.  
  173. /*
  174.  * Read from port 61H.  Use this value with bit 1
  175.  * set and cleared, in ops_begin[] opcodes.
  176.  */
  177. void patch_opcodes( void )
  178.     {
  179.     int val;
  180.  
  181.     val = inportb(0x61);
  182.     val &= ~0x03;               /* lose bits 0, 1 */
  183.     ops_begin[6] = val;
  184.     ops_begin[4] = val | 0x02;  /* add bit 1 */
  185.     }
  186.  
  187.  
  188. /*
  189.  * Calculate the number of bits we can play to the
  190.  * speaker, for each audio sample, in order to make
  191.  * the playback come out at SAMPRATE samples
  192.  * per second.
  193.  */
  194. int best_rate( void )
  195.     {
  196.     int bits1, bits2, ticks1, ticks2;
  197.     float fbits;
  198.  
  199.     /* Start with 10 bits/sample.  Double it until
  200.      * it takes at least one second (18 ticks) */
  201.     printf("I'm timing your CPU, please wait...\n");
  202.     bits1 = 10;
  203.     while ((ticks1 = test_time(bits1)) < 18)
  204.         bits1 *= 2;
  205.  
  206.     /* Now time it at 5x this number of bits. */
  207.     bits2 = bits1 * 5;
  208.     ticks2 = test_time(bits2);
  209.  
  210.     /* Interpolate to get the optimum number of bits
  211.      * per sample for this PC. */
  212.     fbits = (18.2 - ticks1) * (bits2 - bits1);
  213.     fbits = fbits / (ticks2 - ticks1) + bits1;
  214.     return ((int)fbits);
  215.     }
  216.  
  217.  
  218. /*
  219.  * Build a set of instructions into inbuf, which will
  220.  * play n_bits out to the speaker.  Return the number
  221.  * of BIOS ticks it takes to play this many bits out,
  222.  * SAMPRATE times.
  223.  */
  224. int test_time( int n_bits )
  225.     {
  226.     unsigned char *p;
  227.     int i;
  228.     long t1, t2;
  229.     PFV testcode;
  230.  
  231.     p = inbuf;
  232.     memcpy(p, ops_begin, sizeof(ops_begin));
  233.     p += sizeof(ops_begin);
  234.     for (i = 0; i < n_bits; ++i)
  235.         {
  236.         memcpy(p, ops_high, sizeof(ops_high));
  237.         p += sizeof(ops_high);
  238.         }
  239.     memcpy(p, ops_end, sizeof(ops_end));
  240.  
  241.     testcode = (PFV)inbuf;
  242.     t1 = biostime(0, 0L);
  243.     for (i = 0; i < SAMPRATE; ++i)
  244.         (*testcode)();
  245.     t2 = biostime(0, 0L) - t1;
  246.     printf("bits: %d  ticks: %d\n", n_bits, (int)t2);
  247.     return (int)t2;
  248.     }
  249.  
  250.  
  251. /*
  252.  * Generate opcode streams, in allocated memory, to push
  253.  * the proper bit patterns out to the PC speaker.
  254.  */
  255. #define DEBUG_WAVE 0    /* 1 to enable printouts */
  256. void generate_codes( void )
  257.     {
  258.     unsigned char *p;
  259.     unsigned int value, sum, i;
  260.  
  261.     printf("generating code for %d bits per sample...\n",
  262.         bits_per_sample);
  263.     for (value = 0; value < 256; ++value)
  264.         {
  265.         /* Get memory for the next instruction stream
  266.          * we're going to create. */
  267.         p = calloc(1, sizeof(ops_begin) + sizeof(ops_end) +
  268.             bits_per_sample * sizeof(ops_high));
  269.         if (p == NULL)
  270.             {
  271.             printf("out of memory at value %d\n", value);
  272.             exit(1);
  273.             }
  274.  
  275.         playfuncs[value] = (PFV)p;
  276. #if DEBUG_WAVE
  277.         printf("\n%3d: ", value);
  278. #endif
  279.         memcpy(p, ops_begin, sizeof(ops_begin));
  280.         p += sizeof(ops_begin);
  281.  
  282.         /* evenly distribute the ones over the bits. */
  283.         sum = 0;
  284.         for (i = 0; i < bits_per_sample; ++i)
  285.             {
  286.             sum += value;
  287.             if (sum >= 255)
  288.                 {
  289.                 sum -= 255;
  290.                 memcpy(p, ops_high, sizeof(ops_high));
  291. #if DEBUG_WAVE
  292.                 putchar('1');
  293. #endif
  294.                 }
  295.             else
  296.                 {
  297.                 memcpy(p, ops_low, sizeof(ops_low));
  298. #if DEBUG_WAVE
  299.                 putchar('.');
  300. #endif
  301.                 }
  302.             p += sizeof(ops_low);
  303.             }
  304.         memcpy(p, ops_end, sizeof(ops_end));
  305.         }
  306.     }
  307.  
  308.