Files
goldeneye-pc-port/src/snd.c
T
James Dansereau dc5739bc93 D167: D152 addendum -- audio osSetIntMask audit + narrow lock-leak fixes
Static audit of every osSetIntMask(OS_IM_NONE) in the compiled audio code
(event.c, csplayer.c, synaddplayer.c, snd.c): all balanced on every exit
path, including alEvtqPostEvent's freeList-empty early return. No unbalanced
same-thread leak exists -- the D152 s_imLock wedge is not a missing restore.

Two real defects fixed (#ifdef PORT, N64 verbatim under #else):

1. snd.c sndSetSfxSlotVolume walked the live ALSoundState list and posted to
   the shared ALEventQueue without holding OS_IM_NONE, unlike its twin
   sndDeactivateAllSfxByFlag. On PC: unguarded walk racing amMain + one lock
   acquire/release per matching sound. During the mission-failed fade-out
   (sndSetScalerApplyVolumeAllSfxSlot -> sndApplyVolumeAllSfxSlot -> here,
   per frame) that is a per-frame lock-acquire storm. Now holds the mask once
   across the walk; sndApplyVolumeAllSfxSlot batches its slot loop under one
   recursive hold.

2. port/src/libultra.c: portThreadWrapper now calls imThreadExitRelease() when
   a host thread returns from its entry -- if it still owns s_imHeld, release
   the orphan (+ LOG_ERROR). Kills the 'transient thread acquired OS_IM_NONE
   and exited' leak (the §F dump's own alternate hypothesis) and the re-wedge
   when a new thread reuses the dead pthread id and imAcquire mis-detects
   recursion. Steal-lock kept as backstop.

Build 240/240 green; -level_09 frame 300 renders 91.7% non-clear crash-free;
-level_20 boots crash-free. Fade-out repro is playtest-gated (not headless-
reproducible) -- user to confirm no black screen + no D152: log lines.
2026-08-31 12:49:06 -04:00

1168 lines
37 KiB
C

