1 /* ScummVM - Graphic Adventure Engine
2  *
3  * ScummVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the COPYRIGHT
5  * file distributed with this source distribution.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  *
21  */
22 
23 #include "audio/decoders/raw.h"
24 #include "audio/decoders/voc.h"
25 #include "backends/audiocd/audiocd.h"
26 #include "common/config-manager.h"
27 #include "xeen/sound.h"
28 #include "xeen/sound_driver_adlib.h"
29 #include "xeen/xeen.h"
30 
31 namespace Xeen {
32 
Sound(Audio::Mixer * mixer)33 Sound::Sound(Audio::Mixer *mixer) : _mixer(mixer), _fxOn(true), _musicOn(true), _subtitles(false),
34 		_songData(nullptr), _effectsData(nullptr), _musicSide(0), _musicPercent(100),
35 		_musicVolume(0), _sfxVolume(0) {
36 	_SoundDriver = new SoundDriverAdlib();
37 	if (g_vm->getIsCD())
38 		g_system->getAudioCDManager()->open();
39 }
40 
~Sound()41 Sound::~Sound() {
42 	stopAllAudio();
43 	if (g_vm->getIsCD())
44 		g_system->getAudioCDManager()->close();
45 
46 	delete _SoundDriver;
47 	delete[] _effectsData;
48 	delete[] _songData;
49 }
50 
playSound(Common::SeekableReadStream & s,int unused)51 void Sound::playSound(Common::SeekableReadStream &s, int unused) {
52 	stopSound();
53 	if (!_fxOn)
54 		return;
55 
56 	s.seek(0);
57 	Common::SeekableReadStream *srcStream = s.readStream(s.size());
58 	Audio::SeekableAudioStream *stream = Audio::makeVOCStream(srcStream,
59 		Audio::FLAG_UNSIGNED, DisposeAfterUse::YES);
60 	_mixer->playStream(Audio::Mixer::kSFXSoundType, &_soundHandle, stream);
61 }
62 
playSound(const Common::String & name,int unused)63 void Sound::playSound(const Common::String &name, int unused) {
64 	File f;
65 	if (!f.open(name))
66 		error("Could not open sound - %s", name.c_str());
67 
68 	playSound(f);
69 }
70 
playSound(const Common::String & name,int ccNum,int unused)71 void Sound::playSound(const Common::String &name, int ccNum, int unused) {
72 	File f;
73 	if (!f.open(name, ccNum))
74 		error("Could not open sound - %s", name.c_str());
75 
76 	playSound(f);
77 }
78 
playVoice(const Common::String & name,int ccMode)79 void Sound::playVoice(const Common::String &name, int ccMode) {
80 	File f;
81 	bool result = (ccMode == -1) ? f.open(name) : f.open(name, ccMode);
82 	if (!result)
83 		error("Could not open sound - %s", name.c_str());
84 
85 	stopSound();
86 
87 	Common::SeekableReadStream *srcStream = f.readStream(f.size());
88 	Audio::SeekableAudioStream *stream = Audio::makeVOCStream(srcStream,
89 		Audio::FLAG_UNSIGNED, DisposeAfterUse::YES);
90 	_mixer->playStream(Audio::Mixer::kSpeechSoundType, &_soundHandle, stream);
91 }
92 
stopSound()93 void Sound::stopSound() {
94 	_mixer->stopHandle(_soundHandle);
95 }
96 
isSoundPlaying() const97 bool Sound::isSoundPlaying() const {
98 	return _mixer->isSoundHandleActive(_soundHandle);
99 }
100 
stopAllAudio()101 void Sound::stopAllAudio() {
102 	stopSong();
103 	stopFX();
104 	stopSound();
105 	setMusicPercent(100);
106 }
107 
setFxOn(bool isOn)108 void Sound::setFxOn(bool isOn) {
109 	ConfMan.setBool("sfx_mute", !isOn);
110 	if (isOn)
111 		ConfMan.setBool("mute", false);
112 
113 	g_vm->syncSoundSettings();
114 }
115 
loadEffectsData()116 void Sound::loadEffectsData() {
117 	// Stop any prior FX
118 	stopFX();
119 
120 	if (!_effectsData) {
121 		// Load in an entire driver so we have quick access to the effects data that's hardcoded within it
122 		File file("blastmus");
123 		byte *effectsData = new byte[file.size()];
124 		file.seek(0);
125 		file.read(effectsData, file.size());
126 		file.close();
127 		_effectsData = effectsData;
128 
129 		// Locate the playFX routine
130 		const byte *fx = effectsData + READ_LE_UINT16(effectsData + 10) + 12;
131 		assert(READ_BE_UINT16(fx + 28) == 0x81FB);
132 		uint numEffects = READ_LE_UINT16(fx + 30);
133 
134 		assert(READ_BE_UINT16(fx + 36) == 0x8B87);
135 		const byte *table = effectsData + READ_LE_UINT16(fx + 38);
136 
137 		// Extract the effects offsets
138 		_effectsOffsets.resize(numEffects);
139 		for (uint idx = 0; idx < numEffects; ++idx)
140 			_effectsOffsets[idx] = READ_LE_UINT16(&table[idx * 2]);
141 	}
142 }
143 
playFX(uint effectId)144 void Sound::playFX(uint effectId) {
145 	stopFX();
146 	loadEffectsData();
147 
148 	if (effectId < _effectsOffsets.size()) {
149 		const byte *dataP = &_effectsData[_effectsOffsets[effectId]];
150 		_SoundDriver->playFX(effectId, dataP);
151 	}
152 }
153 
stopFX()154 void Sound::stopFX() {
155 	_SoundDriver->stopFX();
156 }
157 
songCommand(uint commandId,byte musicVolume,byte sfxVolume)158 int Sound::songCommand(uint commandId, byte musicVolume, byte sfxVolume) {
159 	int result = _SoundDriver->songCommand(commandId, musicVolume, sfxVolume);
160 	if (commandId == STOP_SONG) {
161 		delete[] _songData;
162 		_songData = nullptr;
163 	}
164 
165 	return result;
166 }
167 
playSong(Common::SeekableReadStream & stream)168 void Sound::playSong(Common::SeekableReadStream &stream) {
169 	stopSong();
170 	if (!_musicOn)
171 		return;
172 
173 	byte *songData = new byte[stream.size()];
174 	stream.seek(0);
175 	stream.read(songData, stream.size());
176 	_songData = songData;
177 
178 	_SoundDriver->playSong(_songData);
179 }
180 
playSong(const Common::String & name,int param)181 void Sound::playSong(const Common::String &name, int param) {
182 	_priorMusic = _currentMusic;
183 	_currentMusic = name;
184 
185 	Common::File mf;
186 	if (mf.open(name)) {
187 		playSong(mf);
188 	} else {
189 		File f(name, _musicSide);
190 		playSong(f);
191 	}
192 }
193 
setMusicOn(bool isOn)194 void Sound::setMusicOn(bool isOn) {
195 	ConfMan.setBool("music_mute", !isOn);
196 	if (isOn)
197 		ConfMan.setBool("mute", false);
198 
199 	g_vm->syncSoundSettings();
200 }
201 
isMusicPlaying() const202 bool Sound::isMusicPlaying() const {
203 	return _SoundDriver->isPlaying();
204 }
205 
setMusicPercent(byte percent)206 void Sound::setMusicPercent(byte percent) {
207 	assert(percent <= 100);
208 	_musicPercent = percent;
209 
210 	updateVolume();
211 }
212 
updateSoundSettings()213 void Sound::updateSoundSettings() {
214 	_fxOn = !ConfMan.getBool("sfx_mute");
215 	if (!_fxOn)
216 		stopFX();
217 
218 	_musicOn = !ConfMan.getBool("music_mute");
219 	if (!_musicOn)
220 		stopSong();
221 
222 	_subtitles = ConfMan.hasKey("subtitles") ? ConfMan.getBool("subtitles") : true;
223 	_musicVolume = CLIP(ConfMan.getInt("music_volume"), 0, 255);
224 	_sfxVolume = CLIP(ConfMan.getInt("sfx_volume"), 0, 255);
225 	updateVolume();
226 }
227 
updateVolume()228 void Sound::updateVolume() {
229 	songCommand(SET_VOLUME, _musicPercent * _musicVolume / 100, _sfxVolume);
230 }
231 
232 } // End of namespace Xeen
233