############################################################################
#                                                                          #
#                            Sample Player v. 1.27                         #
#                                                                          #
#==========================================================================#
#                                                                          #
#                              by David Radford                            #
#                                                                          #
#==========================================================================#
#                                                                          #
#                This program is freeware: it may be copied                #
#            freely as long as no profit is made in the process            #
#                                                                          #
############################################################################

Overview
========

This application allows you to play back compressed samples. These samples
are produced from Armadeus files by a separate program called !Compress.


Why compress samples?
=====================

Samples take up an enormous amount of room. Good quality samples (from
20.833KHz upwards) take up at least 20K for each second. Assuming the
maximum capacity of a standard floppy disc is 780K, then the largest sample
to be stored on disc is 39 seconds. If you want longer samples you have to
reduce the quality. On the other hand, if you compress the data, you can
store much longer samples on a disc. Of course, if you only have a 1
Megabyte machine you would have problems trying to take a sample longer than
780K anyway. Another use of compressed samples is to save space on a hard
disc. Bulletin boards sysops/users may find this particularly useful, as
less disc space is wasted by these files, and download/upload times are
greatly reduced.

Games are another area that can benefit from compressed samples, allowing
either more samples or more code to be crammed onto the disc.


How do I compress samples?
==========================

This application is designed to only play back samples. To compress them and
decompress them a separate utility is required, called !Compress. This is
available from bulletin boards, public domain libraries, etc. (Programmers
may be interested in a series of utilities (!CompUtils) which can be added
to your own programs to provide support for compressing and decompressing
sample. This collection can be obtained from the same sources.)


How to use !Player
==================

Double click on its icon. This will load into memory the module that
decompresses the samples (SamplePlayer), along with a small multitasking
application. There will be no visible change to the desktop, so don't be
fooled into thinking it hasn't loaded! Now, any compressed sample you come
across can be played back by double clicking on it. The file icon for
compressed samples is similar to the icon for the !Player application,
except that the horizontal line is orange not red.

Playback stops when the end of a sample is reached and does not repeat
(though there is an option to allow this - see *PlaySample below). It can
also be stopped using the *PlaySampleAbort command should the need arise.

Please note that !Player is just a desktop application for playing
compressed samples. It was never intended for use in your own programs
(though it can be used in that way), a fact reflected by its size. If you
want to use SamplePlayer in your own program, I would strongly recommend
having a look at !CompUtils first.


Sequences
=========

If you have more than one compressed sample then you can also play them back
in a certain order. This is done by creating a sequence file. This file is a
plain text file with the file type &351 (Sequence). Probably the best way of
writing one of these files is using !Edit and then (if using RISC OS 2)
changing the file type using some file type changing utility (there are
several available in the public domain). Details about the file format are
given later.

When you double click on a sequence file, the samples in the list are played
back one at a time without a break in between. You can't really use it for
making music, but you can use it for playing back samples that are too long
for one disc, for example, or constructing conversations between people
using sample phrases.


Floppy discs
============

If you intend to play back samples held on a floppy disc it is recommended
you increase the buffer sizes using the following *command after loading
!Player:

    *PlaySampleBuffers 200K 320K

This brings it in line with earlier versions of !Player. If you often use
floppies, you can change the !Run file to make this the default setting.
Simply add '-Lcommand' to the last line like this:

    RMRun <Obey$Dir>.SampPlayer -Lcommand %*0


How it works
============

!Player v1.23 or later plays samples and sequences in the background while
you carry on with whatever work you wish to do. To do this it uses two
buffers (large areas of memory) for holding data - one holds data from the
file being decompressed (the input buffer), the other holds the decompressed
data ready to be sent to the speaker (the output buffer).

As data is sent to the speaker, room becomes free in the output buffer for
more data. At this point whatever task the computer is engaged in gets
halted and control passes to the SamplePlayer module, which in turn
decompresses some more of the input file and tops up the output buffer.
Control then returns to the original task which carries on as if nothing had
happened.

While decompressing the input file, it extracts data from the input buffer.
When the buffer becomes empty it is refilled from disc. This is where the
problems can occur, because it isn't possible for SamplePlayer to read the
disc from in the background. To get round this it makes use of callbacks
when it needs to read from a disc.

The problem with callbacks is that if the operating system spends an
excessive amount of time (say 15 secs) doing just one thing (eg. reading a
very long file from a floppy disc), the callback will be delayed until after
it has finished. During this time the output buffer is becoming
progressively emptier, and if it does become empty playback will
mysteriously stop. Once the operating system finishes what it was doing
playback will continue (after a short delay). This is unavoidable, and is a
consequence of having to use callbacks.

