home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Acorn User 7
/
AU_CD7.iso
/
_movies
/
_armovie
/
documents
/
tousesound
< prev
Wrap
Text File
|
1994-01-31
|
24KB
|
526 lines
Using Replay Sound Code
=======================
When sound compression field is 1, there are 10 different sound formats
currently available for Replay:
4bit: mono and stereo 4 bit ADPCM SoundA4, SoundA4x2
8bit: mono and stereo 8 bit linear signed SoundS8, SoundS8x2
linear unsigned SoundU8, SoundU8x2
exponential (uLaw) SoundE8, SoundE8x2
16bit: mono and stereo 16 bit linear signed SoundS16, SoundS16x2
When sound compression field is 2, the actual name of the sound decompressor
follows the field; for example '2 ADPCM' would provide a new format which used
the decompressor components in ADPCM: this is a directory in
<ARMovie$SoundDir>.
For all formats, there is an appropriate piece of machine code which can be
called to play the format directly. For format 2 there is a set of resources
which can be used to play, compress and decompress the sound data, though only
some of these may be present. These are all stored in the named directory
inside <ARMovie$SoundDir>. A full implementation of a format 2 system would
look like:
ADPCM.Play play the data out of the computer
Playx2 ditto, but stereo
Playx??? extra files possible for more channels
To16 convert data to 16 bit linear signed
From16 convert data from 16 bit linear signed
Info find out information on the format
Only the Play (or Playx2) and Info files are essential. The Info file
contains the following:
line 1: name of compression
2: copyright details
3: if 0, decompression can only start at beginning of chunk;
if 1, decompression can start at any sample in chunk;
other values reserved.
4: n: buffer size multiplier - how many bits of storage per sample the
Play program needs to work: see section (3).
5: if 0, compression is in fixed ratio (i.e. constant bits/sample); if
1, compression ratio is variable (but bounded). See section (10).
6: c: maximum size of a compressed sample, in bits. This is a real (not
just integer) number, since some schemes may use fractional bits; but
if a whole number, no `.' or `.0' is needed. See section (10).
7: m: constant (header/framing) overhead for From16 destination buffer,
per channel, in bytes (an integer). See section (10).
line 8: //more data if required...
(1) getting the right piece of code.
The Replay movie header contains textual information which is used to deduce
the format of the data and thus which of the sound code files to use. The
Replay MovingLines sound files should be used only if the sound type is 1 or 2.
In format 2, the file header gives the exact name of the decompressor directory
to use: add the string 'Play' and the number of tracks.
For format 1, the SoundA/SoundS/SoundU/SoundE comes from the comments in the
bits per sample field: if the text fragment ADPCM (in any case) is present,
then SoundA; if LIN, then if UNSIGN then SoundU else SoundS; otherwise SoundE.
4/8/16 bits comes directly from the ARMovie header's bits per sample field.
Mono/Stereo is easy: the number of channels in the ARMovie header is 1 for mono
(no suffix on the file name) and 2 for stereo (suffix x2 on the file name).
Stereo data for both formats 1 and 2 may be in normal left/right sense, or
reversed: it is reversed if the sound channels field contains REVER (in any
case).
To play the desired track, the information for the correct fields must be
extracted! The following BASIC fragment extracts a field from a string j$ for a
desired track I%:
DEF FNa(j$,I%)
IFI%>1 j$=MID$(j$,INSTR(j$,"|"+STR$I%)+LENSTR$I%+2)
IFINSTR(j$,"|") j$=LEFT$(j$,INSTR(j$,"|")-1)
=j$
Assuming an uppercasing function FNuc, then the following is correct code to
find the name of the sound file for track 'track%' from an ARMovie opened as
'file%' reading from :
FORI%=1TO9:a$=GET$#file%:NEXT: REM ignore video header
snd$=FNa(GET$#file%,track%):snd%=VALsnd$:REM sound compression
IFsnd%=1 OR snd%=2 THEN
REM safe to read all the other stuff...
sndrep=VALFNa(GET$#file%,track%): REM sound replay rate (fp value)
lin$=FNa(GET$#file%,track%): REM sound channels
reversed%=FALSE:IFINSTR(FNuc(lin$),"REVER") reversed%=TRUE
channels%=VALlin$: REM channels variable
lin$=FNa(GET$#file%,track%): REM sound precision/linear
sndbits%=VALlin$: REM sound precision
sndmul%=8: REM default value for storage
IFsnd%=1 THEN
IFsndbits%=16 OR INSTR(FNuc(lin$),"LIN") THEN
IFINSTR(FNuc(lin$),"UNSIGN") lin$="U" ELSE lin$="S"
ELSE
IFsndbits%=4 OR INSTR(FNuc(lin$),"ADPCM") THEN
lin$="A":sndmul%=16
ELSE
lin$="E"
ENDIF
ENDIF
lin$+=STR$sndbits%
lin$="Sound"+lin$: REM lin$ is now the name of the sound decompressor
ELSE
lin$=MID$(snd$,3)+" ": REM format asserts this is OK!
lin$=LEFT$(lin$,INSTR(lin$," ")-1)
f2info%=OPENIN(<ARMovie$SoundDir>."+lin$+".Info"):IFf2info% THEN
snd$=GET$#f2info%:REM name
snd$=GET$#f2info%:REM copyright
snd$=GET$#f2info%:REM 0/1
sndmul%=VALGET$#f2info%: REM bits per sample
CLOSE#f2info%
ENDIF
lin$+=".Play"
ENDIF
IFchannels%>1 lin$+="x"+STR$channels%
Now 'lin$' contains the suffix for the sound file name and a variable 'sndmul%'
contains the number of bits per output sample required for decompression (for
format 1, this is 8 or 16 depending on ADPCM - for which the decompression
buffer needs to be larger than the sample buffer supplied). Variables
'reversed%', 'channels%', 'sndbits%' and 'sndrep' will be needed!
(2) loading the code.
The various sound code files are stored as "<ARMovie$SoundDir>."+lin$ and have
different sizes: enough memory should be allocated to load the particular file:
each file is position independant code which can be loaded into RMA memory or
into application memory (from where it must not be multi-tasked out until
finished with!). An array of entry points is at the beginning of the code.
When a sound file (at least, one which drives the internal sound system) has
been loaded it needs to establish the particular clock rate of the VIDC chip:
this will usually be 24MHz, but may be 25.175MHz (VGA) or other frequencies if
a 'VIDC enhancer' is in use. Therefore it is started playing silently,
recording the sound interrupts. After a while the time and number of sound
interrupts can be used to calculate the VIDC clock rate. A control state is
passed in r0 and the address of a control block in r1. The size of the control
block is 64 bytes (older versions of the sound code used just 8 bytes). The
name mute% used below is historical: one of the control functions of the first
word of the block is muting of sound during play.
REM does it exist? how large is it?
SYS"OS_File",17,"<ARMovie$SoundDir>."+lin$ TO r0%,,,,r4%
IFr0%<>1 ERROR 1,"Sound playback code "+lin$+" not found"
DIM sndcode% r4%-1,mute% 63: REM get memory for the sound code and control block
SYS"OS_File",16,"<ARMovie$SoundDir>."+lin$,sndcode%: REM load it
SOUND ON:REM configure the sound system to defaults
SYS"Sound_Configure" TO oldchan%,,oldspeed%
REM symbols for the entry points
sndplay%=sndcode%: REM play the sound or start VIDC timing test.
sndstop%=sndplay%+4: REM stop playing the sound
snddata%=sndstop%+4: REM give data to be played
sndcounter%=snddata%+4: REM count address
sndifflags%=sndcounter%: REM sound interface flags (see below)
sndbuffer%=sndcounter%+4: REM buffer address: 2 words
sndpause=mute%+4: REM if 0 pause when sound data runs out, if
non-zero don't pause
When calling via sndplay%, the value in R0 is key to operation of the sound
code.
If R0 on the call is 0 then it's an old style timing check and the sound code
will from then on assume that old code is calling it and will operate in a mode
which is compatible with such code. Do not use this value in new code.
If R0 on call is >= 0x10000 then it is a compatibility mode play call (and R0
must have been 0 on the preceding sndplay% timing call); in this case it will
start playing with R0 as the sample rate conversion fixed-point interpolation
fraction (in 8.24 format), and R1 assumed to point at a 2 word control block,
where the first word controls muting/stopping of the sound and the second is
used to determine the treatment of data underrun - see section (4).
If R0 = 1 or 2 then it is respectively a new-style timing check or play call.
R1 points at an information block. The first two words of this block have the
same meaning as with old-style calls. For the play call, R1 is assumed to point
at a 16-word information block (format given below) which tells the sound code
what it needs to know: the contents of this block are maintained partly by the
caller, partly by the sound code. Certain parts must be be initialised by the
caller (see below) before the first play call.
Values of R0 between 3 and 0xFFFF inclusive are currently reserved and must not
be used.
The values to initialise the control block (pointed at by mute% in this example
code) are as follows:
REM mute%!0 is global flags word (caller-maintained)
mute%!0 = 0: REM clear all flags
REM mute%!4 is soundpause flag: see section (5)
REM mute%!8 and !12 are the source sample frequency, as a 32.24
REM fixed-point unsigned number, measured in Hz.
This initialisation must be done before the sndplay% call to start playing the
data. It can also be done anytime earlier (obviously!) as convenient.
The value sndrep (a real +ve quantity) taken from the Replay file header, is
the sample frequency in Hz if >= 256.0, or sample period in usec if < 256.0 We
need the frequency form here.
sfr = sndrep: IF sfr <= 255 THEN sfr = 1E6/sfr
mute%!8 = INT(sfr): REM whole part of frequency
mute%!12 = (1<<24) * (sfr - INT(sfr)): REM fractional part, to 24 bits
REM mute%?16 is requested quality flag, treated as an unsigned byte.
Defined quality range is 1..4 (1=lowest quality .. 4=highest). It is up to the
caller to put in a 'suitable' value. It is up to the sound code as to what to
do with this.
As an aid in making the decision, bit 1 of !sndifflags% (only well-defined once
the initial (timing) sndplay% call has returned) provides a hint from the sound
code. It is used to indicate whether quality is `free', or whether it has a
significant cost in terms of cpu or bus bandwidth. If set, there is a
significant cpu cost factor in using higher quality levels, over and above the
normal cost of simply moving the original data around once (or perhaps twice)
at its natural sample rate.
Some sound hardware may be flexible and/or powerful enough to keep the ARM
processing cost to little more than moving the base-rate data around, in which
case !sndifflags% AND 2 would be 0, and the caller would in general then set
the quality level to 4 (or perhaps allowing user choice if appropriate).
In other cases the sound code may use more cpu cycles and/or memory-bandwidth
with increasing quality factor, so the caller may therefore choose an
appropriate quality factor after consideration of the available performance of
the processor/memory system in use (clock speed(s), bus rates, cache, etc), and
also with regard to the demands of any video data processing which may be going
on.
For internal (VIDC) sound, this bit is set, since oversampling is used (with
the factor determined by the quality level), and the processor must therefore
deal with more and more generated data at higher quality levels, for the same
source sample rate. The quality value controls the output sample rate used: 1:
13.889kHz (72usec), 2: 20.833kHz (48usec) 3: 31.250kHz (32usec) 4: 41.667kHz
(24usec); value 0 is treated as 1, values >4 are treated as 4. However, if the
basic source sample rate is over 14.0 kHz and the quality level is 1, then
48usec is used, to avoid too much noise from the implied undersampling.
mute%?16 = 4: REM (using highest quality in this example)
There is one remaining item defined in the control block:
REM mute%?17 is stereo channel ordering: 0 (norm) is {L,R}, 1 is {R,L}.
REM In mono case, value is ignored by sound code.
mute%?17 = reversed% AND 1
All the remaining bytes in the control block must be zero, for now.
REM Clear remaining control words, for future expandability.
FOR I% = 18 TO 63: mute%?I% = 0: NEXT
The first call on the sound code is a timing call, via sndplay%:
REM start sound timing check
!mute%=0: A%=1: B%=mute%:sndtime%=TIME:CALLsndplay%:sndtime%=TIME
[ old code:
!mute%=0: A%=0: B%=mute%:sndtime%=TIME:CALLsndplay%:sndtime%=TIME
]
REM if something goes wrong from now on, then this is how to put it back
ON ERROR CALLsndstop%:REPORT:PRINT" at line ";ERL:END
The purpose of the timing call is to allow for variable rate master clocks used
to drive the sound output sample rate. This problem is only relevant on
VIDC1-based systems, some of which have VIDC-enhancer (multiple video clock)
circuitry, either as standard or as a hardware enhancement: the video and sound
clocks are linked within VIDC1. However, the sound code may know (or be able to
work out) that the master clock driving the sound sample rate does not vary
with video mode, e.g. it might actually be driving separate sound output
hardware (not linked to VIDC), or be driving VIDC20 which has a separate sound
clock input.
Normally (for current and old machines), the master clock for the sound may be
variable according to video mode, so the timing test needs to go on for a
while: the timing will be accurate enough after 1 second. In this case,
initialise other parts of the system or calculate or whatever, in the
intervening time.
If the master sound clock rate does not vary with video mode, then the sound
code will set bit 0 of !sndifflags% during the initial sndplay% timing call.
Hence the calling code can do the sndstop% call (still necessary) whenever
it wishes, and does not need to wait for the normal minimum of a whole
second. See code in section (4).
Once the timing test has been performed, the sound code will have worked out
the current sound master clock rate, and will keep a record of this (but see
next paragraph). It will discard the information if another timing test call
is made, since it does not itself know when the video mode changes. It is up
to the caller to know that the video mode has been (or might have been)
changed, and hence to re-do the timing test. Conversely, the timing test
does not need to be redone (provided that the same sound code module remains
resident, untouched) if the caller is sure that the video clock frequency
has not been changed, or if the sound master clock rate is independent of
video mode, as may be indicated on the first timing call. However, the
timing test MUST be done at least once after loading new sound code.
For internal VIDC sound (which is expected to be the only sound output type
for which the timing may vary with video mode), the timing check cannot be
done if sound is globally disabled (via *audio off or the Sound_Enable SWI),
since no sound DMA interrupts then occur. Hence, after the initial timing
test sndplay% call has returned, if bit 0 of !sndifflags% is not set (and
therefore the sound timing is potentially variable with video mode), bit 2
should also be checked. If this is set, it identifies the fact that the
internal sound system was found to be globally disabled. This has two
consequences: (a) no sound will be output during the subsequent sound
playing time (the data passed to snddata% calls will just be thrown away);
(b) the sound code cannot determine the master clock frequency, and so
cannot record it - therefore the timing test must be performed again, each
time sound is to be played, until the global sound system is finally
re-enabled.
(3) setting up the sound buffers.
The sound play code needs 2 buffers of the duration of the sample chunk size -
i.e. 'maximum possible number of samples in a sound chunk'*'sndmul%'/8 plus
some space for semaphore flags: another 16 bytes - all word aligned. Compute
the maximum number of samples in a sound chunk by reading the ARMovie 'sndrep'
sound repetition rate variable, the 'frames per chunk' and the 'frames per
second' variables and add 1% of this number of samples to allow for variations
in capture programs. Then allocate the buffers, and in each set the word at
byte offset 4 to 1 to signal them being empty and set !sndbuffer% and
sndbuffer%!4 to the addresses:
IFsndrep<256 THEN
Z%=(fpf/fps*(1E6/sndrep)*channels%*1.01*sndmul%+7)DIV8+12+4+3ANDNOT3
ELSE
Z%=(fpf/fps*sndrep*channels%*1.01*sndmul%+7)DIV8+12+4+3ANDNOT3
ENDIF
DIM sndbA% Z%-1,sndbB% Z%-1
sndbA%!4=1:sndbB%!4=1
!sndbuffer%=sndbA%:sndbuffer%!4=sndbB%
Obviously this can be done at any time, but it is something to do while the
sound playback timing test is being performed.
(4) calculating the system sound clock frequency.
This is now done (if necessary) by the sound code itself, either on the
sndstop% call after the timing test, or in the subsequent sndplay% call.
Applications using the sound code in the new standard mode only need to stop
the timing check. Floating point is used, so the FPEmulator (or whatever is
providing ARM FP instructions) should be present.
REM Wait for the rest of the second since starting the timing
REM check, if required, to ensure timing accuracy.
IF (!sndifflags% AND 1) = 0 THEN
REPEAT UNTIL TIME-sndtime%>99
ENDIF
REM stop the timing check
CALLsndstop%:sndtime%=TIME-sndtime%
[For old code, after the requisite second, the sound play code was stopped and
the VIDC clock frequency calculated. The following code computes the frequency
and also 'snaps' values near to 25.175MHz or an integral number of MHz to those
numbers:
IFsndcounter%!12 sndfreq=sndcounter%!16/sndcounter%!12*sndcounter%!20*24 ELSE sndfreq=24
C%=sndfreq+.5
IFsndfreq>.998*25.175 IFsndfreq<1.002*25.175 sndfreq=25.175
IFC%*1.002>sndfreq IFC%*.998<sndfreq sndfreq=C%
'sndfreq' is the VIDC clock rate. The sound code uses a fixed point number to
express the ratio faster or slower to play the sample:
sndratio%=24/sndfreq*(1<<24)
gives 'sndratio%' which will be used to cause the sound code to play the sample
at the correct rate.]
(5) configuring the system for playing the sound.
Most of this is done by the sound code itself, during the main sndplay% call
(see section (7) below), using information in the block pointed to by R1 on
that call.
[In old code, the RISC OS sound system had to be configured by the caller to
play the sound properly: this requires selecting the right number of channels,
and the right basic sound sample rate. Oversampling (selecting a higher play
rate than the sample was recorded at) improves the sound at the cost of greater
processing (more cpu cycles used): the variable 'slow%' is TRUE for a slow
machine. The 'sndrep' value is taken to mean a period in uS if less than 256 or
a frequency in Hz if greater.
IFsndrep>255 THEN
sndrep=sndrep*speed
sndplayrate%=24:IFslow% sndplayrate%=72
SYS"Sound_Configure",channels%,0,sndplayrate%
sndratio%=sndratio%*(sndplayrate%/(1E6/sndrep))
ELSE
sndrep=sndrep/speed
IFslow% OR sndrep<48 THEN
SYS"Sound_Configure",channels%,0,sndrep
ELSE
SYS"Sound_Configure",channels%,0,sndrep DIV3
sndratio%=sndratio%DIV3
ENDIF
ENDIF
IFchannels%=1 THEN
SYS"Sound_Stereo",1,0:REM mono in the middle
ELSE
IFreversed% THEN
SYS"Sound_Stereo",2,-127:SYS"Sound_Stereo",1,127
ELSE
SYS"Sound_Stereo",1,-127:SYS"Sound_Stereo",2,127
ENDIF
ENDIF
]
!sndpause=0
The actual value set in here is treated as a zero/non-zero flag. Zero means
that the sound code should automatically pause whenever sound data runs out;
that is, it will stop incrementing its view of logical time (amount of sound
data played/skipped), and will wait for, and play, late data, rather than
ignoring it and skipping past it when it does arrive. When non zero, it does
the converse, and data arriving after its "play-by" time is skipped over, in
order to catch up with real time.
Set this flag to the desired value before starting to play the sound, and do
not change this flag until sound playing has been stopped, or you will horribly
confuse the sound code!
(6) giving data to the sound code.
The sound code needs to be continually fed data via its 'snddata%' routine: r0
specifies the address of the data, r1 the number of bytes: after the routine
returns, the source is not required anymore. The snddata% routine will wait for
a buffer to become available - callers can check the value at offset 4 in the
sound buffer: if this is non-zero in either buffer, then a call to snddata%
will return as quickly as possible.
IFsndbA%!4<>0 OR sndbB%!4<>0 THEN
A%=dataptr%:B%=length%:CALLsnddata%
ENDIF
The sound code itself doesn't modify interrupt status (other than disabling
them for very short periods during internal operations), so interrupts should
be enabled around the call to ensure that IRQ latency doesn't get hit by e.g.
ADPCM decompression time.
(7) starting it playing.
After giving some data to 'snddata%', the sound code can be started:
[ Old style:
A%=sndratio%:B%=mute%:CALLsndplay%
]
A% = 2: B% = mute%: CALL sndplay%
(8) in flight entertainment.
While the sound code is playing, the value at address 'mute%' can be changed so
as to control it. Bit zero is pause (if set), bit 1 is mute (if set).
Whilst sound is playing (even if muted), the sound code maintains a pair of
words (sndcounter%!24 and sndcounter%!28), representing the time as far as the
sound code is concerned, i.e. how far through the sound track it had got at the
most recent sound IRQ. This is measured in units of input sample times, that
is, 1/(sample playback frequency as specified in the file); the number of
channels in use is not significant in this calculation. sndcounter%!24 records
the number of whole sample times so far, and sndcounter%!28 is the fractional
part (which must be maintained, for accuracy, but can be ignored by code
checking the sound time).
temp_time%=start_time%+((sndcounter%!24/my_sample_rate%)*100)
(9) stopping.
A call to 'sndstop%' stops the sound code. If this is using the RISC OS sound
system, it will itself put the RISC OS sound parameters back as they were
[unless the corresponding sndplay% call was an old-style one].
CALLsndstop%
[ old code had to do:
SYS"Sound_Configure",oldchan%,0,oldspeed%
]
(10) format 2: To16 and From16.
These convert a chunk's worth of samples between the compressed format and raw
16 bit signed linear (sample-by-sample interleaved for multi-channel formats).
The code has one entry point at the beginning and takes the following
parameters in registers:
r0: source data
r1: destination data
r2: number of sample-time units in the chunk
r3: number of channels
On exit it returns updated values of source and destination registers. The
values of r0 and r1 should be word aligned. The value in r2 is measured in
sample-time units, and is independent of the number of channels involved.
The correct number of channels for the data in hand should always be passed in
r3. If the converter cannot handle conversion of the specified number of
channels, for whatever reason, it will return with r0 zero, and no data will
have been written into the destination buffer.
For To16, the required size of the destination data buffer in bytes is easy to
calculate:
destsize = number of sample times * number of channels * 2
For From16, details from the Info file must be used to compute the maximum
required size of the destination buffer. Take the number of sample-times the
source data represents (s%), multiply by c, convert this from (possibly
fractional) total bits to whole bytes (add 1 byte for certainty), round up to 4
bytes, add the per-channel byte overhead (m%), and multiply by the number of
channels. Hence:
destbuffersize% = ((INT(s% * c / 8 + 1) + 3) DIV 4 * 4 + m) * chans%