mirror of
https://github.com/jkdansereau/goldeneye-pc-port.git
synced 2026-10-24 00:02:45 +02:00
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.
1168 lines
37 KiB
C
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
|
|
}
|