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;
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_samplesAn example:channel {left,right,both} sample sample sample ... \...
#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 128comment 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.
#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"); }
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[] = {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., /* DMA_SOUND_LEFT or DMA_SOUND_RIGHT or DMA_SOUND_BOTH */ sample_val1, sample_val2, ... };
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.