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 "ultima/ultima8/misc/pent_include.h"
24 
25 #include "ultima/ultima8/audio/music_flex.h"
26 #include "common/memstream.h"
27 
28 namespace Ultima {
29 namespace Ultima8 {
30 
MusicFlex(Common::SeekableReadStream * rs)31 MusicFlex::MusicFlex(Common::SeekableReadStream *rs) : Archive(rs) {
32 	memset(_info, 0, sizeof(SongInfo *) * 128);
33 	_songs = new XMidiData *[_count];
34 	memset(_songs, 0, sizeof(XMidiData *) * _count);
35 	loadSongInfo();
36 }
37 
~MusicFlex()38 MusicFlex::~MusicFlex() {
39 	uint32 i;
40 	for (i = 0; i < 128; i++) {
41 		delete _info[i];
42 	}
43 	for (i = 0; i < _count; i++) {
44 		delete _songs[i];
45 	}
46 	delete [] _songs;
47 }
48 
SongInfo()49 MusicFlex::SongInfo::SongInfo() : _numMeasures(0), _loopJump(0) {
50 	memset(_filename, 0, 17);
51 	memset(_transitions, 0, 128 * sizeof(int *));
52 }
53 
~SongInfo()54 MusicFlex::SongInfo::~SongInfo() {
55 	for (int i = 0; i < 128; i++) {
56 		delete [] _transitions[i];
57 	}
58 }
59 
getXMidi(uint32 index)60 MusicFlex::XMidiData *MusicFlex::getXMidi(uint32 index) {
61 	if (index >= _count)
62 		return nullptr;
63 	cache(index);
64 	return _songs[index];
65 }
66 
getSongInfo(uint32 index) const67 const MusicFlex::SongInfo *MusicFlex::getSongInfo(uint32 index) const {
68 	if (index > 127)
69 		return nullptr;
70 	return _info[index];
71 }
72 
cache(uint32 index)73 void MusicFlex::cache(uint32 index) {
74 	if (index >= _count) return;
75 	uint32 size;
76 	uint8 *data = getRawObject(index, &size);
77 	if (!data) {
78 		// Note: multiple sorcerer scenes (such as MALCHIR::03F2)
79 		// request track 122, which is blank in the Gold Edition
80 		// music flex.
81 		warning("Unable to cache song %d from sound/music.flx", index);
82 		return;
83 	}
84 	_songs[index] = new XMidiData(data, size);
85 }
86 
uncache(uint32 index)87 void MusicFlex::uncache(uint32 index) {
88 	if (index >= _count) return;
89 	delete _songs[index];
90 	_songs[index] = nullptr;
91 }
92 
isCached(uint32 index) const93 bool MusicFlex::isCached(uint32 index) const {
94 	if (index >= _count) return false;
95 	return (_songs[index] != nullptr);
96 }
97 
getAdlibTimbres()98 Common::SeekableReadStream *MusicFlex::getAdlibTimbres() {
99 	uint32 size;
100 	const uint8 *data = getRawObject(259, &size);
101 	return new Common::MemoryReadStream(data, size, DisposeAfterUse::YES);
102 }
103 
loadSongInfo()104 void MusicFlex::loadSongInfo() {
105 	uint32 size;
106 	const uint8 *buf = getRawObject(0, &size);
107 
108 	if (!buf || !size) {
109 		error("Unable to load song info from sound/music.flx");
110 	}
111 	Common::MemoryReadStream ds(buf, size);
112 	Std::string line;
113 
114 	// Read first section till we hit a #
115 	for (;;) {
116 		line = ds.readLine();
117 
118 		// We have hit the end of the section
119 		if (line.at(0) == '#') break;
120 
121 		Std::string::size_type  begIdx, endIdx;
122 
123 		// Find the first not space, which will get us the name
124 		begIdx = line.findFirstNotOf(' ');
125 		endIdx = line.findFirstOf(' ', begIdx);
126 		Std::string name = line.substr(begIdx, endIdx - begIdx);
127 
128 		// Now find the first not space after the name, which will get us the num
129 		begIdx = line.findFirstNotOf(' ', endIdx);
130 		endIdx = line.findFirstOf(' ', begIdx);
131 		int num = line.at(begIdx);
132 
133 		// Now number of measures
134 		begIdx = line.findFirstNotOf(' ', endIdx);
135 		endIdx = line.findFirstOf(' ', begIdx);
136 		int measures = atoi(line.substr(begIdx, endIdx - begIdx).c_str());
137 
138 		// Now finally _loopJump
139 		begIdx = line.findFirstNotOf(' ', endIdx);
140 		endIdx = line.findFirstOf(' ', begIdx);
141 		int loopJump = atoi(line.substr(begIdx, endIdx - begIdx).c_str());
142 
143 		// Uh oh
144 		if (num < 0 || num > 127)
145 			error("Invalid Section 1 song _info data. num out of range");
146 
147 		if (_info[num])
148 			error("Invalid Section 1 song _info data. num already defined");
149 
150 		_info[num] = new SongInfo();
151 
152 		strncpy(_info[num]->_filename, name.c_str(), 16);
153 		_info[num]->_numMeasures = measures;
154 		_info[num]->_loopJump = loopJump;
155 	};
156 
157 	// Read 'Section2', or more like skip it, since it's only trans.xmi
158 	// Read first section till we hit a #
159 	for (;;) {
160 		line = ds.readLine();
161 
162 		// We have hit the end of the section
163 		if (line.at(0) == '#') break;
164 	}
165 
166 	// Skip 'Section3'
167 	for (;;) {
168 		line = ds.readLine();
169 
170 		// We have hit the end of the section
171 		if (line.at(0) == '#') break;
172 	}
173 
174 	// Read 'Section4' (trans _info)
175 	for (;;) {
176 		line = ds.readLine();
177 
178 		// We have hit the end of the section
179 		if (line.at(0) == '#') break;
180 
181 		Std::string::size_type  begIdx, endIdx;
182 
183 		// Get 'from' name
184 		begIdx = line.findFirstNotOf(' ');
185 		endIdx = line.findFirstOf(' ', begIdx);
186 		Std::string from = line.substr(begIdx, endIdx - begIdx);
187 
188 		// Get 'to' name
189 		begIdx = line.findFirstNotOf(' ', endIdx);
190 		endIdx = line.findFirstOf(' ', begIdx);
191 		Std::string to = line.substr(begIdx, endIdx - begIdx);
192 
193 		// Find index of from name
194 		int fi;
195 		for (fi = 0; fi < 128; fi++) {
196 			if (_info[fi] && from == _info[fi]->_filename) break;
197 		}
198 
199 		if (fi == 128)
200 			error("Invalid Section 4 song _info data. Unable to find 'from' index (%s)", from.c_str());
201 
202 		// Find index of to name
203 		int ti;
204 		for (ti = 0; ti < 128; ti++) {
205 			if (_info[ti] && to == _info[ti]->_filename) break;
206 		}
207 
208 		if (ti == 128)
209 			error("Invalid Section 4 song _info data. Unable to find 'to' index (%s)", to.c_str());
210 
211 		// Allocate Transition _info
212 		_info[fi]->_transitions[ti] = new int[_info[fi]->_numMeasures];
213 
214 		// Now attempt to read the trans _info for the
215 		for (int m = 0; m < _info[fi]->_numMeasures; m++) {
216 			// Get trans _info name
217 			begIdx = line.findFirstNotOf(' ', endIdx);
218 			endIdx = line.findFirstOf(' ', begIdx);
219 
220 			if (begIdx == Std::string::npos)
221 				error("Invalid Section 4 song _info data. Unable to read _transitions for all measures");
222 
223 			Std::string trans = line.substr(begIdx, endIdx - begIdx);
224 			const char *str = trans.c_str();
225 
226 			int num = 0;
227 
228 			// Overlayed
229 			if (*str == '!')
230 				num = 0 - atoi(str + 1);
231 			else
232 				num = atoi(str);
233 
234 			_info[fi]->_transitions[ti][m] = num;
235 		}
236 	}
237 
238 	// Skip all remaining sections
239 
240 	delete[] buf;
241 }
242 
243 } // End of namespace Ultima8
244 } // End of namespace Ultima
245