mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-22 09:49:11 +00:00
d3cf4d10f2
After listening to the original music in a Mac emulator (which unfortunately doesn't handle the music very well), I can only conclude that note value 1 means the note should continue playing. At first I thought maybe it was supposed to fade the current note, or perhaps change its volume, but I can't hear any traces of either. So I'm going to assume it just means "hold the current note", though for the life of me I cannot think of any valid reason for such a command. So it may be wrong, but it sounds closer to the emulator than it did before.
247 lines
6.6 KiB
C++
247 lines
6.6 KiB
C++
/* 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.
|
|
*
|
|
*/
|
|
|
|
/*
|
|
From Markus Magnuson (superqult) we got this information:
|
|
Mac0
|
|
---
|
|
4 bytes - 'SOUN'
|
|
BE 4 bytes - block length
|
|
|
|
4 bytes - 'Mac0'
|
|
BE 4 bytes - (blockLength - 27)
|
|
28 bytes - ???
|
|
|
|
do this three times (once for each channel):
|
|
4 bytes - 'Chan'
|
|
BE 4 bytes - channel length
|
|
4 bytes - instrument name (e.g. 'MARI')
|
|
|
|
do this for ((chanLength-24)/4) times:
|
|
2 bytes - note duration
|
|
1 byte - note value
|
|
1 byte - note velocity
|
|
|
|
4 bytes - ???
|
|
4 bytes - 'Loop'/'Done'
|
|
4 bytes - ???
|
|
|
|
1 byte - 0x09
|
|
---
|
|
|
|
The instruments presumably correspond to the snd resource names in the
|
|
Monkey Island executable:
|
|
|
|
Instruments
|
|
"MARI" - MARIMBA
|
|
"PLUC" - PLUCK
|
|
"HARM" - HARMONIC
|
|
"PIPE" - PIPEORGAN
|
|
"TROM" - TROMBONE
|
|
"STRI" - STRINGS
|
|
"HORN" - HORN
|
|
"VIBE" - VIBES
|
|
"SHAK" - SHAKUHACHI
|
|
"PANP" - PANPIPE
|
|
"WHIS" - WHISTLE
|
|
"ORGA" - ORGAN3
|
|
"BONG" - BONGO
|
|
"BASS" - BASS
|
|
|
|
---
|
|
|
|
Note values <= 1 are silent.
|
|
*/
|
|
|
|
#include "common/macresman.h"
|
|
#include "common/translation.h"
|
|
#include "engines/engine.h"
|
|
#include "gui/message.h"
|
|
#include "scumm/player_v5m.h"
|
|
#include "scumm/scumm.h"
|
|
|
|
namespace Scumm {
|
|
|
|
Player_V5M::Player_V5M(ScummEngine *scumm, Audio::Mixer *mixer)
|
|
: Player_Mac(scumm, mixer, 3, 0x07, false) {
|
|
assert(_vm->_game.id == GID_MONKEY);
|
|
}
|
|
|
|
// Try both with and without underscore in the filename, because hfsutils may
|
|
// turn the space into an underscore. At least, it did for me.
|
|
|
|
static const char *monkeyIslandFileNames[] = {
|
|
"Monkey Island",
|
|
"Monkey_Island"
|
|
};
|
|
|
|
bool Player_V5M::checkMusicAvailable() {
|
|
Common::MacResManager resource;
|
|
|
|
for (int i = 0; i < ARRAYSIZE(monkeyIslandFileNames); i++) {
|
|
if (resource.exists(monkeyIslandFileNames[i])) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
GUI::MessageDialog dialog(_(
|
|
"Could not find the 'Monkey Island' Macintosh executable to read the\n"
|
|
"instruments from. Music will be disabled."), _("OK"));
|
|
dialog.runModal();
|
|
return false;
|
|
}
|
|
|
|
bool Player_V5M::loadMusic(const byte *ptr) {
|
|
Common::MacResManager resource;
|
|
bool found = false;
|
|
uint i;
|
|
|
|
for (i = 0; i < ARRAYSIZE(monkeyIslandFileNames); i++) {
|
|
if (resource.open(monkeyIslandFileNames[i])) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found) {
|
|
return false;
|
|
}
|
|
|
|
ptr += 8;
|
|
// TODO: Decipher the unknown bytes in the header. For now, skip 'em
|
|
ptr += 28;
|
|
|
|
Common::MacResIDArray idArray = resource.getResIDArray(RES_SND);
|
|
|
|
// Load the three channels and their instruments
|
|
for (i = 0; i < 3; i++) {
|
|
assert(READ_BE_UINT32(ptr) == MKTAG('C', 'h', 'a', 'n'));
|
|
uint32 len = READ_BE_UINT32(ptr + 4);
|
|
uint32 instrument = READ_BE_UINT32(ptr + 8);
|
|
|
|
_channel[i]._length = len - 20;
|
|
_channel[i]._data = ptr + 12;
|
|
_channel[i]._looped = (READ_BE_UINT32(ptr + len - 8) == MKTAG('L', 'o', 'o', 'p'));
|
|
_channel[i]._pos = 0;
|
|
_channel[i]._pitchModifier = 0;
|
|
_channel[i]._velocity = 0;
|
|
_channel[i]._remaining = 0;
|
|
_channel[i]._notesLeft = true;
|
|
|
|
for (uint j = 0; j < idArray.size(); j++) {
|
|
Common::String name = resource.getResName(RES_SND, idArray[j]);
|
|
if (instrument == READ_BE_UINT32(name.c_str())) {
|
|
debug(6, "Player_V5M::loadMusic: Channel %d: Loading instrument '%s'", i, name.c_str());
|
|
Common::SeekableReadStream *stream = resource.getResource(RES_SND, idArray[j]);
|
|
|
|
if (!_channel[i].loadInstrument(stream)) {
|
|
resource.close();
|
|
return false;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
ptr += len;
|
|
}
|
|
|
|
resource.close();
|
|
|
|
// The last note of each channel is just zeroes. We will adjust this
|
|
// note so that all the channels end at the same time.
|
|
|
|
uint32 samples[3];
|
|
uint32 maxSamples = 0;
|
|
for (i = 0; i < 3; i++) {
|
|
samples[i] = 0;
|
|
for (uint j = 0; j < _channel[i]._length; j += 4) {
|
|
samples[i] += durationToSamples(READ_BE_UINT16(&_channel[i]._data[j]));
|
|
}
|
|
if (samples[i] > maxSamples) {
|
|
maxSamples = samples[i];
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < 3; i++) {
|
|
_lastNoteSamples[i] = maxSamples - samples[i];
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Player_V5M::getNextNote(int ch, uint32 &samples, int &pitchModifier, byte &velocity) {
|
|
if (_channel[ch]._pos >= _channel[ch]._length) {
|
|
if (!_channel[ch]._looped) {
|
|
_channel[ch]._notesLeft = false;
|
|
return false;
|
|
}
|
|
// FIXME: Jamieson630: The jump seems to be happening
|
|
// too quickly! There should maybe be a pause after
|
|
// the last Note Off? But I couldn't find one in the
|
|
// MI1 Lookout music, where I was hearing problems.
|
|
_channel[ch]._pos = 0;
|
|
}
|
|
uint16 duration = READ_BE_UINT16(&_channel[ch]._data[_channel[ch]._pos]);
|
|
byte note = _channel[ch]._data[_channel[ch]._pos + 2];
|
|
samples = durationToSamples(duration);
|
|
|
|
if (note != 1) {
|
|
_channel[ch]._instrument.newNote();
|
|
}
|
|
|
|
if (note > 1) {
|
|
pitchModifier = noteToPitchModifier(note, &_channel[ch]._instrument);
|
|
velocity = _channel[ch]._data[_channel[ch]._pos + 3];
|
|
} else if (note == 1) {
|
|
// This is guesswork, but Monkey Island uses two different
|
|
// "special" note values: 0, which is clearly a rest, and 1
|
|
// which is... I thought at first it was a "soft" key off, to
|
|
// fade out the note, but listening to the music in a Mac
|
|
// emulator (which unfortunately doesn't work all that well),
|
|
// I hear no trace of fading out.
|
|
//
|
|
// It could mean "change the volume on the current note", but
|
|
// I can't hear that either, and it always seems to use the
|
|
// exact same velocity on this note.
|
|
//
|
|
// So it appears it really just is a "hold the current note",
|
|
// but why? Couldn't they just have made the original note
|
|
// longer?
|
|
|
|
pitchModifier = _channel[ch]._pitchModifier;
|
|
velocity = _channel[ch]._velocity;
|
|
} else {
|
|
pitchModifier = 0;
|
|
velocity = 0;
|
|
}
|
|
|
|
_channel[ch]._pos += 4;
|
|
|
|
if (_channel[ch]._pos >= _channel[ch]._length) {
|
|
samples = _lastNoteSamples[ch];
|
|
}
|
|
return true;
|
|
}
|
|
|
|
} // End of namespace Scumm
|