NAME

dmasound - DMA D/A sound user routines

SYNOPSIS

#include <dmasound_u.h>

unsigned *ToneSinCreate(channel, freq, cycles, amp, ramp)
unsigned channel;
double freq, cycles, amp, ramp;

unsigned *ToneSquareCreate(channel, period, cycles, amp, ramp)
unsigned channel;
double period, cycles, amp, ramp;

unsigned *NoiseCreate(channel, dur, amp)
unsigned channel;
double dur, amp;

unsigned *SoundRead(filename)
char *filename;

int SoundPlay(sound)
unsigned *sound;

void SoundFree(sound)
unsigned *sound;

DESCRIPTION

These routines create and play sound via the DT 2771 DMA D/A converter. Output can take the form of pure tones, white noise, or arbitrary sound such as speech. Monaural and binaural (monophonic and stereophonic) output are supported.

ToneSinCreate(channel, freq, cycles, amp, ramp)
ToneSinCreate() creates a SoundPlay() -compatible representation of a pure tone sine wave. Use caution in applying ToneSinCreate() on a trial-by-trial basis, as the creation of a typical binaural ramped 100msec tone takes approximately 1 second. The resulting sine wave can be thought of as starting at a zero crossing. If ToneSinCreate() detects an error, an explantory message is printed to stderr, and NULL is returned.

Channel is one of {DMA_SOUND_LEFT, DMA_SOUND_RIGHT, DMA_SOUND_BOTH}.

Freq must be in the range [0.0:(DMA_SOUND_FREQ/2)].

Cycles is a measure of duration. Specifying cycles in multiples of 0.5 will tend to yield tone offsets that have less of an audible click (the sampled sine wave will tend to be close to zero). Note that a final zero-ing sample is always appended to the sound object. Amp specifies one-half the peak-to-peak amplitude and must be in the range [0.0:DMA_SOUND_MAXAMP]. DMA_SOUND_MAXAMP is currently 2047.

Ramp specifies the duration (in msec) of the amplitude ramp. Both the onset and offset ramps are of ramp msec in duration. The ramp is in the form of an inverted half-cosine Haan window.

ToneSquareCreate(channel, period, cycles, amp, ramp)
ToneSquareCreate() creates a SoundPlay() -compatible representation of a "pure tone" square wave. Use caution in applying ToneSquareCreate() on a trial-by-trial basis, as the creation of a typical binaural ramped 100msec tone takes approximately 250 msec. Output filtering can have a significant effect on square waves. If ToneSquareCreate() detects an error, an explantory message is printed to stderr, and NULL is returned.

Channel is one of {DMA_SOUND_LEFT, DMA_SOUND_RIGHT, DMA_SOUND_BOTH}.

Period is in msec. The period must be a multiple of 2/DMA_SOUND_FREQ (sec) (this is due to a combination of the quantization of digital representation and the rather discrete (relative to sine waves) nature of square waves).

Cycles is a measure of duration. Note that a final zero-ing sample is always appended to the sound object.

Amp specifies one-half the peak-to-peak amplitude and must be in the range [0.0:DMA_SOUND_MAXAMP] (DMA_SOUND_MAXAMP is currently 2047).

Ramp specifies the duration (in msec) of the amplitude ramp. Both the onset and offset ramps are of ramp msec in duration. The ramp is in the form of an inverted half-cosine Haan window.

NoiseCreate(channel, dur, amp)
NoiseCreate() creates a white-noise-resembling SoundPlay() -compatible sound object. The sound object is created by repeatedly sampling with replacement from a 1000-member gaussian distribution. Since the sampling is random, the user may wish to be aware of the state of the random seed. Note that if DT 2771 output is filtered, the result may sound less "white". NoiseCreate() requires approximately 400 msec to create a binaural 100 msec noise. If NoiseCreate() detects an error, an explanatory message is printed to stderr, and -1 is returned.

Channel is one of {DMA_SOUND_LEFT, DMA_SOUND_RIGHT, DMA_SOUND_BOTH}.

Amp specifies one-half the peak-to-peak amplitude and must be in the range [0.0:DMA_SOUND_MAXAMP] (DMA_SOUND_MAXAMP is currently 2047).

Dur is in msec.

SoundRead(filename)
SoundRead() creates a SoundPlay() -compatible sound object from a conforming user-supplied parasite disk file. SoundRead() requires approximately 1 second to read and process a binaural 100 msec sound file. The file format is:


