# P l a y I t

version 1.31, 11-06-96
by Rick Hudson
rhudson@paradise.net.nz

## Driver Documentation

See the licence for copyright information and conditions of use.

### Contents

- 1. General operating principle
- 2. PlayIt drivers
- 3. Driver header and entry points
- 4. The transfer interrupts
  - 4.1 Operating principle
  - 4.2 Entry points into PlayIt

### 1. General operating principle

This is a general description of what happens when a sample is played using PlayIt. Most of it is not important to the operation of the driver but it may be useful to know how the driver works in the overall scheme of things.

A driver is loaded into the module area by PlayIt and the initialisation code is called. It then sits idle until a sample is to be played. When a sample is about to be played, the pre-play entry point is called with some general information about the sample to be played and the driver returns with information about how it wants the data. The play entry point is called to set up the sound hardware and start the interrupt sequence. Under interrupts, the driver then transfers data from the file buffer to the hardware by calling on PlayIt to do the processing required. The driver need not do anything else as PlayIt can handle everything during processing.

Errors can be generated by the driver in the normal way by setting the V flag and pointing R0 to an error block. Entry points are called with the V flag clear.

I have tried to ensure that the driver mechanism retains as much flexibility as possible to allow the drivers to do as little or as much of the work as necessary to take advantage of the hardware. However if you find writing a driver for your particular hardware is not easy using the existing interface then please contact me with suggested changes or improvements. I cannot guarantee anything as the driver interface is quite well defined.

### 2. PlayIt drivers

PlayIt produces data in one of several fixed formats regardless of original file format. The driver is responsible for transferring this preprocessed data to the hardware that it supports. It must provide all of the pointers and entry points described below and an internal mechanism to drive the hardware.

### 3. Driver header and entry points

The driver header consists of a set of pointers and entry points as follows. All entries must contain/point to valid information or code.

Hex offset | Description
--- | ---
00 | Identifier "PlayItIL" for in-line driver
08 | Driver preference number
0C | Offset to brief title string, max 16 bytes (eg Acorn-16)
10 | Offset to descriptive title, max 64 bytes
14 | Offset to version/copyright/author string, max 64 bytes
18 | Enquire entry point
1C | Init entry point
20 | Quit entry point
24 | PrePlay entry point
28 | Play entry point
2C | Stop entry point
30 | Volume change entry point
34 | Mode change entry point

