From: SNYDER@d0sb06.fnal.gov
Newsgroups: comp.os.vms
Subject: Re: VAXstation 4000 sound port ?? (again)
Date: 22 Jul 92 06:30:33 GMT
Organization: SUNY Stony Brook High Energy Physics
In-reply-to: walsh@roads.sa.gov.au's message of 21 Jul 92 20:21:52 GMT

In article <1992Jul21.202152.46@roads.sa.gov.au> walsh@roads.sa.gov.au writes:
   Hi, does anyone know how to produce sound out of the headphone/telephone
       socket of a VAXstation VLC or model 60 ? ...

       Also, a file we sourced from a.b.s.misc and UUDECODEd gave us an error
       something like `record size too big' when we tried to copy it to the
       device mentioned below. Is that due to the file type created by the 'C'
       version of UUDECODE that we have ? 


If the record type of the file was stream_lf, then you'll probably run into
record length problems trying to use COPY to send it to the SO device (.au
files often don't have many 0x0a's in them).  One thing you could do is
convert the file to fixed length records.  You'll probably need to write
a program to do this; I don't think you can use CONVERT.  (bilf from the
zoo distribution would probably work; i also have a program which will do
this, but it's fairly trivial to write.)

However, I typically use a custom program to do the copy (which I'm
appending below).  This is for several reasons:

  - It can read large stream_lf files directly, as well as other file types.

  - If you copy a fixed-length file with a small record size to SO, you'll
    probably notice a distracting, rapid clicking.  SODRIVER seems to
    generate a click between each I/O (when it resets the AMD chip?  or maybe
    the next I/O can't get started fast enough?).  The only work-around
    I've been able to find is to make the individual I/Os as large as
    possible, so that the clicks become relatively infrequent.  (I'd love
    to hear of a way to get rid of the clicks entriely...)  With my own
    program, I can control the size of the I/Os to the SO device,
    independently of the attributes of the source file.

  - COPY doesn't do enough buffering to prevent audible glitches in many
    cases.  For example, I tried copying a file from 8mm tape to SO.
    There were extremely noticeable glitches in the sound in sync with
    the data transfers from the tape.  I try to keep several I/Os queued to
    the driver, both for the buffering and to attempt to reduce the
    turnaround time between I/Os.

WARNING:
  In VMS 5.5, SODRIVER has several bugs, the most serious of which
  can permanently hang a process during image exit:

  - The I/O completion code for write processing replaces IRP$W_BCNT
    with the number of bytes actually transferred.  Now, the driver locks
    the I/O buffer at the start of the operation with exe$writelock, and
    the QIO postprocessing code uses IRP$W_BCNT to figure out which pages
    to unlock.  The upshot is that if an I/O does not complete, some of
    the buffer pages will not be unlocked.  When the image exits, the
    delete-page code sees that there are still locked pages,  assumes
    that there is still an I/O in progress, and places the process in PFW
    until it completes.  Of course, the I/O has already finished, so the
    process hangs forever.  If you have source listings, you should be
    able to repair this fairly easily.  (If anyone's interested, I have
    some emacs code which will do most of the work in converting
    macro/C/bliss listings back into compilable source...)

  - The byte count for the transfer is of course 16 bits wide.  Testing
    for the end of the transfer is done with a _signed_ _word_comparison
    against 0.  Thus, any operations with a byte count >= 32768 complete
    immediately, without transferring any data (and will provoke
    the previous bug).

  - In any event, I/Os usually seem to stop after transferring ~21k-22k of
    data.  I haven't tracked this down yet; I'm wondering if something
    is timing out...


As an extra treat, here's a summary of the SODRIVER QIOs, gleaned from
the source listing.
I haven't dug into the input code, so don't ask me about that...

The hardware is based on the AMD 79C30A chip; anyone have a summary
of its functions?

IO$_WRITEVBLK:   p1=buffer, p2=buflen, p3=timeout (units of 10ms)
  I don't think the timeout parameter acually does anything.

IO$_READVBLK:    p1=buffer, p2=buflen
  I haven't gotten this to work yet.  I think that reading has to
  first be enabled with one of the other calls.

IO$_CANCEL:

IO$_ACCESS:      p3=code, p4=first parameter, p5=second parameter
  p3=0: no access  (turn off AMD chip?)
  p3=1: ringing tone (p3=frequency (Hz), p4=amplitude (0-100%))
  p3=2: single frequency tone (p3=frequency (Hz), p4=amplitude (0-100%))
  p3=3: DTMF tones (p3 [low word]=first freq (Hz), [high word]=second freq;
                    p4 [low word]=first amplitude (0-100%), [high word]=second)
  p3=4: play single note (p3=note index, p4=amplitude (0-100%))
  p3=5: play ringing note (p3=note index, p4=amplitude (0-100%))
  p3=6: play DTMF note (p3=notes, p4=amplitudes)
  p3=7: readstart (p3=record level (0-100))  (i'm not sure how this works...)

if C3 is middle C, note indices range from 0=B2 to B5, by half-steps.

IO$_ACPCONTROL:      p1=buffer, p2=buflen, p3=code, p4=regno
  p3=1: setqsize
  p3=2: getqsize
  p3=3: getreg
  p3=4: setreg
  p3=5: readstart
  p3=6: stop
  p3=7: pause
  p3=8: resume


IO$_SETMODE:   p1=characteristics


scott snyder
snyder@d0sb10.fnal.gov

----------------------- play.c -----------------------------


#include <stdio.h>
#include <file.h>
#include <iodef.h>
#include <descrip.h>

#define BSIZ 16385
#define MAX_IOS 4

volatile int n_ios = 0;
volatile int io_in_progress[MAX_IOS];
volatile struct {
  short stat, len;
  int dum;
} iosb[MAX_IOS];

void io_end (int bufi)
{
  --n_ios;
  io_in_progress[bufi] = 0;
}


int readbuf (int fd, char *buf, int blen)
{
  int n_read = 0, n;
  while (blen > 0 && (n = read (fd, buf, blen)) > 0) {
    if (n < blen && buf[n-1] == '\n') --n;
    n_read += n;
    buf += n;
    blen -= n;
  }
  return n_read;
}


main (int argc, char **argv)
{
  int fd, n, stat, efn, bufi;
  short chan;
  $DESCRIPTOR (dev_dsc, "soa0:");
  char buf[MAX_IOS][BSIZ];

  if (argc != 2) {
    fprintf (stderr, "usage: play <file>\n");
    exit(1);
  }

  fd = open (argv[1], O_RDONLY, 0);
  if (fd < 0) {
    perror ("opening input");
    exit(1);
  }

  stat = sys$assign (&dev_dsc, &chan, 0, 0);
  if ((stat&1) == 0) lib$signal (stat);

  stat = lib$get_ef (&efn);
  if ((stat&1) == 0) lib$signal (stat);

  bufi = 0;
  while ((n = readbuf (fd, buf[bufi], sizeof (buf[bufi]))) > 0) {
    printf ("%d %d\n", n_ios, n); fflush(stdout);

    printf ("iosb %d %d\n", iosb[bufi].stat, iosb[bufi].len);
    ++n_ios;
    io_in_progress[bufi] = 1;
    stat = sys$qio (efn, chan, IO$_WRITEVBLK, &iosb[bufi], io_end, bufi,
		    buf[bufi], n, 0, 0, 0, 0);
    if ((stat&1) == 0) lib$signal (stat);

    while (n_ios == MAX_IOS) {
      stat = sys$waitfr (efn);
      if ((stat&1) == 0) lib$signal (stat);
      stat = sys$clref (efn);
      if ((stat&1) == 0) lib$signal (stat);
    }
    for (bufi=0; bufi<MAX_IOS; bufi++)
      if ( ! io_in_progress[bufi]) break;
    if (bufi >= MAX_IOS) abort();
  }

  /* wait for all I/O's to complete! */
  while (n_ios > 0) {
    printf ("%d\n", n_ios);
    stat = sys$waitfr (efn);
    if ((stat&1) == 0) lib$signal (stat);
    stat = sys$clref (efn);
    if ((stat&1) == 0) lib$signal (stat);
  }

  for (bufi=0; bufi<MAX_IOS; bufi++)
    printf ("iosb %d %d\n", iosb[bufi].stat, iosb[bufi].len);

  close (fd);
  return 1;
}
Newsgroups: comp.os.vms