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 "agi/agi.h"
24 
25 #include "agi/sound_2gs.h"
26 #include "agi/sound_coco3.h"
27 #include "agi/sound_midi.h"
28 #include "agi/sound_sarien.h"
29 #include "agi/sound_pcjr.h"
30 
31 #include "common/textconsole.h"
32 #include "audio/mixer.h"
33 
34 namespace Agi {
35 
SoundGen(AgiBase * vm,Audio::Mixer * pMixer)36 SoundGen::SoundGen(AgiBase *vm, Audio::Mixer *pMixer) : _vm(vm), _mixer(pMixer) {
37 	_sampleRate = pMixer->getOutputRate();
38 	_soundHandle = new Audio::SoundHandle();
39 }
40 
~SoundGen()41 SoundGen::~SoundGen() {
42 	delete _soundHandle;
43 }
44 
45 //
46 // TODO: add support for variable sampling rate in the output device
47 //
48 
createFromRawResource(uint8 * data,uint32 len,int resnum,int soundemu)49 AgiSound *AgiSound::createFromRawResource(uint8 *data, uint32 len, int resnum, int soundemu) {
50 	if (data == NULL || len < 2) // Check for too small resource or no resource at all
51 		return NULL;
52 	uint16 type = READ_LE_UINT16(data);
53 
54 	// For V1 sound resources
55 	if (type != AGI_SOUND_SAMPLE && (type & 0xFF) == 0x01)
56 		return new PCjrSound(data, len, resnum);
57 
58 	switch (type) { // Create a sound object based on the type
59 	case AGI_SOUND_SAMPLE:
60 		return new IIgsSample(data, len, resnum);
61 	case AGI_SOUND_MIDI:
62 		return new IIgsMidi(data, len, resnum);
63 	case AGI_SOUND_4CHN:
64 		if (soundemu == SOUND_EMU_MIDI) {
65 			return new MIDISound(data, len, resnum);
66 		} else {
67 			return new PCjrSound(data, len, resnum);
68 		}
69 	}
70 
71 	warning("Sound resource (%d) has unknown type (0x%04x). Not using the sound", resnum, type);
72 	return NULL;
73 }
74 
PCjrSound(uint8 * data,uint32 len,int resnum)75 PCjrSound::PCjrSound(uint8 *data, uint32 len, int resnum) : AgiSound() {
76 	_data = data; // Save the resource pointer
77 	_len  = len;  // Save the resource's length
78 	_type = READ_LE_UINT16(data); // Read sound resource's type
79 
80 	// Detect V1 sound resources
81 	if ((_type & 0xFF) == 0x01)
82 		_type = AGI_SOUND_4CHN;
83 
84 	_isValid = (_type == AGI_SOUND_4CHN) && (_data != NULL) && (_len >= 2);
85 
86 	if (!_isValid) // Check for errors
87 		warning("Error creating PCjr 4-channel sound from resource %d (Type %d, length %d)", resnum, _type, len);
88 }
89 
getVoicePointer(uint voiceNum)90 const uint8 *PCjrSound::getVoicePointer(uint voiceNum) {
91 	assert(voiceNum < 4);
92 	uint16 voiceStartOffset = READ_LE_UINT16(_data + voiceNum * 2);
93 
94 	return _data + voiceStartOffset;
95 }
96 
97 #if 0
98 static const uint16 period[] = {
99 	1024, 1085, 1149, 1218, 1290, 1367,
100 	1448, 1534, 1625, 1722, 1825, 1933
101 };
102 
103 static int noteToPeriod(int note) {
104 	return 10 * (period[note % 12] >> (note / 12 - 3));
105 }
106 #endif
107 
unloadSound(int resnum)108 void SoundMgr::unloadSound(int resnum) {
109 	if (_vm->_game.dirSound[resnum].flags & RES_LOADED) {
110 		if (_vm->_game.sounds[resnum]->isPlaying()) {
111 			_vm->_game.sounds[resnum]->stop();
112 		}
113 
114 		// Release the sound resource's data
115 		delete _vm->_game.sounds[resnum];
116 		_vm->_game.sounds[resnum] = NULL;
117 		_vm->_game.dirSound[resnum].flags &= ~RES_LOADED;
118 	}
119 }
120 
121 /**
122  * Start playing a sound resource. The logic here is that when the sound is
123  * finished we set the given flag to be true. This way the condition can be
124  * detected by the game. On the other hand, if the game wishes to start
125  * playing a new sound before the current one is finished, we also let it
126  * do that.
127  * @param resnum  the sound resource number
128  * @param flag    the flag that is wished to be set true when finished
129  */
startSound(int resnum,int flag)130 void SoundMgr::startSound(int resnum, int flag) {
131 	debugC(3, kDebugLevelSound, "startSound(resnum = %d, flag = %d)", resnum, flag);
132 
133 	if (_vm->_game.sounds[resnum] == NULL) // Is this needed at all?
134 		return;
135 
136 	stopSound();
137 
138 	AgiSoundEmuType type = (AgiSoundEmuType)_vm->_game.sounds[resnum]->type();
139 	if (type != AGI_SOUND_SAMPLE && type != AGI_SOUND_MIDI && type != AGI_SOUND_4CHN)
140 		return;
141 	debugC(3, kDebugLevelSound, "    type = %d", type);
142 
143 	_vm->_game.sounds[resnum]->play();
144 	_playingSound = resnum;
145 	_soundGen->play(resnum);
146 
147 	// Reset the flag
148 	_endflag = flag;
149 
150 	if (_vm->getVersion() < 0x2000) {
151 		_vm->_game.vars[_endflag] = 0;
152 	} else {
153 		_vm->setFlag(_endflag, false);
154 	}
155 }
156 
stopSound()157 void SoundMgr::stopSound() {
158 	debugC(3, kDebugLevelSound, "stopSound() --> %d", _playingSound);
159 
160 	if (_playingSound != -1) {
161 		if (_vm->_game.sounds[_playingSound]) // sanity checking
162 			_vm->_game.sounds[_playingSound]->stop();
163 		_soundGen->stop();
164 		_playingSound = -1;
165 	}
166 
167 	// This is needed all the time, some games wait until music got played and when a sound/music got stopped early
168 	// it would otherwise block the game (for example Death Angel jingle in back door poker room in Police Quest 1, room 71)
169 	if (_endflag != -1) {
170 		if (_vm->getVersion() < 0x2000) {
171 			_vm->_game.vars[_endflag] = 1;
172 		} else {
173 			_vm->setFlag(_endflag, true);
174 		}
175 	}
176 
177 	_endflag = -1;
178 }
179 
soundIsFinished()180 void SoundMgr::soundIsFinished() {
181 	if (_endflag != -1)
182 		_vm->setFlag(_endflag, true);
183 
184 	if (_playingSound != -1)
185 		_vm->_game.sounds[_playingSound]->stop();
186 	_playingSound = -1;
187 	_endflag = -1;
188 }
189 
SoundMgr(AgiBase * agi,Audio::Mixer * pMixer)190 SoundMgr::SoundMgr(AgiBase *agi, Audio::Mixer *pMixer) {
191 	_vm = agi;
192 	_endflag = -1;
193 	_playingSound = -1;
194 
195 	switch (_vm->_soundemu) {
196 	default:
197 	case SOUND_EMU_NONE:
198 	case SOUND_EMU_AMIGA:
199 	case SOUND_EMU_MAC:
200 	case SOUND_EMU_PC:
201 		_soundGen = new SoundGenSarien(_vm, pMixer);
202 		break;
203 	case SOUND_EMU_PCJR:
204 		_soundGen = new SoundGenPCJr(_vm, pMixer);
205 		break;
206 	case SOUND_EMU_APPLE2GS:
207 		_soundGen = new SoundGen2GS(_vm, pMixer);
208 		break;
209 	case SOUND_EMU_COCO3:
210 		_soundGen = new SoundGenCoCo3(_vm, pMixer);
211 		break;
212 	case SOUND_EMU_MIDI:
213 		_soundGen = new SoundGenMIDI(_vm, pMixer);
214 		break;
215 	}
216 }
217 
setVolume(uint8 volume)218 void SoundMgr::setVolume(uint8 volume) {
219 	// TODO
220 }
221 
~SoundMgr()222 SoundMgr::~SoundMgr() {
223 	stopSound();
224 
225 	delete _soundGen;
226 }
227 
228 } // End of namespace Agi
229