# Decision Functions

So far, this chapter has described how to perform primarily static actions. These actions include the ability to instruct playback to always branch, continue, or stop at any given `SPMarker`. Any `SPMarker` can have precisely one static action at any time. This can be changed dynamically during playback, but in order to do this intelligently, it is important to know precisely when.

The advanced sound player provides a dynamic action facility known as decision functions. A decision function is a callback function that the advanced sound player calls during playback in order to determine what action to take at selected `SPMarker`s.

Before going into more detail, it is important to understand when decisions about sound playback need to be made. Sound data is read from whatever sound source it comes from (file or memory) and then enqueued to a sound spooler, which is in turn played by the Audio folio. Because of the spooling, there is an certain amount of delay between the time that sound is read and when it is actually played. All decisions about where to branch, or when to stop, take place while reading the sound. Branches do not cause the sound data in the spooler to be flushed, they merely cause the reader to read from a different location in the sound data. Therefore, all decisions have a certain amount of latency before the results are actually heard.

Note: Since the amount of latency is directly related to the size of the sound buffers you supply to the advanced sound player, you have some important design decisions to make when writing a program that uses the advanced sound player. See the Caveats at the end of this chapter.

Decision functions are called when selected `SPMarker`s are encountered while reading sound data. There are two kinds of decision functions:

- Marker decision functions
- Default decision functions

Each `SPMarker` can have its own marker decision function installed, in addition to whatever static action it may have. An `SPMarker`'s decision function is called when that `SPMarker` is encountered while reading. Each `SPPlayer` can have its own default decision function which is called for every `SPMarker` encountered while reading.

Just like static actions set in an `SPMarker`, decision functions can return an action to perform: branch to another `SPMarker` or stop reading. Also, decision functions have the ability to return a don't care result, which causes the advanced sound player to act as if the decision function had never been installed.

The following pseudocode shows the steps that are used in determining what action the advanced sound player takes at an `SPMarker`:

         if SPMarker has a decision function and that decision function specifies an action

         take action specified by decision function

         else if SPPlayer has a default decision function and that decision function specifies an action

         take action specified by decision function

         else if SPMarker has a static action (branch or stop)

         take action specified in the SPMarker

         else if playback has reached the end of the current sound data

         stop

         else

         continue playback after the current SPMarker

## Decision Functions and Actions

Decision functions are functions whose type can be cast to `SPDecisionFunction`. This is defined in <soundplayer.h> as follows:

  `typedef Err (*SPDecisionFunction) (SPAction *resultAction,
  void *decisionData,
  SPSound *sound,
  const char *markerName)`

Decision functions are called with a client supplied data pointer, information about the SPMarker at which the decision is to be made, and an `SPAction` to fill out.

`SPAction` is a black box data type that is used to contain the action that the decision function wishes the advanced sound player to take. The decision function can call either `spSetBranchAction()` or `spSetStopAction()` to set the `SPAction` to instruct the advanced sound player to branch to another `SPMarker` or stop reading. Alternatively, the decision function can simply not set the `SPAction` at all. In this case, the advanced sound player continues as if the decision function had not been installed. This can be used to permit whatever static action is set up in the `SPMarker` to normally take place, but to override this occasionally with some other action from the decision function.

## Example Decision Function

Here are some simple modifications to the previous annotated example to do some simple branching.

### Loop a Sound File a Fixed Number of Times

Substitute this step in the annotated example to play a sound file four times in a loop. This is excerpted from tsp_spoolsoundfile.c.

Set up sound to loop by default. Install a Decision Function at the end `SPMarker` to be called each time playback reaches the end of the sound.

  `spLoopSound (sound);
  RemainingCount = 4; /* play 4 times */
  spSetMarkerDecisionFunction (
  sound, SP_MARKER_NAME_END,
  (SPDecisionFunction)LoopDecisionFunction,
  &RemainingCount);`

The decision function, `LoopDecisionFunction()`, looks like this:

  `/*
  This decision function decrements the RemainingCount variable.
  Normally it does nothing. When the count is exhausted, it
  instructs the advanced sound player to stop reading.
  */
  Err LoopDecisionFunction (
  SPAction *resultAction, int32 *remainingCountP,
  SPSound *sound, const char *markerName)
  {
  /* decrement remaining repeat count */
  (*remainingCountP)--;
  /* loop back to beginning (default action) or stop if
  no more repeats */
  if (*remainingCountP <= 0)
  return spSetStopAction (resultAction);
  else
  return 0;
  }`

## Rules for Decision Functions

Besides setting an `SPAction`, a decision function can do almost anything to the `SPPlayer` that owns this marker including adding new `SPSound`s or `SPMarker`s, changing the static action for this or any other `SPMarker`, changing the default or marker decision functions for this or any other `SPMarker`, deleting this or any other `SPMarker` or `SPSound` (with the caveats listed below in mind).

A decision function must not do any of the following:

- Call any function that changes the player state (e.g. `spDeletePlayer()`, `spStop()`, `spStartReading()`, `spStartPlaying()`, `spService()`, etc.) for the current `SPMarker`'s `SPPlayer`.

- Delete the current `SPMarker`'s `SPSound` since this has the side effect of calling `spStop()`.

- Delete the current `SPMarker` without setting up resultAction to stop or branch to another `SPMarker`.

- Take a long time to execute.

Also note that because decision functions are called on the reader's schedule, not the player's schedule, they cannot be used for notification that a particular part of the sound has actually been played.

See SPDecisionFunction in Music Library Calls for more complete information.