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