#comment
n_samples 
channel {left,right,both}
sample sample sample ...
\...
An example:
#a sound of extremely short duration
n_samples 10
channel   both
128  128
128  128
256  256
256  256
512  512
512  512
256  256
256  256
128  128
128  128
comment lines must begin with a '#' in the first character position. All comment lines must precede the header and data. The n_samples line must be the first non-comment (and non-blank) line in the file. The n_samples keyword must be followed by whitespace and a positive non-zero value. If binaural output is called for, the value of n_samples should reflect the number of output pairs. The channel line must immediately follow the n_samples line. The channel keyword must be followed by whitespace and one of {left,right,both}. The samples section holds the digital values that are to be converted. Multiple samples can appear on one line -- the samples for one pure tone sine wave cycle can conveniently appear on one line. If the channel is both, the samples section should contain one value for the left ear followed by whitespace and the value for the right ear, followed by another pair, etc.. The digital values in the samples section are restricted to a 12-bit range (DMA_SOUND_MINAMP:DMA_SOUND_MAXAMP) (DMA_SOUND_MINAMP is currently -2047, DMA_SOUND_MAXAMP is currently 2047). If SoundRead() detects an error, an explanatory message is printed to stderr, and NULL is returned.

SoundPlay(sound)
SoundPlay() takes a pointer to a sound object and plays it via the DT 2771 DMA D/A. Sound objects are normally created by ToneSinCreate(), ToneSquareCreate(), NoiseCreate(), or SoundRead(). The latency from SoundPlay() call to first output has been conservatively measured to be < 500 microseconds. As a DMA routine, SoundPlay() returns control to the caller very shortly after the sound begins playing. The progress of SoundPlay() can be monitored via the volatile global variable ddadone (see dmada(3U) ). If SoundPlay() detects an error, an explanatory messaged in printed to stderr, and -1 is returned.

SoundFree(sound)
SoundFree() frees the memory taken up by the sound object.

EXAMPLES

The following example reads and repeatedly plays sound information from a user-supplied parasite disk file.
#include <stdio_p.h>
#include <dmasound_u.h>
#include <clock_u.h>

int main() {

  unsigned *sound;
  char      FileName[100];

  printf("Enter sound filename: ");
  scanf("%s",FileName);

  sound = SoundRead(FileName);
  if (sound == NULL) errexit(1,"error returned by SoundRead()\\n");

  for (;;) {
    ckreset(CKR_1MS);
    ckstart;
    if (SoundPlay(sound)) errexit(1,"error returned by SoundPlay()\\n");
    while(ddadone==0);
    ckpause(1000);
    ckstop;
  }
}
The following example creates and repeatedly plays a pure tone sine wave and a noise.
#include <stdio_p.h>
#include <dmasound_u.h>
#include <clock_u.h>

int main() {

  unsigned *tone;
  unsigned *noise;
  unsigned  channel = DMA_SOUND_LEFT;
  double    freq    = 1000.0;  /* in Hz */
  double    cycles  = 100.0;
  double    dur     = 100.0;   /* in msec */
  double    amp     = 256.0;
  double    ramp    = 10.0;    /* in msec */

  tone = ToneSinCreate(channel,freq,cycles,amp,ramp)
  if (tone == NULL) errexit(1,"error returned by ToneSinCreate()\\n");

  noise = NoiseCreate(channel,dur,amp);
  if (noise == NULL) errexit(2,"error returned by NoiseCreate()\\n");

  for (;;) {
    ckreset(CKR_1MS);
    ckstart;
    if (SoundPlay(tone))  errexit(3,"error returned by SoundPlay()\\n");
    if (SoundPlay(noise)) errexit(4,"error returned by SoundPlay()\\n");
    while(ddadone==0);
    ckpause(1000);
    ckstop;
  }
}
The following functions may be useful for examing the internals of a sound object.
/* is the current sample the start of a new sine-wave cycle? */
int NewSinCycle(current,previous) unsigned *current, *previous; {
 
  if (current==NULL)  return(0);
  if (previous==NULL) return(0);
  if ((int)((*previous & 0x0FFF) << 4) >  0) return(0);
  if ((int)((*current  & 0x0FFF) << 4) <= 0) return(0);
  return(1);
}

