Copyright (c) Hyperion Entertainment and contributors.

RealTime Library: Difference between revisions

From AmigaOS Documentation Wiki
Jump to navigation Jump to search
Content deleted Content added
m Text replacement - "<syntaxhighlight>" to "<syntaxhighlight lang="C" line>"
m Unified parameter reference style.
 
(6 intermediate revisions by the same user not shown)
Line 1: Line 1:
The RealTime Library provides a convenient, higher-level interface to underlying hardware timers that is easy to use. The library also provides for the distribution of timing pulses to an unlimited number of client applications on a priority basis, thus supporting multitasking in the most robust manner possible.
The RealTime Library provides a convenient, higher-level interface to underlying hardware timers that is easy to use. The library also provides for the distribution of timing pulses to an unlimited number of client applications on a priority basis, thus supporting multitasking in the most robust manner possible.


= Conductors and PlayerInfos =
= Conductors and Players =


The RealTime Library uses the Conductor structure to manage timing. There can be any number of Conductor structures, each of which represents a separate and independent timing context (i.e. a group of applications that want to be synced together).
The RealTime Library uses the Conductor structure to manage timing. There can be any number of Conductor structures, each of which represents a separate and independent timing context (i.e. a group of applications that want to be synced together).


Each Conductor can have one or more client applications. A second structure called a PlayerInfo is set up by each client application that wants to get timing information from the Conductor. There is typically one PlayerInfo for each task that wants to get timing information (a task could have more than one but this would be unusual).
Each Conductor can have one or more client applications. A second structure called a Player is set up by each client application that wants to get timing information from the Conductor. There is typically one Player for each task that wants to get timing information (a task could have more than one but this would be unusual).


Both the Conductor and PlayerInfo structures are dynamic. You never create these structures by allocating and initializing them yourself. The system provides the functions to do this for you. Also, the structures are read-only. To change the fields within these structures, use the system-provided functions and tags.
Both the Conductor and Player structures are dynamic. You never create these structures by allocating and initializing them yourself. The system provides the functions to do this for you. Also, the structures are read-only. To change the fields within these structures, use the system-provided functions and tags.


An application will usually create a single PlayerInfo structure and then attach it to a Conductor. If the Conductor does not yet exist, it will be created by the system. To create a PlayerInfo structure you call CreatePlayer():
An application will usually create a single Player structure and then attach it to a Conductor. If the Conductor does not yet exist, it will be created by the system. To create a Player structure you call CreatePlayer():


<syntaxhighlight lang="C" line>
<syntaxhighlight lang="C" line>
struct PlayerInfo *pi = IRealTime->CreatePlayer(Tag tag, ...);
struct Player *player = IRealTime->CreatePlayer(Tag tag, ...);
</syntaxhighlight>
</syntaxhighlight>


This call takes a list of tag items that describe the attributes of the PlayerInfo structure you want to create. (A complete list of all the tags available can be found in the Autodoc for SetPlayerAttrs().) It returns a pointer to the PlayerInfo. Here’s a fragment showing how to set up a PlayerInfo:
This call takes a list of tag items that describe the attributes of the Player structure you want to create. (A complete list of all the tags available can be found in the Autodoc for the SetPlayerAttrs() function.) It returns a pointer to the Player structure. Here’s a fragment showing how to set up a Player:


<syntaxhighlight lang="C" line>
<syntaxhighlight lang="C" line>
struct PlayerInfo *pPlayerInfo = IRealTime->CreatePlayer(
struct Player *player = IRealTime->CreatePlayer(
PLAYER_Name, "My_player",
PLAYER_Name, "My_player",
PLAYER_Conductor, "My_conductor",
PLAYER_Conductor, "My_conductor",
TAG_END);
TAG_END);


if (pPlayerInfo != NULL )
if (player != NULL )
{
{
// Your real-time application goes here...
// Your real-time application goes here...


IRealTime->DeletePlayer(pPlayerInfo);
IRealTime->DeletePlayer(player);
}
}
</syntaxhighlight>
</syntaxhighlight>