The problems with callbacks can be reduced slightly by changing the size of
the output buffer. Increasing the buffer will mean it takes longer to become
empty, thus reducing the probability of a long operation causing playback to
halt. The obvious disadvantage of this is that more memory is used.

Playing back from a floppy disc incurs an additional penalty, as floppy
discs are relatively slow, meaning the input buffer takes longer to refill.
Therefore, the minimum reliable size of the output buffer depends on the
storage medium you're using.


Libraries
=========

A sample library is simply a single file containing one or more compressed
samples. At present there isn't a special program to create these libraries,
but a utility distributed with !CompUtils can perform this function.

SamplePlayer can load these libraries into memory, though you can only have
one loaded at a time. From there, all the samples in the library may be
accessed using the *PlaySample command without the usual disc problems (no
disc access is needed since the sample is already in RAM).


*Commands
=========

*PlaySample <filename> [<options>]
----------------------------------

    Starts playback of a sample or sequence. If a sample or sequence is
    already playing, it is aborted and the new one started. There is a
    slight delay before playback starts.

    The options are:

        S - Sequence flag. The file is not a sample file but a sequence
            file, and contains a list of samples to be played. (see below)

        R - Repeat flag. Playback of the sample/sequence repeats
            continuously.

        E - Enhance flag. If a sample is stored in the enhanced form, this
            option allows SamplePlayer to try to compensate for the poor
            filter in the Archimedes by playing back at twice the sample
            rate and applying a low-pass filter. This gives improved
            performance for low sample rates (in theory).

            If used with a sequence file, the enhance flag is only valid if
            the first sample in the sequence is an enhanced sample,
            otherwise it is ignored.

        C - Callback flag. Normally SamplePlayer tries to avoid using
            callbacks until a file access has to be made. The speed
            overheads involved in changing from normal mode to callback mode
            can be removed by setting this flag, which forces SamplePlayer
            to use only callbacks. This flag is not particularly useful and
            not worth bothering about most of the time.

        L - Library flag. If the sequence flag is set, the sequence file
            contains a list of sample names existing in the currently loaded
            library, rather than a list of filenames. In addition, if the
            first line of a sequence file consists only of a single question
            mark, this flag is assumed to be set.

            If the sequence flag is not set, the filename given is actually
            the name of a sample, which can be found in the library.

        M - Monotask flag. If used, playback occurs in the foreground (or
            appears to at least). This is for backwards compatibility with
            older versions of the module. Any errors are reported in the
            same way as other *commands, so the *PlaySampleError setting is
            ignored.

*PlaySequence <filename> [<options>]
------------------------------------

    This is not really a command, just a macro. It simply performs a
    *PlaySample command using the given parameters, but with the addition of
    the S flag being set.

*PlaySampleAbort
----------------

    Attempts to stop playback of the current sample/sequence.

*PlaySampleError [<option>]
---------------------------

    Since playback occurs in the background under interrupts, any errors
    that occur cannot be handled in the usual fashion. SamplePlayer provides
    a special mechanism for detecting and reporting errors occuring in the
    background.

    The command's single option, if present, determines how future errors
    will be dealt with:

        0 - Send error text to VDU stream. This is not particularly
            satisfactory and looks tacky, simply displaying the text message
            whereever the cursor is. The foreground task may not be
            expecting this and so it could muck up the screen display a bit.
            This option should not really be used from the desktop.

        1 - Send error text to wimp task. SamplePlayer provides among its
            many services a module task designed to display background
            errors in the desktop. This task is started up automatically
            when !Player is run. If the task is not running when an error
            occurs, the text is displayed on the screen as in option 0
            above.

        2 - Display nothing. The error is copied away to a safe area and is
            not displayed in any way. *PlaySampleError (without an option)
            can be used to examine this error from a foreground program.
            (see below) This mechanism is intended for grafting SamplePlayer
            into your own programs.

    If no option is present, this command examines the current error state.
    If an error has occured but not yet been reported (ie. option 2 is being
    used, or option 1 in cases where the wimp task has not yet got round to
    processing the error) this command will produce an error and clear the
    error condition, otherwise nothing happens.

*LoadSampleLibrary <filename>
-----------------------------

    Loads a sample library into memory. Only one library can be loaded at
    a time, but all samples within it are immediately available for use, and
    callbacks are not needed when playing from a library. If SamplePlayer is
    currently playing a sample from the library when this command is used,
    playback is aborted and the new library loaded.

    If loading was successful, a system variable will be created called
    PlaySample$Library which contains the filename of the sample library
    loaded. This variable remains in existence while the sample library is
    still loaded.

