Skip to content

Commit bc54f1b

Browse files
committed
[ZH][CMAKE] Separate AudioFileCache from MilesAudioManager
1 parent 2713267 commit bc54f1b

File tree

5 files changed

+383
-309
lines changed

5 files changed

+383
-309
lines changed

GeneralsMD/Code/GameEngineDevice/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ set(GAMEENGINEDEVICE_SRC
9090
Include/Win32Device/GameClient/Win32DIKeyboard.h
9191
Include/Win32Device/GameClient/Win32DIMouse.h
9292
Include/Win32Device/GameClient/Win32Mouse.h
93+
Source/MilesAudioDevice/MilesAudioFileCache.h
94+
Source/MilesAudioDevice/MilesAudioFileCache.cpp
9395
Source/MilesAudioDevice/MilesAudioManager.cpp
9496
Source/VideoDevice/Bink/BinkVideoPlayer.cpp
9597
Source/W3DDevice/Common/System/W3DFunctionLexicon.cpp

GeneralsMD/Code/GameEngineDevice/Include/MilesAudioDevice/MilesAudioManager.h

Lines changed: 4 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
// FILE: MilesAudioManager.h //////////////////////////////////////////////////////////////////////////
2020
// MilesAudioManager implementation
2121
// Author: John K. McDonald, July 2002
22-
22+
#pragma once
2323
#include "Common/AsciiString.h"
2424
#include "Common/GameAudio.h"
2525
#include "mss/mss.h"
@@ -51,87 +51,15 @@ enum PlayingWhich CPP_11(: Int)
5151
PW_INVALID
5252
};
5353

54-
struct PlayingAudio
55-
{
56-
union
57-
{
58-
HSAMPLE m_sample;
59-
H3DSAMPLE m_3DSample;
60-
HSTREAM m_stream;
61-
};
62-
63-
PlayingAudioType m_type;
64-
volatile PlayingStatus m_status; // This member is adjusted by another running thread.
65-
AudioEventRTS *m_audioEventRTS;
66-
void *m_file; // The file that was opened to play this
67-
Bool m_requestStop;
68-
Bool m_cleanupAudioEventRTS;
69-
Int m_framesFaded;
70-
71-
PlayingAudio() :
72-
m_type(PAT_INVALID),
73-
m_audioEventRTS(NULL),
74-
m_requestStop(false),
75-
m_cleanupAudioEventRTS(true),
76-
m_sample(NULL),
77-
m_framesFaded(0)
78-
{ }
79-
};
80-
8154
struct ProviderInfo
8255
{
8356
AsciiString name;
8457
HPROVIDER id;
8558
Bool m_isValid;
8659
};
8760

