Sound

VUEngine supports two types of sound playback through a common interface: the Sound class. A SoundSpec specifies, among other properties, a list of SoundTracks to play:

SoundTrackROMSpec* const MenuSongSoundTracks[] =
{
    &MenuSongSoundTrack1,
    &MenuSongSoundTrack2,
    &MenuSongSoundTrack3,
    &MenuSongSoundTrack4,
    &MenuSongSoundTrack5,
    &MenuSongSoundTrack6,
    NULL,
};

SoundROMSpec MenuSongSoundSpec =
{
    // Name
    "MenuSong",

    // Play in loop
    true,

    // Tick duration in US
    3140,

    // Tracks
    (SoundTrackSpec**)MenuSongSoundTracks
};

A SoundTrackSpec determines if the playback reproduces sounds as natively supported by the Virtual Boy’s Virtual Sound Unit (VSU) or Pulse Code Modulation data (PCM).

SoundTrackROMSpec MenuSongSoundTrack1 =
{
    /// kTrackNative, kTrackPCM
    kTrackNative,

    /// Skip if no sound source available?
    true,

    /// Total number of samples (0 if not PCM)
    0,

    /// Keyframes that define the track
    (SoundTrackKeyframe*)MenuSongSoundTrack1Keyframes,

    /// SxINT values
    (uint8*)MenuSongSoundTrack1SxINT,

    /// SxLRV values
    (uint8*)MenuSongSoundTrack1SxLRV,

    /// SxFQH and SxFQL values
    (uint16*)MenuSongSoundTrack1SxFQ,

    /// SxEV0 values
    (uint8*)MenuSongSoundTrack1SxEV0,

    /// SxEV1 values
    (uint8*)MenuSongSoundTrack1SxEV1,

    /// SxRAM pointers
    (int8**)MenuSongSoundTrack1SxRAM,

    /// SxSWP values
    (uint8*)MenuSongSoundTrack1SxSWP,

    /// SxMOD values
    (int8**)NULL
};

Each SoundTrackSpec must provide arrays for all the VSU’s hardware registers:

/// Sound source mapping
/// @memberof VSUManager
typedef struct VSUSoundSource
{
    uint8 SxINT;
    uint8 spacer1[3];
    uint8 SxLRV;
    uint8 spacer2[3];
    uint8 SxFQL;
    uint8 spacer3[3];
    uint8 SxFQH;
    uint8 spacer4[3];
    uint8 SxEV0;
    uint8 spacer5[3];
    uint8 SxEV1;
    uint8 spacer6[3];
    uint8 SxRAM;
    uint8 spacer7[3];
    uint8 SxSWP;
    uint8 spacer8[35];
} VSUSoundSource;

And it has to provide a list of sound events that represent the duration of each sound played through the hardware’s sound sources:

const SoundTrackKeyframe MenuSongSoundTrack1Keyframes[] =
{
    {60, kSoundTrackEventStart},
    {60, kSoundTrackEventSxEV1},
    {60, kSoundTrackEventSxFQ | kSoundTrackEventSxEV1},
    {60, kSoundTrackEventSxEV1},
    {60, kSoundTrackEventSxFQ | kSoundTrackEventSxEV1},
    {60, kSoundTrackEventSxFQ},
    {60, kSoundTrackEventSxEV1},
    {60, kSoundTrackEventSxFQ | kSoundTrackEventSxEV1},
    {60, kSoundTrackEventSxFQ},
    {60, kSoundTrackEventSxEV1},
    {60, kSoundTrackEventSxFQ | kSoundTrackEventSxEV1},
    {0, kSoundTrackEventEnd}
};

The VUEngine’s sound player is flexible enough to support all sound effects that the VSU is capable off and doesn’t require to reserve in advance any sound source, instead, it dispatches sound playback requests following a FIFO strategy as sound sources become available. This flexibility puts the responsibility of proper usage of the available resources on the sound data. Which means that priority has to be taken into account when producing sound effects and songs since sound playback requests have to be queued or ignored when there are no sound sources available at the moment of the request.

To reproduce a sound, a request to the SoundManager’s instance can be performed as shown below:

SoundManager::playSound
(
    soundSpec, (const Vector3D*)&this->transformation.position,
    kSoundPlaybackNormal, NULL, NULL
);

A Sound can be acquired to control its playback as follows:

extern SoundSpec SampleSoundSpec;

Sound sound = SoundManager::getSound(&SampleSoundSpec, NULL, NULL);

Sound playback supports spatial positioning through stereo separation if a reference to a Transformation is provided when calling Sound::play:

if(!isDeleted(sound))
{
    Sound::play(sound, &this->transformation.position, kSoundPlaybackFadeIn);
}

Sounds can be set to auto release on completion. This is the default behavior when they are simply reproduced by calling SoundManager::playSound.

# Timer settings

The engine plays sounds during the timer interrupts, which are controlled by the TimerManager. The configuration of the hardware’s timer can affect the output sound, this is a pressing fact when playing back PCM sound tracks.

The timer can be configured at any time during the execution of a program, but it is usual to configure it once per Stage. In the StageSpec there is a field for the timer settings:

/// An Stage Spec
/// @memberof Stage
typedef struct StageSpec
{
    AllocatorPointer allocator;

    /// Timer config
    struct Timer
    {
        /// Timer's resolution (__TIMER_100US or __TIMER_20US)
        uint16 resolution;

        /// Target elapsed time between timer interrupts
        uint16 targetTimePerInterrupt;

        /// Timer interrupt's target time units
        uint16 targetTimePerInterrupttUnits;

    } timer;

    [...]
};

The resolution corresponds to the hardware’s capabilities of ticking at 20 or 100 us intervals. Hence, a resolution of __TIMER_20US allows for greater precision between interrupts.

The target time per interrupt, measured either in milliseconds or in microseconds depending on the value of the target timer per interrupt units attribute (kMS or kUS), is the disired time interval between interrupts.

Usually, a target timer per interrupt of 5, 10, or even 20 ms is good enough for rich sound effects and songs during gameplay. Depending on the Stage, firing more interrupts per second can have a negative impact on the performance of the game.

In the case of PCM playback, a high frequency interrupt triggering is required to achieve acceptable sound playback and to reduce the noise, product of the fact that the VSU is not designed to reproduce such sounds and they are basically hacked together to make them possible on the platform.

The following table shows some general guidance on what is achievable, although the actual results are highly dependent on the load on the Stage’s complexity:

Target Hz Theoretical us/interrupt Real us/interrupt Effective us/interrupt Real Hz Notes
44000 23 -25 -20 43478 Impossible
24000 42 -6 0 23810 Impossible
16000 63 15 20 15873 Unfeasible / Not selectable
11025 91 43 40 10989 Unfeasible / Crashes
9000 111 63 60 9009 Maximum with an empty stage
8000 125 77 80 8000 Maximum, without animations
7000 143 95 100 6993 Achievable in very simple scenes
6000 167 119 120 5988 Realistic target
5500 182 134 140 5495 Realistic target
5000 200 152 160 5000 Realistic target, with some animations
4500 222 174 180 4505 Minimum acceptable quality
4000 250 202 200 4000 Sounds terrible