Information pointers are stored as an offset from the start of the file to the string. Entry points are called with the return address on the top of the full descending stack pointed to by R13 (ie return with LDMFD R13!,{PC}. Unless otherwise stated, R0-R7 may be corrupted and R8-R13 must be preserved. Errors are returned by pointing R0 to a standard error block and setting the V flag. All entry points are called with R12 pointing to the public part of (of what?! where did the text go?)

Example of header from standard driver:

        .start    EQUS "PlayItIL"
                  EQUD preference     \ assigned number for auto selection
                  EQUD title-start
                  EQUD descr-start
                  EQUD version-start
                  B enquire           \ enquiry
                  B init              \ init
                  LDMFD R13!,{PC}     \ quit (not required - immediate return)
                  B preplay           \ getting ready for play
                  B play              \ start playing
                  B stop              \ stop playing
                  LDMFD R13!,{PC}     \ new volume selected
                  B newmode           \ video mode has changed

        .title    EQUS "Standard":EQUB 0
        .descr    EQUS "PlayIt driver for standard sound system":EQUB 0
        .version  EQUS "1.00 (29 Feb 1996) by Rick Hudson":EQUB 0
                  ALIGN

#### Identifier (offset &00)

The eight byte string "PlayItIL" must be present to identify the file as a PlayIt in-line driver. The "IL" stands for in-line and is to distinguish the driver from other methods used previously (or in the future).

#### Preference (offset &08)

Each driver has a unique identifier between 1 and 255. A higher number indicates a higher preference to use this driver. This is used by the automatic driver selection by choosing the one with the highest preference that works. Currently assigned preferences are:

        32   Standard
        64   ISA/SoundBlaster
        96   Lark 16-bit audio card
       128   Acorn 16-bit card

If you create a new driver (and especially if you wish to distribute it), please e-mail me with your chosen preference so I can keep the list up to date to avoid clashes.

#### Pointer to brief title string (offset &0C)

This string briefly identifies the hardware that its intended to drive. It may be up to 16 characters and is terminated by a null (0). This is used by software to produce a list of available drivers and to identify the driver currently active.

#### Pointer to descriptive title string (offset &10)

This string contains a more descriptive version of above (up to 64 characters + null). It could contain the full name of the device and the manufacturer, for example. It is used for hardware information dialogues.

#### Pointer to version string (offset &14)

This string contains brief information on the driver preferably in following example format (ie similar to modules):

        1.00 (29 Feb 1996) by Rick Hudson

The string should be no longer than 64 characters + null.

#### Enquire entry point (offset &18)

On entry | R12 = pointer to workspace R13 = full descending stack with return address on top
On exit: | -

This is the first entry point called when a driver is loaded. The purpose is to establish whether the driver will work with the computer that it is being run on. It should return with the V flag clear if sound playback is currently possible with the hardware that it supports. If it can't it should return with V set and R0 pointing to an error block describing why the driver can't be used. Errorbase+12 is reserved for drivers to report errors of this form.

This is not an initialising entry point and the driver must not exit with extra memory or hardware claimed. The driver will probably be removed from memory upon return from this entry point.

The driver must not return with V unconditionally clear as the selection mechanism will not work.

Processor is in SVC mode with interrupts undefined.

#### Init entry point (offset &1C)

This is called once when the driver is loaded to set up anything required. If more workspace is required than that reserved by PlayIt then this should be claimed here. It is not necessary to test the hardware again at this point since the enquiry entry point is guaranteed to be have been called immediately prior to this. If no special action is required then init may just return immediately or enter the same code as enquire.

On entry | R12 = pointer to workspace R13 = full descending stack with return address on top
On exit: | -

If the driver cannot initialise then it should return with the V flag set and R0 pointing to a standard error block. A typical reason for returning with an error is insufficient memory for workspace. If init returns an error, the driver is removed from memory and PlayIt is left in a state with no driver installed.

Init errors should be rare as problems should be pre-empted in the enquire entry point wherever possible (which does not leave PlayIt in a driverless state).

Processor is in SVC mode with interrupts undefined.

#### Quit entry point (offset &20)

This is called when the driver is about to be removed from memory either because a new driver is being loaded in its place or PlayIt is quitting. This is mainly for releasing workspace. If necessary, the stop entry point will have already been called before the quit entry point so it should not be necessary to release the hardware first (stop should do this).

On entry | R12 = pointer to workspace R13 = full descending stack with return address on top
On exit: | -

Processor is in SVC mode with interrupts undefined.

#### PrePlay entry point (offset &24)

This is called just prior to play entry point. It is to determine the format of the data to be made available to the driver. Do not setup the sound hardware at this point.

On entry | R12 = pointer to workspace R13 = full descending stack with return address on top
On exit: | Workspace updated

An error should only be generated if there is some reason why the sample cannot be played through this device. As PlayIt preprocesses data into a standard format this should rarely, if ever, occur. Problems with the hardware should be reported in the play entry point.

There are four 'variables' that the driver should update:

        onch    (byte at &2A)   output number of channels
        outform (byte at &2B}   output format
        outrate (word at &2C)   output frame rate
        fxflags (word at &30)   effects flags

onch is set by default to the number of channels in the source file. If the driver doesn't change this it will need to be aware of the fact that the data that PlayIt gives it will either be mono or stereo as indicated by this byte. If the device is mono only then set onch to one and PlayIt will generate mono format data (mixing left and right if the source is stereo). If the device is stereo only then set onch to two and PlayIt will generate stereo format data - possibly with stereo simulation effects added.

outform describes the format of sample required by the driver. The code is similar to format used to specify the source sample:

        0   8-bit signed linear
        1   8-bit unisigned linear
        2   reserved for A-law (not implemented)
        3   reserved for mu-law (not implemented)
        4   VIDC
        8   16-bit signed linear little endian
        9   16-bit unsigned linear little endian
        A   16-bit signed linear big endian
        B   16-bit unsigned linear big endian

All other values are reserved and undefined. The default is 0. The exact arrangement of returned data depends on fill entry conditions described later.

outrate is set by default to be the frame rate of the source sample to inform the driver what it is. This can be changed to anything the driver can use. For example, the standard driver unconditionally sets this to the current system speed so as not to upset co-existing sound drivers. Bit 2 in fxflags indicates a user preference for frequency modulation (or not) and the driver can choose to ignore this or make some effort to comply. To work unmodulated, outrate should be left as is (it will be forced to the same as inrate later) and a throughput frequency will need to be chosen as close as possible to outrate. PlayIt can interpolate between samples when shifting frequencies (at the discretion of the controlling software) so sound quality shouldn't be significantly lower anyway.

The effect settings control various aspects of the sound output and can be implemented by the driver or by letting PlayIt preprocess the data.

      bit 0  volume
      bit 1  balance
      bit 2  FM mode
      bit 3  VU

Bits 0 and/or 1 should be set if the driver wishes to handle volume and/or balance. This is mainly to support hardware having separate volume controls so that samples need not be scaled in software. When the bits are clear (default), PlayIt handles the effects in the conversion stage. FM mode is initially set according the configuration preference option and the driver should update this to reflect whether it wants FM enabled to implement frequency shifting. FM should only be disabled by devices capable of setting their output frequency to a resolution of about 1kHz or better. It is a question of whether the resulting closest speed sounds acceptable to not. The driver may calcuate VU information instead of PlayIt by setting bit 3 - see later for more information.

When the driver handles volume and/or balance it should take note of the various volume settings held in the public array (see later) to get the volume the user has requested. The driver must not change any of the volume settings, just use them. The driver is informed of mid-playback volume changes through the volume entry point.

Processor is in SVC mode with interrupts disabled.

#### Play entry point (offset &28)

This is called when PlayIt_Play is called. The driver should set up the hardware for immediate sound generation. It should be arranged that an interrupt sequence is started before returning from here. By this point, the file buffers are already filled and data can be processed immediately. See later for guidelines on writing the interrupt code.

On entry | R12 = pointer to workspace R13 = full descending stack with return address on top
On exit: | -

If the hardware cannot be set up (eg presently unavailable) then an error should be returned (it will be reported) and PlayIt will not expect playback to start.

Processor is in SVC mode with interrupts enabled.

#### Stop entry point (offset &2C)

On entry | R12 = pointer to workspace
On exit: | -

This entry point is called indirectly from the driver interrupt routine (see later) when sound is to stop. It should wait (if necessary) until interrupt activity has stopped. The hardware then should then be 'released'. Once control has returned from this entry point pointers and data relating to the sample may become invalid. Errors may be reported if there is a problem.

Processor is in SVC or IRQ mode with interrupts enabled.

#### Volume entry point (&30)

On entry | R12 = pointer to workspace
On exit: | -

This is called when PlayIt's volume or balance is changed (ie from PlayIt_Volume or PlayIt_Balance) to inform the driver. It will also be called if system volume scaling is enabled and PlayIt detects a change in system volume.

If the driver is not handling volume or balance then no action is required as PlayIt will scale the samples instead . The collection of volume data in the public array should provide the necessary information for the driver to act on. The volume settings are all logarithmic. The driver does not have to adhere to any particular log scale but PlayIt uses the same convention as the system sound whereby a increase/decrease of 16 on the log scale results in a doubling/halving of the linear amplitude.

This entry point is mainly for use by hardware that has its volume and/or balance (if applicable) controlled by a means other than scaling the samples sent to it - for example writing the volume to a hardware register in a mixer. For drivers wanting to implement software control of volume and/or balance see the interrupt guidelines later.

Processor is in SVC mode with interrupts disabled.

#### Mode change entry point (&34)

On entry | R12 = pointer to workspace
On exit: | -

This entry point exists mainly for the standard sound system driver so it can deal with the problem of when the VIDC generates non-standard frequencies for the sound hardware in some modes. The standard sound system driver needs to adjust the target frequency (outrate) to compensate for this frequency error. This is unlikely to be useful for other drivers.

Processor is in SVC mode with interrupts undefined.

#### The workspace

All driver entry points are entered with R12 pointing to a 256-byte workspace. The first half of this has some of PlayIt's 'variables' that may be useful to drivers. The second half is free for drivers. The names are those defined in the Basic library PubArrLib.

     offset size  name        description

       &00  word  pbuff       address of play buffer1
       &04  word  pbend       end of play buffer1
       &08  word  pbuff2      address of play buffer2
       &0C  word  pbend2      end of play buffer2
       &10  word  pbptr       pointer into play buffer
       &14  word  pbeof       eof address in current play buffer
       &18  word  pbeofa      eof address in alternate play buffer
       &1C  word  pbsize      size of play buffers

       &20  byte  balance     balance as set by user
       &21  byte  sysvol      current system volume
       &22  byte  logvol      user set log volume
       &23  byte  logvolL     left log volume from balance only
       &24  byte  logvolR     right log volume from balance only
       &25  byte  abslogvol   log volume as &22 but scaled for system vol
       &26  byte  abslogvolL  log volume as &23 but scaled as above
       &27  byte  abslogvolR  log volume as &24 but scaled as above
       &28  byte              reserved
       &29  byte              reserved

       &2A  byte  onch        output number of channels
       &2B  byte  outform     output format
       &2C  word  outrate     output frame rate
       &30  word  fxflags     effects flags

       &34  word  epfill      address of entry point into PlayIt for fill request
       &38  word  epstop      address of entry point into PlayIt for stop request

       &3C  word  plintovidc  pointer to the linear to VIDC lookup table

       &40  word  VUleftsum   sum absolute values of left channel (each 0-127)
       &44  word  VUrightsum  sum absolute values of right channel (each 0-127)
       &48  word  VUframes    number of frames that above are summed over

       &4C-&7F                reserved for PlayIt

       &80-&FF    user        available for driver

A lot of this is unused by in-line drivers (reserved for callback drivers). The only ones used by in-line drivers are &2A-&3B.

If system volume scaling is disabled, the abslogvol* variables will be the same as their logvol* counterparts otherwise they contain system volume scaled volumes. The abslogvol* entries should be used in preference.

The use of these data are described in the interrupt guidelines below and in the entry point descriptions above.

### 4. The transfer interrupts

All the real work is done in the interrupt routine which is setup from the play entry point. It is the job of this routine to transfer data to the sound hardware (possibly with some modification) and to repsond to the end-of-sample status returned by PlayIt.

### 4.1 Operating principle

The actual interrupt mechanism is hardware specific but it is expected that it, or some software aspect of it, can generate interrupts whenever it needs more data. The driver provides a handler for these interrupts and the interface with PlayIt is outlined here.

The driver can tell PlayIt the characteristics of the hardware buffer and get it to do all the work or it can specify an interim buffer which the driver then copies to the right place. The second method provides the opportunity to further manipulate the data at the expense of more data movement.

### 4.2 Entry points into PlayIt

There are two points at which the driver can call PlayIt; the fill request and stop request entry points (epfill and epstop). As they are called from the data transfer interrupt some special conditions apply.

Because of the possibility of R14 being corrupted inside an interrupt routine, a branch with link (BL) should not be used. Entry points should be used as follows (R8 points to the variable array):

        ADR Rx,return_address     \ return to this address upon return
        STMFD R13!,{Rx}           \ push return address onto stack
        LDR PC,[R8,#epfill]       \ jump to entry point, epfill = &14

or:

        STMFD R13!,{PC}           \ stack current PC (here + 12 bytes)
        LDR PC,[R8,#epfill]       \ jump to the entry point
        EQUD 0                    \ space filler
        <code>                    \ PlayIt returns to this point

The entry points return the driver with LDMFD R13!,{PC} or equivalent.

#### Refill request entry

On entry | R12 = address of base of buffer to fill R11 = byte offset between frames R10 = address of end of buffer (fill while R12<R10) R9 = byte offset between channels (ignored for mono) R8 = workspace pointer (usually R12 in other entry points)
On exit: | All registers preserved except flags C flag set if end of playback reached

When PlayIt requires playback to stop, it sets up a callback which eventually calls the driver's stop entry point. If C is set on exit it should make this the last fill interrupt, if possible. If interrupts *do* continue then the stop entry point must be able to stop things from there.

For mono sound, the data is written to [R12], [R12+R11], [R12+2*R11] etc while [R12+i*R11] < [R10]. For stereo sound, the left data is written exactly as for mono and right data is written to [R12+R9], [R12+R9+R11], [R12+R9+2*R11], etc while [R12+i*R11] < [R10]. 16-bit data is always written with the bytes in adjacent memory locations. The byte order is defined by the user from the PrePlay entry point.

eg to fill a buffer with 16-bit stereo interleaved data use R12=base, R11=4, R10=end, R9=2. To write the left and right channels into two separate buffers use R12=base of left buffer, R11=2, R10=end of left buffer, R9=(base of right buffer)-(base of left buffer).

#### stop request entry

On entry | -
On exit: | All registers preserved except flags

This entry point is used if the driver needs to force playback to stop. Normally this is not necessary since this happens automatically when PlayIt decides to stop playing. If the refill entry point is called again, it will fill the buffer with silence and return with C set. Soon afterwards, the driver's stop entry point will be called. An example use of this is when the voice being used by the standard driver is reassigned and the sound output must stop.