/* 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) : Player_Mac(scumm, mixer, 5, 0x1E, true) { assert(_vm->_game.id == GID_LOOM); // 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. } // \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" }; bool Player_V3M::checkMusicAvailable() { Common::MacResManager resource; for (int i = 0; i < ARRAYSIZE(loomFileNames); i++) { if (resource.exists(loomFileNames[i])) { return true; } } 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; } bool Player_V3M::loadMusic(const byte *ptr) { Common::MacResManager resource; bool found = false; for (int i = 0; i < ARRAYSIZE(loomFileNames); i++) { if (resource.open(loomFileNames[i])) { found = true; break; } } if (!found) { return false; } if (ptr[4] != 's' || ptr[5] != 'o') { // Like the original we ignore all sound resources which do not have // a 'so' tag in them. // See bug #3602239 ("Mac Loom crashes using opening spell on // 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]); resource.close(); return false; } uint i; for (i = 0; i < 5; i++) { int instrument = READ_BE_UINT16(ptr + 20 + 2 * i); int offset = READ_BE_UINT16(ptr + 30 + 2 * i); _channel[i]._looped = false; _channel[i]._length = READ_BE_UINT16(ptr + offset + 4) * 3; _channel[i]._data = ptr + offset + 6; _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)) { debug(6, "Player_V3M::loadMusic: Channel %d - Loaded Instrument %d (%s)", i, instrument, resource.getResName(RES_SND, instrument).c_str()); } else { resource.close(); return false; } } resource.close(); return true; } bool Player_V3M::getNextNote(int ch, uint32 &samples, int &pitchModifier, byte &velocity) { _channel[ch]._instrument.newNote(); if (_channel[ch]._pos >= _channel[ch]._length) { if (!_channel[ch]._looped) { _channel[ch]._notesLeft = false; return false; } _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 > 0) { pitchModifier = noteToPitchModifier(note, &_channel[ch]._instrument); velocity = 127; } else { pitchModifier = 0; velocity = 0; } _channel[ch]._pos += 3; return true; } } // End of namespace Scumm