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 |