88-
struct OpenAudioFile
89-
{
90-
AILSOUNDINFO m_soundInfo;
91-
void *m_file;
92-
UnsignedInt m_openCount;
93-
UnsignedInt m_fileSize;
94-
95-
Bool m_compressed; // if the file was compressed, then we need to free it with a miles function.
96-
97-
// Note: OpenAudioFile does not own this m_eventInfo, and should not delete it.
98-
const AudioEventInfo *m_eventInfo; // Not mutable, unlike the one on AudioEventRTS.
99-
};
100-
101-
typedef std::hash_map< AsciiString, OpenAudioFile, rts::hash<AsciiString>, rts::equal_to<AsciiString> > OpenFilesHash;
102-
typedef OpenFilesHash::iterator OpenFilesHashIt;
103-
104-
class AudioFileCache
105-
{
106-
public:
107-
AudioFileCache();
108-
109-
// Protected by mutex
110-
virtual ~AudioFileCache();
111-
void *openFile( AudioEventRTS *eventToOpenFrom );
112-
void closeFile( void *fileToClose );
113-
void setMaxSize( UnsignedInt size );
114-
// End Protected by mutex
115-
116-
// Note: These functions should be used for informational purposes only. For speed reasons,
117-
// they are not protected by the mutex, so they are not guarenteed to be valid if called from
118-
// outside the audio cache. They should be used as a rough estimate only.
119-
UnsignedInt getCurrentlyUsedSize() const { return m_currentlyUsedSize; }
120-
UnsignedInt getMaxSize() const { return m_maxSize; }
121-
122-
protected:
123-
void releaseOpenAudioFile( OpenAudioFile *fileToRelease );
124-
125-
// This function will return TRUE if it was able to free enough space, and FALSE otherwise.
126-
Bool freeEnoughSpaceForSample(const OpenAudioFile& sampleThatNeedsSpace);
127-
128-
OpenFilesHash m_openFiles;
129-
UnsignedInt m_currentlyUsedSize;
130-
UnsignedInt m_maxSize;
131-
HANDLE m_mutex;
132-
const char *m_mutexName;
133-
};
134-
61+
struct PlayingAudio;
62+
class MilesAudioFileCache;
13563
class MilesAudioManager : public AudioManager
13664
{
13765

@@ -315,7 +243,7 @@ class MilesAudioManager : public AudioManager
315243
// in the sound engine
316244
std::list<PlayingAudio *> m_stoppedAudio;
317245

318-
AudioFileCache *m_audioCache;
246+
MilesAudioFileCache *m_audioCache;
319247
PlayingAudio *m_binkHandle;
320248
UnsignedInt m_num2DSamples;
321249
UnsignedInt m_num3DSamples;
Lines changed: 279 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,279 @@
1+
/*
2+
** Command & Conquer Generals Zero Hour(tm)
3+
** Copyright 2025 Electronic Arts Inc.
4+
**
5+
** This program is free software: you can redistribute it and/or modify
6+
** it under the terms of the GNU General Public License as published by
7+
** the Free Software Foundation, either version 3 of the License, or
8+
** (at your option) any later version.
9+
**
10+
** This program is distributed in the hope that it will be useful,
11+
** but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
** GNU General Public License for more details.
14+
**
15+
** You should have received a copy of the GNU General Public License
16+
** along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
19+
////////////////////////////////////////////////////////////////////////////////
20+
// //
21+
// (c) 2001-2003 Electronic Arts Inc. //
22+
// //
23+
////////////////////////////////////////////////////////////////////////////////
24+
25+
// FILE: MilesAudioFileCache.cpp
26+
/*---------------------------------------------------------------------------*/
27+
/* EA Pacific */
28+
/* Confidential Information */
29+
/* Copyright (C) 2001 - All Rights Reserved */
30+
/* DO NOT DISTRIBUTE */
31+
/*---------------------------------------------------------------------------*/
32+
/* Project: RTS3 */
33+
/* File name: MilesAudioFileCache.cpp */
34+
/* Created: John K. McDonald, Jr., 3/21/2002 */
35+
/* Desc: This is the implementation for the MilesAudioManager, which */
36+
/* interfaces with the Miles Sound System. */
37+
/* Revision History: */
38+
/* 7/18/2002 : Initial creation */
39+
/* 4/29/2025 : Moved AudioFileCache into a separate file and renamed */
40+
/*---------------------------------------------------------------------------*/
41+
42+
#include "MilesAudioFileCache.h"
43+
44+
#include "Common/AudioEventInfo.h"
45+
#include "Common/AudioEventRTS.h"
46+
#include "Common/File.h"
47+
#include "Common/FileSystem.h"
48+
#include "Common/ScopedMutex.h"
49+
50+
//-------------------------------------------------------------------------------------------------
51+
//-------------------------------------------------------------------------------------------------
52+
//-------------------------------------------------------------------------------------------------
53+
MilesAudioFileCache::MilesAudioFileCache() : m_maxSize(0), m_currentlyUsedSize(0), m_mutexName("AudioFileCacheMutex")
54+
{
55+
m_mutex = CreateMutex(NULL, FALSE, m_mutexName);
56+
}
57+
58+
//-------------------------------------------------------------------------------------------------
59+
MilesAudioFileCache::~MilesAudioFileCache()
60+
{
61+
{
62+
ScopedMutex mut(m_mutex);
63+
64+
// Free all the samples that are open.
65+
OpenFilesHashIt it;
66+
for ( it = m_openFiles.begin(); it != m_openFiles.end(); ++it ) {
67+
if (it->second.m_openCount > 0) {
68+
DEBUG_CRASH(("Sample '%s' is still playing, and we're trying to quit.\n", it->second.m_eventInfo->m_audioName.str()));
69+
}
70+
71+
releaseOpenAudioFile(&it->second);
72+
// Don't erase it from the map, cause it makes this whole process way more complicated, and
73+
// we're about to go away anyways.
74+
}
75+
}
76+
77+
CloseHandle(m_mutex);
78+
}
79+
80+
//-------------------------------------------------------------------------------------------------
81+
void *MilesAudioFileCache::openFile( AudioEventRTS *eventToOpenFrom )
82+
{
83+
// Protect the entire openFile function
84+
ScopedMutex mut(m_mutex);
85+
86+
AsciiString strToFind;
87+
switch (eventToOpenFrom->getNextPlayPortion())
88+
{
89+
case PP_Attack:
90+
strToFind = eventToOpenFrom->getAttackFilename();
91+
break;
92+
case PP_Sound:
93+
strToFind = eventToOpenFrom->getFilename();
94+
break;
95+
case PP_Decay:
96+
strToFind = eventToOpenFrom->getDecayFilename();
97+
break;
98+
case PP_Done:
99+
return NULL;
100+
}
101+
102+
OpenFilesHash::iterator it;
103+
it = m_openFiles.find(strToFind);
104+
105+
if (it != m_openFiles.end()) {
106+
++it->second.m_openCount;
107+
return it->second.m_file;
108+
}
109+
110+
// Couldn't find the file, so actually open it.
111+
File *file = TheFileSystem->openFile(strToFind.str());
112+
if (!file) {
113+
DEBUG_ASSERTLOG(strToFind.isEmpty(), ("Missing Audio File: '%s'\n", strToFind.str()));
114+
return NULL;
115+
}
116+
117+
UnsignedInt fileSize = file->size();
118+
char* buffer = file->readEntireAndClose();
119+
120+
OpenAudioFile openedAudioFile;
121+
openedAudioFile.m_eventInfo = eventToOpenFrom->getAudioEventInfo();
122+
123+
AILSOUNDINFO soundInfo;
124+
AIL_WAV_info(buffer, &soundInfo);
125+
126+
if (eventToOpenFrom->isPositionalAudio()) {
127+
if (soundInfo.channels > 1) {
128+
DEBUG_CRASH(("Requested Positional Play of audio '%s', but it is in stereo.", strToFind.str()));
129+
delete [] buffer;
130+
return NULL;
131+
}
132+
}
133+
134+
if (soundInfo.format == WAVE_FORMAT_IMA_ADPCM) {
135+
void *decompressFileBuffer;
136+
U32 newFileSize;
137+
AIL_decompress_ADPCM(&soundInfo, &decompressFileBuffer, &newFileSize);
138+
fileSize = newFileSize;
139+
openedAudioFile.m_compressed = TRUE;
140+
delete [] buffer;
141+
openedAudioFile.m_file = decompressFileBuffer;
142+
openedAudioFile.m_soundInfo = soundInfo;
143+
openedAudioFile.m_openCount = 1;
144+
} else if (soundInfo.format == WAVE_FORMAT_PCM) {
145+
openedAudioFile.m_compressed = FALSE;
146+
openedAudioFile.m_file = buffer;
147+
openedAudioFile.m_soundInfo = soundInfo;
148+
openedAudioFile.m_openCount = 1;
149+
} else {
150+
DEBUG_CRASH(("Unexpected compression type in '%s'\n", strToFind.str()));
151+
// prevent leaks
152+
delete [] buffer;
153+
return NULL;
154+
}
155+
156+
openedAudioFile.m_fileSize = fileSize;
157+
m_currentlyUsedSize += openedAudioFile.m_fileSize;
158+
if (m_currentlyUsedSize > m_maxSize) {
159+
// We need to free some samples, or we're not going to be able to play this sound.
160+
if (!freeEnoughSpaceForSample(openedAudioFile)) {
161+
m_currentlyUsedSize -= openedAudioFile.m_fileSize;
162+
releaseOpenAudioFile(&openedAudioFile);
163+
return NULL;
164+
}
165+
}
166+
167+
m_openFiles[strToFind] = openedAudioFile;
168+
return openedAudioFile.m_file;
169+
}
170+
171+
//-------------------------------------------------------------------------------------------------
172+
void MilesAudioFileCache::closeFile( void *fileToClose )
173+
{
174+
if (!fileToClose) {
175+
return;
176+
}
177+
178+
// Protect the entire closeFile function
179+
ScopedMutex mut(m_mutex);
180+
181+
OpenFilesHash::iterator it;
182+
for ( it = m_openFiles.begin(); it != m_openFiles.end(); ++it ) {
183+
if ( it->second.m_file == fileToClose ) {
184+
--it->second.m_openCount;
185+
return;
186+
}
187+
}
188+
}
189+
190+
//-------------------------------------------------------------------------------------------------
191+
void MilesAudioFileCache::setMaxSize( UnsignedInt size )
192+
{
193+
// Protect the function, in case we're trying to use this value elsewhere.
194+
ScopedMutex mut(m_mutex);
195+
196+
m_maxSize = size;
197+
}
198+
199+
//-------------------------------------------------------------------------------------------------
200+
void MilesAudioFileCache::releaseOpenAudioFile( OpenAudioFile *fileToRelease )
201+
{
202+
if (fileToRelease->m_openCount > 0) {
203+
// This thing needs to be terminated IMMEDIATELY.
204+
TheAudio->closeAnySamplesUsingFile(fileToRelease->m_file);
205+
}
206+
207+
if (fileToRelease->m_file) {
208+
if (fileToRelease->m_compressed) {
209+
// Files read in via AIL_decompress_ADPCM must be freed with AIL_mem_free_lock.
210+
AIL_mem_free_lock(fileToRelease->m_file);
211+
} else {
212+
// Otherwise, we read it, we own it, blow it away.
213+
delete [] fileToRelease->m_file;
214+
}
215+
fileToRelease->m_file = NULL;
216+
fileToRelease->m_eventInfo = NULL;
217+
}
218+
}
219+
220+
//-------------------------------------------------------------------------------------------------
221+
Bool MilesAudioFileCache::freeEnoughSpaceForSample(const OpenAudioFile& sampleThatNeedsSpace)
222+
{
223+
224+
Int spaceRequired = m_currentlyUsedSize - m_maxSize;
225+
Int runningTotal = 0;
226+
227+
std::list<AsciiString> filesToClose;
228+
// First, search for any samples that have ref counts of 0. They are low-hanging fruit, and
229+
// should be considered immediately.
230+
OpenFilesHashIt it;
231+
for (it = m_openFiles.begin(); it != m_openFiles.end(); ++it) {
232+
if (it->second.m_openCount == 0) {
233+
// This is said low-hanging fruit.
234+
filesToClose.push_back(it->first);
235+
236+
runningTotal += it->second.m_fileSize;
237+
238+
if (runningTotal >= spaceRequired) {
239+
break;
240+
}
241+
}
242+
}
243+
244+
// If we don't have enough space yet, then search through the events who have a count of 1 or more
245+
// and who are lower priority than this sound.
246+
// Mical said that at this point, sounds shouldn't care if other sounds are interruptable or not.
247+
// Kill any files of lower priority necessary to clear our the buffer.
248+
if (runningTotal < spaceRequired) {
249+
for (it = m_openFiles.begin(); it != m_openFiles.end(); ++it) {
250+
if (it->second.m_openCount > 0) {
251+
if (it->second.m_eventInfo->m_priority < sampleThatNeedsSpace.m_eventInfo->m_priority) {
252+
filesToClose.push_back(it->first);
253+
runningTotal += it->second.m_fileSize;
254+
255+
if (runningTotal >= spaceRequired) {
256+
break;
257+
}
258+
}
259+
}
260+
}
261+
}
262+
263+
// We weren't able to find enough sounds to truncate. Therefore, this sound is not going to play.
264+
if (runningTotal < spaceRequired) {
265+
return FALSE;
266+
}
267+
268+
std::list<AsciiString>::iterator ait;
269+
for (ait = filesToClose.begin(); ait != filesToClose.end(); ++ait) {
270+
OpenFilesHashIt itToErase = m_openFiles.find(*ait);
271+
if (itToErase != m_openFiles.end()) {
272+
releaseOpenAudioFile(&itToErase->second);
273+
m_currentlyUsedSize -= itToErase->second.m_fileSize;
274+
m_openFiles.erase(itToErase);
275+
}
276+
}
277+
278+
return TRUE;
279+
}

0 commit comments

Comments
 (0)