From mailserv@gaia.ucs.orst.edu Wed Apr 27 19:31:51 1994
Precedence: Bulk
Date: Wed Apr 27 17:23:29 PDT 1994
From: gus-sdk-request@gaia.ucs.orst.edu (GUS Programmer's Server)
Reply-To: gus-sdk@gaia.ucs.orst.edu (GUS Programmer's Digest)
Subject: GUS Programmer's Digest V10 #18

GUS Programmer's Digest     Wed, 27 Apr 94 17:23 PST     Volume 10: Issue  18 

Today's Topics:
           GF1 Looping Mechanism and Clicks at Loop Points

Standard Info:
	- Meta-info about the GUS can be found at the end of the Digest.
	- Before you ask a question, please READ THE FAQ.

----------------------------------------------------------------------

Date: Tue, 26 Apr 1994 21:07:19 -0400 (EDT)
From: Phat H Tran <ptran@sciborg.uwaterloo.ca>
Subject: GF1 Looping Mechanism and Clicks at Loop Points

The GUS SDK, as good as it is, is sorely lacking in a few areas, one of
which is details on the exact looping mechanism of the GF1.  This has
resulted in annoying and perplexing clicks in GUS software that
otherwise would appear to the programmer to be properly written.  David
MacMahon and I have been twiddling with the GF1 over the past few days
to figure out precisely what happens at the loop points and have
elucidated some details which must be taken into consideration when
programming the GUS.

In a nutshell, the looping mechanism of the GF1 may be considered
"counter-intuitive" (assuming that everyone's intuition operates in a
similar manner).  Consider the string of samples "A B C D E" and a voice
with its start loop point set to sample 'A' and its end loop point set
to sample 'E'.  If the FC (frequency control) register for the voice is
set to 0.5, one might expect that the looping would result in an output
stream of

   "A a B b C c D d E e A a B b C c D d E e A a B b C c D d E e ..."

where each lower case letter is a value interpolated between the
previous actual sample point and the point immediately following it _in
memory_, with the exception of 'e', which is interpolated between 'E'
and 'A'. For the sake of labels, let's call this the "intuitive"
approach to looping.

However, the actual output from the GF1 is

   "A a B b C c D d E a B b C c D d E a B b C c D d E e ..."

Note that sample 'A' is played only at the start and skipped in subsequent
rounds through the loop.  This method of looping does away with the
"long-distance" interpolation between 'E' and 'A', which would require
additional steps at the end loop point, but at the expense of samples 'e'
and 'A'.  For a GF1 loop to provide output identical to the intuitive loop
and not click, the loop must be extended to include a sample point 'F'.
Sample point 'F' must have (nearly) the same value as sample point 'A'.
Then the GF1 would provide the output

   "A a B b C c D d E e F a B b C c D d E e F a B b C c D d E e ..."

Note that 'a' is still interpolated between 'A' and 'B' while 'e' is
interpolated between 'E' and 'F'.  Since 'F' has the same value as 'A', this
GF1 output is exactly the same as the intuitive output which is

   "A a B b C c D d E e A a B b C c D d E e A a B b C c D d E e ..."

Basically, from the above scheme, the looping algorithm of the GF1
appears to be:

    1.  Increment the voice position by the value in its FC register.
    2.  Compare the voice position with the loop end point:
        If the voice position is greater than the loop end point,
        then decrement the voice position by the loop length (which
        is simply [end point - start point]).
    3.  Move on to the next voice and repeat from 1.

The GF1's looping mechanism impacts programming practices significantly.
We'll describe how to handle streaming digital audio playback through
the GF1 without clicks, and how to accomodate samples with loop points
designed for synths which loop in the "intuitive" manner.

Streaming Digital Audio
=======================

The method of handling streaming digital audio as outlined by the SDK
(version 2.10) will result in clicking at the end of the buffer.  The trick
to getting rid of the clicks is to realize that a sample from one end of the
buffer must be copied to a "pad location" adjacent to the other end of the
buffer.  The loop must then be extended by one sample to include this "pad
location".  The example below demonstrates padding at the beginning of the
buffer.  This example also assumes 8-bit data so that one sample point fits 
neatly into one byte, but the technique is just as applicable to 16-bit 
data.

For streaming digital audio playback, the first step is to allocate the
playback buffer.  Assume that the SDK routines have been used to
allocated a buffer that is at address A and is N bytes long, where N is
a multiple of 32.  The voice should be set to loop from (A+31) to
(A+N-1). Then, every time data are transferred into the end of the
buffer, a copy of the sample that will be at location (A+N-1) must be
UltraPoked into (A+31).  Here is a picture of how the buffer will be
used.

                              v-------<-------<-------<
                              S                       E
                              >-------B------->-------^
 +-------+-------+-------+--------+-------+-------+-------+
 | A+000 | A+001 |  ...  |  A+031 | A+032 |  ...  | A+N-1 |
 +-------+-------+-------+--------+-------+-------+-------+
                              |       |       |       |
Pad location -----------------+       |       |       |
Start of main portion of the buffer --+       |       |
Middle of main portion of the buffer ---------+       |
End of main portion of the buffer --------------------+

B, S, and E are the Begin, Start, and End parameters (respectively) that
are passed to UltraStartVoice or UltraPrimeVoice.

Most of the data transfers will be to the main portion of the buffer
from locations (A+32) through locations (A+N-1).  The main portion of
the buffer will most likely be treated as two sub-buffers for
double-bufferring.  These transfers will be most likely, but not
necessarrily, DMA transfers.  Most of this is the same as regular
double-bufferring.  The three things that must be done differently are:

1) "Dead space" must be left for the pad location at the beginning of
   the allocated buffer.

2) Whenever a sample is transferred to location (A+N-1), a copy of this
   sample must be transferred to location (A+31) as well.