In the code above, a PlayerInfo will be created with the name of "My_player". It will be attached to the Conductor structure named "My_conductor". If a Conductor structure named "My_conductor" does not already exist, the system will create one. Other applications could also attach their PlayerInfos to "My_conductor".
In the code above, a Player will be created with the name of "My_player". It will be attached to the Conductor structure named "My_conductor". If a Conductor structure named "My_conductor" does not already exist, the system will create one. Other applications could also attach their Players to "My_conductor".


When your application finishes, you should delete any PlayerInfos you have created by calling DeletePlayer(). The Conductor will be automatically deleted by the system (however, this won’t happen until '''all''' the PlayerInfos attached to a Conductor are deleted).
When your application finishes, you should delete any Players you have created by calling DeletePlayer(). The Conductor will be automatically deleted by the system (however, this won’t happen until '''all''' the Players attached to a Conductor are deleted).


Once you have a PlayerInfo and Conductor set up, you can obtain timing pulses for your application. Timing pulses come from underlying timer chips and are passed to your application through the Conductor.
Once you have a Player and Conductor set up, you can obtain timing pulses for your application. Timing pulses come from underlying timer chips and are passed to your application through the Conductor.


= Getting Clock Ticks =
= Getting Clock Ticks =


The RealTime Library uses a tick frequency of 600 Hz so clock pulses are delivered approximately every 1.66 ms. There are two ways to get this timing information:
The RealTime Library uses a tick frequency of 1200 Hz, so clock pulses are delivered approximately every 0.83 ms. This is good enough even for timing-critical applications such as MIDI sequencing.

There are two ways to get this timing information:


* An alarm's signal
* An alarm's signal
Line 55: Line 57:
if (midiSignal != -1)
if (midiSignal != -1)
{
{
struct PlayerInfo *pPlayerInfo = IRealTime->CreatePlayer(
struct Player *player = IRealTime->CreatePlayer(
PLAYER_Name, "My_player",
PLAYER_Name, "My_player",
PLAYER_Conductor, "My_conductor",
PLAYER_Conductor, "My_conductor",
PLAYER_SignalTask, IExec->FindTask(NULL),
PLAYER_AlarmSigTask, IExec->FindTask(NULL),
PLAYER_AlarmSigBit, midiSignal,
PLAYER_AlarmSigBit, midiSignal,
TAG_END);
TAG_END);


if (pPlayerInfo != NULL)
if (player != NULL)
{
{
// Start the realtime clock running.
// Start the realtime clock running.
int32 res = IRealTime->SetConductorState(pPlayerInfo, CLOCKSTATE_RUNNING, 0);
int32 res = IRealTime->SetConductorState(player, CONDSTATE_RUNNING, 0);
if (!res)
if (!res)
{
{
BOOL timerr = IRealTime->SetPlayerAttrs(pPlayerInfo,
BOOL timerr = IRealTime->SetPlayerAttrs(player,
PLAYER_AlarmTime, 1000,
PLAYER_AlarmTime, 1000,
PLAYER_Ready, TRUE,
PLAYER_Ready, TRUE,
TAG_END);
TAG_END);
if (timerr)
if (timerr)
{
{
Line 82: Line 84:
else IDOS->Printf("Couldn't start clock\n");
else IDOS->Printf("Couldn't start clock\n");
}
}
else IDOS->Printf("Couldn't set up PlayerInfo structure.\n");
else IDOS->Printf("Couldn't set up Player structure.\n");
}
}
else IDOS->Printf("Couldn't allocate signal.\n");
else IDOS->Printf("Couldn't allocate signal.\n");
</syntaxhighlight>
</syntaxhighlight>


In the fragment above, the calling task requests a PlayerInfo with an alarm clock feature by passing the PLAYER_AlarmSigBit tag to CreatePlayer() The ti_Data field of this tag contains the signal bit that will be set by the RealTime Library when the alarm goes off. The PLAYER_SignalTask tag indicates which task will be signaled.
In the fragment above, the calling task requests a Player with an alarm clock feature by passing the PLAYER_AlarmSigBit tag to CreatePlayer() The ti_Data field of this tag contains the signal bit that will be set by the RealTime Library when the alarm goes off. The PLAYER_AlarmSigTask tag indicates which task will be signaled.


The realtime clock is then started by calling SetConductorState() (discussed below). This is important since any alarm requests made when the clock is stopped will be ignored.
The realtime clock is then started by calling SetConductorState(); [[#More About Conductors|see below]]. This is important since any alarm requests made when the clock is stopped will be ignored.


Finally, the alarm time is set by calling SetPlayerAttrs(). The parameters to this call are:
Finally, the alarm time is set by calling SetPlayerAttrs(). The parameters to this call are:
<syntaxhighlight lang="C" line>
<syntaxhighlight lang="C" line>
BOOL result = SetPlayerAttrs(struct PlayerInfo *pi, Tag tag, ...);
BOOL result = SetPlayerAttrs(struct Player *player, Tag tag, ...);
</syntaxhighlight>
</syntaxhighlight>


The pi parameter indicates which PlayerInfo structure is to have its attributes changed. The Tag items indicate the attributes and their new values. If the change is made successfully, then TRUE is returned. FALSE indicates failure. In the fragment above, a wake-up time is
The ''player'' parameter indicates which Player structure is to have its attributes changed. The Tag items indicate the attributes and their new values. If the change is made successfully, then TRUE is returned. FALSE indicates failure. In the fragment above, a wake-up time is requested using the PLAYER_AlarmTime tag. Also, the calling task indicates to the Conductor that it is ready by using the PLAYER_Ready tag (more on this below).
requested using the PLAYER_AlarmTime tag. Also, the calling task indicates to the Conductor that it is ready by using the PLAYER_Ready tag (more on this below).


At this point, the call to Wait(1UL << midiSignal) causes the task to sleep until time = 1000 ticks.
At this point, the call to Wait(1UL << midiSignal) causes the task to sleep until time = 1000 ticks.
Line 105: Line 106:
The discussion so far has concentrated on the alarm facility of the RealTime Library. An even finer level of control over time is available using the clock tick callback hook facility. Instead of setting an alarm to signal your task at some future time, the hook facilities allow your application code to be invoked whenever Conductor time is updated.
The discussion so far has concentrated on the alarm facility of the RealTime Library. An even finer level of control over time is available using the clock tick callback hook facility. Instead of setting an alarm to signal your task at some future time, the hook facilities allow your application code to be invoked whenever Conductor time is updated.


The realtime clock is driven by an interrupt that simply increments the base time and then uses software interrupts to distribute the time to any Conductors. By using a callback hook, you can have your custom code invoked at the software interrupt level (before tasks)
The realtime clock is driven by an interrupt that simply increments the base time and then uses software interrupts to distribute the time to any Conductors. By using a callback hook, you can have your custom code invoked at the software interrupt level (before tasks) whenever Conductor time is refreshed.
whenever Conductor time is refreshed.


To set up a callback hook, you use the PLAYER_Hook tag with the address of a standard Hook structure as defined in <utilities/hook.h>. This structure contains the address of the code you want to be invoked whenever the RealTime Library updates your Conductor. Here’s a code fragment showing how this is done:
To set up a callback hook, you use the PLAYER_Hook tag with the address of a standard Hook structure as defined in <utilities/hook.h>. This structure contains the address of the code you want to be invoked whenever the RealTime Library updates your Conductor. Here’s a code fragment showing how this is done:
Line 114: Line 114:
int8 My_signal;
int8 My_signal;


uint32 My_hookFunc(struct Hook *hook, struct pmTime *msg, struct PlayerInfo *pi);
uint32 My_hookFunc(struct Hook *hook, struct pmTime *msg, struct Player *pl);


int main()
int main()
Line 124: Line 124:
uint32 ticks = 1000;
uint32 ticks = 1000;
stuct PlayerInfo *pPlayerInfo = IRealTime->CreatePlayer(
stuct Player *player = IRealTime->CreatePlayer(
PLAYER_Name, "My_player",
PLAYER_Name, "My_player",
PLAYER_Conductor, "My_conductor",
PLAYER_Conductor, "My_conductor",
PLAYER_Hook, &My_hook,
PLAYER_Hook, &My_hook,
PLAYER_UserData, &ticks,
PLAYER_UserData, &ticks,
TAG_END);
TAG_END);
if (pPlayerInfo != NULL)
if (player != NULL)
{
{
My_task = IExec->FindTask(NULL); // Initialize these globals so that
My_task = IExec->FindTask(NULL); // Initialize these globals so that
Line 138: Line 138:
{
{
// Start the clock running.
// Start the clock running.
int32 res = IRealTime->SetConductorState(pPlayerInfo, CLOCKSTATE_RUNNING, 0);
int32 res = IRealTime->SetConductorState(player, CONDSTATE_RUNNING, 0);
if (!res)
if (!res)
{
{
Line 148: Line 148:
}
}
IRealTime->DeletePlayer(pPlayerInfo);
IRealTime->DeletePlayer(player);
}
}


Line 157: Line 157:
In the code above the function named My_hookFunc() will be called by the RealTime Library whenever it updates Conductor time.
In the code above the function named My_hookFunc() will be called by the RealTime Library whenever it updates Conductor time.


Here’s the callback hook function itself. This simply compares Conductor time to a variable, ticks, whose address is pointed to by pi­>pi_UserData. Notice how this address was filled in using the PLAYER_UserData tag in the call to SetPlayerAttrs() in main() above. When
Here’s the callback hook function itself. This simply compares Conductor time to a variable, ticks, whose address is pointed to by pl->pl_UserData. Notice how this address was filled in using the PLAYER_UserData tag in the call to SetPlayerAttrs() in main() above. When Conductor time equals or exceeds ticks, the hook function signals the main task.
Conductor time equals or exceeds ticks, the hook function signals the main task.


<syntaxhighlight lang="C" line>
<syntaxhighlight lang="C" line>
// Hook code called whenever the RealTime Library updates Conductor time.
// Hook code called whenever the RealTime Library updates Conductor time.
// Normally this is 600 times/sec.
// Normally this is 1200 times/sec.
uint32 My_hookFunc(struct Hook *hook, struct pmTime *msg, struct PlayerInfo *pi)
uint32 My_hookFunc(struct Hook *hook, struct pmTime *msg, struct Player *pl)
{
{
switch (msg->pmt_Method)
switch (msg->pmt_Method)
Line 170: Line 169:
// Test whether Conductor time has exceeded the number in *ticks*.
// Test whether Conductor time has exceeded the number in *ticks*.
// If it has, then signal the parent task.
// If it has, then signal the parent task.
if ((*uint32*)(pi->pi_Userdata)) < pi->pi_Source->cdt_ClockTime)
if ((*uint32*)(pl->pl_Userdata)) < pl->pl_Source->cdt_ClockTime)
IExec->Signal(My_task, 1UL << My_signal);
IExec->Signal(My_task, 1UL << My_signal);
break;
break;
Line 184: Line 183:
= More About Conductors =
= More About Conductors =


It is the job of the Conductor to act as middleman between the Amiga's timing hardware and client tasks represented by PlayerInfo structures. Each Conductor keeps track of:
It is the job of the Conductor to act as middleman between the Amiga's timing hardware and client tasks represented by Player structures. Each Conductor keeps track of:
* an Exec list of all its client players
* an Exec list of all its client players
* the state of the conductor (i.e. running, stopped, paused, locating – see below)
* the state of the conductor (i.e. running, stopped, paused, locating – see below)
Line 193: Line 192:


<syntaxhighlight lang="C" line>
<syntaxhighlight lang="C" line>
int32 res = IRealTime->SetConductorState(struct PlayerInfo *pi, int32 newstate, int32 ti);
int32 res = IRealTime->SetConductorState(struct Player *player, int32 newstate, int32 ti);
</syntaxhighlight>
</syntaxhighlight>


The pi parameter is a pointer to a PlayerInfo structure that is linked to the Conductor you want to change. The ti parameter is a time offset used for special cases (typically set to zero). The ''newstate'' parameter is one of the following:
The ''player'' parameter is a pointer to a Player structure that is linked to the Conductor you want to change. The ''ti'' parameter is a time offset used for special cases (typically set to zero). The ''newstate'' parameter is one of the following:
{| class="wikitable"
{| class="wikitable"
| CLOCKSTATE_STOPPED || The clock is not running
| CONDSTATE_STOPPED || The clock is not running
|-
|-
| CLOCKSTATE_PAUSED || To the RealTime Library, this is exactly the same as stopped. This is provided as a convenience to those applications that wish to make a distinction between the two.
| CONDSTATE_PAUSED || To the RealTime Library, this is exactly the same as stopped. This is provided as a convenience to those applications that wish to make a distinction between the two.
|-
|-
| CLOCKSTATE_RUNNING || The clock is running and time pulses are being distributed to any client applications (PlayerInfos) that are ready.
| CONDSTATE_RUNNING || The clock is running and time pulses are being distributed to any client applications (Players) that are ready.
|-
|-
| CLOCKSTATE_LOCATE || This is the same as running with one exception: the clock does not actually start until ''all'' client applications have indicated that they are ready by setting the PLAYER_Ready attribute of their PlayerInfo to TRUE. This allows applications that cannot start immediately to set up before timing pulses actually begin.
| CONDSTATE_LOCATE || This is the same as running with one exception: the clock does not actually start until ''all'' client applications have indicated that they are ready by setting the PLAYER_Ready attribute of their Player to TRUE. This allows applications that cannot start immediately to set up before timing pulses actually begin.
|}
|}


The difference between locating and running requires some explanation. Each PlayerInfo has a "ready bit" which is used to tell the RealTime Library that the player is ready to receive ticks. This bit is reset each time the library relocates the clock to a new time.
The difference between locating and running requires some explanation. Each Player has a "ready bit" which is used to tell the RealTime Library that the player is ready to receive ticks. This bit is reset each time the library relocates the clock to a new time.


The reason for the ready bit is that some applications need to find the appropriate location in the multimedia sequence or score before they can start playing at the new location. In some cases this can take a considerable amount of time. Hence, CLOCKSTATE__LOCATE is used with the ready bit to allow all players that are sharing a timing context to be synchronized together.
The reason for the ready bit is that some applications need to find the appropriate location in the multimedia sequence or score before they can start playing at the new location. In some cases this can take a considerable amount of time. Hence, CONDSTATE_LOCATE is used with the ready bit to allow all players that are sharing a timing context to be synchronized together.


Players can set the state of their ready bit by using the PLAYER_Ready tag attribute either when the PlayerInfo is created with CreatePlayer() or later with SetPlayerAttrs(). See the first code fragment above for an example of this.
Players can set the state of their ready bit by using the PLAYER_Ready tag attribute either when the Player is created with CreatePlayer() or later with SetPlayerAttrs(). See the first code fragment above for an example of this.

Latest revision as of 07:11, 13 April 2026

The RealTime Library provides a convenient, higher-level interface to underlying hardware timers that is easy to use. The library also provides for the distribution of timing pulses to an unlimited number of client applications on a priority basis, thus supporting multitasking in the most robust manner possible.

Conductors and Players

The RealTime Library uses the Conductor structure to manage timing. There can be any number of Conductor structures, each of which represents a separate and independent timing context (i.e. a group of applications that want to be synced together).

Each Conductor can have one or more client applications. A second structure called a Player is set up by each client application that wants to get timing information from the Conductor. There is typically one Player for each task that wants to get timing information (a task could have more than one but this would be unusual).

Both the Conductor and Player structures are dynamic. You never create these structures by allocating and initializing them yourself. The system provides the functions to do this for you. Also, the structures are read-only. To change the fields within these structures, use the system-provided functions and tags.

An application will usually create a single Player structure and then attach it to a Conductor. If the Conductor does not yet exist, it will be created by the system. To create a Player structure you call CreatePlayer():

struct Player *player = IRealTime->CreatePlayer(Tag tag, ...);

This call takes a list of tag items that describe the attributes of the Player structure you want to create. (A complete list of all the tags available can be found in the Autodoc for the SetPlayerAttrs() function.) It returns a pointer to the Player structure. Here’s a fragment showing how to set up a Player:

struct Player *player = IRealTime->CreatePlayer(
  PLAYER_Name, "My_player",
  PLAYER_Conductor, "My_conductor",
  TAG_END);

if (player != NULL )
{
  // Your real-time application goes here...

  IRealTime->DeletePlayer(player);
}

In the code above, a Player will be created with the name of "My_player". It will be attached to the Conductor structure named "My_conductor". If a Conductor structure named "My_conductor" does not already exist, the system will create one. Other applications could also attach their Players to "My_conductor".

When your application finishes, you should delete any Players you have created by calling DeletePlayer(). The Conductor will be automatically deleted by the system (however, this won’t happen until all the Players attached to a Conductor are deleted).

Once you have a Player and Conductor set up, you can obtain timing pulses for your application. Timing pulses come from underlying timer chips and are passed to your application through the Conductor.

Getting Clock Ticks

The RealTime Library uses a tick frequency of 1200 Hz, so clock pulses are delivered approximately every 0.83 ms. This is good enough even for timing-critical applications such as MIDI sequencing.

There are two ways to get this timing information:

  • An alarm's signal
  • A clock tick callback hook

Using the alarm facility

You can ask the RealTime Library to signal your task at some future time by using its alarm facility. This allows you to operate asynchronously. For instance, you could start a group of MIDI notes playing and set the realtime alarm to signal you when they should be stopped, then go on to some other job such as preparing the next group of notes before calling Wait() on the alarm signal.

The fragment below shows how to set up the library’s alarm clock to signal the calling task at time = 1000 ticks (the fragment assumes that the RealTime Library interface is already obtained).

// This fragment assumes the that IRealTime is already obtained.
int8 midiSignal = IExec->AllocSignal(-1);  // Allocate a wake-up signal bit
if (midiSignal != -1)
{
  struct Player *player = IRealTime->CreatePlayer(
    PLAYER_Name, "My_player",
    PLAYER_Conductor, "My_conductor",
    PLAYER_AlarmSigTask, IExec->FindTask(NULL),
    PLAYER_AlarmSigBit, midiSignal,
    TAG_END);

  if (player != NULL)
  {
    // Start the realtime clock running.
    int32 res = IRealTime->SetConductorState(player, CONDSTATE_RUNNING, 0);
    if (!res)
    {
      BOOL timerr = IRealTime->SetPlayerAttrs(player,
           PLAYER_AlarmTime, 1000,
           PLAYER_Ready, TRUE,
           TAG_END);
      if (timerr)
      {
        // You could do some other job before
        // calling Wait() on the alarm signal.
        IExec->Wait(1UL << midiSignal);
      }
      else IDOS->Print("Couldn't set alarm\n");
    }
    else IDOS->Printf("Couldn't start clock\n");
  }
  else IDOS->Printf("Couldn't set up Player structure.\n");
}
else IDOS->Printf("Couldn't allocate signal.\n");

In the fragment above, the calling task requests a Player with an alarm clock feature by passing the PLAYER_AlarmSigBit tag to CreatePlayer() The ti_Data field of this tag contains the signal bit that will be set by the RealTime Library when the alarm goes off. The PLAYER_AlarmSigTask tag indicates which task will be signaled.

The realtime clock is then started by calling SetConductorState(); see below. This is important since any alarm requests made when the clock is stopped will be ignored.

Finally, the alarm time is set by calling SetPlayerAttrs(). The parameters to this call are:

BOOL result = SetPlayerAttrs(struct Player *player, Tag tag, ...);

The player parameter indicates which Player structure is to have its attributes changed. The Tag items indicate the attributes and their new values. If the change is made successfully, then TRUE is returned. FALSE indicates failure. In the fragment above, a wake-up time is requested using the PLAYER_AlarmTime tag. Also, the calling task indicates to the Conductor that it is ready by using the PLAYER_Ready tag (more on this below).

At this point, the call to Wait(1UL << midiSignal) causes the task to sleep until time = 1000 ticks.

Using the clock tick callback facility

The discussion so far has concentrated on the alarm facility of the RealTime Library. An even finer level of control over time is available using the clock tick callback hook facility. Instead of setting an alarm to signal your task at some future time, the hook facilities allow your application code to be invoked whenever Conductor time is updated.

The realtime clock is driven by an interrupt that simply increments the base time and then uses software interrupts to distribute the time to any Conductors. By using a callback hook, you can have your custom code invoked at the software interrupt level (before tasks) whenever Conductor time is refreshed.

To set up a callback hook, you use the PLAYER_Hook tag with the address of a standard Hook structure as defined in <utilities/hook.h>. This structure contains the address of the code you want to be invoked whenever the RealTime Library updates your Conductor. Here’s a code fragment showing how this is done:

struct Task *My_task;
int8 My_signal;

uint32 My_hookFunc(struct Hook *hook, struct pmTime *msg, struct Player *pl);

int main()
{
  // ...Open the RealTime Library and do other set up here...

  struct Hook My_hook;
  My_hook.h_Entry = (HOOKFUNC)My_hookFunc;
  
  uint32 ticks = 1000;
  stuct Player *player = IRealTime->CreatePlayer(
        PLAYER_Name, "My_player",
        PLAYER_Conductor, "My_conductor",
        PLAYER_Hook, &My_hook,
        PLAYER_UserData, &ticks,
        TAG_END);
    
  if (player != NULL)
  {
    My_task   = IExec->FindTask(NULL);  // Initialize these globals so that
    My_signal = IExec->AllocSignal(-1); // the hook function can signal us.
    if (My_signal != -1)
    {
      // Start the clock running.
      int32 res = IRealTime->SetConductorState(player, CONDSTATE_RUNNING, 0);
      if (!res)
      {
        // ...your code goes here...
        IExec->Wait(1UL << My_signal | SIGBREAKF_CTRL_C);
      }
      
      IExec->FreeSignal(My_signal);
    }
    
    IRealTime->DeletePlayer(player);
  }

  // ...Close the library, etc...
}

In the code above the function named My_hookFunc() will be called by the RealTime Library whenever it updates Conductor time.

Here’s the callback hook function itself. This simply compares Conductor time to a variable, ticks, whose address is pointed to by pl->pl_UserData. Notice how this address was filled in using the PLAYER_UserData tag in the call to SetPlayerAttrs() in main() above. When Conductor time equals or exceeds ticks, the hook function signals the main task.

// Hook code called whenever the RealTime Library updates Conductor time.
// Normally this is 1200 times/sec.
uint32 My_hookFunc(struct Hook *hook, struct pmTime *msg, struct Player *pl)
{
  switch (msg->pmt_Method)
  {
   case PM_TICK:
     // Test whether Conductor time has exceeded the number in *ticks*.
     // If it has, then signal the parent task.
     if ((*uint32*)(pl->pl_Userdata)) < pl->pl_Source->cdt_ClockTime)
       IExec->Signal(My_task, 1UL << My_signal);
     break;

   default:
     break;     
  }
  
  return 0;
}

More About Conductors

It is the job of the Conductor to act as middleman between the Amiga's timing hardware and client tasks represented by Player structures. Each Conductor keeps track of:

  • an Exec list of all its client players
  • the state of the conductor (i.e. running, stopped, paused, locating – see below)
  • what time it is relative to start time
  • whether the Conductor is using the Amiga’s internal hardware for its timing pulses or an external source

As shown in the examples above, the "state" of the Conductor can be changed at any time by calling SetConductorState(). If the call succeeds, zero is returned:

int32 res = IRealTime->SetConductorState(struct Player *player, int32 newstate, int32 ti);

The player parameter is a pointer to a Player structure that is linked to the Conductor you want to change. The ti parameter is a time offset used for special cases (typically set to zero). The newstate parameter is one of the following:

CONDSTATE_STOPPED The clock is not running
CONDSTATE_PAUSED To the RealTime Library, this is exactly the same as stopped. This is provided as a convenience to those applications that wish to make a distinction between the two.
CONDSTATE_RUNNING The clock is running and time pulses are being distributed to any client applications (Players) that are ready.
CONDSTATE_LOCATE This is the same as running with one exception: the clock does not actually start until all client applications have indicated that they are ready by setting the PLAYER_Ready attribute of their Player to TRUE. This allows applications that cannot start immediately to set up before timing pulses actually begin.

The difference between locating and running requires some explanation. Each Player has a "ready bit" which is used to tell the RealTime Library that the player is ready to receive ticks. This bit is reset each time the library relocates the clock to a new time.

The reason for the ready bit is that some applications need to find the appropriate location in the multimedia sequence or score before they can start playing at the new location. In some cases this can take a considerable amount of time. Hence, CONDSTATE_LOCATE is used with the ready bit to allow all players that are sharing a timing context to be synchronized together.

Players can set the state of their ready bit by using the PLAYER_Ready tag attribute either when the Player is created with CreatePlayer() or later with SetPlayerAttrs(). See the first code fragment above for an example of this.