#include <ultra64.h>
#include <PR/libaudio.h>
#include <os_extension.h>
#include "music.h"
#include "snd.h"
//likely named gslibaudio.c from xbla
/**
* EU .data, offset from start of data_seg : 0x3620
*/
/**
* @file snd.c
* This file contains code to deal with snd.
*/
#define DEFAULT_SETUP_PITCH_SHIFT (-0x1770)
/**
* Based on \n64devkit\ultra\usr\src\pr\libsrc\libultra\audio\sndp.h
* ALSndpEvent
*/
typedef union ALSndpEvent_u {
struct {
// offset 0
u16 type;
// offset 4
ALSoundState *state;
} common;
struct {
u16 type;
ALSoundState *state;
s32 vol;
} vol;
struct {
u16 type;
ALSoundState *state;
f32 pitch;
} pitch;
struct {
u16 type;
ALSoundState *state;
s32 pan32;
} pan32;
struct {
u16 type;
ALSoundState *state;
s32 mix32;
} fx32;
struct {
u16 type;
ALSoundState *state;
s32 soundIndex;
struct ALBankAlt_s *soundBank;
} playSfx;
struct {
s16 type;
ALSoundState *state;
s32 val8;
s32 valc;
} unks32;
struct {
s32 unk0;
s32 unk4;
s32 unk8;
s32 unkC;
} align_size;
} ALSndpEvent;
union ALSndpSmallEvent_u {
struct {
u16 type;
ALSoundState *state;
} msg;
union {
s32 unk0;
s32 unk4;
} align_size;
};
// TODO: is this struct really the answer?
// 800243E4
struct D_800243E4_s {
// address 800243E4 and 800243E8
ALLink node;
// address 800243EC
struct ALSoundState_s *g_sndPlayerSoundStatePtr;
};
s32 g_sndUnused800243E0 = 0;
// // TODO: is this struct really the answer?
struct D_800243E4_s D_800243E4 = { {NULL, NULL}, NULL};
ALSndPlayer *g_sndPlayerPtr = &g_sndPlayer;
/**
* Current number of allocated voices, via alSynAllocVoice
*/
s16 g_sndAllocatedVoicesCount = 0;
/**
* Boot flag. If set, sound is disabled.
*/
s8 g_sndBootswitchSound = 0;
/**
* Used in level load/setup, sound effect slot volume will be scaled by this amount.
*/
f32 g_sndSfxVolumeScale = 1.0;
// forward declarations
ALMicroTime sndPlayerVoiceHandler(void *node);
void sndHandleEvent(ALSndPlayer *sndp, ALSndpEvent *event);
void sndDisposeSound(ALSoundState *state);
void sndCreatePitchEvent(ALSoundState *state);
void sndRemoveEvents(ALEventQueue *evtq, ALSoundState *state, u16 eventType);
s32 sndCountAllocList(s16 *allocListCount, s16 *freeListCount);
ALSoundState *sndSetupSound(struct ALBankAlt_s *soundBank, ALSound* sound);
void sndUnlinkClearSound(ALSoundState *state);
void sndSetPriority(ALSoundState *state, u8 priority);
u8 sndGetPlayingState(ALSoundState *state);
void sndDeactivateAllSfxByFlag(u8 flag);
void sndDeactivateAllSfxByFlag_1(void);
void sndDeactivateAllSfxByFlag_11(void);
void sndDeactivateAllSfxByFlag_3(void);
u16 sndGetSfxSlotFirstNaturalVolume(void);
void sndApplyVolumeAllSfxSlot(u16 arg0);
void sndSetScalerApplyVolumeAllSfxSlot(f32 arg0);
u16 sndGetSfxSlotNaturalVolume(u8 arg0);
void sndSetSfxSlotVolume(u8 arg0, u16 arg1);
// end forward declarations
/**
* 8720 70007B20
*
* Mostly identical to n64devkit\ultra\usr\src\pr\libsrc\libultra\audio\sndplayer.c
* method alSndpNew.
*/
void sndNewPlayerInit(ALSeqpSfxConfig *sfxSeqpConfig)
{
u8 *ptr;
struct ALSoundState_s *sState;
ALEvent evt;
u32 i;
/*
* Init member variables
*/
g_sndPlayerPtr->maxSounds = sfxSeqpConfig->maybeMaxSounds;
g_sndPlayerPtr->target = 0;
g_sndPlayerPtr->frameTime = AL_USEC_PER_FRAME_30FPS;
sState = alHeapAlloc(sfxSeqpConfig->heap, 1, sfxSeqpConfig->maybeSndStateCount * sizeof(struct ALSoundState_s));
g_sndPlayerPtr->sndState = sState;
/*
* init the event queue
*/
ptr = alHeapAlloc(sfxSeqpConfig->heap, 1, sfxSeqpConfig->maxEvents * sizeof(ALEventListItem));
alEvtqNew(&g_sndPlayerPtr->evtq, (ALEventListItem *)ptr, sfxSeqpConfig->maxEvents);
D_800243E4.g_sndPlayerSoundStatePtr = g_sndPlayerPtr->sndState;
for(i = 1; i < sfxSeqpConfig->maybeSndStateCount; i++)
{
// The compiler says this reassignment matters ...
sState = (struct ALSoundState_s*)g_sndPlayerPtr->sndState;
// this works because `ALLink node` is at offset zero.
alLink((ALLink*)(&sState[i]), (ALLink*)(&sState[i]-1));
}
g_sndSfxSlotVolume = alHeapAlloc(sfxSeqpConfig->heap, sizeof(s16), SFX_SLOT_COUNT);
g_sndSfxSlotNaturalVolume = alHeapAlloc(sfxSeqpConfig->heap, sizeof(s16), SFX_SLOT_COUNT);
for(i = 0; i < SFX_SLOT_COUNT; i++)
{
g_sndSfxSlotNaturalVolume[i] = \
g_sndSfxSlotVolume[i] = (s16)0x7FFF;
}
/*
* add ourselves to the driver
*/
g_sndPlayerPtr->drvr = &alGlobals->drvr;
g_sndPlayerPtr->node.next = NULL;
g_sndPlayerPtr->node.handler = &sndPlayerVoiceHandler;
g_sndPlayerPtr->node.clientData = g_sndPlayerPtr;
alSynAddPlayer(g_sndPlayerPtr->drvr, &g_sndPlayerPtr->node);
/*
* Start responding to API events
*/
evt.type = AL_SNDP_API_EVT;
alEvtqPostEvent(&g_sndPlayerPtr->evtq, (ALEvent *)&evt, g_sndPlayerPtr->frameTime);
g_sndPlayerPtr->nextDelta = alEvtqNextEvent(&g_sndPlayerPtr->evtq, &g_sndPlayerPtr->nextEvent);
}
/**
* 89DC 70007DDC
*
* Almost identical to \n64devkit\ultra\usr\src\pr\libsrc\libultra\audio\sndplayer.c
* method ALMicroTime _sndpVoiceHandler(void *node).
*/
ALMicroTime sndPlayerVoiceHandler(void *node)
{
ALSndPlayer *sndp = (ALSndPlayer *) node;
ALSndpEvent evt;
do
{
switch (sndp->nextEvent.type)
{
case (AL_SNDP_API_EVT):
evt.common.type = (s16)AL_SNDP_API_EVT;
alEvtqPostEvent(&sndp->evtq, (ALEvent *)&evt, sndp->frameTime);
break;
default:
sndHandleEvent(sndp, (ALSndpEvent *)&sndp->nextEvent);
break;
}
sndp->nextDelta = alEvtqNextEvent(&sndp->evtq, &sndp->nextEvent);
} while (sndp->nextDelta == 0);
sndp->curTime += sndp->nextDelta;
return sndp->nextDelta;
}
void sndHandleEvent(ALSndPlayer *sndp, ALSndpEvent *event) {
ALVoiceConfig config;
ALVoice *voice; // dead but load-bearing. Do not remove.
s32 delta;
s32 limitReached;
ALSndpEvent spAC;
ALSndpEvent nextStateEvent;
ALSound *sound;
ALKeyMap *keyMap;
s32 volume;
s32 fxMix;
s32 isEventForSingleSound;
ALPan pan;
s32 lastInSequence;
s32 isVoiceAllocated;
ALSoundState *soundState;
ALSoundState *nextState;
lastInSequence = TRUE;
isVoiceAllocated = FALSE;
nextState = NULL;
do {
if (nextState != NULL) {
// NB: soundState is uninitialised on the first pass — original bug, preserved.
nextStateEvent.common.state = soundState;
nextStateEvent.common.type = event->common.type;
nextStateEvent.vol.vol = event->vol.vol;
event = &nextStateEvent;
}
soundState = event->common.state;
sound = soundState->sound;
if (sound == NULL) {
s16 numFree, numAlloc;
sndCountAllocList(&numFree, &numAlloc);
return;
}
keyMap = sound->keyMap;
nextState = (ALSoundState *) soundState->link.next;
switch (event->common.type) {
case AL_SNDP_PLAY_EVT:
if (1) {} // fake to get s5/s6 swapped
if (soundState->playingState != SOUND_STATE_INIT && soundState->playingState != SOUND_STATE_WAIT_VOICE) {
return;
}
config.fxBus = 0;
config.priority = soundState->priority;
config.unityPitch = 0;
limitReached = sndp->maxSounds <= g_sndAllocatedVoicesCount;
if (!limitReached || (soundState->unk3e & SOUND_FLAG_RETRIGGER)) {
// for retriggered sounds, ignore the limit
isVoiceAllocated = alSynAllocVoice(sndp->drvr, &soundState->voice, &config);
}
if (!isVoiceAllocated) {
// No free voices available, wait for another sound to stop.
if ((soundState->unk3e & (SOUND_FLAG_RETRIGGER | SOUND_FLAG_LOOPED)) || soundState->unk38 > 0) {
// Retry on the next frame.
// For looped and retriggered sounds, keep retrying on each frame.
soundState->playingState = SOUND_STATE_WAIT_VOICE;
soundState->unk38--;
alEvtqPostEvent(&sndp->evtq, (ALEvent *) event, DELTA_33_MS);
} else {
// Not a looped or retriggered sound, and all retries have been exhausted.
if (limitReached) {
// Check if we can preempt a lower-priority sound.
ALSoundState *iterState = (ALSoundState *) D_800243E4.node.prev;
do {
if (!(iterState->unk3e & 0x12) && (iterState->unk3e & 0x4) &&
iterState->playingState != SOUND_STATE_PREEMPT) {
// Found a lower-priority sound; it can be preempted
ALSndpEvent interruptEvent;
interruptEvent.common.type = AL_SNDP_END_EVT;
interruptEvent.common.state = iterState;
iterState->playingState = SOUND_STATE_PREEMPT;
limitReached = FALSE;
alEvtqPostEvent(&sndp->evtq, (ALEvent *) &interruptEvent, DELTA_1_MS);
alSynSetVol(sndp->drvr, &iterState->voice, (soundState->playingState == 1) * 0, DELTA_1_MS); // FAKE
}
iterState = (ALSoundState *) iterState->link.prev;
} while (limitReached && iterState != NULL);
if (!limitReached) {
// Retry the sound that was preempted.
soundState->unk38 = 2;
alEvtqPostEvent(&sndp->evtq, (ALEvent *) event, DELTA_1_MS + 1);
} else {
// No lower-priority sound to preempt, so stop the sound.
sndDisposeSound(soundState);
}
} else {
// It seems the developers made a mistake with the logic here.
// Should we stop the sound immediately if the maximum number of sounds hasn't been reached?
// Perhaps it would be better to look for a sound to preempt, just like when the limit is
// reached. It's strange that we only check for sounds to preempt when the limit is reached,
// but not when it hasn't been.
sndDisposeSound(soundState);
}
}
return;
}
// Set volume
soundState->unk3e |= SOUND_FLAG_PLAYING;
alSynStartVoice(sndp->drvr, &soundState->voice, sound->wavetable);
soundState->playingState = SOUND_STATE_PLAYING;
g_sndAllocatedVoicesCount++;
delta = sound->envelope->attackTime / soundState->pitch_2c / soundState->pitch_28;
volume =
MAX(0, g_sndSfxSlotVolume[SOUND_PARAM_GROUP(keyMap)] *
(sound->envelope->attackVolume * soundState->vol * sound->sampleVolume / 16129) /
AL_SNDP_GROUP_VOLUME_MAX - 1);
alSynSetVol(sndp->drvr, &soundState->voice, 0, 0);
alSynSetVol(sndp->drvr, &soundState->voice, volume, delta);
// Set pan
pan = MIN(MAX((soundState->pan + sound->samplePan - AL_PAN_CENTER), AL_PAN_LEFT), AL_PAN_RIGHT);
alSynSetPan(sndp->drvr, &soundState->voice, pan);
// Set pitch
alSynSetPitch(sndp->drvr, &soundState->voice, soundState->pitch_2c * soundState->pitch_28);
// Set FX mix
//!@bug: SOUND_PARAM_FXMIX is allocated only four bits, so it needs to be multiplied by 8
// to scale it to a range of 0 to 127.
// However, it's unclear why soundState->fxmix also needs to be multiplied by 8.
// The same issue appears in the AL_SNDP_FX_EVT handler.
fxMix = (soundState->fxMix + SOUND_PARAM_FXMIX(keyMap)) * 8;
fxMix = MIN(127, MAX(0, fxMix));
alSynSetFXMix(sndp->drvr, &soundState->voice, fxMix);
// Queue the decay event
spAC.common.type = AL_SNDP_DECAY_EVT;
spAC.common.state = soundState;
alEvtqPostEvent(&sndp->evtq, (ALEvent *) &spAC,
sound->envelope->attackTime / soundState->pitch_2c / soundState->pitch_28);
break;
case AL_SNDP_STOP_EVT:
case AL_SNDP_DEACTIVATE_EVT:
case AL_SNDP_UNKNOWN_12_EVT:
// If any sound in the composite sound is in the release phase, ignore this event for all other sounds
// in the sequence, because they haven't started yet.
// However, if the other sound is looped, process the event anyway.
//
// The purpose of checking for a looped sound seems unclear.
// Does it imply that a composite sound can't contain looped simple sounds?
// It seems logical, but the check may still be redundant.
if (event->common.type != AL_SNDP_UNKNOWN_12_EVT || (soundState->unk3e & SOUND_FLAG_LOOPED)) {
switch (soundState->playingState) {
case SOUND_STATE_PLAYING:
sndRemoveEvents(&sndp->evtq, soundState, AL_SNDP_DECAY_EVT);
delta = sound->envelope->releaseTime / soundState->pitch_28 / soundState->pitch_2c;
alSynSetVol(sndp->drvr, &soundState->voice, 0, delta);
if (delta != 0) {
spAC.common.type = AL_SNDP_END_EVT;
spAC.common.state = soundState;
alEvtqPostEvent(&sndp->evtq, (ALEvent *) &spAC, delta);
soundState->playingState = SOUND_STATE_STOPPING;
} else {
sndDisposeSound(soundState);
}
break;
case SOUND_STATE_WAIT_VOICE:
case SOUND_STATE_INIT:
sndDisposeSound(soundState);
break;
}
if (event->common.type == AL_SNDP_STOP_EVT) {
event->common.type = AL_SNDP_UNKNOWN_12_EVT;
}
}
break;
case AL_SNDP_PAN_EVT:
soundState->pan = event->pan32.pan32;
if (soundState->playingState == SOUND_STATE_PLAYING) {
pan = MIN(MAX((soundState->pan + sound->samplePan - AL_PAN_CENTER), AL_PAN_LEFT), AL_PAN_RIGHT);
alSynSetPan(sndp->drvr, &soundState->voice, pan);
}
break;
case AL_SNDP_PITCH_EVT:
soundState->pitch_2c = event->pitch.pitch;
if (soundState->playingState == SOUND_STATE_PLAYING) {
alSynSetPitch(sndp->drvr, &soundState->voice, soundState->pitch_2c * soundState->pitch_28);
if (soundState->unk3e & SOUND_FLAG_PITCH_SLIDE) {
sndCreatePitchEvent(soundState);
}
}
break;
case AL_SNDP_FX_EVT:
soundState->fxMix = event->fx32.mix32;
if (soundState->playingState == SOUND_STATE_PLAYING) {
//!@bug: unnecessary multiplication by 8, as in AL_SNDP_PLAY_EVT.
// The same issue appears in the AL_SNDP_PLAY_EVT handler.
fxMix = (soundState->fxMix + SOUND_PARAM_FXMIX(keyMap)) * 8;
fxMix = MIN(127, MAX(0, fxMix));
alSynSetFXMix(sndp->drvr, &soundState->voice, fxMix);
}
break;
case AL_SNDP_VOL_EVT:
soundState->vol = event->vol.vol;
if (soundState->playingState == SOUND_STATE_PLAYING) {
volume = MAX(
0, g_sndSfxSlotVolume[SOUND_PARAM_GROUP(keyMap)] *
(sound->envelope->decayVolume * soundState->vol * sound->sampleVolume / 16129) /
AL_SNDP_GROUP_VOLUME_MAX -
1);
alSynSetVol(sndp->drvr, &soundState->voice, volume, 1000);
}
break;
case AL_SNDP_RELEASE_EVT:
if (soundState->playingState == SOUND_STATE_PLAYING) {
delta = sound->envelope->releaseTime / soundState->pitch_28 / soundState->pitch_2c;
volume = MAX(
0, g_sndSfxSlotVolume[SOUND_PARAM_GROUP(keyMap)] *
(sound->envelope->decayVolume * soundState->vol * sound->sampleVolume / 16129) /
AL_SNDP_GROUP_VOLUME_MAX -
1);
alSynSetVol(sndp->drvr, &soundState->voice, volume, delta);
}
break;
case AL_SNDP_DECAY_EVT:
/*
* The voice has theoretically reached its attack velocity,
* set up callback for release envelope - except for a looped sound
*/
if (!(soundState->unk3e & SOUND_FLAG_LOOPED)) {
volume = MAX(
0, g_sndSfxSlotVolume[SOUND_PARAM_GROUP(keyMap)] *
(sound->envelope->decayVolume * soundState->vol * sound->sampleVolume / 16129) /
AL_SNDP_GROUP_VOLUME_MAX -
1);
delta = sound->envelope->decayTime / soundState->pitch_28 / soundState->pitch_2c;
alSynSetVol(sndp->drvr, &soundState->voice, volume, delta);
spAC.common.type = AL_SNDP_STOP_EVT;
spAC.common.state = soundState;
alEvtqPostEvent(&sndp->evtq, (ALEvent *) &spAC, delta);
// Start applying the pitch slide only when the decay phase is reached.
if (soundState->unk3e & SOUND_FLAG_PITCH_SLIDE) {
sndCreatePitchEvent(soundState);
}
}
break;
case AL_SNDP_END_EVT:
sndDisposeSound(soundState);
break;
case AL_SNDP_PLAY_SFX_EVT:
if (soundState->unk3e & SOUND_FLAG_RETRIGGER) {
sndPlaySfx(event->playSfx.soundBank, event->playSfx.soundIndex, soundState->state);
}
break;
default:
break;
}
soundState = nextState;
isEventForSingleSound = event->common.type & (AL_SNDP_PLAY_EVT | AL_SNDP_PITCH_EVT | AL_SNDP_DECAY_EVT |
AL_SNDP_END_EVT | AL_SNDP_PLAY_SFX_EVT);
if (soundState != NULL && !isEventForSingleSound) {
lastInSequence = soundState->unk3e & SOUND_FLAG_FINAL_IN_SEQUENCE;
}
} while (!lastInSequence && soundState != NULL && !isEventForSingleSound);
}
/**
* 9548 70008948
*/
void sndDisposeSound(ALSoundState *state)
{
if (state->unk3e & 4)
{
alSynStopVoice(g_sndPlayerPtr->drvr, &state->voice);
alSynFreeVoice(g_sndPlayerPtr->drvr, &state->voice);
}
sndUnlinkClearSound(state);
sndRemoveEvents(&g_sndPlayerPtr->evtq, state, 0xffff);
}
/**
* 95C4 700089C4
*/
void sndCreatePitchEvent(ALSoundState *state)
{
ALSndpEvent evt;
f32 pitch;
pitch = (f32) (alCents2Ratio(state->sound->keyMap->detune) * (f32)state->pitch_2c);
evt.pitch.state = state;
evt.pitch.type = AL_SNDP_PITCH_EVT;
// TODO: surely there's a better way to match target, especially since there's already a union type used with f32 for pitch.
evt.unks32.val8 = *(s32*)&pitch;
alEvtqPostEvent(&g_sndPlayerPtr->evtq, (ALEvent *)&evt, DELTA_33_MS);
}
/**
* 9630 70008A30
* Based on (almost identical to) the method
* static void _removeEvents(ALEventQueue *evtq, ALSoundState *state)
* from n64devkit\ultra\usr\src\pr\libsrc\libultra\audio\sndplayer.c
*/
void sndRemoveEvents(ALEventQueue *evtq, ALSoundState *state, u16 eventType)
{
ALLink *thisNode;
ALLink *nextNode;
ALEventListItem *thisItem;
ALEventListItem *nextItem;
ALSndpEvent *thisEvent;
OSIntMask mask;
mask = osSetIntMask(OS_IM_NONE);
thisNode = evtq->allocList.next;
while(thisNode != NULL)
{
nextNode = thisNode->next;
thisItem = (ALEventListItem *)thisNode;
nextItem = (ALEventListItem *)nextNode;
thisEvent = (ALSndpEvent *)&thisItem->evt;
if (thisEvent->common.state == state && (((u16)thisItem->evt.type & (u16)eventType) != 0))
{
if (nextItem != NULL)
{
nextItem->delta += thisItem->delta;
}
alUnlink(thisNode);
alLink(thisNode, &evtq->freeList);
}
thisNode = nextNode;
}
osSetIntMask(mask);
}
/**
* 96F0 70008AF0
* Has similarities to
* void alEvtqPrintEvtQueue(ALEventQueue *evtq)
* from n64devkit\ultra\usr\src\pr\libsrc\libultra\audio\event.c
*
* @param allocListCount Out param. Will contain the number of (next) nodes in the D_800243E4 allocList.
* @param freeListCount Out param. Will contain the number of (next) nodes in the D_800243E4 freeList.
* @return Number of (prev) nodes in the D_800243E4 freeList.
*/
s32 sndCountAllocList(s16 *allocListCount, s16 *freeListCount)
{
u16 counter1;
u16 counter2;
u16 returnCounter;
ALEventQueue *evtq = (ALEventQueue *)&D_800243E4;
ALLink *freeListNodeForward = evtq->freeList.next;
ALLink *allocListNodeForward = evtq->allocList.next;
ALLink *freeListNodeBackward = evtq->freeList.prev;
for (counter1 = 0; freeListNodeForward != NULL; freeListNodeForward = freeListNodeForward->next)
{
counter1++;
}
for (counter2 = 0; allocListNodeForward != NULL; allocListNodeForward = allocListNodeForward->next)
{
counter2++;
}
for (returnCounter = 0; freeListNodeBackward != NULL; freeListNodeBackward = freeListNodeBackward->prev)
{
returnCounter++;
}
*allocListCount = (s16) counter2;
*freeListCount = (s16) counter1;
return returnCounter;
}
/**
* 9770 70008B70
* initializes soundstate to sound based on global g_sndPlayerSoundStatePtr.
* accepts: A0=sound data offset?, A1=sample address?
*
* @param soundBank unused.
* @param sound sound to use.
*/
ALSoundState *sndSetupSound(struct ALBankAlt_s *soundBank, ALSound* sound)
{
s32 decayTimeFlag;
ALKeyMap *keymap = sound->keyMap;
ALSoundState *state = (ALSoundState *)D_800243E4.g_sndPlayerSoundStatePtr;
OSIntMask mask;
if (state != NULL)
{
mask = osSetIntMask(OS_IM_NONE);
D_800243E4.g_sndPlayerSoundStatePtr = (void *)state->link.next;
alUnlink(&state->link);
if (D_800243E4.node.next != NULL)
{
state->link.next = (void *)D_800243E4.node.next;
state->link.prev = NULL;
D_800243E4.node.next->prev = (void *)state; // what?
D_800243E4.node.next = (void *)state;
}
else
{
state->link.prev = NULL;
state->link.next = NULL;
D_800243E4.node.next = (void *)state;
D_800243E4.node.prev = (void *)state;
}
osSetIntMask(mask);
decayTimeFlag = (sound->envelope->decayTime == -1);
state->priority = decayTimeFlag + 0x40;
state->playingState = AL_UNKOWN_5;
state->unk38 = 2;
state->sound = sound;
state->pitch_2c = 1.0f;
state->unk3e = (keymap->keyMax & (u8)0xf0);
state->state = NULL;
if ((state->unk3e & 0x20) != 0)
{
state->pitch_28 = alCents2Ratio(((keymap->keyBase * 100) + DEFAULT_SETUP_PITCH_SHIFT));
}
else
{
state->pitch_28 = alCents2Ratio((((keymap->keyBase * 100) + keymap->detune) + DEFAULT_SETUP_PITCH_SHIFT));
}
if (decayTimeFlag)
{
state->unk3e |= 2;
}
state->fxMix = (u8)AL_DEFAULT_FXMIX;
state->pan = (u8)AL_PAN_CENTER;
state->vol = (u16)0x7fff;
}
return state;
}
/**
* 9904 70008D04
* some kind of dispose method, unlinks next/prev pointers.
*/
void sndUnlinkClearSound(ALSoundState *state)
{
if (state == (ALSoundState *)D_800243E4.node.next)
{
D_800243E4.node.next = state->link.next;
}
if (state == (ALSoundState *)D_800243E4.node.prev)
{
D_800243E4.node.prev = state->link.prev;
}
alUnlink(&state->link);
if (D_800243E4.g_sndPlayerSoundStatePtr != NULL)
{
state->link.next = (void *)D_800243E4.g_sndPlayerSoundStatePtr;
state->link.prev = NULL;
D_800243E4.g_sndPlayerSoundStatePtr->link.prev = (void *)state;
D_800243E4.g_sndPlayerSoundStatePtr = state;
}
else
{
state->link.prev = NULL;
state->link.next = NULL;
D_800243E4.g_sndPlayerSoundStatePtr = state;
}
if ((state->unk3e & 4) != 0)
{
g_sndAllocatedVoicesCount--;
}
state->playingState = AL_STOPPED;
if (state->state != NULL)
{
if (state == (ALSoundState *)state->state->link.next)
{
state->state->link.next = NULL;
}
state->state = NULL;
}
}
/**
* 99D8 70008DD8
* Sets priority of ALSoundState.
*/
void sndSetPriority(ALSoundState *state, u8 priority)
{
if (state != NULL)
{
state->priority = priority;
}
}
/**
* 99F0 70008DF0
* Gets Playing State if a state is available
* @param state: the state to check
* @return AL_PLAYSTATE
*/
u8 sndGetPlayingState(ALSoundState *state)
{
if (state != NULL)
{
return state->playingState;
}
return AL_STOPPED;
}
#ifdef DEBUG
# define _sndPlaySfx(sbank, id, state) sndPlaySfx(sbank, id, state, g_sndSfxVolume, __FILE__, __LINE__)
ALSoundState *sndPlaySfx(struct ALBankAlt_s *soundBank, s16 soundIndex, ALSoundState *pendingState, f32 volume, char*file, int line)
#else
/**
* 9A08 70008E08
* sets sound effect; used by sound effect routines
*
* Old comments:
*
* accepts: A0=p->SE buffer, A1=SE #, A2=p->data
* data: 0x0 4 p->SE entry
* 0x4 4 target volume
* 0x8 4 audible range (timer)
* 0xC 4 initial volume
* 0x10 4 p->preset emitting sound
* 0x14 4 p->object emitting sound
*
* // end old comments.
*
* @param soundBank sound bank
* @param soundIndex index into sound bank: soundBank->instArray[0]->soundArray[soundIndex]
* @param pendingState Optional pointer. If provided, its link.next pointer will be
* to the newly created soundState.
*/
ALSoundState *sndPlaySfx(struct ALBankAlt_s *soundBank, s16 soundIndex, ALSoundState *pendingState)
#endif
{
// declarations
// declaration order doesn't seem to matter for these.
ALMicroTime deltaTotal;
ALSound *sound;
ALSoundState *newState;
ALSoundState *nextState;
// declaration order matters:
s16 eventSoundIndex; // 110(sp)
s16 unused_sp6c; // 108(sp)
ALMicroTime playSfxDelta; // 104(sp)
ALMicroTime deltaLoop; // 100(sp)
// end declarations
nextState = NULL;
eventSoundIndex = 0;
deltaTotal = 0;
if(0); // debug?
if (g_sndBootswitchSound)
{
return NULL;
}
if (soundIndex == 0)
{
return NULL;
}
do
{
ALKeyMap *keyMap;
sound = (soundBank->instArray[0]->soundArray[soundIndex]);
#ifdef PORT
/* C7 / D127: the libaudio subsystem is Phase-3 parked (no real bank
* playback yet). On some levels (Surface1, -level_36) a requested
* soundIndex resolves to a bogus ALSound* -- the converted bank tree
* (port/src/romdata.c afFixupInst) has fewer/rearranged soundArray
* slots than the N64 image -- and sndSetupSound() then faults on
* sound->keyMap. Until audio lands, skip a sound whose pointer is
* plainly not a mapped address rather than crash the level.
* ALSound lives in game DRAM (~0x7000_0000..) or the cart image
* (0x1_4000_0000..); anything else (e.g. 0x0000_5622_0001_0001) is a
* byte-scrambled / OOB read. */
{
uintptr_t sp = (uintptr_t)sound;
if (sp < 0x10000 || sp >= 0x400000000ULL) {
return NULL;
}
}
#endif
newState = sndSetupSound(soundBank, sound);
if (newState != NULL)
{
ALSndpEvent playEvent;
g_sndPlayerPtr->target = (s32)newState;
playEvent.common.type = AL_SNDP_PLAY_EVT;
playEvent.common.state = newState;
deltaLoop = sound->keyMap->velocityMax * DELTA_33_MS;
if (newState->unk3e & 0x10)
{
newState->unk3e &= (~(s16)(0x10));
alEvtqPostEvent(&g_sndPlayerPtr->evtq, (ALEvent *)&playEvent, deltaTotal + 1);
playSfxDelta = deltaLoop + 1;
eventSoundIndex = soundIndex;
}
else
{
alEvtqPostEvent(&g_sndPlayerPtr->evtq, (ALEvent *)&playEvent, deltaLoop + 1);
}
nextState = newState;
}
deltaTotal += deltaLoop;
keyMap = sound->keyMap;
soundIndex = (s16)((s32)keyMap->velocityMin + ((s32)(keyMap->keyMin & 0xC0) * 4));
} while (soundIndex != 0 && newState != NULL);
if(!soundIndex)
{
// removed
}
if(!sound)
{
// removed
}
if (nextState != NULL)
{
nextState->unk3e |= 0x1;
nextState->state = pendingState;
if (eventSoundIndex != 0)
{
ALSndpEvent playSfxEvent;
nextState->unk3e |= 0x10;
playSfxEvent.playSfx.type = AL_SNDP_PLAY_SFX_EVT;
playSfxEvent.playSfx.state = nextState;
playSfxEvent.playSfx.soundIndex = eventSoundIndex; // types dont match
playSfxEvent.playSfx.soundBank = soundBank;
alEvtqPostEvent(&g_sndPlayerPtr->evtq, (ALEvent *)&playSfxEvent, playSfxDelta);
}
}
if (pendingState != NULL)
{
pendingState->link.next = (void*)nextState;
}
return nextState;
}
/**
* 9C20 70009020
* decativates sound effect
* accepts: A0=p->SE buffer
*/
void sndDeactivate(ALSoundState *state)
{
ALSndpEvent evt;
evt.common.type = AL_SNDP_DEACTIVATE_EVT;
evt.common.state = state;
if (state != NULL)
{
state->unk3e = (s8) (state->unk3e & (~(s16)(0x10)));
alEvtqPostEvent(&g_sndPlayerPtr->evtq, (ALEvent *)&evt, 0);
}
}
/**
* 9C6C 7000906C
* Similar to sndDeactivate, but iterates the global list and deactivates
* items with the same unk3e flag.
*
* @param flag flag bitmask to match item on.
*/
void sndDeactivateAllSfxByFlag(u8 flag)
{
OSIntMask mask;
ALSndpEvent evt;
ALSoundState *item;
mask = osSetIntMask(OS_IM_NONE);
item = (ALSoundState *)D_800243E4.node.next;
while (item != NULL)
{
evt.common.type = AL_SNDP_DEACTIVATE_EVT;
evt.common.state = item;
if ((item->unk3e & flag) == flag)
{
item->unk3e = (s8) (item->unk3e & (~(s16)(0x10)));
alEvtqPostEvent(&g_sndPlayerPtr->evtq, (ALEvent *)&evt, 0);
}
item = (ALSoundState *)item->link.next;
}
osSetIntMask(mask);
}
/**
* 9D24 70009124
* redirect to 7000906C: A0=1
*/
void sndDeactivateAllSfxByFlag_1(void)
{
sndDeactivateAllSfxByFlag(1);
}
/**
* 9D44 70009144
* redirect to 7000906C: A0=11
*/
void sndDeactivateAllSfxByFlag_11(void)
{
sndDeactivateAllSfxByFlag(0x11);
}
/**
* 9D64 70009164
* redirect to 7000906C: A0=3
*/
void sndDeactivateAllSfxByFlag_3(void)
{
sndDeactivateAllSfxByFlag(3);
}
/**
* 9D84 70009184
* Calls alEvtqPostEvent with the method parameters and delta=0.
*
* @param state sound state.
* @param eventType type of event to post.
* @param arg2 event data value (interpretation depends on eventType).
*/
void sndCreatePostEvent(ALSoundState *state, s16 eventType, s32 arg2)
{
ALSndpEvent evt;
#ifdef PORT
/* D138: libaudio is Phase-3 parked. The audio thread never drains
* g_sndPlayerPtr->evtq->allocList, so every positional-sound tick
* (chrobjSndCreatePostEvent, called per objTick for each ambient-sound
* object) appends an item and alEvtqPostEvent walks the whole list to
* insert it -> O(n^2). Facility has enough machinery ambience that after
* a few thousand frames one insert takes seconds and the kernel-heartbeat
* watchdog trips ("no frame rendered"). Skip event posting until audio
* lands (D77); no gameplay effect. */
(void) evt; (void) eventType; (void) arg2; (void) state;
return;
#else
evt.common.type = eventType;
evt.common.state = state;
evt.unks32.val8 = arg2;
if (state != NULL)
{
alEvtqPostEvent(&g_sndPlayerPtr->evtq, (ALEvent *)&evt, 0);
}
#endif
}
/**
* 9DC8 700091C8
* redirect to 70009264: A0=0
*/
u16 sndGetSfxSlotFirstNaturalVolume(void)
{
return sndGetSfxSlotNaturalVolume(0);
}
/**
* 9DE8 700091E8
*/
void sndApplyVolumeAllSfxSlot(u16 volume)
{
u8 i;
#ifdef PORT
/* D152: collapse the per-slot sndSetSfxSlotVolume() lock acquisitions
* into one recursive hold for the whole update (the fade-out calls this
* every frame). One real lock acquire/release per frame instead of
* SFX_SLOT_COUNT * (active sounds). No behaviour change. */
OSIntMask mask = osSetIntMask(OS_IM_NONE);
#endif
for (i = 0; i < SFX_SLOT_COUNT; i++)
{
sndSetSfxSlotVolume(i, volume);
}
#ifdef PORT
osSetIntMask(mask);
#endif
}
/**
* 9E38 70009238
*/
void sndSetScalerApplyVolumeAllSfxSlot(f32 volumeScale)
{
g_sndSfxVolumeScale = volumeScale;
sndApplyVolumeAllSfxSlot(sndGetSfxSlotFirstNaturalVolume());
}
/**
* 9E64 70009264
* V0= halfword A0 in table at [80063BA8]; fries T6,T7,T8,T9
*/
u16 sndGetSfxSlotNaturalVolume(u8 sfxIndex)
{
return g_sndSfxSlotNaturalVolume[sfxIndex];
}
/**
* 9E84 70009284
*/
void sndSetSfxSlotVolume(u8 sfxIndex, u16 volume)
{
// Not sure if these are debug leftovers, or is the type `ALSndpEvent` wrong?
u32 unused[2];
ALSndpEvent evt;
ALSoundState *item;
#ifdef PORT
/* D152: this walks the live ALSoundState list and posts to the shared
* ALEventQueue, exactly like its sibling sndDeactivateAllSfxByFlag() --
* which DOES take OS_IM_NONE around its walk. The omission here is benign
* on N64 (alEvtqPostEvent masks internally, and the audio job is a
* cooperative RSP task) but not on PC: the audio manager (amMain) is a
* real preemptible thread, so the unguarded walk both races a concurrent
* list mutation AND takes/drops the interrupt-mask lock (D147/D152)
* once per matching sound. During the mission-failed audio fade-out
* (sndSetScalerApplyVolumeAllSfxSlot -> sndApplyVolumeAllSfxSlot -> here,
* every frame, over every active sound) that is dozens of lock
* acquire/release per frame on the game thread, each a fresh window to
* collide with amMain and eat the 2 s steal-lock timeout -> the frame
* never renders -> permanent black screen. Hold the mask once across the
* whole walk: the nested alEvtqPostEvent() calls then hit the recursive
* fast path (depth++) and open no new contention window, and the walk is
* atomic w.r.t. amMain as the N64 "interrupts off" intent requires.
* Concurrency-correctness only; no behaviour change. */
OSIntMask mask = osSetIntMask(OS_IM_NONE);
#endif
item = (ALSoundState *)D_800243E4.node.next;
g_sndSfxSlotNaturalVolume[sfxIndex] = volume;
g_sndSfxSlotVolume[sfxIndex] = (s16) ((f32) volume * g_sndSfxVolumeScale);
while (item != NULL)
{
if (item->sound != NULL)
{
if ((item->sound->keyMap->keyMin & 0x3f) == sfxIndex)
{
evt.common.type = AL_SNDP_RELEASE_EVT;
evt.common.state = item;
alEvtqPostEvent(&g_sndPlayerPtr->evtq, (ALEvent *)&evt, 0);
}
}
item = (ALSoundState *)item->link.next;
}
#ifdef PORT
osSetIntMask(mask);
#endif
}