1 /*
2  *  This file is part of Dune Legacy.
3  *
4  *  Dune Legacy is free software: you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation, either version 2 of the License, or
7  *  (at your option) any later version.
8  *
9  *  Dune Legacy is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15  *  along with Dune Legacy.  If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 #include <FileClasses/SFXManager.h>
19 
20 #include <globals.h>
21 
22 #include <FileClasses/FileManager.h>
23 #include <FileClasses/Vocfile.h>
24 
25 #include <FileClasses/adl/sound_adlib.h>
26 
27 #include <misc/sound_util.h>
28 #include <misc/exceptions.h>
29 
30 // Not used:
31 // - EXCANNON.VOC (same as EXSMALL.VOC)
32 // - DROPEQ2P.VOC
33 // - POPPA.VOC
34 
SFXManager()35 SFXManager::SFXManager() {
36     // load voice and language specific sounds
37     if(settings.general.language == "de") {
38         loadNonEnglishVoice("G");
39     } else if(settings.general.language == "fr") {
40         loadNonEnglishVoice("F");
41     } else {
42         loadEnglishVoice();
43     }
44 
45     for(int i = 0; i < NUM_SOUNDCHUNK; i++) {
46         if(soundChunk[i] == nullptr) {
47             THROW(std::runtime_error, "Not all sounds could be loaded: soundChunk[%d] == nullptr!", i);
48         }
49     }
50 }
51 
~SFXManager()52 SFXManager::~SFXManager() {
53     // unload voice
54     for(int i = 0; i < numLngVoice; i++) {
55         if(lngVoice[i] != nullptr) {
56             Mix_FreeChunk(lngVoice[i]);
57             lngVoice[i] = nullptr;
58         }
59     }
60 
61     free(lngVoice);
62 
63     // unload sound
64     for(int i = 0; i < NUM_SOUNDCHUNK; i++) {
65         if(soundChunk[i] != nullptr) {
66             Mix_FreeChunk(soundChunk[i]);
67             soundChunk[i] = nullptr;
68         }
69     }
70 }
71 
getVoice(Voice_enum id,int house)72 Mix_Chunk* SFXManager::getVoice(Voice_enum id, int house) {
73     if(settings.general.language == "de" || settings.general.language == "fr") {
74         return getNonEnglishVoice(id,house);
75     } else {
76         return getEnglishVoice(id,house);
77     }
78 }
79 
getSound(Sound_enum id)80 Mix_Chunk* SFXManager::getSound(Sound_enum id) {
81     if(id >= NUM_SOUNDCHUNK)
82         return nullptr;
83 
84     return soundChunk[id];
85 }
86 
loadMixFromADL(const std::string & adlFile,int index,int volume)87 Mix_Chunk* SFXManager::loadMixFromADL(const std::string& adlFile, int index, int volume) {
88 
89     SDL_RWops* rwop = pFileManager->openFile(adlFile);
90     SoundAdlibPC *pSoundAdlibPC = new SoundAdlibPC(rwop, AUDIO_FREQUENCY);
91     pSoundAdlibPC->setVolume(volume);
92     Mix_Chunk* chunk = pSoundAdlibPC->getSubsong(index);
93     delete pSoundAdlibPC;
94     SDL_RWclose(rwop);
95 
96     return chunk;
97 }
98 
loadEnglishVoice()99 void SFXManager::loadEnglishVoice() {
100     numLngVoice = NUM_VOICE*NUM_HOUSES;
101 
102     if((lngVoice = (Mix_Chunk**) malloc(sizeof(Mix_Chunk*) * numLngVoice)) == nullptr) {
103         THROW(std::runtime_error, "Cannot allocate memory");
104     }
105 
106     for(int i = 0; i < numLngVoice; i++) {
107         lngVoice[i] = nullptr;
108     }
109 
110     // now we can load
111     for(int house = 0; house < NUM_HOUSES; house++) {
112         Mix_Chunk* HouseNameChunk = nullptr;
113 
114         std::string HouseString;
115         int VoiceNum = house;
116         switch(house) {
117             case HOUSE_HARKONNEN:
118                 HouseString = "H";
119                 HouseNameChunk = getChunkFromFile(HouseString + "HARK.VOC");
120                 break;
121             case HOUSE_ATREIDES:
122                 HouseString = "A";
123                 HouseNameChunk = getChunkFromFile(HouseString + "ATRE.VOC");
124                 break;
125             case HOUSE_ORDOS:
126                 HouseString = "O";
127                 HouseNameChunk = getChunkFromFile(HouseString + "ORDOS.VOC");
128                 break;
129             case HOUSE_FREMEN:
130                 HouseString = "A";
131                 HouseNameChunk = getChunkFromFile(HouseString + "FREMEN.VOC");
132                 break;
133             case HOUSE_SARDAUKAR:
134                 HouseString = "H";
135                 HouseNameChunk = getChunkFromFile(HouseString + "SARD.VOC");
136                 break;
137             case HOUSE_MERCENARY:
138                 HouseString = "O";
139                 HouseNameChunk = getChunkFromFile(HouseString + "MERC.VOC");
140                 break;
141         }
142 
143         // "... Harvester deployed", "... Unit deployed" and "... Unit launched"
144         Mix_Chunk* Harvester = getChunkFromFile(HouseString + "HARVEST.VOC");
145         Mix_Chunk* Unit = getChunkFromFile(HouseString + "UNIT.VOC");
146         Mix_Chunk* Deployed = getChunkFromFile(HouseString + "DEPLOY.VOC");
147         Mix_Chunk* Launched = getChunkFromFile(HouseString + "LAUNCH.VOC");
148         lngVoice[HarvesterDeployed*NUM_HOUSES+VoiceNum] = concat3Chunks(HouseNameChunk, Harvester, Deployed);
149         lngVoice[UnitDeployed*NUM_HOUSES+VoiceNum] = concat3Chunks(HouseNameChunk, Unit, Deployed);
150         lngVoice[UnitLaunched*NUM_HOUSES+VoiceNum] = concat3Chunks(HouseNameChunk, Unit, Launched);
151         Mix_FreeChunk(Harvester);
152         Mix_FreeChunk(Unit);
153         Mix_FreeChunk(Deployed);
154         Mix_FreeChunk(Launched);
155 
156         // "Contruction complete"
157         lngVoice[ConstructionComplete*NUM_HOUSES+VoiceNum] = getChunkFromFile(HouseString + "CONST.VOC");
158 
159         // "Vehicle repaired"
160         Mix_Chunk* Vehicle = getChunkFromFile(HouseString + "VEHICLE.VOC");
161         Mix_Chunk* Repaired = getChunkFromFile(HouseString + "REPAIR.VOC");
162         lngVoice[VehicleRepaired*NUM_HOUSES+VoiceNum] = concat2Chunks(Vehicle, Repaired);
163         Mix_FreeChunk(Vehicle);
164         Mix_FreeChunk(Repaired);
165 
166         // "Frigate has arrived"
167         Mix_Chunk* FrigateChunk = getChunkFromFile(HouseString + "FRIGATE.VOC");
168         Mix_Chunk* HasArrivedChunk = getChunkFromFile(HouseString + "ARRIVE.VOC");
169         lngVoice[FrigateHasArrived*NUM_HOUSES+VoiceNum] = concat2Chunks(FrigateChunk, HasArrivedChunk);
170         Mix_FreeChunk(FrigateChunk);
171         Mix_FreeChunk(HasArrivedChunk);
172 
173         // "Your mission is complete"
174         lngVoice[YourMissionIsComplete*NUM_HOUSES+VoiceNum] = getChunkFromFile(HouseString + "WIN.VOC");
175 
176         // "You have failed your mission"
177         lngVoice[YouHaveFailedYourMission*NUM_HOUSES+VoiceNum] = getChunkFromFile(HouseString + "LOSE.VOC");
178 
179         // "Radar activated"/"Radar deactivated"
180         Mix_Chunk* RadarChunk = getChunkFromFile(HouseString + "RADAR.VOC");
181         Mix_Chunk* RadarActivatedChunk = getChunkFromFile(HouseString + "ON.VOC");
182         Mix_Chunk* RadarDeactivatedChunk = getChunkFromFile(HouseString + "OFF.VOC");
183         lngVoice[RadarActivated*NUM_HOUSES+VoiceNum] = concat2Chunks(RadarChunk, RadarActivatedChunk);
184         lngVoice[RadarDeactivated*NUM_HOUSES+VoiceNum] = concat2Chunks(RadarChunk, RadarDeactivatedChunk);
185         Mix_FreeChunk(RadarChunk);
186         Mix_FreeChunk(RadarActivatedChunk);
187         Mix_FreeChunk(RadarDeactivatedChunk);
188 
189         // "Bloom located"
190         Mix_Chunk* Bloom = getChunkFromFile(HouseString + "BLOOM.VOC");
191         Mix_Chunk* Located = getChunkFromFile(HouseString + "LOCATED.VOC");
192         lngVoice[BloomLocated*NUM_HOUSES+VoiceNum] = concat2Chunks(Bloom, Located);
193         Mix_FreeChunk(Bloom);
194         Mix_FreeChunk(Located);
195 
196         // "Warning Wormsign"
197         Mix_Chunk* WarningChunk = getChunkFromFile(HouseString + "WARNING.VOC");
198         Mix_Chunk* WormSignChunk = getChunkFromFile(HouseString + "WORMY.VOC");
199         lngVoice[WarningWormSign*NUM_HOUSES+VoiceNum] = concat2Chunks(WarningChunk, WormSignChunk);
200         Mix_FreeChunk(WarningChunk);
201         Mix_FreeChunk(WormSignChunk);
202 
203         // "Our base is under attack"
204         lngVoice[BaseIsUnderAttack*NUM_HOUSES+VoiceNum] = getChunkFromFile(HouseString + "ATTACK.VOC");
205 
206         // "Saboteur approaching" and "Missile approaching"
207         Mix_Chunk* SabotChunk = getChunkFromFile(HouseString + "SABOT.VOC");
208         Mix_Chunk* MissileChunk = getChunkFromFile(HouseString + "MISSILE.VOC");
209         Mix_Chunk* ApproachingChunk = getChunkFromFile(HouseString + "APPRCH.VOC");
210         lngVoice[SaboteurApproaching*NUM_HOUSES+VoiceNum] = concat2Chunks(SabotChunk, ApproachingChunk);
211         lngVoice[MissileApproaching*NUM_HOUSES+VoiceNum] = concat2Chunks(MissileChunk, ApproachingChunk);
212         Mix_FreeChunk(SabotChunk);
213         Mix_FreeChunk(MissileChunk);
214         Mix_FreeChunk(ApproachingChunk);
215 
216         Mix_FreeChunk(HouseNameChunk);
217 
218         // "Yes Sir"
219         lngVoice[YesSir*NUM_HOUSES+VoiceNum] = getChunkFromFile("ZREPORT1.VOC", "REPORT1.VOC");
220 
221         // "Reporting"
222         lngVoice[Reporting*NUM_HOUSES+VoiceNum] = getChunkFromFile("ZREPORT2.VOC", "REPORT2.VOC");
223 
224         // "Acknowledged"
225         lngVoice[Acknowledged*NUM_HOUSES+VoiceNum] = getChunkFromFile("ZREPORT3.VOC", "REPORT3.VOC");
226 
227         // "Affirmative"
228         lngVoice[Affirmative*NUM_HOUSES+VoiceNum] = getChunkFromFile("ZAFFIRM.VOC", "AFFIRM.VOC");
229 
230         // "Moving out"
231         lngVoice[MovingOut*NUM_HOUSES+VoiceNum] = getChunkFromFile("ZMOVEOUT.VOC", "MOVEOUT.VOC");
232 
233         // "Infantry out"
234         lngVoice[InfantryOut*NUM_HOUSES+VoiceNum] = getChunkFromFile("ZOVEROUT.VOC", "OVEROUT.VOC");
235 
236         // "Somthing's under the sand"
237         lngVoice[SomethingUnderTheSand*NUM_HOUSES+VoiceNum] = getChunkFromFile("SANDBUG.VOC");
238 
239         // "House Harkonnen"
240         lngVoice[HouseHarkonnen*NUM_HOUSES+VoiceNum] = getChunkFromFile("MHARK.VOC");
241 
242         // "House Atreides"
243         lngVoice[HouseAtreides*NUM_HOUSES+VoiceNum] = getChunkFromFile("MATRE.VOC");
244 
245         // "House Ordos"
246         lngVoice[HouseOrdos*NUM_HOUSES+VoiceNum] = getChunkFromFile("MORDOS.VOC");
247     }
248 
249     for(int i = 0; i < numLngVoice; i++) {
250         if(lngVoice[i] == nullptr) {
251             THROW(std::runtime_error, "Not all voice sounds could be loaded: lngVoice[%d] == nullptr!", i);
252         }
253     }
254 
255     // Sfx
256     soundChunk[Sound_PlaceStructure] = getChunkFromFile("EXDUD.VOC");
257     soundChunk[Sound_ButtonClick] = getChunkFromFile("BUTTON.VOC");
258     soundChunk[Sound_InvalidAction] = loadMixFromADL("DUNE1.ADL", 47);
259     soundChunk[Sound_CreditsTick] = loadMixFromADL("DUNE1.ADL", 52, 4*MIX_MAX_VOLUME);
260     soundChunk[Sound_Tick] = loadMixFromADL("DUNE1.ADL", 38);
261     soundChunk[Sound_RadarNoise] = getChunkFromFile("STATICP.VOC");
262     soundChunk[Sound_ExplosionGas] = getChunkFromFile("EXGAS.VOC");
263     soundChunk[Sound_ExplosionTiny] = getChunkFromFile("EXTINY.VOC");
264     soundChunk[Sound_ExplosionSmall] = getChunkFromFile("EXSMALL.VOC");
265     soundChunk[Sound_ExplosionMedium] = getChunkFromFile("EXMED.VOC");
266     soundChunk[Sound_ExplosionLarge] = getChunkFromFile("EXLARGE.VOC");
267     soundChunk[Sound_ExplosionStructure] = getChunkFromFile("CRUMBLE.VOC");
268     soundChunk[Sound_WormAttack] = getChunkFromFile("WORMET3P.VOC");
269     soundChunk[Sound_Gun] = getChunkFromFile("GUN.VOC");
270     soundChunk[Sound_Rocket] = getChunkFromFile("ROCKET.VOC");
271     soundChunk[Sound_Bloom] = getChunkFromFile("EXSAND.VOC");
272     soundChunk[Sound_Scream1] = getChunkFromFile("VSCREAM1.VOC");
273     soundChunk[Sound_Scream2] = getChunkFromFile("VSCREAM2.VOC");
274     soundChunk[Sound_Scream3] = getChunkFromFile("VSCREAM3.VOC");
275     soundChunk[Sound_Scream4] = getChunkFromFile("VSCREAM4.VOC");
276     soundChunk[Sound_Scream5] = getChunkFromFile("VSCREAM5.VOC");
277     soundChunk[Sound_Trumpet] = loadMixFromADL("DUNE1.ADL", 30);
278     soundChunk[Sound_Drop] = loadMixFromADL("DUNE1.ADL", 24);
279     soundChunk[Sound_Squashed] = getChunkFromFile("SQUISH2.VOC");
280     soundChunk[Sound_MachineGun] = getChunkFromFile("GUNMULTI.VOC");
281     soundChunk[Sound_Sonic] = loadMixFromADL("DUNE1.ADL", 43);
282     soundChunk[Sound_RocketSmall] = getChunkFromFile("MISLTINP.VOC");
283 }
284 
285 
getEnglishVoice(Voice_enum id,int house)286 Mix_Chunk* SFXManager::getEnglishVoice(Voice_enum id, int house) {
287     if((int) id >= numLngVoice)
288         return nullptr;
289 
290     return lngVoice[id*NUM_HOUSES + house];
291 }
292 
loadNonEnglishVoice(const std::string & languagePrefix)293 void SFXManager::loadNonEnglishVoice(const std::string& languagePrefix) {
294     numLngVoice = NUM_VOICE;
295 
296     if((lngVoice = (Mix_Chunk**) malloc(sizeof(Mix_Chunk*) * NUM_VOICE)) == nullptr) {
297         THROW(std::runtime_error, "Cannot allocate memory");
298     }
299 
300     for(int i = 0; i < NUM_VOICE; i++) {
301         lngVoice[i] = nullptr;
302     }
303 
304     // "Harvester deployed"
305     lngVoice[HarvesterDeployed] = getChunkFromFile(languagePrefix + "HARVEST.VOC");
306 
307     // "Unit deployed"
308     lngVoice[UnitDeployed] = getChunkFromFile(languagePrefix + "DEPLOY.VOC");
309 
310     // "Unit launched"
311     lngVoice[UnitLaunched] = getChunkFromFile(languagePrefix + "LAUNCH.VOC");
312 
313     // "Contruction complete"
314     lngVoice[ConstructionComplete] = getChunkFromFile(languagePrefix + "CONST.VOC");
315 
316     // "Vehicle repaired"
317     lngVoice[VehicleRepaired] = getChunkFromFile(languagePrefix + "REPAIR.VOC");
318 
319     // "Frigate has arrived"
320     lngVoice[FrigateHasArrived] = getChunkFromFile(languagePrefix + "FRIGATE.VOC");
321 
322     // "Your mission is complete" (No non-english voc available)
323     lngVoice[YourMissionIsComplete] = createEmptyChunk();
324 
325     // "You have failed your mission" (No non-english voc available)
326     lngVoice[YouHaveFailedYourMission] = createEmptyChunk();
327 
328     // "Radar activated"/"Radar deactivated"
329     lngVoice[RadarActivated] = getChunkFromFile(languagePrefix + "ON.VOC");
330     lngVoice[RadarDeactivated] = getChunkFromFile(languagePrefix + "OFF.VOC");
331 
332     // "Bloom located"
333     lngVoice[BloomLocated] = getChunkFromFile(languagePrefix + "BLOOM.VOC");
334 
335     // "Warning Wormsign"
336     if(pFileManager->exists(languagePrefix + "WORMY.VOC")) {
337         Mix_Chunk* WarningChunk = getChunkFromFile(languagePrefix + "WARNING.VOC");
338         Mix_Chunk* WormSignChunk = getChunkFromFile(languagePrefix + "WORMY.VOC");
339         lngVoice[WarningWormSign] = concat2Chunks(WarningChunk, WormSignChunk);
340         Mix_FreeChunk(WarningChunk);
341         Mix_FreeChunk(WormSignChunk);
342     } else {
343         lngVoice[WarningWormSign] = getChunkFromFile(languagePrefix + "WARNING.VOC");
344     }
345 
346     // "Our base is under attack"
347     lngVoice[BaseIsUnderAttack] = getChunkFromFile(languagePrefix + "ATTACK.VOC");
348 
349     // "Saboteur approaching"
350     lngVoice[SaboteurApproaching] = getChunkFromFile(languagePrefix + "SABOT.VOC");
351 
352     // "Missile approaching"
353     lngVoice[MissileApproaching] = getChunkFromFile(languagePrefix + "MISSILE.VOC");
354 
355         // "Yes Sir"
356     lngVoice[YesSir] = getChunkFromFile(languagePrefix + "REPORT1.VOC");
357 
358     // "Reporting"
359     lngVoice[Reporting] = getChunkFromFile(languagePrefix + "REPORT2.VOC");
360 
361     // "Acknowledged"
362     lngVoice[Acknowledged] = getChunkFromFile(languagePrefix + "REPORT3.VOC");
363 
364     // "Affirmative"
365     lngVoice[Affirmative] = getChunkFromFile(languagePrefix + "AFFIRM.VOC");
366 
367     // "Moving out"
368     lngVoice[MovingOut] = getChunkFromFile(languagePrefix + "MOVEOUT.VOC");
369 
370     // "Infantry out"
371     lngVoice[InfantryOut] = getChunkFromFile(languagePrefix + "OVEROUT.VOC");
372 
373     // "Somthing's under the sand"
374     lngVoice[SomethingUnderTheSand] = getChunkFromFile("SANDBUG.VOC");
375 
376     // "House Atreides"
377     lngVoice[HouseAtreides] = getChunkFromFile(languagePrefix + "ATRE.VOC");
378 
379     // "House Ordos"
380     lngVoice[HouseOrdos] = getChunkFromFile(languagePrefix + "ORDOS.VOC");
381 
382     // "House Harkonnen"
383     lngVoice[HouseHarkonnen] = getChunkFromFile(languagePrefix + "HARK.VOC");
384 
385     for(int i = 0; i < numLngVoice; i++) {
386         if(lngVoice[i] == nullptr) {
387             THROW(std::runtime_error, "Not all voice sounds could be loaded: lngVoice[%d] == nullptr!", i);
388         }
389     }
390 
391     // Sfx
392     soundChunk[Sound_PlaceStructure] = getChunkFromFile("EXDUD.VOC");
393     soundChunk[Sound_ButtonClick] = getChunkFromFile("BUTTON.VOC");
394     soundChunk[Sound_InvalidAction] = loadMixFromADL("DUNE1.ADL", 47);
395     soundChunk[Sound_CreditsTick] = loadMixFromADL("DUNE1.ADL", 52, 4*MIX_MAX_VOLUME);
396     soundChunk[Sound_Tick] = loadMixFromADL("DUNE1.ADL", 38);
397     soundChunk[Sound_RadarNoise] = getChunkFromFile("STATICP.VOC");
398     soundChunk[Sound_ExplosionGas] = getChunkFromFile("EXGAS.VOC");
399     soundChunk[Sound_ExplosionTiny] = getChunkFromFile("EXTINY.VOC");
400     soundChunk[Sound_ExplosionSmall] = getChunkFromFile("EXSMALL.VOC");
401     soundChunk[Sound_ExplosionMedium] = getChunkFromFile("EXMED.VOC");
402     soundChunk[Sound_ExplosionLarge] = getChunkFromFile("EXLARGE.VOC");
403     soundChunk[Sound_ExplosionStructure] = getChunkFromFile("CRUMBLE.VOC");
404     soundChunk[Sound_WormAttack] = getChunkFromFile("WORMET3P.VOC");
405     soundChunk[Sound_Gun] = getChunkFromFile("GUN.VOC");
406     soundChunk[Sound_Rocket] = getChunkFromFile("ROCKET.VOC");
407     soundChunk[Sound_Bloom] = getChunkFromFile("EXSAND.VOC");
408     soundChunk[Sound_Scream1] = getChunkFromFile("VSCREAM1.VOC");
409     soundChunk[Sound_Scream2] = getChunkFromFile("VSCREAM2.VOC");
410     soundChunk[Sound_Scream3] = getChunkFromFile("VSCREAM3.VOC");
411     soundChunk[Sound_Scream4] = getChunkFromFile("VSCREAM4.VOC");
412     soundChunk[Sound_Scream5] = getChunkFromFile("VSCREAM5.VOC");
413     soundChunk[Sound_Trumpet] = loadMixFromADL("DUNE1.ADL", 30);
414     soundChunk[Sound_Drop] = loadMixFromADL("DUNE1.ADL", 24);
415     soundChunk[Sound_Squashed] = getChunkFromFile("SQUISH2.VOC");
416     soundChunk[Sound_MachineGun] = getChunkFromFile("GUNMULTI.VOC");
417     soundChunk[Sound_Sonic] = loadMixFromADL("DUNE1.ADL", 43);
418     soundChunk[Sound_RocketSmall] = getChunkFromFile("MISLTINP.VOC");
419 }
420 
getNonEnglishVoice(Voice_enum id,int house)421 Mix_Chunk* SFXManager::getNonEnglishVoice(Voice_enum id, int house) {
422     if((int)id >= numLngVoice)
423         return nullptr;
424 
425     return lngVoice[id];
426 }
427