*KillSampleLibrary
------------------

    If a sample library has been loaded, this command will remove it and
    free the memory. If SamplePlayer is currently playing a sample from the
    library, playback is aborted.

    The PlaySample$Library variable is deleted by this command.

*ListSampleLibrary
------------------

    Lists the names of all the samples in the currently loaded sample
    library.

*PlaySampleBuffers <single size>[K] <sequence size>[K]
------------------------------------------------------

    Sets the size in kilobytes of the output buffer to be used for playback
    (takes effect from the next *PlaySample/*PlaySequence command). Two
    values are given: the first is the size to be used when playing a single
    sample, and the second is used when playing a sequence. Usually the
    second will be larger than the first, since there are large overheads
    involved in changing from one sample to another. The minimum reliable
    sizes of these values will depend on the speed of your computer and the
    speed of the medium holding the samples eg. floppy disc (slow, so large
    buffer), hard disc (fast), RAM (very fast, so small buffer), etc.


Memory requirements
===================

The samples are read straight from disc to save memory, except in the case
of sample libraries. To make this possible, 2 buffers are needed: an input
buffer and an output buffer. The input buffer is currently fixed at
80K, but the output buffer size can be set using *PlaySampleBuffers. The
default values are 80K for single samples and 160K for sequences.

In addition a small amount of workspace is required for internal purposes.
This shouldn't come to more than about 2K though. All buffers and workspace
are claimed from the module area.


How to write sequence files
===========================

The current version of !Player will any control character as an end of line
marker (and space as well). Older versions do not, so it's better to stick
to using Edit to create the file and putting on item on each line. In
addition, older versions disallowed blank lines and spaces at the start of
lines.

The first line of a sequence file gives information on the location of the
samples that make up the sequence. There are several alternatives:

    1) A single question mark. This indicates that the samples in the list
       are actually names of samples stored in the currently loaded sample
       library. This line is assumed if the 'L' flag was used in the
       *PlaySample/*PlaySequence command.

       This option is not available before version 1.23.

    2) A single hash (#). All filenames of samples are given relative to the
       directory holding the sequence file. This is the best option.

    3) A path string. eg.
           idefs::4.$.Samples.Compressed.
           <Obey$Dir>.
           ram:
       which will be assumed to precede all filenames unless overridden.

       System variables may not be used before version 1.23.

    4) A blank line. This means that each filename contains enough
       information to be able to locate the file. This option is useful when
       samples are spread across more than one filing system, or more than
       one disc.

Following this line is a list of samples, one per 'line'. Each gives the
filename (or sample name if using the library) of the sample to play. The
name can include sub-directories, etc. They are played in the order given in
the list.

The list is terminated by a line containing a single asterisk (*). This is
no longer needed but should be added for backwards compatibility. It should
be followed immediately by a line feed.

Here is an example file:

                       adfs::0.$.Samples
                       IWouldLike
                       MoreSamps.ALoaf
                       OfBread
                       *

and another :

                       #
                       SampleDir.Sample1
                       SampleDir.SampleDir2.Sample2
                       Sample3
                       *

Please note that you should not mix compressed samples with different sample
rates, and you should not mix non-enhanced files with enhanced files. If you
do, then you may produce some interesting noises!


Synchronizing foreground programs with sample playback
======================================================

A mechanism exists whereby you can synchronize foreground events to the
sound being produced in the background, possibly useful in demos etc.
SamplePlayer provides a 64-bit index into the output data, so if 9000 bytes
have been sent to the speaker so far, the index would give a value of 9000.
In actual fact the index is always a multiple of the DMA buffer size
(presently fixed at 256 bytes).

The index is stored as two words: the first gives the lower 32 bits, the
second gives the upper 32. The index is stored in the module's private
workspace AND MUST THEREFORE NOT BE ALTERED DIRECTLY. When the module is not
playing anything (ie. not started yet, or just finished, etc) the second
word of the index is set to -1.

The index is reset to zero whenever playback returns to the start of the
sample/sequence (ie. when using the repeat option).

You should not rely on being able to exactly match the index with a target
value ie. use:

    if (index!0 >= target!0)
        and (index!4 >= target!4)
        and (index!4 <> -1)
    then
        action
    endif

instead of:

    if (index!0 = target!0)
        and (index!4 = target!4)     \ assuming (target!4 <> -1)
    then
        action
    endif

If possible, disable interrupts when reading the index:
    1. disable interrupts
    2. make copies of both index words
    3. restore interrupts
    4. process copies

NB - Enhanced samples produce twice as much data as normal samples when the
     E flag is set. The index will therefore increase at twice the normal
     rate.

The following code fragments can be used to find the address of the index:

Basic
-----

    SYS "OS_Module",18,"SamplePlayer" TO ,,,,indexptr%
    indexptr%+=92

Assembler
---------

.getindexptr
    \ this gets the address of the index, and generates errors if something
    \ is wrong with the module. Note: this is needlessly
    \ over-the-top.
    \ on exit, r0 = ptr to 64-bit index word, in module's own workspace

    \ first, get data on SamplePlayer module
    MOV     R0,#18              \ reason code to look up module
    ADR     R1,modulename       \ full module name
    SWI     "XOS_Module"        \ do call
    BVS     module_not_present  \ error if module not found

    \ r4 = contents of private word
    TEQ     R4,#0        \ check there's workspace
    BEQ     no_workspace \ could be old version of SamplePlayer if none

    LDR     R1,deaddead  \ check the module hasn't died horribly
    TEQ     R4,R1
    BEQ     module_dead

    ADD     R0,R4,#92    \ offset to 64-bit index data
    MOV     PC,R14       \ return to caller

.deaddead   EQUD &DEADDEAD

.module_not_present
    \ cause an appropriate error
    ADR     R0,no_module_error
    SWI     "OS_GenerateError"

.no_workspace
    \ cause an appropriate error
    ADR     R0,no_workspace_error
    SWI     "OS_GenerateError"

.module_dead
    \ module has dies horribly - it's still active but can't be used and
    \ can't be deleted (hence can't be reloaded) - have to do hard reset to
    \ clear the condition
    ADR     R0,module_dead_error
    SWI     "OS_GenerateError"

.modulename
    EQUS    "SamplePlayer"+CHR$0
    ALIGN

.no_module_error
    \ module couldn't be found - name wrong/module not loaded/RMA corrupt
    EQUD    1
    EQUS    "SamplePlayer module not loaded"+CHR$0
    ALIGN

.no_workspace_error
    \ module has no workspace - most likely an old version of the module
    EQUD    1
    EQUS    "Version of SamplePlayer module is too old"+CHR$0
    ALIGN

.module_dead_error
    \ module couldn't be killed when asked to, so now it's a zombie
    EQUD    1
    EQUS    "SamplePlayer module is dead"+CHR$0
    ALIGN


At present, the index is stored at offset +92 in the module's workspace. In
future versions I'll try not to change this position but there are no
guarantees.


Conflicts over sample libraries
===============================

Since only one sample library can be loaded at a time, programs may have to
fight for control of this resource. Multitasking programs should use the
DeviceClaim protocol to gain control of the sample library. Monotasking
programs can take over control directly *provided* they restore the sample
library on exit. To aid this, a variable is maintained by the module called
PlaySample$Library, and this contains the filename of the currently loaded
library. If there isn't one then PlaySample$Library doesn't exist.

So, to preserve the sample library:

    1. Check for the presence of PlaySample$Library.
    2. If it exists, make a copy of it and flag this fact.
    3. Load your own library.
    4. Do your stuff!
    5. Kill the library.
    6. If PlaySample$Library did exist, then use the copy of it you made to
       reload that library.
    7. Return to the desktop.


Possible future updates
=======================

    1. Add the ability to load more than one sample library at once by
       means of a sample pool.
    2. Provide commands to add new samples to, and delete old samples from,
       the sample pool.
    3. Provide a command to save the current sample pool as a library.
    4. Move all text and errors into a standard message file.
    5. Update !Compress to give greater compression and optionally output
       samples to a library file instead of a directory.

Please send any other ideas you may have to the address below (email's best
if you have the access). But I do only have limited free time so don't
expect to see any updates for a couple of months (if at all).


The author
==========

If you want to send me comments, suggestions, bug reports, etc. (or just
want a chat) you can contact me at any of the following addresses:

    Email:   radford@argonet.co.uk

    Fidonet: David Radford at 2:250/219.6

    Snail:   David Radford
             64 Molyneux Road,
             Westhoughton,
             Bolton,
             Lancs.
             BL5 3EU
             
    Web:     http://www.argonet.co.uk/users/radford/

Email is preferred. It's the easiest to forward on, and harder for me to
forget to send a reply (I'm very easily distracted!). Updates will be made
available via my web pages, so check there if you're looking for the
latest version.

Please mention the version number of !Player in any correspondance - your
problems may be the result of using an ancient version of the program. I
regularly find people uploading versions 3 years old to bulletin boards,
which really annoys me, especially if I've just uploaded the latest version
there already.


Copyright notice
================

This application and its contents are the copyright of David Radford.
However, they may be freely distributed by P.D. libraries, bulletin boards,
individuals, magazine discs, etc., providing the application is copied
unaltered. No part of this application may be used in any commercial
programs without the author's permission.

                        (c) David Radford 1991-1996