/* take the unsigned channel value and print a symbolic. */
void PrintChannel(channel) unsigned channel; {
 
  switch((int)channel) {
    case DMA_SOUND_LEFT:  printf("channel=DMA_SOUND_LEFT\\n"); return;
    case DMA_SOUND_RIGHT: printf("channel=DMA_SOUND_RIGHT\\n"); return;
    case DMA_SOUND_BOTH:  printf("channel=DMA_SOUND_BOTH\\n"); return;
    default: printf("unrecognized channel=%u\\n",channel);
  }
}

/* translate a 12-bit unsigned sample into a signed integer. */
int stoi(sample) unsigned sample; {
 
  int signed;
 
  signed = (int)(sample & 0x0FFF);  /* extract 12-bit sample */
  if ((int)(sample << 4) > 0)  return(signed);
  if ((int)(sample << 4) == 0) return(signed);
  if ((int)(sample << 4) < 0)  return(signed-4096);
  fprintf(stderr,"Hmmmm\\n");
  exit(-1);
#ifdef lint
  return(-1);
#endif
}


/* pretty-print a sine-wave sound object. */
void PrintTone(tone) unsigned *tone; {
 
  unsigned *previous, *current;
  unsigned  sample;
 
  printf("n_samples=%u\\n",tone[0]);
  PrintChannel(tone[1]);
  current = tone + DMA_SOUND_HEADER_SIZE;
  previous = NULL;
  for (sample=0; sample < tone[0]; sample++) {
    if (NewCycle(current,previous)) printf("\\n");
    if (tone[1]==DMA_SOUND_BOTH)
      printf("%5d %5d",stoi(*current), stoi(*(current+1)));
    else printf("%5d ",stoi(*current));
    previous = current;
    current += (tone[1]==DMA_SOUND_BOTH) ? 2 : 1;
  }
  printf("\\n\\n");
}

DETAILS

Hardware
The Data Translation DT 2771 is a 2-channel, 12-bit direct memory access (DMA) digital-to-analog (D/A) converter. The DT 2771 is currently being driven by a 10KHz external oscillator. To avoid aliasing, 10KHz DT 2771 output should be filtered by a pair of low-pass 4KHz filters. The dmasound routines are blissfully unaware of any filtering.

Format of Sound Object
The user is free to create sound objects without the help of SoundRead(), ToneSinCreate(), ToneSquareCreate(), or NoiseCreate(). The format of a sound object is:

  static unsigned tone[] = {
    
    ,   /* DMA_SOUND_LEFT or DMA_SOUND_RIGHT or DMA_SOUND_BOTH */
    sample_val1,
    sample_val2,
    ...
  };
The result can be played by SoundPlay(). This technique may be useful to the user wishing to anticipatorally create a large number of [static] tones, thus bypassing the overhead of SoundRead(), ToneSinCreate(), etc.. For monaural output, the sample must be marked (i.e. a single bit changed) to reflect the channel -- see dmada or the source to this library for marking details.

LIMITATIONS

No Recording/Sampling Functionality
No functionality is provided for recording 12-bit 10KHz sound. At one point parasite was used for this purpose, so it is capable, probably with the DMA A/D. Sound can be recorded/created elsewhere and transferred to parasite. If the sound can be described succinctly mathematically, S-PLUS and MATLAB can be helpful. Sampling can be done by a Sun/PC/Mac, but 12-bit 10KHz is not very common. The sox program is handy for converting from one audio format to another, possibly custom, format. sox supports sample-width and sample-rate conversion, but often with a noticable degradation in playback quality. In most Sun/PC/Mac cases, the native sound file is fairly human-readable, so home-grown programs/scripts can be used.

Limited Sound Duration
The duration of sound objects can be limited by the amount of available memory. The current 256K parasite systems can accomodate a typical running program and about 7 seconds of 10KHz monaural sound objects. Installing the existing 1MB memory board could increase the capacity substantially. The current parasite CPU (ISI-68) can accomodate a total of 4MB. The duration of repeating sounds can be indefinitely extended through use of the DT 2771's double buffering, but these routines do not support that hardware capability.

Audio Quality
The DT 2771 is currently being driven by a 10KHz external oscillator. Higher clock rates are supported by the DT 2771, including the option of using an on-board 33.3KHz oscillator, or possibly a 44.1KHz (CD-standard) external oscillator. At rates above 30KHz, headphones and speakers may provide all the filtering that is necessary. However, tone creation will be slower, and more memory will be consumed. To support non-10KHz operation, the parameter DMA_SOUND_FREQ must be changed, and this library then re-compiled.

SEE ALSO

dmada(3U)