2012-11-13 21:49:12 +00:00
|
|
|
/* ScummVM - Graphic Adventure Engine
|
|
|
|
*
|
|
|
|
* ScummVM is the legal property of its developers, whose names
|
|
|
|
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
|
|
* file distributed with this source distribution.
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU General Public License
|
|
|
|
* as published by the Free Software Foundation; either version 2
|
|
|
|
* of the License, or (at your option) any later version.
|
|
|
|
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program; if not, write to the Free Software
|
|
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
We have the following information from Lars Christensen (lechimp) and
|
|
|
|
Jamieson Christian (jamieson630):
|
|
|
|
|
|
|
|
RESOURCE DATA
|
|
|
|
LE 2 bytes Resource size
|
|
|
|
2 bytes Unknown
|
|
|
|
2 bytes 'so'
|
|
|
|
14 bytes Unknown
|
|
|
|
BE 2 bytes Instrument for Stream 1
|
|
|
|
BE 2 bytes Instrument for Stream 2
|
|
|
|
BE 2 bytes Instrument for Stream 3
|
|
|
|
BE 2 bytes Instrument for Stream 4
|
|
|
|
BE 2 bytes Instrument for Stream 5
|
|
|
|
BE 2 bytes Offset to Stream 1
|
|
|
|
BE 2 bytes Offset to Stream 2
|
|
|
|
BE 2 bytes Offset to Stream 3
|
|
|
|
BE 2 bytes Offset to Stream 4
|
|
|
|
BE 2 bytes Offset to Stream 5
|
|
|
|
? bytes The streams
|
|
|
|
|
|
|
|
STREAM DATA
|
|
|
|
BE 2 bytes Unknown (always 1?)
|
|
|
|
2 bytes Unknown (always 0?)
|
|
|
|
BE 2 bytes Number of events in stream
|
|
|
|
? bytes Stream data
|
|
|
|
|
|
|
|
Each stream event is exactly 3 bytes, therefore one can
|
|
|
|
assert that numEvents == (streamSize - 6) / 3. The
|
|
|
|
polyphony of a stream appears to be 1; in other words, only
|
|
|
|
one note at a time can be playing in each stream. The next
|
|
|
|
event is not executed until the current note (or rest) is
|
|
|
|
finished playing; therefore, note duration also serves as the
|
|
|
|
time delta between events.
|
|
|
|
|
|
|
|
FOR EACH EVENTS
|
|
|
|
BE 2 bytes Note duration
|
|
|
|
1 byte Note number to play (0 = rest/silent)
|
|
|
|
|
|
|
|
Oh, and quick speculation -- Stream 1 may be used for a
|
|
|
|
single-voice interleaved version of the music, where Stream 2-
|
|
|
|
5 represent a version of the music in up to 4-voice
|
|
|
|
polyphony, one voice per stream. I postulate thus because
|
|
|
|
the first stream of the Mac Loom theme music contains
|
|
|
|
interleaved voices, whereas the second stream seemed to
|
|
|
|
contain only the pizzicato bottom-end harp. Stream 5, in this
|
|
|
|
example, is empty, so if my speculation is correct, this
|
|
|
|
particular musical number supports 3-voice polyphony at
|
|
|
|
most. I must check out Streams 3 and 4 to see what they
|
|
|
|
contain.
|
|
|
|
|
|
|
|
==========
|
|
|
|
|
|
|
|
The instruments appear to be identified by their resource IDs:
|
|
|
|
|
|
|
|
1000 Dual Harp
|
|
|
|
10895 harp1
|
|
|
|
11445 strings1
|
|
|
|
11548 silent
|
|
|
|
13811 staff1
|
|
|
|
15703 brass1
|
|
|
|
16324 flute1
|
|
|
|
25614 accordian 1
|
|
|
|
28110 f horn1
|
|
|
|
29042 bassoon1
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "common/macresman.h"
|
|
|
|
#include "common/translation.h"
|
|
|
|
#include "engines/engine.h"
|
|
|
|
#include "gui/message.h"
|
|
|
|
#include "scumm/player_v3m.h"
|
|
|
|
#include "scumm/scumm.h"
|
|
|
|
|
|
|
|
namespace Scumm {
|
|
|
|
|
|
|
|
Player_V3M::Player_V3M(ScummEngine *scumm, Audio::Mixer *mixer)
|
2012-11-24 00:39:16 +00:00
|
|
|
: Player_Mac(scumm, mixer, 5, 0x1E, true) {
|
2012-11-13 21:49:12 +00:00
|
|
|
assert(_vm->_game.id == GID_LOOM);
|
|
|
|
|
2012-11-14 00:27:53 +00:00
|
|
|
// Channel 0 seems to be what was played on low-end macs, that couldn't
|
|
|
|
// handle multi-channel music and play the game at the same time. I'm
|
|
|
|
// not sure if stream 4 is ever used, but let's use it just in case.
|
|
|
|
}
|
2012-11-13 21:49:12 +00:00
|
|
|
|
2012-11-19 06:16:42 +00:00
|
|
|
// \xAA is a trademark glyph in Mac OS Roman. We try that, but also the Windows
|
|
|
|
// version, the UTF-8 version, and just plain without in case the file system
|
|
|
|
// can't handle exotic characters like that.
|
|
|
|
|
|
|
|
static const char *loomFileNames[] = {
|
|
|
|
"Loom\xAA",
|
|
|
|
"Loom\x99",
|
|
|
|
"Loom\xE2\x84\xA2",
|
|
|
|
"Loom"
|
|
|
|
};
|
|
|
|
|
2012-11-14 00:27:53 +00:00
|
|
|
bool Player_V3M::checkMusicAvailable() {
|
2012-11-13 21:49:12 +00:00
|
|
|
Common::MacResManager resource;
|
2012-11-19 06:16:42 +00:00
|
|
|
|
|
|
|
for (int i = 0; i < ARRAYSIZE(loomFileNames); i++) {
|
|
|
|
if (resource.exists(loomFileNames[i])) {
|
2012-11-19 06:25:42 +00:00
|
|
|
return true;
|
2012-11-19 06:16:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-11-19 06:25:42 +00:00
|
|
|
GUI::MessageDialog dialog(_(
|
|
|
|
"Could not find the 'Loom' Macintosh executable to read the\n"
|
|
|
|
"instruments from. Music will be disabled."), _("OK"));
|
|
|
|
dialog.runModal();
|
|
|
|
return false;
|
2012-11-13 21:49:12 +00:00
|
|
|
}
|
|
|
|
|
2012-11-14 00:27:53 +00:00
|
|
|
bool Player_V3M::loadMusic(const byte *ptr) {
|
2012-11-13 21:49:12 +00:00
|
|
|
Common::MacResManager resource;
|
2012-11-19 06:16:42 +00:00
|
|
|
bool found = false;
|
|
|
|
|
|
|
|
for (int i = 0; i < ARRAYSIZE(loomFileNames); i++) {
|
|
|
|
if (resource.open(loomFileNames[i])) {
|
|
|
|
found = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!found) {
|
2012-11-14 00:27:53 +00:00
|
|
|
return false;
|
2012-11-13 21:49:12 +00:00
|
|
|
}
|
|
|
|
|
2013-01-27 08:38:01 +00:00
|
|
|
if (ptr[4] != 's' || ptr[5] != 'o') {
|
2013-01-27 13:52:07 +00:00
|
|
|
// Like the original we ignore all sound resources which do not have
|
|
|
|
// a 'so' tag in them.
|
2013-01-27 08:38:01 +00:00
|
|
|
// See bug #3602239 ("Mac Loom crashes using opening spell on
|
2013-01-27 13:52:07 +00:00
|
|
|
// gravestone") for a case where this is required. Loom Mac tries to
|
|
|
|
// play resource 11 here. This resource is no Mac sound resource
|
|
|
|
// though, it is a PC Speaker resource. A test with the original
|
|
|
|
// interpreter also has shown that no sound is played while the
|
|
|
|
// screen is shaking.
|
|
|
|
debug(5, "Player_V3M::loadMusic: Skipping unknown music type %02X%02X", ptr[4], ptr[5]);
|
2013-01-27 08:38:01 +00:00
|
|
|
resource.close();
|
|
|
|
return false;
|
|
|
|
}
|
2012-11-13 21:49:12 +00:00
|
|
|
|
|
|
|
uint i;
|
|
|
|
for (i = 0; i < 5; i++) {
|
2012-11-14 00:27:53 +00:00
|
|
|
int instrument = READ_BE_UINT16(ptr + 20 + 2 * i);
|
|
|
|
int offset = READ_BE_UINT16(ptr + 30 + 2 * i);
|
|
|
|
|
2012-11-13 21:49:12 +00:00
|
|
|
_channel[i]._looped = false;
|
2012-11-14 00:27:53 +00:00
|
|
|
_channel[i]._length = READ_BE_UINT16(ptr + offset + 4) * 3;
|
|
|
|
_channel[i]._data = ptr + offset + 6;
|
2012-11-13 21:49:12 +00:00
|
|
|
_channel[i]._pos = 0;
|
|
|
|
_channel[i]._pitchModifier = 0;
|
|
|
|
_channel[i]._velocity = 0;
|
|
|
|
_channel[i]._remaining = 0;
|
|
|
|
_channel[i]._notesLeft = true;
|
|
|
|
|
|
|
|
Common::SeekableReadStream *stream = resource.getResource(RES_SND, instrument);
|
|
|
|
if (_channel[i].loadInstrument(stream)) {
|
2012-11-14 00:27:53 +00:00
|
|
|
debug(6, "Player_V3M::loadMusic: Channel %d - Loaded Instrument %d (%s)", i, instrument, resource.getResName(RES_SND, instrument).c_str());
|
2012-11-13 21:49:12 +00:00
|
|
|
} else {
|
|
|
|
resource.close();
|
2012-11-14 00:27:53 +00:00
|
|
|
return false;
|
2012-11-13 21:49:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
resource.close();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2012-11-18 13:30:17 +00:00
|
|
|
bool Player_V3M::getNextNote(int ch, uint32 &samples, int &pitchModifier, byte &velocity) {
|
2012-11-14 00:27:53 +00:00
|
|
|
_channel[ch]._instrument.newNote();
|
|
|
|
if (_channel[ch]._pos >= _channel[ch]._length) {
|
|
|
|
if (!_channel[ch]._looped) {
|
|
|
|
_channel[ch]._notesLeft = false;
|
2012-11-13 21:49:12 +00:00
|
|
|
return false;
|
|
|
|
}
|
2012-11-14 00:27:53 +00:00
|
|
|
_channel[ch]._pos = 0;
|
2012-11-13 21:49:12 +00:00
|
|
|
}
|
2012-11-18 13:30:17 +00:00
|
|
|
uint16 duration = READ_BE_UINT16(&_channel[ch]._data[_channel[ch]._pos]);
|
|
|
|
byte note = _channel[ch]._data[_channel[ch]._pos + 2];
|
|
|
|
samples = durationToSamples(duration);
|
2012-11-18 16:49:23 +00:00
|
|
|
if (note > 0) {
|
|
|
|
pitchModifier = noteToPitchModifier(note, &_channel[ch]._instrument);
|
|
|
|
velocity = 127;
|
|
|
|
} else {
|
|
|
|
pitchModifier = 0;
|
|
|
|
velocity = 0;
|
|
|
|
}
|
2012-11-14 00:27:53 +00:00
|
|
|
_channel[ch]._pos += 3;
|
2012-11-13 21:49:12 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
} // End of namespace Scumm
|