3) The voice must be set to loop from location (A+31) through location
   (A+N-1).

Accomodating loop points set for other synths
=============================================

The problem with "Ultra-clicks" in many MOD players has led Dave and I
to think that the Amiga loops samples in a different manner from the
GF1.  We believe that it loops according to the "intuitive" model.
(We'd appreciate it if any Amiga-literate person can confirm or deny
this hunch for us.)

Assume we have the sample [a b c d e f g h i j k l m n o p q], and the
Amiga loop points are at e and k:

      a b c d e f g h i j k l m n o p q
      B       S           E

      B = Begin point of sample
      S = Start loop point
      E = End loop point
      The portion of the sample past the end loop point is the sampled
      release.

If the Amiga loops the way we think it does, then we'd get the following
output stream:

      abcdefghijkefghijkefghijklmnopq stop
      ^note on            ^note off

However, the same sample with the same loop points played on the GF1
would result in (disregarding interpolation):

      abcdefghijkfghijkfghijklmnopq stop
      ^note on            ^note off

which is significantly different from the output on the Amiga.  However,
if we simply move the end loop point one sample to the right:

      a b c d e f g h i j k l m n o p q
      B       S             E

we would get an output stream of:

      abcdefghijklfghijklfghijklmnopq stop
      ^note on            ^note off

(c.f. abcdefghijkefghijkefghijklmnopq stop on the Amiga)
                --     --

Note that instead of "ke" in the loop, we'd get "kl".  However, 'l'
would be almost the exact same sample as 'e' due to the periodicity of
waveforms.

We, could, if we like, just copy 'e' on top of 'l' and we still wouldn't
affect anything noticeably.  In fact, it might be better to do this so
that the looping of the GF1 would resemble the looping of the Amiga
exactly (disregarding interpolation), and reduce the discrepancy to the
one instance where the sample 'e' gets output where the Amiga would
output 'l' during the release.

For end loop points that fall right on the end of the sample, we would
merely append the sample point from the beginning of the loop to the end
of the entire sample and move the endloop point out by one.

However, correct loop points themselves do not prevent all clicks.
Remember to ramp volumes, "zero" inactive voices (that is, to stop them
and ramp their volumes down, and, ideally, move them to a location
containing 0), etc.

Phat Tran and David MacMahon

------------------------------

End of GUS Programmer's Digest V10 #18
**************************************

To post to tomorrow's digest:                    <gus-sdk@mail.orst.edu>
To (un)subscribe or get help:            <gus-sdk-request@mail.orst.edu>
To contact a human (last resort):          <gus-sdk-owner@mail.orst.edu>

FTP Sites              Archive                       Directories
---------              -------                       -----------
Main N.American Site:  archive.orst.edu              pub/packages/gravis
                       wuarchive.wustl.edu           systems/ibmpc/ultrasound
Main Asian Site:       nctuccca.edu.tw               PC/ultrasound
European Callers ONLY: theoris.rz.uni-konstanz.de    pub/sound/gus
Submissions:           archive.epas.utoronto.ca      pub/pc/ultrasound/submit
Newly Validated Files: archive.epas.utoronto.ca      pub/pc/ultrasound
Mirrors:               garbo.uwasa.fi                mirror/ultrasound
 
MailServer For Archive Access: Email to <mail-server@nike.rz.uni-konstanz.de>

Hints:
      - Get the FAQ from the FTP sites or the request server.
      - Mail to <gus-sdk-request@mail.orst.edu> for info about other GUS
	related mailing lists (general use, musician's, etc.).


