// SoundDev.cpp // General sound functions that can be applied to derived sound classes // (eg SbDevice) // Written by Christopher M. Box (1993) #include #include #include #include #include "sndclass.h" #define DEFAULTRECORDLEN 10000000L // 10 megabytes // Forward function definitions static void bar_display(char val); static void bar_display(int val); // Function: setsizes // Given a buffer size ('bufsize') and remaining file length ('len'), this // determines lengths for the lower and upper half-buffers. It is used four // times in the file_dma function, so it is brought out here as an inline // function. inline void SoundDevice::setsizes(unsigned &bufsize, long &len, unsigned &lo_sz, unsigned &hi_sz) { len -= (lo_sz = (bufsize < len) ? bufsize : (unsigned) len); len -= (hi_sz = (bufsize < len) ? bufsize : (unsigned) len); } // Function: file_dma // Handles the running of buffered DMA from a sound device to a disk file, // or in the opposite direction. 'handle' is the handle of a file opened // with _open(), 'lobuf' is a pointer to the bottom of the single DMA buffer, // 'bufsize' is the size of it (up to a maximum of 64K), 'len' is the desired // length of file to play (or zero for the full length) and 'dir' sets the // direction (recording or playback). void SoundDevice::file_dma(int handle, byte far *lobuf, unsigned bufsize, long len, byte dir) { reset(); byte far *hibuf = lobuf + bufsize; // Calculate pos of high half-buffer unsigned lo_sz, hi_sz, cur_sz; dt_min=65535U; // Initialise disk time statistics dt_max=0; if (dir == RECORD) { if (len == 0) len = DEFAULTRECORDLEN; setsizes(bufsize,len,lo_sz,hi_sz); buf_dma_start(lobuf,lo_sz+hi_sz,RECORD); // Wait for the transfer to start to avoid confusing buf_dma_lo(). while (dma_addr() == 0); while (1) { if (lo_sz == 0) break; if (buf_dma_lo(lo_sz)) { // if terminated by keypress lo_sz = dma_addr(); Dprint(("Saving up to position %u.\r\n",lo_sz)); hi_sz = 0; } if (disk_io(handle,lobuf,lo_sz,RECORD)) return; if (hi_sz == 0) break; cur_sz = hi_sz; setsizes(bufsize,len,lo_sz,hi_sz); if (buf_dma_hi(cur_sz,lo_sz+hi_sz)) { cur_sz = dma_addr() - bufsize; Dprint(("Saving hibuf, length %u.\r\n",cur_sz)); lo_sz = 0; } if (disk_io(handle,hibuf,cur_sz,RECORD)) return; } } else { // Play function if (len == 0) len = filelength(handle); setsizes(bufsize,len,lo_sz,hi_sz); if (lo_sz == 0) { cprintf("Error: zero length.\r\n"); exit(-1); } if (_read(handle,lobuf,lo_sz) != lo_sz) { // Fill first buffer cprintf("File read error.\r\n"); exit(-1); } buf_dma_start(lobuf,lo_sz+hi_sz,PLAY); while (dma_addr() == 0); // Wait for it to start while (1) { // Main buffer loop - forever if (hi_sz == 0) { buf_dma_lo(lo_sz); break; } else { if (disk_io(handle,hibuf,hi_sz,PLAY)) return; if (buf_dma_lo(lo_sz)) break; } if (len == 0) { buf_dma_hi(hi_sz, 0); break; } else { setsizes(bufsize,len,lo_sz,hi_sz); if (disk_io(handle,lobuf,lo_sz,PLAY)) return; if (buf_dma_hi(bufsize, lo_sz+hi_sz)) break; } } } // At the end print out min and max times per disk access, // in terms of both bytes and milliseconds. unsigned bytes_per_ms = (unsigned) (((long) get_rate() * get_width() + 500) / 1000); cprintf("Disk stats: min %u (%ums), max %u (%ums).\r\n", dt_min, dt_min / bytes_per_ms, dt_max, dt_max / bytes_per_ms); } // Function: disk_io // Takes care of the time-consuming disk reads/writes of file 'handle' // from/into buffer 'buf', of 'len' bytes in direction 'dir'. // Automatically measures the time taken in terms of the number of bytes // advanced by the DMA and updates the min/max times. int SoundDevice::disk_io(int handle, byte far *buf, unsigned len, byte dir) { unsigned old_addr = dma_addr(); if (dir == RECORD) { if (_write(handle,buf,len) != len) { cprintf("Write error: disk full?\r\n"); return 1; } } else { if (_read(handle,buf,len) != len) { cprintf("File read error.\r\n"); return 1; } } unsigned new_addr = dma_addr(); unsigned disktime = new_addr - old_addr; if (new_addr < old_addr) { Dprint(("Long disk wait. ")); // Overflow - can't measure the time } else { if (disktime && disktimedt_max) dt_max = disktime; Dprint(("DT = %X. ",disktime)); } return 0; } // Function: max // Defined both for integers and bytes, for use in monitor_input inline int max(int v1, int v2) { return (v1 > v2 ? v1 : v2); } inline byte max(byte v1, byte v2) { return (v1 > v2 ? v1 : v2); } // Function: monitor_input // Displays input levels by continously reading in DMA for 'blocklen' bytes // and showing the maximum value within the block as a bar graph from left // to right. 80 columns represents full scale. The value chosen for // 'blocklen' affects the responsiveness and accuracy of the bar graph. // I have included code for both the SB (unsigned mono bytes) data format // and the CD (signed stereo words) format to demonstrate how both types can // be used. void SoundDevice::monitor_input(byte far *dmabuf, unsigned blocklen) { if (blocklen > 65000) { cprintf("Use a smaller block length.\r\n"); return; } blocklen &= ~3; // Round down to a double-word boundary byte maxbyte; int maxleft, maxright; byte far *bdata; // Different pointer types for SB (byte) int far *idata; // and CD (signed int) byte far *data_end = dmabuf + blocklen; clrscr(); // Start by clearing the screen and disabling the cursor _setcursortype(_NOCURSOR); while (1) { buf_dma_start(dmabuf,65500U,RECORD); while ((dma_addr() < blocklen) && !kbhit());// Wait for buffer to fill halt(); // Prevent further DMA writes reset(); if (kbhit()) break; maxbyte = 0; // Initialise max variables maxleft = maxright = -MAXINT; switch(identify()) { // Execute different loops depending on data type case SB_ID: for (bdata = dmabuf; bdata < data_end; bdata++) { maxbyte = max(maxbyte, *bdata); } break; case CD_ID: for (idata = (int *) dmabuf; idata < (int *) data_end; idata++) { maxleft = max(maxleft, *idata); idata++; maxright = max(maxright, *idata); } break; } gotoxy(1,1); // Move invisible cursor to top left switch (identify()) { case SB_ID: // Subtract 0x80 before displaying bar_display((char) (maxbyte - 128)); break; case CD_ID: bar_display(maxleft); bar_display(maxright); break; } } getch(); // Remove character from keyboard buffer _setcursortype(_NORMALCURSOR); // Restore cursor return; } // Function: bar_display (signed byte) // Converts byte (0 to 7F) to int and calls the real bar_display function static void bar_display(char val) { int ival = val * (MAXINT / 127); bar_display(ival); } // Function: bar_display (int) // Prints a row of asterisks corresponding to the size of 'val'. Always // completes the line with spaces to eliminate any previous characters. static void bar_display(int val) { if (val < 0) val = 0; int bar_len = val / (MAXINT / 80); int i; for (i = 0; i < 80; i++) { putch(i <= bar_len ? '*' : ' '); } return; }