home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Fresh Fish 8
/
FreshFishVol8-CD2.bin
/
bbs
/
dev
/
cmanual-3.0.lha
/
CManual
/
Devices
/
AudioDevice
/
AudioDevice.doc
< prev
next >
Wrap
Text File
|
1993-10-12
|
67KB
|
1,800 lines
4 AUDIO DEVICE
4.1 INTRODUCTION
The Amiga was designed to be the first home computer which
combined state of the art graphics and impressive sound
capabilities. Although more than six years have passed since
the first Amiga was released, very few other computers can
compete with the sound quality and flexibility offered by the
Amiga.
In this chapter we look at how you can use the sound system to
produce sound effects as well as compose melodies. Since the
sound system has been built into the rest of the operating
system, it will work smoothly together with other processes, and
can without problems be used by several tasks at the same time.
4.1.1 SOUND
Sound is simply vibrating air that is registered by our ears
and interpreted as lovely music, speech, white noise, etc...
Although the simplicity it is possible to produce almost
unlimited number of variations of even a simple tune. Even a
single note sound very different if it was made by a piano or
violin. By altering the volume and rate it is played it is
possible to produce even more variations.
To better understand what sound is and how it is produced we
will first look at some commonly used technical words.
The "amplitude" of sound is the height of one of the waveforms.
The higher amplitude the louder will the waveform sound.
The "frequency" is the number of waveforms produced each
second. The higher frequency the higher will the tone be.
There exist a third sound variable called "timbre". The problem
with it is that it is very hard to describe, but is still
very important for how the sound will be perceived. It can
be called "the sound identity". A violin and a piano can play
a tone with the same amplitude and frequency, but it will
still sound very different. Even two violins can sound very
different.
The problem with reproducing the timbre is that by altering the
frequency or amplitude just a little (does not even need to be
a noticeable change) it can completely change the timbre. If you
have read about "chaos theories" the timbre can be described
with the "butterfly effect"; even a small change in wind by a
butterfly can tip the balance and in the end cause a hurricane
on the other side of the world.
By combining several frequencies each with its own amplitude
and timbre we can produce unlimited numbers of variations.
4.1.2 DIFFERENT WAVEFORMS
Although each waveform from a note is different from another
we usually do not bother too much about it. It is usually
enough to use just a few different elementary waveforms. By
altering the frequency and amplitude of these waveforms we can
then still produce millions of different sounds.
The most commonly used waveforms are the sine, triangular and
square waveforms. See illustration "Waveforms".
4.1.3 DIGITAL AND ANALOG WAVEFORMS
When we are using computers we can only handle digital numbers.
To convert these digital numbers into sound we must first
convert the digital numbers to analog values which can then
be sent to a loud speaker. See illustration "AnalogDigital".
On each Amiga computer there exist four "sound channels" which
each can convert digital numbers into analogue values. We can
therefore play four different sounds at the same time, which
means that very complex sounds can be produced.
Two of the sound channels are connected to the right audio
port, and the other two are connected to the left audio port.
We can therefore produce stereo sound on the Amiga.
The sound channels are sometimes called "eight-bit channels"
because they convert eight-bit values into analog values. The
Amiga can therefore produce sounds were the values in the
waveforms ranges from -128 to 127. Compact Disk (CD) players
use 16 bits and can therefore use even more values. For
computers today eight bits are usually more than enough, and
can produce very impressive sounds.
4.1.4 PLAY SAMPLED SOUNDS OR CREATE YOUR OWN TUNES
On the Amiga you can create your own tunes by using a waveform
and modulating it (changing the amplitude and frequency). You
can also play sound data that has been sampled (converted from
analog sound to digital data). By using sampled data you can
reproduce very complex waveforms, and it will sound natural.
To load and use already sampled data can very easily be done
with help of the two utilities I have included in the "Sound"
manual, "Easy Sound" and "Include Sound".
4.2 PREPARE THE AUDIO DEVICE
4.2.1 PRIORITY
Since several programs may be running at the same time it may
happen that two or more programs tries to use the same sound
channel at the same time. To solve problems like this we have
to use priorities.
If a program tries to use a sound channel that some other
program is already using two things may happen. If the new
program has the same or lower priority than the other program
the new one is refused any access to the sound channel and the
old program may continue to use it. On the other hand, if the
new program has higher priority it will "steal" the sound
channel from the old program.
Some sounds are more important than others. An emergency alarm
is of course much more important than a background tune, and
thus the emergency alarm should have higher priority. Commodore
has suggested the following priorities:
Type of sound Suggested priority
--------------------------------------------
Unstoppable sound 127
Emergencies 95
Attention 85
Speech 75
Information 60
Music 0
Sound effects -35
Background music -90
Silence -128
If a sound is so important that no one what ever happens should
be able to interrupt it we should use the "unstoppable sound
priority". You should never start a sound with this priority.
You should instead start it with a lower (for example with the
"emergency") priority, and then increase it.
The "emergency" priority should be used when something happens
that is so important that the user must immediately know about
it. Examples: the program is going to visit the guru if the
user does not immediately do some thing.
The "attention" priority is used to alert the user that
something has happen and the user must as soon as possible know
about it.
The "speech" priority is used by the "Narrator Device" when it
is producing synthesized speech. Spoken information is usually
more important for the user than a nice tune.
The "information" priority should be used when you want to tell
the user something, but the user must not immediately know
about it. This priority can be used when you want to tell the
user to read something, or start to input some value etc...
When you are plying music you should use the "music" priority.
This is for programs that are using music as one of the primary
functions. Background tunes should use the "background
priority".
The lowest priority level is "silent", and should actually never
be used. If you do not want to play the sound you should free
the sound channel instead.
The priority can be varied inside each priority group. For
example; soft and quiet instruments/notes should have lower
priority than loud and characteristic instruments/notes. You
can also alter the priority while the sound is being played.
The first part of an emergency sound can for example have
higher priority than the rest of the sound.
Sadly there does not exist any header file that defines the
different priorities. In my examples I will therefore use my
own constants which are declared like this:
Type of sound Priority
---------------------------
SOUND_UNSTOPPABLE 127
SOUND_EMERGENCIES 95
SOUND_ATTENTION 85
SOUND_SPEECH 75
SOUND_INFORMATION 60
SOUND_MUSIC 0
SOUND_EFFECT -35
SOUND_BACKGROUND -90
SOUND_SILENCE -128
4.2.2 ALLOCATING CHANNELS
There exist four sound channels which each one can convert
digital sound data into analogue sound which is sent to a
loudspeaker. The first and last sound channel is connected to
the left audio port, while the second and third is connected to
the right audio port.
When you are reserving a sound channel you specify which
channels you want to use by using a value where the first bit
represents the first sound channel, the second bit the second
sound channel and so on...
Bit: 3 2 1 0
-------------------------------------
Value: 8 4 2 1
Channel: 3 2 1 0
Audio port: Left Right Right Left
To reserve the first and last channel (both using the left
sound channel) you would use the value 9 (1001[bin] = 9[dec]).
To reserve the second and third channel (both using the right
sound channel) you would use the value 6 (0110[bin] = 6[dec]).
To reserve all four sound channels set the value to 15
(1111[bin] = 15[dec]).
Since other programs may be running at the same time it may
happen that they are already using one or more of the sound
channels. If they have lower priority than your program, you
will "steal" the sound channel from them. However, if you have
the same or lower priority, you are refused any access to that
sound channel.
The good thing is that you may use an array of allocation
values, and if the first value could not be satisfied the
audio device will automatically try the next value. If you are
lucky there exist a combination of sound channels which is not
currently being used.
If you want to play some sounds in stereo you have to use one
sound channel from the right and one from the left port. There
exist four possible combinations which satisfy this, and the
"allocation array" should therefore look like this:
Dec Bin Description
---------------------------------------------------
3 0011 First left and first right channel.
5 0101 First left and second right channel.
10 1010 Second left and first right channel.
12 1100 Second left and second right channel.
UBYTE allocation_array = { 3, 5, 10,12 };
The audio device will first try all combinations in the
allocation array before it will use the priority to "steal"
the channels for you.
4.2.3 CREATE WAVEFORMS
The audio device can play any type of waveform that is ranging
from -128 up to 127. Usually you will use the normal sine,
triangle, and square wave forms, but you can equally well play
more complex waveforms and even directly sample sound.
It is important to note that the audio device is a part of the
special custom chips in the Amiga and can therefore only access
sound data which is located in the "chip memory". The data must
also start on a word boundary, and consists of an even number
of bytes.
The more data used for the waveform the more complex waves can
be used. The smallest waveforms consists of only two values.
Example: We will create a sin waveform with 16 values.
/* If we are using mathematical functions */
/* like sin() we must include this file: */
#include <math.h>
/* Define a constant for the number of values in */
/* the wave form. It can then easily be changed. */
#define SINE_DATA_LENGTH 16
/* Declare a pointer to our sine wave: */
BYTE *sine_wave;
/* ... */
/* Allocate some chip memory for the sine wave: */
/* (All memory allocated by AllocMem() will */
/* always start on a word boundary.) */
sine_wave = (BYTE *) AllocMem( SINE_DATA_LENGTH, MEMF_CHIP );
/* Have we got the memory? */
if( !sine_wave )
clean_up( "Not enough memory!" );
/* Initialize the sine waveform: */
for( loop = 0; loop < SINE_DATA_LENGTH; loop++ )
sine_wave[ loop ] =
127 * sin( loop * 2 * PI / SINE_DATA_LENGTH );
4.2.4 NOTES AND FREQUENCIES
On octave consists of 12 notes. Here is a list of the
frequencies that represents the notes which are one octave
higher than the middle octave on a piano:
Note Frequency
---------------
A 880.0
A# 932.3
B 987.8
C 1046.5
C# 1108.7
D 1174.7
D# 1244.5
E 1318.5
F 1396.9
F# 1480.0
G 1568.0
G# 1661.2
To change octave you simply double/half these values. When you
double the values you go one octave up, and when you half the
values you go one octave down. Example, A = 880, one octave
lower A = 440, one octave higher A = 1760, two octaves higher
and A = 3520.
When the audio device should play a waveform you do not specify
which frequency should be used. Instead you specify the
"period" value which should be used for each sample (value) in
the waveform. The period can be calculated with the following
formula:
Period = Clock ticks per second / Frequency / Number of samples
The speed of the clock depends on if you have an European (PAL)
or American (NTSC) Amiga. On an NTSC machine the clock ticks
3579545 times per second, while on a PAL machine it only ticks
3546895 times per second.
Example: We want to play the sine waveform we previously
made with the frequency 880 (note A). (We are using an
European PAL Amiga.)
period = 3546895 / 880 / SINE_DATA_LENGTH;
Some extra information: If you have not already noticed it you
may get the wrong answer when you are dividing values with each
other. The problem is that C is always using the most accurate
value in the formula as the base on which it does the
calculations. If you multiply an integer value and a float value
the C compiler will do the intermediate calculations with float
values. If you on the other hand multiply two integers with
each other, the C compiler will do the intermediate calculations
with integer values.
The problem is that if you divide two values with each other,
and both are integers, the calculations will be preformed with
integer values. Since you do not have any decimals you can end
up with a completely wrong answer. With multiplications (and
addition and subtraction as well) this will never cause any
errors.
When you are dividing two values with each other you should
therefore always convert the value (which you divide the other
value with) to a float (or double if necessary). Simply use
normal casting. The rule is to always put the word "(float)"
in front of the bottom value when you are using division. In
our example the formula should look like this: (The second
float is actually unnecessary, but it does not matter.)
period = 3546895 / (float) 880 / (float) SINE_DATA_LENGTH;
4.2.5 THE AUDIO REQUEST BLOCK
The audio device is controlled like any other device with help
of request blocks. When you are using the audio device you
should use the extended "IOAudio" structure which is declared
in the header file "devices/audio.h" as this:
struct IOAudio
{
struct IORequest ioa_Request;
WORD ioa_AllocKey;
UBYTE *ioa_Data;
ULONG ioa_Length;
UWORD ioa_Period;
UWORD ioa_Volume;
UWORD ioa_Cycles;
struct Message ioa_WriteMsg;
};
ioa_Request: The top part of the request block consists as
always of an IORequest structure. See below
for more information about this structure.
ioa_AllocKey: When you reserve a sound channel this field is
given an unique "key" value. Each time you pass
the request to the channel this value will be
compared with the channel's current key value.
If these two values are not the same, the channel
has been stolen by some other program with higher
priority, and your command is returned with the
AUDIO_NOALLOCATION flag set.
If you are going to use another request block,
and not the one you used to allocate the channel
with, you have to copy this key value to the
other request block.
ioa_Data: Pointer to the waveform data. Note that the data
must be in chip memory, and start on a word
boundary.
ioa_Length: If you are going to play a waveform this filed
should be given the length (number of samples/
values in the wave data).
ioa_Period: This field should be given a period value. The
period value is the number of clock ticks that
should be used for each sample (value in the
waveform). See above for more information about
how to calculate the period value.
ioa_Volume: The volume, which should be set to any value
between 0 and 64. The maximum value is 64, and
the minimum value is 0 (silent). Many programs
are always using the maximum volume, but if you
use different volume for different situations
you will gain a lot of "depth".
Imagine an adventure game where most of the sound
effects are played rather softly. The user will
then use a higher volume on his/her own stereo
(or whatever the sound ports are connected to) to
compensate for the low volume. No imagine the
feeling when a monster appear and you at the same
time suddenly increase the volume to maximum!
Sadly very few programs are using this very
effective technique.
ioa_Cycles: Before you play a waveform you should set this
field to the number of times the waveform should
be played. If you set this field to 0 the
waveform will continuously be played until you
aborts the command.
ioa_WriteMsg: If you have set the flag "ADIO_WRITEMESSAGE" in
the "io_Flag" field, and this request will start
a sound, this message will be sent when the sound
starts to be played. Remember to give the
"mn_ReplyPort" field of the Message structure a
pointer to a message port to which the message
should be sent.
The IORequest structure (which is a part of the IOAudio
structure) is defined in the "exec/io.h" header file like this:
struct IORequest
{
struct Message io_Message;
struct Device *io_Device;
struct Unit *io_Unit;
UWORD io_Command;
UBYTE io_Flags;
BYTE io_Error;
};
io_Message: The top part consists of a Message structure which
will be sent to us when the request has been
completed (successfully or not).
The Message structure is defined in the header file
"exec/ports.h" like this:
struct Message
{
struct Node mn_Node;
struct MsgPort *mn_ReplyPort;
UWORD mn_Length;
};
mn_Node: It is only one field of the Node structure
that should be used, and it is the "ln_Pri"
filed, which should be given this sound's
priority value.
mn_ReplyPort: This field should be given a pointer
to the message port the reply should
be sent to.
mn_Length: This value can be ignored.
io_Device: This field will automatically be initialized when
you use the request block together with an
OpenDevice() function call. It is simply a pointer
to the device which this request block is made for.
io_Unit: This field will automatically be initialized when
you reserve an audio channel for this request
block.
io_Command: All commands should be set here. The following
commands are accepted by the audio device, and may
be used: (Will be explained below.)
The unique audio device commands:
ADCMD_FREE Free a sound channel.
ADCMD_SETPREC Set priority.
ADCMD_FINISH Stop playing the sound.
ADCMD_PERVOL Set period and volume.
ADCMD_LOCK Lock a sound channel.
ADCMD_WAITCYCLE Wait for the cycle to finish.
ADCMD_ALLOCATE Reserve a sound channel.
The normal commands which are accepted by most of
the devices:
CMD_WRITE Play the waveform.
CMD_READ Get a pointer to current req.
CMD_STOP Stop and do not start any sound.
CMD_START Start to play sound again.
CMD_FLUSH Remove all queued requests.
CMD_RESET Reset the audio device.
io_Flags: The following flags may be used:
IOF_QUICK If this flag is set no messages
will be sent to the reply port.
This will speed up the
execution, and can be useful if
you have to use a lot of
request blocks.
ADIOF_PERVOL When you start to play a sound
you can either use the volume
and period values which were
used last time, or you may use
new values. If you want to use
new volume and period values
you should set this flag, and
also set the new values in
this request block. Otherwise
the old values will be used.
ADIOF_SYNCCYCLE Set this flag if you want that
the command should be
synchronized with the wave form.
If this flag is set, any
modifications will first occur
when the whole waveform (cycle)
have been played.
ADIOF_NOWAIT When you try to reserve a sound
channel (with ADCMD_ALLOCATE)
and the audio device can not
find any channel for you, it
will continue to try until it
succeeds. The request will
firs be returned when the
request has successfully been
completed.
If you do not want to wait for
the request to be completed
you can set this flag. If the
audio device can not find any
sound channel it will
immediately return the request
with the "ADIOERR_ALLOCFAILED"
error flag set.
ADIOF_WRITEMESSAGE Normally you will only receive
a message at your reply port
when a request has been
completed. It may however
sometimes be necessary to know
when an audio request have
started.
If you set this flag the
message at the end of the
request ("ioa_WriteMsg") will
be sent. Remember to give the
message structure a pointer to
a reply port if you are using
this flag.
io_Error: If the request can not successfully be executed by
the audio device, it will be returned with one of
the following error flags:
ADIOERR_NOALLOCATION When you reserve a sound
channel you are given a
"key" value (set in the
"ioa_AllocKey" field).
Every time you send a
request this key value will
be compared to the sound
channel's current key
value. If these two values
do not match the channel
have been "stolen" by some
other program. The request
will then be returned with
this error flag set.
Note that you do not have
to free a sound channel
that has been stolen from
you.
ADIOERR_ALLOCFAILED When you try to reserve a
sound channel it may happen
that all desired channels
are occupied. If the audio
device can not find a
channel it will normally
continue to try until it
found one. However, if you
have set the "ADIOF_NOWAIT"
flag the request will
immediately be returned if
no sound channels could be
allocated, and this error
flag is then set.
Remember to always check if
you have received the
channel or not if you are
using the "ADIOF_NOWAIT"
flag.
ADIOERR_CHANNELSTOLEN If you have "locked" a
channel, and another
program tries to steal
it, the lock request will
be returned to you with
this flag set.
If the lock request is
returned to you with this
flag set you should as
fast as possible clean up
after you and unlock the
channel. It is first when
you free the locked channel
the other program gets it.
NOTE! You must free the
channel as soon as
possible! The other program
has higher priority, but
since you have locked the
channel it can not get it.
4.2.6 OPEN THE AUDIO DEVICE
The audio device is very similar to other devices. You open
a message port through which the audio device can reply to
you. You then allocate the request block(s), and finally open
the device.
1. Open a message port. Since it is only our task and the
audio device that will use the message port, we do not
need to make it "public", therefore no name. Priority
should as usual be set to 0, normal priority. (Do not
mix up this priority for the message port with the
priority for the sound channel, which will later be set
in the request block.)
struct MsgPort *replymp;
replymp = (struct MsgPort *)
CreatePort( NULL, 0 );
if( !replymp )
clean_up( "Could not create the reply port!" );
2. Allocate a request block of type IOAudio structure.
The IOAudio structure is an extended version of the
normal request block, and should therefore be allocated
with help of the CreateExtIO() function with the size
set to sizeof( struct IOAudio ).
struct IOAudio *audio_req;
audio_req = (struct IOAudio *)
CreateExtIO( replymp, sizeof( struct IOAudio ) );
if( !audio_req )
clean_up( "Not enough memory!" );
3. Once the message port and the request block have
successfully been created you can "open" (gain access to)
the audio device.
error = OpenDevice( AUDIONAME, 0, audio_req, 0 );
if( error )
clean_up( "Could not open the Audio Device!" );
If you want you can reserve sound channels at the same
time as you open the device. You must then:
(a) Set the sound priority. The top part of the request
block, the IORequest structure (ioa_Request) contains
a Message structure ("io_Message") which contains a
Node structure (mn_Node) which finally contains a
"ln_Pri" field. (Piece of cake.) It is in this field
you set the sound priority.
(b) Give the "ioa_Data" field a pointer to an array were
the desired channel combinations are listed. This
array of desired channels are usually called "the
allocation array".
(c) Set the length of the allocation array in the
"ioa_Length" field.
If the "ioa_Length" is not zero when you try to open the
device, the audio device will also try to reserve the
desired channel(s). If the audio device can not find any
sound channels free, it will immediately (regardless if
you have set the "ADIOF_NOWAIT" flag or not) return with
the "ADIOERR_ALLOCFAILED" error message.
4.2.7 RESERVE CHANNELS
If you have not reserved any channel(s) when you opened the
audio device, or you want to reserve even more channels, you
should use the "ADCMD_ALLOCATE" command. The follwing things
must be done:
1. Set the "ADCMD_ALLOCATE" flag in the "io_Command" field
of the IORequest structure.
audio_req->ioa_Request.io_Command = ADCMD_ALLOCATE;
2. Set the sound priority. The top part of the request block,
the IORequest structure (ioa_Request) contains a Message
structure ("io_Message") which contains a Node structure
(mn_Node) which finally contains a "ln_Pri" field. It is
in this field you set the sound priority. Be careful so
you set the correct sound priority.
audio_req->ioa_Request.io_Message.mn_Node.ln_Pri =
SOUND_EFFECT;
3. Normally will the request first be returned when the
audio device has successfully reserved the sound
channel(s). If you set the "ADIOF_NOWAIT" flag in the
"io_Flags" field of the IORequest structure, the request
will immediately be returned, successfully or not. You can
examine the "io_Error" field of the IORequest structure to
see if the request was successful or not. If no channels
could be reserved (allocated) the "ADIOERR_ALLOCFAILED"
error flag is set.
audio_req->ioa_Request.io_Flags = ADIOF_NOWAIT;
4. Give the "ioa_Data" field a pointer to an array were
the desired channel combinations are listed.
audio_req->ioa_Data = allocation_array;
5. Set the length of the allocation array in the
"ioa_Length" field.
audio_req->ioa_Length = sizeof( allocation_array );
6. The audio request block has now been properly initialized,
and it should now be sent to the audio device. Normally
you use the DoIO() and SendIO() commands, but they may not
be used when you are allocating channels. Instead you must
use the low level function BeginIO(), which is very
similar to SendIO().
The reason why you may not use the DoIO() and SendIO() is
that they will erase some parts of the request block that
may not be altered when you are using the audio device.
Since BeginIO() is an asynchronous command, it will
immediately return the control to the program, you have to
wait for the request to be executed. Simply use the
WaitIO() function which will put our program to sleep
while we are waiting.
WaitIO() will return 0 when the request has successfully
been executed, else a non zero value is returned.
BeginIO( audio_req );
error = WaitIO( audio_req );
if( error )
clean_up( "No channels!" );
After you have successfully allocated a channel you can
check which channel(s) you received by looking at the
"io_Unit" field of the IORequest structure. (Note that
normally the "io_Unit" field of a IORequest structure
contains a pointer to a Unit structure. However, when
you are using the audio device this field is used to store
an integer value, where the first four bits are used to
identify which channels are reserved. As before, bit zero
represents channel 0 (left), bit one channel 1 (right),
bit two channel 2 (right) and finally bit three represents
channel 3 (left).
channel = (UBYTE) audio_req->ioa_Request.io_Unit;
if( channel & 1 )
printf( "First left channel!\n" );
if( channel & 2 )
printf( "First right channel!\n" );
if( channel & 4 )
printf( "Second right channel!\n" );
if( channel & 8 )
printf( "Second left channel!\n" );
4.2.8 LOCK CHANNELS
After you have reserved a channel you may start to use it. When
you reserved the sound channel was the "ioa_AllocKey" given an
unique key number. This key number will be compared with the
sound channels current key number, and if they do not match,
the channel(s) has/have been stolen and your request is
returned with the error flag "ADIOERR_NOALLOCATION" set.
If you want to use several request blocks for the same
channel(s) you must therefore copy the key value to all request
blocks.
When you are using the request block to send commands the key
value will always be checked before any change is made. It can
however sometimes be necessary to skip the request block and
directly modify the hardware registers. Using request blocks
take some time, and if you have to make changes several times
each second it may not be fast enough. You must then directly
modify the sound registers.
Normally you are not allowed to hit the hardware like this, but
when you are using the audio device you are allowed to do it.
Of course, if you can manage with the request blocks you should
use them.
If you are using the hardware registers you may get into
trouble if you are not careful. If some other program has
stolen the channel(s) from you and you use the hardware
registers, you will not receive any error messages. You can now
end up with modifying the other programs sounds, and this is
not to be recommended!
Before you may use the hardware registers you must therefore
"lock" the channel(s). To lock a channel you have to use a
separate request block. This request block is allocated and
pre-initialized as usual with help of the CreateExtIO()
function: (We can of course use a different message port if
you want to separate all lock messages, but it is usually
easier to use only one message port for all audio messages.)
struct IOAudio *audio_lock;
audio_lock = (struct IOAudio *)
CreateExtIO( replymp, sizeof( struct IOAudio ) );
if( !audio_lock )
clean_up( "Not enough memory!" );
The lock should then have the following fields set:
1. The "ADCMD_LOCK" flag in the io_Command field of the
IORequest structure must be set.
audio_lock->ioa_Request.io_Command = ADCMD_LOCK;
2. The lock must be linked to the already opened audio
device. The address to the device can be found in the
"io_Device" field of the IORequest structure.
audio_lock->ioa_Request.io_Device =
audio_req->ioa_Request.io_Device;
3. You must tell the device which channels you want to
lock. Normally you will try to lock the channels you
previously have reserved, and thus you can find the
channel value in the "io_Unit" field of the IORequest
block.
audio_lock->ioa_Request.io_Unit =
audio_req->ioa_Request.io_Unit;
4. Since you are going to use a new request block for some
channels you have previously reserved with another request
block, you must copy the key value:
audio_lock->ioa_AllocKey = audio_req->ioa_AllocKey;
Once the lock request has been initialized it should be send
by calling the BeginIO() function.
BeginIO( audio_lock );
Once you have lock the channel(s) the request block will first
be sent back when some other programs tries to steal the sound
channel(s) or when you free the channel(s). If some other
program wants the channel(s) and has higher priority, the lock
will be returned with the error flag "ADIOERR_CHANNELSTOLEN"
set. The channel has not yet been stolen, but you MUST
immediately clean up all necessary things and free the
channel(s) as soon as possible so the other program can get
them. You may NOT keep the channels once the lock has been
returned!
While you are cleaning up and freeing the channels the other
program is put to sleep and does not know what is happening.
If you are not fast enough the other program will continue to
"sleep" although it has higher priority and should therefore
immediately get the channels if you have not locked them. You
must therefore free the channels as fast as possible.
You may of course not modify the hardware registers any more
after you have deallocated the audio channel(s).
You can use the function CheckIO() to see if the lock has been
sent back. See below for more information about how to free a
sound channel.
/* Has the lock been sent back? */
if( CheckIO( audio_lock ) )
{
/* Another programs what our channel(s)! */
/* Clean up. Reset any hardware */
/* registers etc, if necessary. */
/* Free the channel: */
audio_req->ioa_Request.io_Command = ADCMD_FREE;
/* Send the request: (We may use the DoIO() function */
/* when we deallocate a channel.) */
DoIO( audio_req );
}
4.3 USE THE AUDIO DEVICE
Once you have declared a request block and successfully opened
the audio device you may start to use it. When you are playing
sounds you may either use the request block to issue the sound
commands, or you can directly modify the hardware registers.
The first method is cleaner, but the second method is much
faster and may sometimes be necessary if you intend to issue
many commands.
4.3.1 PLAY SOUNDS
To play some sound you have to do the following things:
1. Give the "ioa_Data" field of the request block a pointer to
a waveform you want to play. (The waveform data must be
located in chip memory, and start on an even byte address.)
audio_req->ioa_Data = sine_wave;
2. Set the length of the waveform in the "ioa_Length" field.
(Must be an even number of bytes.)
audio_req->ioa_Length = SINE_DATA_LENGTH;
3. Set the number of times (cycles) the waveform should be
played in the "ioa_Cycles" field. If you want to play the
waveform continuously until you abort the request you
should set the field to 0.
audio_req->ioa_Cycles = 3;
4. Set the command "CMD_WRITE" flag in the "io_Command" field
which tells the device that you want to play a tune.
audio_req->ioa_Request.io_Command = CMD_WRITE;
5. If you want to use a specific volume and period values you
must set the "ADIOF_PERVOL" flag in the "io_Flags" field.
The values in the "ioa_Volume" and "ioa_Period" fields
will then be used, else the previous volume and period
values will be used.
audio_req->ioa_Request.io_Flags = ADIOF_PERVOL;
audio_req->ioa_Volume = 32;
audio_req->ioa_Period = 3546895 / 880 / SINE_DATA_LENGTH;
Once all d3esired values have been set you send the request to
the audio device, by calling the BeginIO() function. If you
want to wait for the sound to be completed you can use the
WaitIO() function. Note that you should NEVER try to wait for
a request that has not been started, or if the "ioa_Cycles"
field has been set to 0 (forever). If the request already has
been completed the WaitIO() function will immediately return.
BeginIO( audio_req );
error = WaitIO( audio_req );
if( error )
clean_up( "Error while playing the sound!" );
4.3.2 USE SEVERAL REQUEST BLOCKS
Remember that you may not modify the request block once it has
been sent, by calling BeginIO(), and have not yet been
completed. Once the request has been completed you of course
start to modify it again. If you want to play several notes at
the same times in different sound channels, you may therefore
have to use several request blocks.
If you want to use some of the audio commands that is changing
the sound which is currently being played you also have to a
use separate request blocks. In many programs you will see
four request blocks where each one handles one audio channel.
Very often there also exist one or more extra request blocks
that are used to modify sounds which are currently being
played.
If you have reserved a sound channel with a request block you
can not use another request block to play sounds on it if you
do not first copy some important values to the new request
block.
When you reserve a channel the request block is given a unique
key value, and if this key value does not match with the sound
channel's current key value the request will fail. Before you
may use a new request block you must therefore first copy the
"ioa_AllocKey" value to the new request.
You must also copy the pointer to the audio device from the
old request to the new one. The unit number and all stuff in
the Message structure must also be copied. In the end, it is
actually easier to copy the whole request block, even if some
fields does not have to be copied.
Here is a small demonstration on how to copy all values in a
request block to a new one. The "audio_req" is a pointer to an
already initialized audio request block, where "change_req" is
a new and uninitialized request block.
/* Source and destination pointers: */
BYTE *first_ptr;
BYTE *second_ptr;
/* Allocate request blocks, open audio device, */
/* reserve channels, etc... */
/* Copy the first request block to the */
/* second one: (byte by byte) */
/* Get the start addresses of both request blocks: */
first_ptr = (BYTE *) audio_req;
second_ptr = (BYTE *) change_req;
/* Copy byte by byte: */
for( loop = 0; loop < sizeof( struct IOAudio ); loop++ )
{
/* Copy: */
*second_ptr = *first_ptr;
/* Next byte: */
first_ptr++;
second_ptr++;
}
4.3.2 PLAY DOUBLE BUFFERED SOUNDS
If you play several sounds after each other you will hear a
"click" between the sounds. The reason why you hear this click
is that once a sound has been completed it takes some time for
your program to initialize a new request and send it to the
audio device. Although this delay is very short it is long
enough to be noticed, especially if several programs are
running at the same time.
Luckily there is a solution for this problem. Before the first
sound has been complete you should send the next sound request.
When the first sound has stopped the second sound is already
available for the audio device, and it will immediately be
played. Because of this, there is such a short interrupt
between the sounds that you will not hear any annoying clicks.
This technique of sending the next sound when the first one
is still being played is usually called "double buffering",
and can in many ways be compared with "double buffered
displays" which is described in the manual "Graphics",
chapter "Graphical Tricks".
1. Prepare the first request block and send it to the audio
device.
2. Prepare the second request block, and send it to the audio
device.
3. Wait for the first request block to be completed. When the
request block is returned you prepare it again for a new
sound, and send it to the audio device.
4. Wait for the second request block to be completed. When
the request block is returned you prepare it again for a
new sound, and send it to the audio device.
5. Jump back to step 3 until all sounds have been played.
6. Wait for both requests to be completed, and quit.
4.3.3 MODIFY THE HARDWARE REGISTERS
Usually you should never try to modify hardware registers
directly but instead use special functions that does it for
you. However, when you are using the audio device you may alter
the hardware registers of the sound channels. Modifying
hardware registers directly is much faster than using functions
that does it for you, and when playing sounds speed can
sometimes be crucial.
Before you may alter any of the sound registers you must first
lock the channel(s)! When you modify hardware registers no one
is no checking that you really own the channel(s). If you have
reserved a channel and not locked it, some other program may
steal it and you would not notice that. You could then end up
altering the other programs sound channel(s), which is rather
unpolite not to say dangerous.
To use the hardware registers you have to declare an external
array of "AudChannel" structures. The AudChannel structure is
defined in the Custom structure which is declared in the header
file "hardware/custom.h". The whole Custom structure is
automatically initialized by the Amiga. The AudChannel array,
which must be called "aud" is also initialized.
The AudChannel structure is defined like this: (Declared in
header file "hardware/custom.h")
struct AudChannel
{
UWORD *ac_ptr;
UWORD ac_len;
UWORD ac_per;
UWORD ac_vol;
UWORD ac_dat;
UWORD ac_pad[2];
} aud[4];
ac_ptr: Pointer to the waveform data.
ac_len: The length of the waveform.
ac_per: The period value.
ac_vol: The volume.
ac_dat: This field contains the first two bytes which will
be played. The DMA channels will automatically
move data from the waveform to this field, two
bytes each time, when needed. You can alter these
values directly, but this will take longer time
than letting the DMA fetch the sound
automatically, so it is not recommended.
ac_pad[2]: Unused areas, should not be used.
When you declare the "aud" array you probably have to use the
keyword "far" since the hardware data is most certainly far
away from your program. Remember that the aud array has been
declared and initialized by the Amiga itself, and you should
therefore declare it as an external array. Example:
extern struct AudChannel far aud[];
To use the hardware audio channels you must also declare an
external UWORD which must be called "dmacon". This variable
is also automatically initialized by the Amiga. Example:
extern UWORD far dmacon;
This "dmacon" variable controls the DMA channels. A DMA channel
can be described as a data cable where data can be sent without
disturbing the main processor. It is because of the DMA
(DMA = "Direct Memory Access") you can play sounds at the same
time as your program can use the main processor to do something
else.
It is this variable which controls all "DMA writing", and must
therefore be used when you want to play a sound. To play a
sound on one of the audio channels you have to give it a value
where the leftmost bit is set (the DMAF_SETCLR constant) and
the bit which correspond to the audio channel you want to start
playing. Bit one represents the first audio channel, bit two
the second audio channel and so on. You should however not use
the direct values but instead the constants which are declared
in the "hardware/dmabits.h" header file.
DMAF_SETCLR This flag should always be set together with the
channel(s) you want to be played.
DMAF_AUD0 First left audio channel.
DMAF_AUD1 First right audio channel.
DMAF_AUD2 Second right audio channel.
DMAF_AUD3 Second left audio channel.
If you want to use several audio channels you simple set all
desired channel flags separated with the binary OR operator "|".
To start all audio channels do like this: (You must of course
first have reserved and locked them before you may do this!)
dmacon = DMAF_SETCLR|DMAF_AUD0|DMAF_AUD1|DMAF_AUD2|DMAF_AUD3;
Since you can now directly alter the volume, period, and
waveform by directly writing to the hardware the changes will
be very simple and quick. Just remember to now and then check
if your lock has been returned, and if so clean up after you
and free the channel(s) as soon as possible. Do not be greedy
and ignore the lock, too many programs do that which is very
irritating!
4.4 CLEAN UP AFTERWARDS
When your program terminates you have to clean up after
yourself, as usual. It is important that you do not forget to
do this since other programs are denied any access to the
audio channels and memory you forget to deallocate. Remember
that your programs must also be able to clean up after itself
if it suddenly has to terminate because of some error.
4.4.1 UNLOCK CHANNELS
All audio channels that you have reserved must be given back to
the system before your program may terminate. If you have
locked a channel and another programs wants it, you must return
it as soon as possible. However, if you have not locked the
channel and another program has stolen it you do not have to
free it.
To free one or more audio channels you simply send a request
with the "ADCMD_FREE" command flag set. All channels which are
selected in the "io_Unit" field of the request block will be
resetted and given back to the system. Any locks on the
channels will be removed.
If some other program has stolen the channel(s) from you the
request will be returned with the error message
"ADIOERR_NOALLOCATION". You do not have to do anything with
channel(s) which has/have been stolen from you. It is the other
program's duty to clean it/them up.
When you want to free a channel you may use the DoIO()
function, instead of BeginIO(). Normally you should only use
the BeginIO() function when working with the audio device, but
when you free channel(s) the DoIO() will work equally well.
audio_req->ioa_Request.io_Command = ADCMD_FREE;
DoIO( audio_req );
4.4.2 REMOVE ALL MESSAGES AND CLOSE THE REPLY PORT
You must close all message ports you have opened. Just remember
to remove all messages before you close it. A short while loop
that removes the messages until it can not find any more is
usually enough.
while( GetMsg( replymp ) )
printf( "Collected a message at the reply port.\n" );
DeletePort( replymp);
4.4.3 CLOSE THE DEVICE
The audio device itself must of course also be closed if you
have opened it. If you have open it several times with
different request blocks you have to close all of them.
CloseDevice( audio_req );
4.4.4 DEALLOCATE THE REQUEST BLOCKS
All audio request blocks you have created must be deallocated
with help of the DeleteExtIO() function. The size should be set
to the size of an IOAudio structure. Just remember to close the
device before you deallocate the request block.
DeleteExtIO( audio_req, sizeof( struct IOAudio ) );
4.4.5 DEALLOCATE SOUND BUFFERS
If you have allocated a sound buffer to store the waveform in
you must also deallocate it. Remember to deallocate the same
amount of memory as when you allocated it.
FreeMem( square_wave, SINE_DATA_LENGTH );
4.5 AUDIO DEVICE COMMANDS
The audio device is controlled by set of commands. We have
looked at the ADCMD_ALLOCATE, ADCMD_FREE, and CMD_WRITE
commands. Most things can be managed with these commands, but
there exist a set of other commands that may sometimes be
needed. These new commands can be divided into two groups. The
first group consists of the general device commands which can
be used on most devices. The second group consists of a set
special commands that only the audio device can understand
and handle.
4.5.1 GENERAL DEVICE COMMANDS
Here is the complete list of general device commands, together
with a short description:
CMD_WRITE Start to play a sound on one channel.
CMD_READ Check which request is currently playing.
CMD_STOP Temporarily stop all audio requests.
CMD_START Restart the previously stopped audio requests.
CMD_FLUSH Remove all queued requests.
CMD_RESET Reset the audio device.
4.5.1.1 CMD_WRITE
The CMD_WRITE command is used when you want to start to play
a sound on a specific sound channel. Note that it can only
start one sound channel.
This command has already been explained, so see previous
sections for more information.
4.5.1.2 CMD_READ
The CMD_READ command is used when you want to get the address
of the request which is currently being used. You specify a
sound channel you want to check by setting the corresponding
bit in the io_Unit field. If a sound is currently being played
on that channel the "io_Data" field of our request block will
contain the address of the other request which is currently
playing the sound. If no sound is being played the "io_Data"
field is set to NULL.
This command is useful if you have sent a lot of requests to
the audio device and you want to see which request is currently
being played. Note that you must copy the sound channels key
value to your request before you can check it. The key value
can be found in the "ioa_AllocKey" field of the request which
reserved the channel. If your request block's "ioa_AllocKey"
value does not match the sound channel's current key value the
ADIOERR_NOALLOCATION error message is returned.
4.5.1.3 CMD_STOP
The CMD_STOP command will temporarily stop all requests for the
specified channel(s). All requests will be queued, and first
processed when you later restart the channel(s) by using the
CMD_START command. The "io_Unit" field of the request specifies
which channels that should be stopped. Remember to copy the
key value from the request block that reserved the sound
channel(s). If your request block's "ioa_AllocKey" value does
not match the sound channel's current key value the
ADIOERR_NOALLOCATION error message is returned.
4.5.1.4 CMD_START
The CMD_START command should be used when you want to restart
one or more previously stopped sound channel(s). The "io_Unit"
field of the request specifies which channels that should be
restarted. Remember to copy the key value from the request
block that reserved the sound channel(s). If your request
block's "ioa_AllocKey" value does not match the sound channel's
current key value the ADIOERR_NOALLOCATION error message is
returned.
4.5.1.5 CMD_FLUSH
The CMD_FLUSH command will remove all currently queued commands
from the specified channels. Remember to copy the key value
from the request block that reserved the sound channel(s). If
your request block's "ioa_AllocKey" value does not match the
sound channel's current key value the ADIOERR_NOALLOCATION
error message is returned.
4.5.1.6 CMD_RESET
The CMD_RESET command will reset all specified channels. All
requests currently queued will be removed, the audio hardware
registers are cleared, and if the channel has been stopped it
is restarted. Remember to copy the key value from the request
block that reserved the sound channel(s). If your request
block's "ioa_AllocKey" value does not match the sound channel's
current key value the ADIOERR_NOALLOCATION error message is
returned.
4.5.2 SPECIAL AUDIO DEVICE COMMANDS
Here is the complete list of special audio device commands.
These commands can only be used with the audio device.
ADCMD_ALLOCATE Tries to reserve one or more audio channels.
ADCMD_FREE Frees one or more audio channels.
ADCMD_SETPREC Change the priority of a sound.
ADCMD_FINISH Finish (aborts) a sound.
ADCMD_PERVOL Change the volume and period of a sound.
ADCMD_LOCK Will lock audio one or more channel(s).
ADCMD_WAITCYCLE Waits for a cycle to be completed.
4.5.1.1 ADCMD_ALLOCATE
The ADCMD_ALLOCATE command will reserve one or more audio
channels. This command has already been discussed, so see
previous sections for more information about this command.
4.5.1.2 ADCMD_FREE
The ADCMD_FREE command will free one or more audio channels
that has(have) previously been stolen. This command has already
been discussed, so see previous sections for more information
about this command.
4.5.1.3 ADCMD_SETPREC
The ADCMD_SETPREC command allows you to change the priority
of a sound channel that has previously been reserved. You may
for example want to use a very high priority at the beginning
of the sound, and later decrease it. Remember to copy the key
value from the request block that reserved the sound channel(s).
If your request block's "ioa_AllocKey" value does not match the
sound channel's current key value the ADIOERR_NOALLOCATION
error message is returned.
4.5.1.4 ADCMD_FINISH
The ADCMD_FINISH command will finish an already started sound.
This may be needed if you have set a very high cycle value, or
you are playing a sound "for ever" (the ioa_Cycle field set to
zero.) This command has the same effect as calling the
AbortIO() function. Both options are equally good, but the
ADCMD_COMMAND looks a bit "cleaner" (at least some C
programmers think so).
Remember to copy the key value from the request block that
reserved the sound channel(s). If your request block's
"ioa_AllocKey" value does not match the sound channel's current
key value the ADIOERR_NOALLOCATION error message is returned.
4.5.1.5 ADCMD_PERVOL
The ADCMD_PERVOL command can be useful if you want to change
the period and/or volume value(s) of a sound which is already
playing. Just remember to set the ADIOF_PERVOL flag in the
"io_Flags" field, or else your command will have no effect.
Remember to also set the new volume and period values you want
to be used in the "ioa_Volume" and "ioa_Period" fields.
As always, remember to copy the key value from the request
block that reserved the sound channel(s). If your request
block's "ioa_AllocKey" value does not match the sound channel's
current key value the ADIOERR_NOALLOCATION error message is
returned.
4.5.1.6 ADCMD_LOCK
The ADCMD_LOCK command is used to lock one or more channels.
If some other program has higher priority and tries to steal
our channel(s), the lock is returned to us. The channels are
however still not stolen. When the lock is returned we should
as fast as possible clean up and free the channels so the other
program can get it.
See the previous sections for more information about this
command.
4.5.1.7 ADCMD_WAITCYCLE
If you send a request with the "ADCMD_WAITCYCLE" command set,
the request will first be sent back when the sound which is
currently being played has reached the end of the cycle. If
no sound is being played it will immediately return.
Remember to copy the key value from the request block that
reserved the sound channel(s). If your request block's
"ioa_AllocKey" value does not match the sound channel's current
key value the ADIOERR_NOALLOCATION error message is returned.
4.6 FUNCTIONS
BeginIO()
BeginIO() is a low level form of the SendIO() function. The
advantage with BeginIO() is that no fields of the request
block will be altered as which is the case with SendIO().
When you are using the audio device you should almost always
use the BeginIO() and not DoIO() or SendIO(). If you are
freeing channels you may however use these functions too.
Synopsis: BeginIO( req )
req: (struct IORequest *) Pointer to the request you
want to have executed. In this case a pointer to
an IOAudio structure.
DoIO()
DoIO() is used to send requests to a device, and waits for it
to be completed. While the program is waiting it is put to
sleep so it will not waste any computer time. DoIO() will
return first when the request have been completed or failed,
and no message is therefore sent to the reply port. When you
are using the audio device you may only use this function
when you free audio channel(s).
Synopsis: error = DoIO( req );
error: (long) DoIO() will return first when the request has
been completed or something has failed. If the
request was successfully completed zero is returned,
else an error number is returned. What error number
depends on which device was used.
req: (struct IORequest *) Pointer to the request you
want to have executed. In this case a pointer to
an IOAudio structure.
SendIO()
SendIO() is used to send requests to a device, but will
return immediately without any delay. To check if the request
have been completed use the CheckIO() function, or look
at the request's reply port for any messages. Once the
request has been completed you must remove the message at
the reply port. (CheckIO() will not do it.) To remove a
message use the function Remove(). Note that you may NOT
close the device before all requests have been completed or
aborted!
When you are using the audio device you may only use this
function when you free audio channel(s).
Synopsis: SendIO( req )
req: (struct IORequest *) Pointer to the request you
want to have executed. In this case a pointer to
an IOAudio structure.
CheckIO()
CheckIO() is used to check if a previously started request
has been completed. Note that this function will not remove
the message at the reply port. This must be done with the
Remove() function.
Synopsis: ptr = CheckIO( req );
ptr: (long) CheckIO() will either return NULL if the
request have not been completed or it will return a
pointer to the request block.
req: (struct IORequest *) Pointer to the request you
want to check. In this case a pointer to
an IOAudio structure.
WaitIO()
WaitIO() will wait for the request to be completed, and while
the program is waiting it is put to sleep so no computer time
is wasted.
Synopsis: error = WaitIO( req );
error: (long) WaitIO() will return first when the request,
that has previously been sent, has been completed or
something has failed. If the request was successfully
completed zero is returned, else an error number is
returned. What error number depends on which device
was used.
req: (struct IORequest *) Pointer to the request you
want to wait for to be completed. In this case a
pointer to an IOAudio structure. Note that the
request must have already been sent to the device
by either a SendIO() or BeginIO() function call.
AbortIO()
AbortIO() will try to abort a previously started request.
Instead of using this function you can use the ADCMD_FINISH
command as explained above.
A request that is aborted will have its io_Error field set
to IOERR_ABORTED (defined in header file "exec/errors.h").
Synopsis: AbortIO( req )
req: (struct IORequest *) Pointer to the request you
want to abort. In this case a pointer to an IOAudio
structure.
CloseDevice()
CloseDevice() will close a device. Note that you should NOT
close the device before all started asynchronous requests
have either been completed or aborted.
Synopsis: CloseDevice( ioreq );
ioreg: (struct IORequest *) Pointer to the device's
request block. In this case a pointer to an IOAudio
structure.
OpenDevice()
OpenDevice() will try to open the specified device.
Synopsis: error = OpenDevice( name, unit, req, flags );
error: (long) If OpenDevice() managed to open the device
it returns 0, else an error number is returned.
If you try to reserve one or more audio channels
at the same time, and there are not any available,
the error message "IOERR_ALLOCFAILED" is returned.
name: (char *) Name of the device you want to open.
The name of the audio device is defined as
AUDIONAME in header file "audio.device".
unit: (long) Not used by the audio device.
req: (struct IORequest *) Pointer to a request block.
In this case a pointer to an IOAudio structure.
flags: (long) Not used by the Audio device.
4.7 EXAMPLES
Example 1
This program will play some notes (A to G#) with help of the
Audio Device. It will use one of the audio channels (the
first one which is free).
Example 2
This program is very similar to the previous example, but
this time are we using double buffered sounds, and there will
therefore not be any annoying "clicks" between the notes.
The technique with double buffered sound is that while the
first sound is played the second sound is already sent to
the audio device. When the first sound terminates the second
sound can immediately start without any delay. While the
second sound is being played the first sound is prepared and
sent and so on... Because there is never any delay between
the sounds there will never be any annoying clicks.
Example 3
This program demonstrates how you can play some sampled data.
Remember that sampled data is just a more complicated
waveform.
The sampled sound has been converted into numbers by
"PrintSound", a utility included in the "Sound" manual.
Example 4
This program demonstrates how you can play a sound
continuously. While the sound is being played we slowly alter
the period and volume values.
Example 5
This example demonstrates how you can play sounds in STEREO.
First we play a sound in the left channel, then we switch to
the right, and then back again, and so on...
In this example are we reserving the audio channels at the
same time as we open the audio device. We use two audio
requests, one for the left channel and the other one for the
right channel.
Example 6
This program will play some notes (A to G#) with help of the
Audio Device. It will use as many audio channels as possible,
and we are modifying the hardware registers directly instead
of using the special Audio Device commands.
You are allowed to use the hardware registers directly if you
make sure that no other task can steel them from you before
you have cleared all necessary registers.