From b93dd59a44f73f744bbf3d0d60e0ce509667eccc Mon Sep 17 00:00:00 2001 From: Max Horn Date: Fri, 17 Dec 2004 22:27:15 +0000 Subject: Reduce code size of resource.cpp to help PalmOS port, by moving util code to a new file util.cpp and sound resource code to sound.cpp svn-id: r16105 --- scumm/module.mk | 1 + scumm/resource.cpp | 1191 ---------------------------------------------------- scumm/scumm.cpp | 85 ---- scumm/scumm.h | 39 +- scumm/sound.cpp | 1046 +++++++++++++++++++++++++++++++++++++++++++++ scumm/util.cpp | 272 ++++++++++++ scumm/util.h | 71 ++++ 7 files changed, 1391 insertions(+), 1314 deletions(-) create mode 100644 scumm/util.cpp create mode 100644 scumm/util.h diff --git a/scumm/module.mk b/scumm/module.mk index 4e23c3aa93..38a7a9b662 100644 --- a/scumm/module.mk +++ b/scumm/module.mk @@ -49,6 +49,7 @@ MODULE_OBJS := \ scumm/sound.o \ scumm/string.o \ scumm/usage_bits.o \ + scumm/util.o \ scumm/vars.o \ scumm/verbs.o \ scumm/wiz_he.o \ diff --git a/scumm/resource.cpp b/scumm/resource.cpp index 67fdf23327..6276ca822e 100644 --- a/scumm/resource.cpp +++ b/scumm/resource.cpp @@ -38,165 +38,6 @@ static uint16 newTag2Old(uint32 oldTag); static const char *resTypeFromId(int id); - -ScummFile::ScummFile() : _encbyte(0), _subFileStart(0), _subFileLen(0) { -} - -void ScummFile::setEnc(byte value) { - _encbyte = value; -} - -void ScummFile::setSubfileRange(uint32 start, uint32 len) { - // TODO: Add sanity checks - const uint32 fileSize = File::size(); - assert(start <= fileSize); - assert(start + len <= fileSize); - _subFileStart = start; - _subFileLen = len; - seek(0, SEEK_SET); -} - -void ScummFile::resetSubfile() { - _subFileStart = 0; - _subFileLen = 0; - seek(0, SEEK_SET); -} - -bool ScummFile::open(const char *filename, AccessMode mode) { - if (File::open(filename, mode)) { - resetSubfile(); - return true; - } else { - return false; - } -} - -bool ScummFile::openSubFile(const char *filename) { - assert(isOpen()); - - // Disable the XOR encryption and reset any current subfile range - setEnc(0); - resetSubfile(); - - // Read in the filename table and look for the specified file - - unsigned long file_off, file_len; - char file_name[0x20+1]; - unsigned long i; - - // Get the length of the data file to use for consistency checks - const uint32 data_file_len = size(); - - // Read offset and length to the file records */ - const uint32 file_record_off = readUint32BE(); - const uint32 file_record_len = readUint32BE(); - - // Do a quick check to make sure the offset and length are good - if (file_record_off + file_record_len > data_file_len) { - return false; - } - - // Do a little consistancy check on file_record_length - if (file_record_len % 0x28) { - return false; - } - - // Scan through the files - for (i = 0; i < file_record_len; i += 0x28) { - // read a file record - seek(file_record_off + i, SEEK_SET); - file_off = readUint32BE(); - file_len = readUint32BE(); - read(file_name, 0x20); - file_name[0x20] = 0; - - assert(file_name[0]); - //debug(7, " extracting \'%s\'", file_name); - - // Consistency check. make sure the file data is in the file - if (file_off + file_len > data_file_len) { - return false; - } - - if (scumm_stricmp(file_name, filename) == 0) { - // We got a match! - setSubfileRange(file_off, file_len); - return true; - } - } - - return false; -} - - -bool ScummFile::eof() { - return _subFileLen ? (pos() >= _subFileLen) : File::eof(); -} - -uint32 ScummFile::pos() { - return File::pos() - _subFileStart; -} - -uint32 ScummFile::size() { - return _subFileLen ? _subFileLen : File::size(); -} - -void ScummFile::seek(int32 offs, int whence) { - if (_subFileLen) { - // Constrain the seek to the subfile - switch (whence) { - case SEEK_END: - offs = _subFileStart + _subFileLen - offs; - break; - case SEEK_SET: - offs += _subFileStart; - break; - case SEEK_CUR: - offs += File::pos(); - break; - } - assert((int32)_subFileStart <= offs && offs <= (int32)(_subFileStart + _subFileLen)); - whence = SEEK_SET; - } - File::seek(offs, whence); -} - -uint32 ScummFile::read(void *ptr, uint32 len) { - uint32 realLen; - - if (_subFileLen) { - // Limit the amount we read by the subfile boundaries. - const uint32 curPos = pos(); - assert(_subFileLen >= curPos); - uint32 newPos = curPos + len; - if (newPos > _subFileLen) { - len = _subFileLen - curPos; - _ioFailed = true; - } - } - - realLen = File::read(ptr, len); - - - // If an encryption byte was specified, XOR the data we just read by it. - // This simple kind of "encryption" was used by some of the older SCUMM - // games. - if (_encbyte) { - byte *p = (byte *)ptr; - byte *end = p + realLen; - while (p < end) - *p++ ^= _encbyte; - } - - return realLen; -} - -uint32 ScummFile::write(const void *, uint32) { - error("ScummFile does not support writing!"); -} - - - /* Open a room */ void ScummEngine::openRoom(int room) { int room_offs, roomlimit; @@ -885,1038 +726,6 @@ int ScummEngine::loadResource(int type, int idx) { error("Cannot read resource"); } -int ScummEngine::readSoundResource(int type, int idx) { - uint32 pos, total_size, size, tag, basetag, max_total_size; - int pri, best_pri; - uint32 best_size = 0, best_offs = 0; - - debugC(DEBUG_RESOURCE, "readSoundResource(%s,%d)", resTypeFromId(type), idx); - - pos = 0; - - _fileHandle.readUint32LE(); - max_total_size = _fileHandle.readUint32BE() - 8; - basetag = fileReadDword(); - total_size = _fileHandle.readUint32BE(); - - debugC(DEBUG_RESOURCE, " basetag: %s, total_size=%d", tag2str(TO_BE_32(basetag)), total_size); - - if (basetag == MKID('MIDI') || basetag == MKID('iMUS')) { - if (_midiDriver != MD_PCSPK && _midiDriver != MD_PCJR) { - _fileHandle.seek(-8, SEEK_CUR); - _fileHandle.read(createResource(type, idx, total_size + 8), total_size + 8); - return 1; - } - } else if (basetag == MKID('SOU ')) { - best_pri = -1; - while (pos < total_size) { - tag = fileReadDword(); - size = _fileHandle.readUint32BE() + 8; - pos += size; - - pri = -1; - - switch (tag) { - case MKID('TOWS'): - pri = 16; - break; - case MKID('SBL '): - pri = 15; - break; - case MKID('ADL '): - pri = 1; - if (_midiDriver == MD_ADLIB) - pri = 10; - break; - case MKID('AMI '): - pri = 3; - break; - case MKID('ROL '): - pri = 3; - if (_native_mt32) - pri = 5; - break; - case MKID('GMD '): - pri = 4; - break; - case MKID('MAC '): - pri = 2; - break; - case MKID('SPK '): - pri = -1; -// if (_midiDriver == MD_PCSPK) -// pri = 11; - break; - } - - if ((_midiDriver == MD_PCSPK || _midiDriver == MD_PCJR) && pri != 11) - pri = -1; - - debugC(DEBUG_RESOURCE, " tag: %s, total_size=%d, pri=%d", tag2str(TO_BE_32(tag)), size, pri); - - - if (pri > best_pri) { - best_pri = pri; - best_size = size; - best_offs = _fileHandle.pos(); - } - - _fileHandle.seek(size - 8, SEEK_CUR); - } - - if (best_pri != -1) { - _fileHandle.seek(best_offs - 8, SEEK_SET); - _fileHandle.read(createResource(type, idx, best_size), best_size); - return 1; - } - } else if (basetag == MKID('Mac0')) { - _fileHandle.seek(-12, SEEK_CUR); - total_size = _fileHandle.readUint32BE() - 8; - byte *ptr = (byte *)calloc(total_size, 1); - _fileHandle.read(ptr, total_size); -// dumpResource("sound-", idx, ptr); - convertMac0Resource(type, idx, ptr, total_size); - free(ptr); - return 1; - } else if (basetag == MKID('Mac1')) { - _fileHandle.seek(-12, SEEK_CUR); - total_size = _fileHandle.readUint32BE(); - _fileHandle.read(createResource(type, idx, total_size), total_size - 8); - return 1; - } else if (basetag == MKID('HSHD')) { - _fileHandle.seek(-12, SEEK_CUR); - total_size = _fileHandle.readUint32BE(); - _fileHandle.read(createResource(type, idx, total_size), total_size - 8); - return 1; - } else if (basetag == MKID('TALK')) { - _fileHandle.seek(-12, SEEK_CUR); - total_size = _fileHandle.readUint32BE(); - _fileHandle.read(createResource(type, idx, total_size), total_size - 8); - return 1; - } else if (basetag == MKID('DIGI')) { - _fileHandle.seek(-12, SEEK_CUR); - total_size = _fileHandle.readUint32BE(); - _fileHandle.read(createResource(type, idx, total_size), total_size - 8); - return 1; - } else if (basetag == MKID('FMUS')) { - // Used in 3DO version of puttputt joins the parade and probably others - // Specifies a separate file to be used for music from what I gather. - int tmpsize; - File dmuFile; - char buffer[128]; - debugC(DEBUG_SOUND, "Found base tag FMUS in sound %d, size %d", idx, total_size); - debugC(DEBUG_SOUND, "It was at position %d", _fileHandle.pos()); - - _fileHandle.seek(4, SEEK_CUR); - // HSHD size - tmpsize = _fileHandle.readUint32BE(); - // skip to size part of the SDAT block - _fileHandle.seek(tmpsize - 4, SEEK_CUR); - // SDAT size - tmpsize = _fileHandle.readUint32BE(); - - // SDAT contains name of file we want - _fileHandle.read(buffer, tmpsize - 8); - // files seem to be 11 chars (8.3) unused space is replaced by spaces - *(strstr(buffer, " ")) = '\0'; - - debugC(DEBUG_SOUND, "FMUS file %s", buffer); - if (dmuFile.open(buffer) == false) { - warning("Can't open music file %s*", buffer); - res.roomoffs[type][idx] = 0xFFFFFFFF; - return 0; - } - dmuFile.seek(4, SEEK_SET); - total_size = dmuFile.readUint32BE(); - debugC(DEBUG_SOUND, "dmu file size %d", total_size); - dmuFile.seek(-8, SEEK_CUR); - dmuFile.read(createResource(type, idx, total_size), total_size); - dmuFile.close(); - return 1; - } else if (basetag == MKID('Crea')) { - _fileHandle.seek(-12, SEEK_CUR); - total_size = _fileHandle.readUint32BE(); - _fileHandle.read(createResource(type, idx, total_size), total_size - 8); - return 1; - } else if (FROM_LE_32(basetag) == max_total_size) { - _fileHandle.seek(-12, SEEK_CUR); - total_size = _fileHandle.readUint32BE(); - _fileHandle.seek(-8, SEEK_CUR); - _fileHandle.read(createResource(type, idx, total_size), total_size); - return 1; - } else { - warning("Unrecognized base tag 0x%08x in sound %d", basetag, idx); - } - res.roomoffs[type][idx] = 0xFFFFFFFF; - return 0; -} - -// Adlib MIDI-SYSEX to set MIDI instruments for small header games. -static byte ADLIB_INSTR_MIDI_HACK[95] = { - 0x00, 0xf0, 0x14, 0x7d, 0x00, // sysex 00: part on/off - 0x00, 0x00, 0x03, // part/channel (offset 5) - 0x00, 0x00, 0x07, 0x0f, 0x00, 0x00, 0x08, 0x00, - 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0xf7, - 0x00, 0xf0, 0x41, 0x7d, 0x10, // sysex 16: set instrument - 0x00, 0x01, // part/channel (offset 28) - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0xf7, - 0x00, 0xb0, 0x07, 0x64 // Controller 7 = 100 (offset 92) -}; - -static const byte map_param[7] = { - 0, 2, 3, 4, 8, 9, 0, -}; - -static const byte freq2note[128] = { - /*128*/ 6, 6, 6, 6, - /*132*/ 7, 7, 7, 7, 7, 7, 7, - /*139*/ 8, 8, 8, 8, 8, 8, 8, 8, 8, - /*148*/ 9, 9, 9, 9, 9, 9, 9, 9, 9, - /*157*/ 10, 10, 10, 10, 10, 10, 10, 10, 10, - /*166*/ 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, - /*176*/ 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, - /*186*/ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, - /*197*/ 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, - /*209*/ 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, - /*222*/ 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - /*235*/ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, - /*249*/ 18, 18, 18, 18, 18, 18, 18 -}; - -static const uint16 num_steps_table[] = { - 1, 2, 4, 5, - 6, 7, 8, 9, - 10, 12, 14, 16, - 18, 21, 24, 30, - 36, 50, 64, 82, - 100, 136, 160, 192, - 240, 276, 340, 460, - 600, 860, 1200, 1600 -}; - -int ScummEngine::convert_extraflags(byte * ptr, byte * src_ptr) { - int flags = src_ptr[0]; - - int t1, t2, t3, t4, time; - int v1, v2, v3; - - if (!(flags & 0x80)) - return -1; - - t1 = (src_ptr[1] & 0xf0) >> 3; - t2 = (src_ptr[2] & 0xf0) >> 3; - t3 = (src_ptr[3] & 0xf0) >> 3 | (flags & 0x40 ? 0x80 : 0); - t4 = (src_ptr[3] & 0x0f) << 1; - v1 = (src_ptr[1] & 0x0f); - v2 = (src_ptr[2] & 0x0f); - v3 = 31; - if ((flags & 0x7) == 0) { - v1 = v1 + 31 + 8; - v2 = v2 + 31 + 8; - } else { - v1 = v1 * 2 + 31; - v2 = v2 * 2 + 31; - } - - /* flags a */ - if ((flags & 0x7) == 6) - ptr[0] = 0; - else { - ptr[0] = (flags >> 4) & 0xb; - ptr[1] = map_param[flags & 0x7]; - } - - /* extra a */ - ptr[2] = 0; - ptr[3] = 0; - ptr[4] = t1 >> 4; - ptr[5] = t1 & 0xf; - ptr[6] = v1 >> 4; - ptr[7] = v1 & 0xf; - ptr[8] = t2 >> 4; - ptr[9] = t2 & 0xf; - ptr[10] = v2 >> 4; - ptr[11] = v2 & 0xf; - ptr[12] = t3 >> 4; - ptr[13] = t3 & 0xf; - ptr[14] = t4 >> 4; - ptr[15] = t4 & 0xf; - ptr[16] = v3 >> 4; - ptr[17] = v3 & 0xf; - - time = num_steps_table[t1] + num_steps_table[t2] - + num_steps_table[t3 & 0x7f] + num_steps_table[t4]; - if (flags & 0x20) { - int playtime = ((src_ptr[4] >> 4) & 0xf) * 118 + - (src_ptr[4] & 0xf) * 8; - if (playtime > time) - time = playtime; - } - /* - time = ((src_ptr[4] >> 4) & 0xf) * 118 + - (src_ptr[4] & 0xf) * 8; - */ - return time; -} - -#define kMIDIHeaderSize 46 -static inline byte *writeMIDIHeader(byte *ptr, const char *type, int ppqn, int total_size) { - uint32 dw = TO_BE_32(total_size); - - memcpy(ptr, type, 4); ptr += 4; - memcpy(ptr, &dw, 4); ptr += 4; - memcpy(ptr, "MDhd", 4); ptr += 4; - ptr[0] = 0; ptr[1] = 0; ptr[2] = 0; ptr[3] = 8; - ptr += 4; - memset(ptr, 0, 8), ptr += 8; - memcpy(ptr, "MThd", 4); ptr += 4; - ptr[0] = 0; ptr[1] = 0; ptr[2] = 0; ptr[3] = 6; - ptr += 4; - ptr[0] = 0; ptr[1] = 0; ptr[2] = 0; ptr[3] = 1; // MIDI format 0 with 1 track - ptr += 4; - - *ptr++ = ppqn >> 8; - *ptr++ = ppqn & 0xFF; - - memcpy(ptr, "MTrk", 4); ptr += 4; - memcpy(ptr, &dw, 4); ptr += 4; - - return ptr; -} - -static inline byte *writeVLQ(byte *ptr, int value) { - if (value > 0x7f) { - if (value > 0x3fff) { - *ptr++ = (value >> 14) | 0x80; - value &= 0x3fff; - } - *ptr++ = (value >> 7) | 0x80; - value &= 0x7f; - } - *ptr++ = value; - return ptr; -} - -static inline byte Mac0ToGMInstrument(uint32 type, int &transpose) { - transpose = 0; - switch (type) { - case MKID('MARI'): return 12; - case MKID('PLUC'): return 45; - case MKID('HARM'): return 22; - case MKID('PIPE'): return 19; - case MKID('TROM'): transpose = -12; return 57; - case MKID('STRI'): return 48; - case MKID('HORN'): return 60; - case MKID('VIBE'): return 11; - case MKID('SHAK'): return 77; - case MKID('PANP'): return 75; - case MKID('WHIS'): return 76; - case MKID('ORGA'): return 17; - case MKID('BONG'): return 115; - case MKID('BASS'): transpose = -24; return 35; - default: - error("Unknown Mac0 instrument %s found", tag2str(type)); - } -} - -void ScummEngine::convertMac0Resource(int type, int idx, byte *src_ptr, int size) { - /* - 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 - --- - - Instruments (General Midi): - "MARI" - Marimba (12) - "PLUC" - Pizzicato Strings (45) - "HARM" - Harmonica (22) - "PIPE" - Church Organ? (19) or Flute? (73) or Bag Pipe (109) - "TROM" - Trombone (57) - "STRI" - String Ensemble (48 or 49) - "HORN" - French Horn? (60) or English Horn? (69) - "VIBE" - Vibraphone (11) - "SHAK" - Shakuhachi? (77) - "PANP" - Pan Flute (75) - "WHIS" - Whistle (78) / Bottle (76) - "ORGA" - Drawbar Organ (16; but could also be 17-20) - "BONG" - Woodblock? (115) - "BASS" - Bass (32-39) - - - Now the task could be to convert this into MIDI, to be fed into iMuse. - Or we do something similiar to what is done in Player_V3, assuming - we can identify SFX in the MI datafiles for each of the instruments - listed above. - */ - -#if 0 - byte *ptr = createResource(type, idx, size); - memcpy(ptr, src_ptr, size); -#else - const int ppqn = 480; - byte *ptr, *start_ptr; - - int total_size = 0; - total_size += kMIDIHeaderSize; // Header - total_size += 7; // Tempo META - total_size += 3 * 3; // Three program change mesages - total_size += 22; // Possible jump SysEx - total_size += 5; // EOT META - - int i, len; - byte track_instr[3]; - byte *track_data[3]; - int track_len[3]; - int track_transpose[3]; - bool looped = false; - - src_ptr += 8; - // TODO: Decipher the unknown bytes in the header. For now, skip 'em - src_ptr += 28; - - // Parse the three channels - for (i = 0; i < 3; i++) { - assert(*((uint32*)src_ptr) == MKID('Chan')); - len = READ_BE_UINT32(src_ptr + 4); - track_len[i] = len - 24; - track_instr[i] = Mac0ToGMInstrument(*(uint32*)(src_ptr + 8), track_transpose[i]); - track_data[i] = src_ptr + 12; - src_ptr += len; - looped = (*((uint32*)(src_ptr - 8)) == MKID('Loop')); - - // For each note event, we need up to 6 bytes for the - // Note On (3 VLQ, 3 event), and 6 bytes for the Note - // Off (3 VLQ, 3 event). So 12 bytes total. - total_size += 12 * track_len[i]; - } - assert(*src_ptr == 0x09); - - // Create sound resource - start_ptr = createResource(type, idx, total_size); - - // Insert MIDI header - ptr = writeMIDIHeader(start_ptr, "GMD ", ppqn, total_size); - - // Write a tempo change Meta event - // 473 / 4 Hz, convert to micro seconds. - uint32 dw = 1000000 * 437 / 4 / ppqn; // 1000000 * ppqn * 4 / 473; - memcpy(ptr, "\x00\xFF\x51\x03", 4); ptr += 4; - *ptr++ = (byte)((dw >> 16) & 0xFF); - *ptr++ = (byte)((dw >> 8) & 0xFF); - *ptr++ = (byte)(dw & 0xFF); - - // Insert program change messages - *ptr++ = 0; // VLQ - *ptr++ = 0xC0; - *ptr++ = track_instr[0]; - *ptr++ = 0; // VLQ - *ptr++ = 0xC1; - *ptr++ = track_instr[1]; - *ptr++ = 0; // VLQ - *ptr++ = 0xC2; - *ptr++ = track_instr[2]; - - // And now, the actual composition. Please turn all cell phones - // and pagers off during the performance. Thank you. - uint16 nextTime[3] = { 1, 1, 1 }; - int stage[3] = { 0, 0, 0 }; - - while (track_len[0] | track_len[1] | track_len[2]) { - int best = -1; - uint16 bestTime = 0xFFFF; - for (i = 0; i < 3; ++i) { - if (track_len[i] && nextTime[i] < bestTime) { - bestTime = nextTime[i]; - best = i; - } - } - assert (best != -1); - - if (!stage[best]) { - // We are STARTING this event. - if (track_data[best][2] > 1) { - // Note On - ptr = writeVLQ(ptr, nextTime[best]); - *ptr++ = 0x90 | best; - *ptr++ = track_data[best][2] + track_transpose[best]; - *ptr++ = track_data[best][3] * 127 / 100; // Scale velocity - for (i = 0; i < 3; ++i) - nextTime[i] -= bestTime; - } - nextTime[best] += READ_BE_UINT16 (track_data[best]); - stage[best] = 1; - } else { - // We are ENDING this event. - if (track_data[best][2] > 1) { - // There was a Note On, so do a Note Off - ptr = writeVLQ(ptr, nextTime[best]); - *ptr++ = 0x80 | best; - *ptr++ = track_data[best][2] + track_transpose[best]; - *ptr++ = track_data[best][3] * 127 / 100; // Scale velocity - for (i = 0; i < 3; ++i) - nextTime[i] -= bestTime; - } - track_data[best] += 4; - track_len[best] -= 4; - stage[best] = 0; - } - } - - // Is this a looped song? If so, effect a loop by - // using the S&M maybe_jump SysEx command. - // 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. - if (looped) { - memcpy(ptr, "\x00\xf0\x13\x7d\x30\00", 6); ptr += 6; // maybe_jump - memcpy(ptr, "\x00\x00", 2); ptr += 2; // cmd -> 0 means always jump - memcpy(ptr, "\x00\x00\x00\x00", 4); ptr += 4; // track -> 0 (only track) - memcpy(ptr, "\x00\x00\x00\x01", 4); ptr += 4; // beat -> 1 (first beat) - memcpy(ptr, "\x00\x00\x00\x01", 4); ptr += 4; // tick -> 1 - memcpy(ptr, "\x00\xf7", 2); ptr += 2; // SysEx end marker - } - - // Insert end of song META - memcpy(ptr, "\x00\xff\x2f\x00\x00", 5); ptr += 5; - - assert(ptr <= start_ptr + total_size); - - // Rewrite MIDI header, this time with true size - total_size = ptr - start_ptr; - ptr = writeMIDIHeader(start_ptr, "GMD ", ppqn, total_size); -#endif -} - -void ScummEngine::convertADResource(int type, int idx, byte *src_ptr, int size) { - - // We will ignore the PPQN in the original resource, because - // it's invalid anyway. We use a constant PPQN of 480. - const int ppqn = 480; - uint32 dw; - int i, ch; - byte *ptr; - int total_size = kMIDIHeaderSize + 7 + 8 * sizeof(ADLIB_INSTR_MIDI_HACK) + size; - total_size += 24; // Up to 24 additional bytes are needed for the jump sysex - - ptr = createResource(type, idx, total_size); - - src_ptr += 2; - size -= 2; - - // 0x80 marks a music resource. Otherwise it's a SFX - if (*src_ptr == 0x80) { - byte ticks, play_once; - byte num_instr; - byte *channel, *instr, *track; - - ptr = writeMIDIHeader(ptr, "ADL ", ppqn, total_size); - - // The "speed" of the song - ticks = *(src_ptr + 1); - - // Flag that tells us whether we should loop the song (0) or play it only once (1) - play_once = *(src_ptr + 2); - - // Number of instruments used - num_instr = *(src_ptr + 8); // Normally 8 - - // copy the pointer to instrument data - channel = src_ptr + 9; - instr = src_ptr + 0x11; - - // skip over the rest of the header and copy the MIDI data into a buffer - src_ptr += 0x11 + 8 * 16; - size -= 0x11 + 8 * 16; - - CHECK_HEAP - - track = src_ptr; - - // Convert the ticks into a MIDI tempo. - // Unfortunate LOOM and INDY3 have different interpretation - // of the ticks value. - if (_gameId == GID_INDY3) { - // Note: since we fix ppqn at 480, ppqn/473 is almost 1 - dw = 500000 * 256 / 473 * ppqn / ticks; - } else if (_gameId == GID_LOOM) { - dw = 500000 * ppqn / 4 / ticks; - } else { - dw = 500000 * 256 / ticks; - } - debugC(DEBUG_SOUND, " ticks = %d, speed = %ld", ticks, dw); - - // Write a tempo change Meta event - memcpy(ptr, "\x00\xFF\x51\x03", 4); ptr += 4; - *ptr++ = (byte)((dw >> 16) & 0xFF); - *ptr++ = (byte)((dw >> 8) & 0xFF); - *ptr++ = (byte)(dw & 0xFF); - - // Copy our hardcoded instrument table into it - // Then, convert the instrument table as given in this song resource - // And write it *over* the hardcoded table. - // Note: we deliberately. - - /* now fill in the instruments */ - for (i = 0; i < num_instr; i++) { - ch = channel[i] - 1; - if (ch < 0 || ch > 15) - continue; - - if (instr[i*16 + 13]) - warning("Sound %d instrument %d uses percussion", idx, i); - - debugC(DEBUG_SOUND, "Sound %d: instrument %d on channel %d.", idx, i, ch); - - memcpy(ptr, ADLIB_INSTR_MIDI_HACK, sizeof(ADLIB_INSTR_MIDI_HACK)); - - ptr[5] += ch; - ptr[28] += ch; - ptr[92] += ch; - - /* flags_1 */ - ptr[30 + 0] = (instr[i * 16 + 3] >> 4) & 0xf; - ptr[30 + 1] = instr[i * 16 + 3] & 0xf; - - /* oplvl_1 */ - ptr[30 + 2] = (instr[i * 16 + 4] >> 4) & 0xf; - ptr[30 + 3] = instr[i * 16 + 4] & 0xf; - - /* atdec_1 */ - ptr[30 + 4] = ((~instr[i * 16 + 5]) >> 4) & 0xf; - ptr[30 + 5] = (~instr[i * 16 + 5]) & 0xf; - - /* sustrel_1 */ - ptr[30 + 6] = ((~instr[i * 16 + 6]) >> 4) & 0xf; - ptr[30 + 7] = (~instr[i * 16 + 6]) & 0xf; - - /* waveform_1 */ - ptr[30 + 8] = (instr[i * 16 + 7] >> 4) & 0xf; - ptr[30 + 9] = instr[i * 16 + 7] & 0xf; - - /* flags_2 */ - ptr[30 + 10] = (instr[i * 16 + 8] >> 4) & 0xf; - ptr[30 + 11] = instr[i * 16 + 8] & 0xf; - - /* oplvl_2 */ - ptr[30 + 12] = (instr[i * 16 + 9] >> 4) & 0xf; - ptr[30 + 13] = instr[i * 16 + 9] & 0xf; - - /* atdec_2 */ - ptr[30 + 14] = ((~instr[i * 16 + 10]) >> 4) & 0xf; - ptr[30 + 15] = (~instr[i * 16 + 10]) & 0xf; - - /* sustrel_2 */ - ptr[30 + 16] = ((~instr[i * 16 + 11]) >> 4) & 0xf; - ptr[30 + 17] = (~instr[i * 16 + 11]) & 0xf; - - /* waveform_2 */ - ptr[30 + 18] = (instr[i * 16 + 12] >> 4) & 0xf; - ptr[30 + 19] = instr[i * 16 + 12] & 0xf; - - /* feedback */ - ptr[30 + 20] = (instr[i * 16 + 2] >> 4) & 0xf; - ptr[30 + 21] = instr[i * 16 + 2] & 0xf; - ptr += sizeof(ADLIB_INSTR_MIDI_HACK); - } - - // There is a constant delay of ppqn/3 before the music starts. - if (ppqn / 3 >= 128) - *ptr++ = (ppqn / 3 >> 7) | 0x80; - *ptr++ = ppqn / 3 & 0x7f; - - // Now copy the actual music data - memcpy(ptr, track, size); - ptr += size; - - if (!play_once) { - // The song is meant to be looped. We achieve this by inserting just - // before the song end a jump to the song start. More precisely we abuse - // a S&M sysex, "maybe_jump" to achieve this effect. We could also - // use a set_loop sysex, but it's a bit longer, a little more complicated, - // and has no advantage either. - - // First, find the track end - byte *end = ptr; - ptr -= size; - for (; ptr < end; ptr++) { - if (*ptr == 0xff && *(ptr + 1) == 0x2f) - break; - } - assert(ptr < end); - - // Now insert the jump. The jump offset is measured in ticks. - // We have ppqn/3 ticks before the first note. - - const int jump_offset = ppqn / 3; - memcpy(ptr, "\xf0\x13\x7d\x30\00", 5); ptr += 5; // maybe_jump - memcpy(ptr, "\x00\x00", 2); ptr += 2; // cmd -> 0 means always jump - memcpy(ptr, "\x00\x00\x00\x00", 4); ptr += 4; // track -> there is only one track, 0 - memcpy(ptr, "\x00\x00\x00\x01", 4); ptr += 4; // beat -> for now, 1 (first beat) - // Ticks - *ptr++ = (byte)((jump_offset >> 12) & 0x0F); - *ptr++ = (byte)((jump_offset >> 8) & 0x0F); - *ptr++ = (byte)((jump_offset >> 4) & 0x0F); - *ptr++ = (byte)(jump_offset & 0x0F); - memcpy(ptr, "\x00\xf7", 2); ptr += 2; // sysex end marker - } - } else { - - /* This is a sfx resource. First parse it quickly to find the parallel - * tracks. - */ - ptr = writeMIDIHeader(ptr, "ASFX", ppqn, total_size); - - byte current_instr[3][14]; - int current_note[3]; - int track_time[3]; - byte *track_data[3]; - - int track_ctr = 0; - byte chunk_type = 0; - int delay, delay2, olddelay; - - // Write a tempo change Meta event - // 473 / 4 Hz, convert to micro seconds. - dw = 1000000 * ppqn * 4 / 473; - memcpy(ptr, "\x00\xFF\x51\x03", 4); ptr += 4; - *ptr++ = (byte)((dw >> 16) & 0xFF); - *ptr++ = (byte)((dw >> 8) & 0xFF); - *ptr++ = (byte)(dw & 0xFF); - - for (i = 0; i < 3; i++) { - track_time[i] = -1; - current_note[i] = -1; - } - while (size > 0) { - assert(track_ctr < 3); - track_data[track_ctr] = src_ptr; - track_time[track_ctr] = 0; - track_ctr++; - while (size > 0) { - chunk_type = *(src_ptr); - if (chunk_type == 1) { - src_ptr += 15; - size -= 15; - } else if (chunk_type == 2) { - src_ptr += 11; - size -= 11; - } else if (chunk_type == 0x80) { - src_ptr ++; - size --; - } else { - break; - } - } - if (chunk_type == 0xff) - break; - src_ptr++; - } - - int curtime = 0; - for (;;) { - int mintime = -1; - ch = -1; - for (i = 0; i < 3; i++) { - if (track_time[i] >= 0 && - (mintime == -1 || mintime > track_time[i])) { - mintime = track_time[i]; - ch = i; - } - } - if (mintime < 0) - break; - - src_ptr = track_data[ch]; - chunk_type = *src_ptr; - - if (current_note[ch] >= 0) { - delay = mintime - curtime; - curtime = mintime; - ptr = writeVLQ(ptr, delay); - *ptr++ = 0x80 + ch; // key off channel; - *ptr++ = current_note[ch]; - *ptr++ = 0; - current_note[ch] = -1; - } - - switch (chunk_type) { - case 1: - /* Instrument definition */ - memcpy(current_instr[ch], src_ptr + 1, 14); - src_ptr += 15; - break; - - case 2: - /* tone/parammodulation */ - memcpy(ptr, ADLIB_INSTR_MIDI_HACK, - sizeof(ADLIB_INSTR_MIDI_HACK)); - - ptr[5] += ch; - ptr[28] += ch; - ptr[92] += ch; - - /* flags_1 */ - ptr[30 + 0] = (current_instr[ch][3] >> 4) & 0xf; - ptr[30 + 1] = current_instr[ch][3] & 0xf; - - /* oplvl_1 */ - ptr[30 + 2] = (current_instr[ch][4] >> 4) & 0xf; - ptr[30 + 3] = current_instr[ch][4] & 0xf; - - /* atdec_1 */ - ptr[30 + 4] = ((~current_instr[ch][5]) >> 4) & 0xf; - ptr[30 + 5] = (~current_instr[ch][5]) & 0xf; - - /* sustrel_1 */ - ptr[30 + 6] = ((~current_instr[ch][6]) >> 4) & 0xf; - ptr[30 + 7] = (~current_instr[ch][6]) & 0xf; - - /* waveform_1 */ - ptr[30 + 8] = (current_instr[ch][7] >> 4) & 0xf; - ptr[30 + 9] = current_instr[ch][7] & 0xf; - - /* flags_2 */ - ptr[30 + 10] = (current_instr[ch][8] >> 4) & 0xf; - ptr[30 + 11] = current_instr[ch][8] & 0xf; - - /* oplvl_2 */ - ptr[30 + 12] = ((current_instr[ch][9]) >> 4) & 0xf; - ptr[30 + 13] = (current_instr[ch][9]) & 0xf; - - /* atdec_2 */ - ptr[30 + 14] = ((~current_instr[ch][10]) >> 4) & 0xf; - ptr[30 + 15] = (~current_instr[ch][10]) & 0xf; - - /* sustrel_2 */ - ptr[30 + 16] = ((~current_instr[ch][11]) >> 4) & 0xf; - ptr[30 + 17] = (~current_instr[ch][11]) & 0xf; - - /* waveform_2 */ - ptr[30 + 18] = (current_instr[ch][12] >> 4) & 0xf; - ptr[30 + 19] = current_instr[ch][12] & 0xf; - - /* feedback */ - ptr[30 + 20] = (current_instr[ch][2] >> 4) & 0xf; - ptr[30 + 21] = current_instr[ch][2] & 0xf; - - delay = mintime - curtime; - curtime = mintime; - - { - delay = convert_extraflags(ptr + 30 + 22, src_ptr + 1); - delay2 = convert_extraflags(ptr + 30 + 40, src_ptr + 6); - debugC(DEBUG_SOUND, "delays: %d / %d", delay, delay2); - if (delay2 >= 0 && delay2 < delay) - delay = delay2; - if (delay == -1) - delay = 0; - } - - /* duration */ - ptr[30 + 58] = 0; // ((delay * 17 / 63) >> 4) & 0xf; - ptr[30 + 59] = 0; // (delay * 17 / 63) & 0xf; - - ptr += sizeof(ADLIB_INSTR_MIDI_HACK); - - olddelay = mintime - curtime; - curtime = mintime; - ptr = writeVLQ(ptr, olddelay); - - { - int freq = ((current_instr[ch][1] & 3) << 8) - | current_instr[ch][0]; - if (!freq) - freq = 0x80; - freq <<= ((current_instr[ch][1] >> 2) & 7) + 1; - int note = -11; - while (freq >= 0x100) { - note += 12; - freq >>= 1; - } - debugC(DEBUG_SOUND, "Freq: %d (%x) Note: %d", freq, freq, note); - if (freq < 0x80) - note = 0; - else - note += freq2note[freq - 0x80]; - - debugC(DEBUG_SOUND, "Note: %d", note); - if (note <= 0) - note = 1; - else if (note > 127) - note = 127; - - // Insert a note on event - *ptr++ = 0x90 + ch; // key on channel - *ptr++ = note; - *ptr++ = 63; - current_note[ch] = note; - track_time[ch] = curtime + delay; - } - src_ptr += 11; - break; - - case 0x80: - track_time[ch] = -1; - src_ptr ++; - break; - - default: - track_time[ch] = -1; - } - track_data[ch] = src_ptr; - } - } - - // Insert end of song sysex - memcpy(ptr, "\x00\xff\x2f\x00\x00", 5); ptr += 5; -} - - -int ScummEngine::readSoundResourceSmallHeader(int type, int idx) { - uint32 pos, total_size, size, tag; - uint32 ad_size = 0, ad_offs = 0; - uint32 ro_size = 0, ro_offs = 0; - uint32 wa_size = 0, wa_offs = 0; - - debug(4, "readSoundResourceSmallHeader(%s,%d)", resTypeFromId(type), idx); - - if ((_gameId == GID_LOOM) && (_features & GF_PC) && VAR(VAR_SOUNDCARD) == 4) { - // Roland resources in Loom are tagless - // So we add an RO tag to allow imuse to detect format - byte *ptr, *src_ptr; - ro_offs = _fileHandle.pos(); - ro_size = _fileHandle.readUint16LE(); - - src_ptr = (byte *) calloc(ro_size - 4, 1); - _fileHandle.seek(ro_offs + 4, SEEK_SET); - _fileHandle.read(src_ptr, ro_size -4); - - ptr = createResource(type, idx, ro_size + 2); - memcpy(ptr, "RO", 2); ptr += 2; - memcpy(ptr, src_ptr, ro_size - 4); ptr += ro_size - 4; - return 1; - } else if (_features & GF_OLD_BUNDLE) { - wa_offs = _fileHandle.pos(); - wa_size = _fileHandle.readUint16LE(); - _fileHandle.seek(wa_size - 2, SEEK_CUR); - - if (!(_features & GF_ATARI_ST || _features & GF_MACINTOSH)) { - ad_offs = _fileHandle.pos(); - ad_size = _fileHandle.readUint16LE(); - } - _fileHandle.seek(4, SEEK_CUR); - total_size = wa_size + ad_size; - } else { - total_size = size = _fileHandle.readUint32LE(); - tag = _fileHandle.readUint16LE(); - debug(4, " tag='%c%c', size=%d", (char) (tag & 0xff), - (char) ((tag >> 8) & 0xff), size); - - if (tag == 0x4F52) { // RO - ro_offs = _fileHandle.pos(); - ro_size = size; - } else { - pos = 6; - while (pos < total_size) { - size = _fileHandle.readUint32LE(); - tag = _fileHandle.readUint16LE(); - debug(4, " tag='%c%c', size=%d", (char) (tag & 0xff), - (char) ((tag >> 8) & 0xff), size); - pos += size; - - // MI1 and Indy3 uses one or more nested SO resources, which contains AD and WA - // resources. - if ((tag == 0x4441) && !(ad_offs)) { // AD - ad_size = size; - ad_offs = _fileHandle.pos(); - } else if ((tag == 0x4157) && !(wa_offs)) { // WA - wa_size = size; - wa_offs = _fileHandle.pos(); - } else { // other AD, WA and nested SO resources - if (tag == 0x4F53) { // SO - pos -= size; - size = 6; - pos += 6; - } - } - _fileHandle.seek(size - 6, SEEK_CUR); - } - } - } - - if ((_midiDriver == MD_ADLIB) && ad_offs != 0) { - // AD resources have a header, instrument definitions and one MIDI track. - // We build an 'ADL ' resource from that: - // 8 bytes resource header - // 16 bytes MDhd header - // 14 bytes MThd header - // 8 bytes MTrk header - // 7 bytes MIDI tempo sysex - // + some default instruments - byte *ptr; - if (_features & GF_OLD_BUNDLE) { - ptr = (byte *) calloc(ad_size - 4, 1); - _fileHandle.seek(ad_offs + 4, SEEK_SET); - _fileHandle.read(ptr, ad_size - 4); - convertADResource(type, idx, ptr, ad_size - 4); - free(ptr); - return 1; - } else { - ptr = (byte *) calloc(ad_size - 6, 1); - _fileHandle.seek(ad_offs, SEEK_SET); - _fileHandle.read(ptr, ad_size - 6); - convertADResource(type, idx, ptr, ad_size - 6); - free(ptr); - return 1; - } - } else if (((_midiDriver == MD_PCJR) || (_midiDriver == MD_PCSPK)) && wa_offs != 0) { - if (_features & GF_OLD_BUNDLE) { - _fileHandle.seek(wa_offs, SEEK_SET); - _fileHandle.read(createResource(type, idx, wa_size), wa_size); - } else { - _fileHandle.seek(wa_offs - 6, SEEK_SET); - _fileHandle.read(createResource(type, idx, wa_size + 6), wa_size + 6); - } - return 1; - } else if (ro_offs != 0) { - _fileHandle.seek(ro_offs - 2, SEEK_SET); - _fileHandle.read(createResource(type, idx, ro_size - 4), ro_size - 4); - return 1; - } - res.roomoffs[type][idx] = 0xFFFFFFFF; - return 0; -} - int ScummEngine::getResourceRoomNr(int type, int idx) { if (type == rtRoom && _heversion < 70) return idx; diff --git a/scumm/scumm.cpp b/scumm/scumm.cpp index 18f746f73c..d3a5ad94d7 100644 --- a/scumm/scumm.cpp +++ b/scumm/scumm.cpp @@ -2545,91 +2545,6 @@ void ScummEngine::errorString(const char *buf1, char *buf2) { } } -#pragma mark - -#pragma mark --- Utilities --- -#pragma mark - - -void checkRange(int max, int min, int no, const char *str) { - if (no < min || no > max) { - char buf[256]; - snprintf(buf, sizeof(buf), str, no); - error("Value %d is out of bounds (%d,%d) (%s)", no, min, max, buf); - } -} - -/** - * Convert an old style direction to a new style one (angle), - */ -int newDirToOldDir(int dir) { - if (dir >= 71 && dir <= 109) - return 1; - if (dir >= 109 && dir <= 251) - return 2; - if (dir >= 251 && dir <= 289) - return 0; - return 3; -} - -/** - * Convert an new style (angle) direction to an old style one. - */ -int oldDirToNewDir(int dir) { - assert(0 <= dir && dir <= 3); - const int new_dir_table[4] = { 270, 90, 180, 0 }; - return new_dir_table[dir]; -} - -/** - * Convert an angle to a simple direction. - */ -int toSimpleDir(int dirType, int dir) { - if (dirType) { - const int16 directions[] = { 22, 72, 107, 157, 202, 252, 287, 337 }; - for (int i = 0; i < 7; i++) - if (dir >= directions[i] && dir <= directions[i+1]) - return i+1; - } else { - const int16 directions[] = { 71, 109, 251, 289 }; - for (int i = 0; i < 3; i++) - if (dir >= directions[i] && dir <= directions[i+1]) - return i+1; - } - - return 0; -} - -/** - * Convert a simple direction to an angle. - */ -int fromSimpleDir(int dirType, int dir) { - if (dirType) - return dir * 45; - else - return dir * 90; -} - -/** - * Normalize the given angle - that means, ensure it is positive, and - * change it to the closest multiple of 45 degree by abusing toSimpleDir. - */ -int normalizeAngle(int angle) { - int temp; - - temp = (angle + 360) % 360; - - return toSimpleDir(1, temp) * 45; -} - -const char *tag2str(uint32 tag) { - static char str[5]; - str[0] = (char)(tag >> 24); - str[1] = (char)(tag >> 16); - str[2] = (char)(tag >> 8); - str[3] = (char)tag; - str[4] = '\0'; - return str; -} - } // End of namespace Scumm using namespace Scumm; diff --git a/scumm/scumm.h b/scumm/scumm.h index 79ece5d928..df1fddfa7b 100644 --- a/scumm/scumm.h +++ b/scumm/scumm.h @@ -31,6 +31,7 @@ #include "scumm/gfx.h" #include "scumm/script.h" +#include "scumm/util.h" #include "scumm/wiz_he.h" namespace GUI { @@ -61,29 +62,6 @@ struct ScummGameSettings; typedef Common::Map ObjectIDMap; -class ScummFile : public File { -private: - byte _encbyte; - uint32 _subFileStart; - uint32 _subFileLen; -public: - ScummFile(); - void setEnc(byte value); - - void setSubfileRange(uint32 start, uint32 len); - void resetSubfile(); - - bool open(const char *filename, AccessMode mode = kFileReadMode); - bool openSubFile(const char *filename); - - bool eof(); - uint32 pos(); - uint32 size(); - void seek(int32 offs, int whence = SEEK_SET); - uint32 read(void *ptr, uint32 size); - uint32 write(const void *ptr, uint32 size); -}; - // Use g_scumm from error() ONLY extern ScummEngine *g_scumm; @@ -1333,21 +1311,6 @@ public: byte VAR_WIZ_TCOLOR; }; -// This is a constant lookup table of reverse bit masks -extern const byte revBitMask[8]; - -/* Direction conversion functions (between old dir and new dir format) */ -int newDirToOldDir(int dir); -int oldDirToNewDir(int dir); - -int normalizeAngle(int angle); -int fromSimpleDir(int dirtype, int dir); -int toSimpleDir(int dirtype, int dir); - -void checkRange(int max, int min, int no, const char *str); - -const char *tag2str(uint32 tag); - } // End of namespace Scumm #endif diff --git a/scumm/sound.cpp b/scumm/sound.cpp index 720909480b..a54f45d957 100644 --- a/scumm/sound.cpp +++ b/scumm/sound.cpp @@ -1195,4 +1195,1050 @@ const SaveLoadEntry *Sound::getSaveLoadEntries() { return soundEntries; } + +#pragma mark - +#pragma mark --- Sound resource handling --- +#pragma mark - + +/* + * TODO: The way we handle sound/music resources really is one huge hack. + * We probably should reconsider how we do this, and maybe come up with a + * better/cleaner solution. Even if we keep the existing code, it really + * could stand a thorough cleanup! + */ + + +int ScummEngine::readSoundResource(int type, int idx) { + uint32 pos, total_size, size, tag, basetag, max_total_size; + int pri, best_pri; + uint32 best_size = 0, best_offs = 0; + + debugC(DEBUG_RESOURCE, "readSoundResource(%d)", idx); + + pos = 0; + + _fileHandle.readUint32LE(); + max_total_size = _fileHandle.readUint32BE() - 8; + basetag = fileReadDword(); + total_size = _fileHandle.readUint32BE(); + + debugC(DEBUG_RESOURCE, " basetag: %s, total_size=%d", tag2str(TO_BE_32(basetag)), total_size); + + if (basetag == MKID('MIDI') || basetag == MKID('iMUS')) { + if (_midiDriver != MD_PCSPK && _midiDriver != MD_PCJR) { + _fileHandle.seek(-8, SEEK_CUR); + _fileHandle.read(createResource(type, idx, total_size + 8), total_size + 8); + return 1; + } + } else if (basetag == MKID('SOU ')) { + best_pri = -1; + while (pos < total_size) { + tag = fileReadDword(); + size = _fileHandle.readUint32BE() + 8; + pos += size; + + pri = -1; + + switch (tag) { + case MKID('TOWS'): + pri = 16; + break; + case MKID('SBL '): + pri = 15; + break; + case MKID('ADL '): + pri = 1; + if (_midiDriver == MD_ADLIB) + pri = 10; + break; + case MKID('AMI '): + pri = 3; + break; + case MKID('ROL '): + pri = 3; + if (_native_mt32) + pri = 5; + break; + case MKID('GMD '): + pri = 4; + break; + case MKID('MAC '): + pri = 2; + break; + case MKID('SPK '): + pri = -1; +// if (_midiDriver == MD_PCSPK) +// pri = 11; + break; + } + + if ((_midiDriver == MD_PCSPK || _midiDriver == MD_PCJR) && pri != 11) + pri = -1; + + debugC(DEBUG_RESOURCE, " tag: %s, total_size=%d, pri=%d", tag2str(TO_BE_32(tag)), size, pri); + + + if (pri > best_pri) { + best_pri = pri; + best_size = size; + best_offs = _fileHandle.pos(); + } + + _fileHandle.seek(size - 8, SEEK_CUR); + } + + if (best_pri != -1) { + _fileHandle.seek(best_offs - 8, SEEK_SET); + _fileHandle.read(createResource(type, idx, best_size), best_size); + return 1; + } + } else if (basetag == MKID('Mac0')) { + _fileHandle.seek(-12, SEEK_CUR); + total_size = _fileHandle.readUint32BE() - 8; + byte *ptr = (byte *)calloc(total_size, 1); + _fileHandle.read(ptr, total_size); +// dumpResource("sound-", idx, ptr); + convertMac0Resource(type, idx, ptr, total_size); + free(ptr); + return 1; + } else if (basetag == MKID('Mac1')) { + _fileHandle.seek(-12, SEEK_CUR); + total_size = _fileHandle.readUint32BE(); + _fileHandle.read(createResource(type, idx, total_size), total_size - 8); + return 1; + } else if (basetag == MKID('HSHD')) { + _fileHandle.seek(-12, SEEK_CUR); + total_size = _fileHandle.readUint32BE(); + _fileHandle.read(createResource(type, idx, total_size), total_size - 8); + return 1; + } else if (basetag == MKID('TALK')) { + _fileHandle.seek(-12, SEEK_CUR); + total_size = _fileHandle.readUint32BE(); + _fileHandle.read(createResource(type, idx, total_size), total_size - 8); + return 1; + } else if (basetag == MKID('DIGI')) { + _fileHandle.seek(-12, SEEK_CUR); + total_size = _fileHandle.readUint32BE(); + _fileHandle.read(createResource(type, idx, total_size), total_size - 8); + return 1; + } else if (basetag == MKID('FMUS')) { + // Used in 3DO version of puttputt joins the parade and probably others + // Specifies a separate file to be used for music from what I gather. + int tmpsize; + File dmuFile; + char buffer[128]; + debugC(DEBUG_SOUND, "Found base tag FMUS in sound %d, size %d", idx, total_size); + debugC(DEBUG_SOUND, "It was at position %d", _fileHandle.pos()); + + _fileHandle.seek(4, SEEK_CUR); + // HSHD size + tmpsize = _fileHandle.readUint32BE(); + // skip to size part of the SDAT block + _fileHandle.seek(tmpsize - 4, SEEK_CUR); + // SDAT size + tmpsize = _fileHandle.readUint32BE(); + + // SDAT contains name of file we want + _fileHandle.read(buffer, tmpsize - 8); + // files seem to be 11 chars (8.3) unused space is replaced by spaces + *(strstr(buffer, " ")) = '\0'; + + debugC(DEBUG_SOUND, "FMUS file %s", buffer); + if (dmuFile.open(buffer) == false) { + warning("Can't open music file %s*", buffer); + res.roomoffs[type][idx] = 0xFFFFFFFF; + return 0; + } + dmuFile.seek(4, SEEK_SET); + total_size = dmuFile.readUint32BE(); + debugC(DEBUG_SOUND, "dmu file size %d", total_size); + dmuFile.seek(-8, SEEK_CUR); + dmuFile.read(createResource(type, idx, total_size), total_size); + dmuFile.close(); + return 1; + } else if (basetag == MKID('Crea')) { + _fileHandle.seek(-12, SEEK_CUR); + total_size = _fileHandle.readUint32BE(); + _fileHandle.read(createResource(type, idx, total_size), total_size - 8); + return 1; + } else if (FROM_LE_32(basetag) == max_total_size) { + _fileHandle.seek(-12, SEEK_CUR); + total_size = _fileHandle.readUint32BE(); + _fileHandle.seek(-8, SEEK_CUR); + _fileHandle.read(createResource(type, idx, total_size), total_size); + return 1; + } else { + warning("Unrecognized base tag 0x%08x in sound %d", basetag, idx); + } + res.roomoffs[type][idx] = 0xFFFFFFFF; + return 0; +} + +// Adlib MIDI-SYSEX to set MIDI instruments for small header games. +static byte ADLIB_INSTR_MIDI_HACK[95] = { + 0x00, 0xf0, 0x14, 0x7d, 0x00, // sysex 00: part on/off + 0x00, 0x00, 0x03, // part/channel (offset 5) + 0x00, 0x00, 0x07, 0x0f, 0x00, 0x00, 0x08, 0x00, + 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0xf7, + 0x00, 0xf0, 0x41, 0x7d, 0x10, // sysex 16: set instrument + 0x00, 0x01, // part/channel (offset 28) + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xf7, + 0x00, 0xb0, 0x07, 0x64 // Controller 7 = 100 (offset 92) +}; + +static const byte map_param[7] = { + 0, 2, 3, 4, 8, 9, 0, +}; + +static const byte freq2note[128] = { + /*128*/ 6, 6, 6, 6, + /*132*/ 7, 7, 7, 7, 7, 7, 7, + /*139*/ 8, 8, 8, 8, 8, 8, 8, 8, 8, + /*148*/ 9, 9, 9, 9, 9, 9, 9, 9, 9, + /*157*/ 10, 10, 10, 10, 10, 10, 10, 10, 10, + /*166*/ 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, + /*176*/ 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, + /*186*/ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + /*197*/ 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, + /*209*/ 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + /*222*/ 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + /*235*/ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, + /*249*/ 18, 18, 18, 18, 18, 18, 18 +}; + +static const uint16 num_steps_table[] = { + 1, 2, 4, 5, + 6, 7, 8, 9, + 10, 12, 14, 16, + 18, 21, 24, 30, + 36, 50, 64, 82, + 100, 136, 160, 192, + 240, 276, 340, 460, + 600, 860, 1200, 1600 +}; + +int ScummEngine::convert_extraflags(byte * ptr, byte * src_ptr) { + int flags = src_ptr[0]; + + int t1, t2, t3, t4, time; + int v1, v2, v3; + + if (!(flags & 0x80)) + return -1; + + t1 = (src_ptr[1] & 0xf0) >> 3; + t2 = (src_ptr[2] & 0xf0) >> 3; + t3 = (src_ptr[3] & 0xf0) >> 3 | (flags & 0x40 ? 0x80 : 0); + t4 = (src_ptr[3] & 0x0f) << 1; + v1 = (src_ptr[1] & 0x0f); + v2 = (src_ptr[2] & 0x0f); + v3 = 31; + if ((flags & 0x7) == 0) { + v1 = v1 + 31 + 8; + v2 = v2 + 31 + 8; + } else { + v1 = v1 * 2 + 31; + v2 = v2 * 2 + 31; + } + + /* flags a */ + if ((flags & 0x7) == 6) + ptr[0] = 0; + else { + ptr[0] = (flags >> 4) & 0xb; + ptr[1] = map_param[flags & 0x7]; + } + + /* extra a */ + ptr[2] = 0; + ptr[3] = 0; + ptr[4] = t1 >> 4; + ptr[5] = t1 & 0xf; + ptr[6] = v1 >> 4; + ptr[7] = v1 & 0xf; + ptr[8] = t2 >> 4; + ptr[9] = t2 & 0xf; + ptr[10] = v2 >> 4; + ptr[11] = v2 & 0xf; + ptr[12] = t3 >> 4; + ptr[13] = t3 & 0xf; + ptr[14] = t4 >> 4; + ptr[15] = t4 & 0xf; + ptr[16] = v3 >> 4; + ptr[17] = v3 & 0xf; + + time = num_steps_table[t1] + num_steps_table[t2] + + num_steps_table[t3 & 0x7f] + num_steps_table[t4]; + if (flags & 0x20) { + int playtime = ((src_ptr[4] >> 4) & 0xf) * 118 + + (src_ptr[4] & 0xf) * 8; + if (playtime > time) + time = playtime; + } + /* + time = ((src_ptr[4] >> 4) & 0xf) * 118 + + (src_ptr[4] & 0xf) * 8; + */ + return time; +} + +#define kMIDIHeaderSize 46 +static inline byte *writeMIDIHeader(byte *ptr, const char *type, int ppqn, int total_size) { + uint32 dw = TO_BE_32(total_size); + + memcpy(ptr, type, 4); ptr += 4; + memcpy(ptr, &dw, 4); ptr += 4; + memcpy(ptr, "MDhd", 4); ptr += 4; + ptr[0] = 0; ptr[1] = 0; ptr[2] = 0; ptr[3] = 8; + ptr += 4; + memset(ptr, 0, 8), ptr += 8; + memcpy(ptr, "MThd", 4); ptr += 4; + ptr[0] = 0; ptr[1] = 0; ptr[2] = 0; ptr[3] = 6; + ptr += 4; + ptr[0] = 0; ptr[1] = 0; ptr[2] = 0; ptr[3] = 1; // MIDI format 0 with 1 track + ptr += 4; + + *ptr++ = ppqn >> 8; + *ptr++ = ppqn & 0xFF; + + memcpy(ptr, "MTrk", 4); ptr += 4; + memcpy(ptr, &dw, 4); ptr += 4; + + return ptr; +} + +static inline byte *writeVLQ(byte *ptr, int value) { + if (value > 0x7f) { + if (value > 0x3fff) { + *ptr++ = (value >> 14) | 0x80; + value &= 0x3fff; + } + *ptr++ = (value >> 7) | 0x80; + value &= 0x7f; + } + *ptr++ = value; + return ptr; +} + +static inline byte Mac0ToGMInstrument(uint32 type, int &transpose) { + transpose = 0; + switch (type) { + case MKID('MARI'): return 12; + case MKID('PLUC'): return 45; + case MKID('HARM'): return 22; + case MKID('PIPE'): return 19; + case MKID('TROM'): transpose = -12; return 57; + case MKID('STRI'): return 48; + case MKID('HORN'): return 60; + case MKID('VIBE'): return 11; + case MKID('SHAK'): return 77; + case MKID('PANP'): return 75; + case MKID('WHIS'): return 76; + case MKID('ORGA'): return 17; + case MKID('BONG'): return 115; + case MKID('BASS'): transpose = -24; return 35; + default: + error("Unknown Mac0 instrument %s found", tag2str(type)); + } +} + +void ScummEngine::convertMac0Resource(int type, int idx, byte *src_ptr, int size) { + /* + 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 + --- + + Instruments (General Midi): + "MARI" - Marimba (12) + "PLUC" - Pizzicato Strings (45) + "HARM" - Harmonica (22) + "PIPE" - Church Organ? (19) or Flute? (73) or Bag Pipe (109) + "TROM" - Trombone (57) + "STRI" - String Ensemble (48 or 49) + "HORN" - French Horn? (60) or English Horn? (69) + "VIBE" - Vibraphone (11) + "SHAK" - Shakuhachi? (77) + "PANP" - Pan Flute (75) + "WHIS" - Whistle (78) / Bottle (76) + "ORGA" - Drawbar Organ (16; but could also be 17-20) + "BONG" - Woodblock? (115) + "BASS" - Bass (32-39) + + + Now the task could be to convert this into MIDI, to be fed into iMuse. + Or we do something similiar to what is done in Player_V3, assuming + we can identify SFX in the MI datafiles for each of the instruments + listed above. + */ + +#if 0 + byte *ptr = createResource(type, idx, size); + memcpy(ptr, src_ptr, size); +#else + const int ppqn = 480; + byte *ptr, *start_ptr; + + int total_size = 0; + total_size += kMIDIHeaderSize; // Header + total_size += 7; // Tempo META + total_size += 3 * 3; // Three program change mesages + total_size += 22; // Possible jump SysEx + total_size += 5; // EOT META + + int i, len; + byte track_instr[3]; + byte *track_data[3]; + int track_len[3]; + int track_transpose[3]; + bool looped = false; + + src_ptr += 8; + // TODO: Decipher the unknown bytes in the header. For now, skip 'em + src_ptr += 28; + + // Parse the three channels + for (i = 0; i < 3; i++) { + assert(*((uint32*)src_ptr) == MKID('Chan')); + len = READ_BE_UINT32(src_ptr + 4); + track_len[i] = len - 24; + track_instr[i] = Mac0ToGMInstrument(*(uint32*)(src_ptr + 8), track_transpose[i]); + track_data[i] = src_ptr + 12; + src_ptr += len; + looped = (*((uint32*)(src_ptr - 8)) == MKID('Loop')); + + // For each note event, we need up to 6 bytes for the + // Note On (3 VLQ, 3 event), and 6 bytes for the Note + // Off (3 VLQ, 3 event). So 12 bytes total. + total_size += 12 * track_len[i]; + } + assert(*src_ptr == 0x09); + + // Create sound resource + start_ptr = createResource(type, idx, total_size); + + // Insert MIDI header + ptr = writeMIDIHeader(start_ptr, "GMD ", ppqn, total_size); + + // Write a tempo change Meta event + // 473 / 4 Hz, convert to micro seconds. + uint32 dw = 1000000 * 437 / 4 / ppqn; // 1000000 * ppqn * 4 / 473; + memcpy(ptr, "\x00\xFF\x51\x03", 4); ptr += 4; + *ptr++ = (byte)((dw >> 16) & 0xFF); + *ptr++ = (byte)((dw >> 8) & 0xFF); + *ptr++ = (byte)(dw & 0xFF); + + // Insert program change messages + *ptr++ = 0; // VLQ + *ptr++ = 0xC0; + *ptr++ = track_instr[0]; + *ptr++ = 0; // VLQ + *ptr++ = 0xC1; + *ptr++ = track_instr[1]; + *ptr++ = 0; // VLQ + *ptr++ = 0xC2; + *ptr++ = track_instr[2]; + + // And now, the actual composition. Please turn all cell phones + // and pagers off during the performance. Thank you. + uint16 nextTime[3] = { 1, 1, 1 }; + int stage[3] = { 0, 0, 0 }; + + while (track_len[0] | track_len[1] | track_len[2]) { + int best = -1; + uint16 bestTime = 0xFFFF; + for (i = 0; i < 3; ++i) { + if (track_len[i] && nextTime[i] < bestTime) { + bestTime = nextTime[i]; + best = i; + } + } + assert (best != -1); + + if (!stage[best]) { + // We are STARTING this event. + if (track_data[best][2] > 1) { + // Note On + ptr = writeVLQ(ptr, nextTime[best]); + *ptr++ = 0x90 | best; + *ptr++ = track_data[best][2] + track_transpose[best]; + *ptr++ = track_data[best][3] * 127 / 100; // Scale velocity + for (i = 0; i < 3; ++i) + nextTime[i] -= bestTime; + } + nextTime[best] += READ_BE_UINT16 (track_data[best]); + stage[best] = 1; + } else { + // We are ENDING this event. + if (track_data[best][2] > 1) { + // There was a Note On, so do a Note Off + ptr = writeVLQ(ptr, nextTime[best]); + *ptr++ = 0x80 | best; + *ptr++ = track_data[best][2] + track_transpose[best]; + *ptr++ = track_data[best][3] * 127 / 100; // Scale velocity + for (i = 0; i < 3; ++i) + nextTime[i] -= bestTime; + } + track_data[best] += 4; + track_len[best] -= 4; + stage[best] = 0; + } + } + + // Is this a looped song? If so, effect a loop by + // using the S&M maybe_jump SysEx command. + // 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. + if (looped) { + memcpy(ptr, "\x00\xf0\x13\x7d\x30\00", 6); ptr += 6; // maybe_jump + memcpy(ptr, "\x00\x00", 2); ptr += 2; // cmd -> 0 means always jump + memcpy(ptr, "\x00\x00\x00\x00", 4); ptr += 4; // track -> 0 (only track) + memcpy(ptr, "\x00\x00\x00\x01", 4); ptr += 4; // beat -> 1 (first beat) + memcpy(ptr, "\x00\x00\x00\x01", 4); ptr += 4; // tick -> 1 + memcpy(ptr, "\x00\xf7", 2); ptr += 2; // SysEx end marker + } + + // Insert end of song META + memcpy(ptr, "\x00\xff\x2f\x00\x00", 5); ptr += 5; + + assert(ptr <= start_ptr + total_size); + + // Rewrite MIDI header, this time with true size + total_size = ptr - start_ptr; + ptr = writeMIDIHeader(start_ptr, "GMD ", ppqn, total_size); +#endif +} + +void ScummEngine::convertADResource(int type, int idx, byte *src_ptr, int size) { + + // We will ignore the PPQN in the original resource, because + // it's invalid anyway. We use a constant PPQN of 480. + const int ppqn = 480; + uint32 dw; + int i, ch; + byte *ptr; + int total_size = kMIDIHeaderSize + 7 + 8 * sizeof(ADLIB_INSTR_MIDI_HACK) + size; + total_size += 24; // Up to 24 additional bytes are needed for the jump sysex + + ptr = createResource(type, idx, total_size); + + src_ptr += 2; + size -= 2; + + // 0x80 marks a music resource. Otherwise it's a SFX + if (*src_ptr == 0x80) { + byte ticks, play_once; + byte num_instr; + byte *channel, *instr, *track; + + ptr = writeMIDIHeader(ptr, "ADL ", ppqn, total_size); + + // The "speed" of the song + ticks = *(src_ptr + 1); + + // Flag that tells us whether we should loop the song (0) or play it only once (1) + play_once = *(src_ptr + 2); + + // Number of instruments used + num_instr = *(src_ptr + 8); // Normally 8 + + // copy the pointer to instrument data + channel = src_ptr + 9; + instr = src_ptr + 0x11; + + // skip over the rest of the header and copy the MIDI data into a buffer + src_ptr += 0x11 + 8 * 16; + size -= 0x11 + 8 * 16; + + CHECK_HEAP + + track = src_ptr; + + // Convert the ticks into a MIDI tempo. + // Unfortunate LOOM and INDY3 have different interpretation + // of the ticks value. + if (_gameId == GID_INDY3) { + // Note: since we fix ppqn at 480, ppqn/473 is almost 1 + dw = 500000 * 256 / 473 * ppqn / ticks; + } else if (_gameId == GID_LOOM) { + dw = 500000 * ppqn / 4 / ticks; + } else { + dw = 500000 * 256 / ticks; + } + debugC(DEBUG_SOUND, " ticks = %d, speed = %ld", ticks, dw); + + // Write a tempo change Meta event + memcpy(ptr, "\x00\xFF\x51\x03", 4); ptr += 4; + *ptr++ = (byte)((dw >> 16) & 0xFF); + *ptr++ = (byte)((dw >> 8) & 0xFF); + *ptr++ = (byte)(dw & 0xFF); + + // Copy our hardcoded instrument table into it + // Then, convert the instrument table as given in this song resource + // And write it *over* the hardcoded table. + // Note: we deliberately. + + /* now fill in the instruments */ + for (i = 0; i < num_instr; i++) { + ch = channel[i] - 1; + if (ch < 0 || ch > 15) + continue; + + if (instr[i*16 + 13]) + warning("Sound %d instrument %d uses percussion", idx, i); + + debugC(DEBUG_SOUND, "Sound %d: instrument %d on channel %d.", idx, i, ch); + + memcpy(ptr, ADLIB_INSTR_MIDI_HACK, sizeof(ADLIB_INSTR_MIDI_HACK)); + + ptr[5] += ch; + ptr[28] += ch; + ptr[92] += ch; + + /* flags_1 */ + ptr[30 + 0] = (instr[i * 16 + 3] >> 4) & 0xf; + ptr[30 + 1] = instr[i * 16 + 3] & 0xf; + + /* oplvl_1 */ + ptr[30 + 2] = (instr[i * 16 + 4] >> 4) & 0xf; + ptr[30 + 3] = instr[i * 16 + 4] & 0xf; + + /* atdec_1 */ + ptr[30 + 4] = ((~instr[i * 16 + 5]) >> 4) & 0xf; + ptr[30 + 5] = (~instr[i * 16 + 5]) & 0xf; + + /* sustrel_1 */ + ptr[30 + 6] = ((~instr[i * 16 + 6]) >> 4) & 0xf; + ptr[30 + 7] = (~instr[i * 16 + 6]) & 0xf; + + /* waveform_1 */ + ptr[30 + 8] = (instr[i * 16 + 7] >> 4) & 0xf; + ptr[30 + 9] = instr[i * 16 + 7] & 0xf; + + /* flags_2 */ + ptr[30 + 10] = (instr[i * 16 + 8] >> 4) & 0xf; + ptr[30 + 11] = instr[i * 16 + 8] & 0xf; + + /* oplvl_2 */ + ptr[30 + 12] = (instr[i * 16 + 9] >> 4) & 0xf; + ptr[30 + 13] = instr[i * 16 + 9] & 0xf; + + /* atdec_2 */ + ptr[30 + 14] = ((~instr[i * 16 + 10]) >> 4) & 0xf; + ptr[30 + 15] = (~instr[i * 16 + 10]) & 0xf; + + /* sustrel_2 */ + ptr[30 + 16] = ((~instr[i * 16 + 11]) >> 4) & 0xf; + ptr[30 + 17] = (~instr[i * 16 + 11]) & 0xf; + + /* waveform_2 */ + ptr[30 + 18] = (instr[i * 16 + 12] >> 4) & 0xf; + ptr[30 + 19] = instr[i * 16 + 12] & 0xf; + + /* feedback */ + ptr[30 + 20] = (instr[i * 16 + 2] >> 4) & 0xf; + ptr[30 + 21] = instr[i * 16 + 2] & 0xf; + ptr += sizeof(ADLIB_INSTR_MIDI_HACK); + } + + // There is a constant delay of ppqn/3 before the music starts. + if (ppqn / 3 >= 128) + *ptr++ = (ppqn / 3 >> 7) | 0x80; + *ptr++ = ppqn / 3 & 0x7f; + + // Now copy the actual music data + memcpy(ptr, track, size); + ptr += size; + + if (!play_once) { + // The song is meant to be looped. We achieve this by inserting just + // before the song end a jump to the song start. More precisely we abuse + // a S&M sysex, "maybe_jump" to achieve this effect. We could also + // use a set_loop sysex, but it's a bit longer, a little more complicated, + // and has no advantage either. + + // First, find the track end + byte *end = ptr; + ptr -= size; + for (; ptr < end; ptr++) { + if (*ptr == 0xff && *(ptr + 1) == 0x2f) + break; + } + assert(ptr < end); + + // Now insert the jump. The jump offset is measured in ticks. + // We have ppqn/3 ticks before the first note. + + const int jump_offset = ppqn / 3; + memcpy(ptr, "\xf0\x13\x7d\x30\00", 5); ptr += 5; // maybe_jump + memcpy(ptr, "\x00\x00", 2); ptr += 2; // cmd -> 0 means always jump + memcpy(ptr, "\x00\x00\x00\x00", 4); ptr += 4; // track -> there is only one track, 0 + memcpy(ptr, "\x00\x00\x00\x01", 4); ptr += 4; // beat -> for now, 1 (first beat) + // Ticks + *ptr++ = (byte)((jump_offset >> 12) & 0x0F); + *ptr++ = (byte)((jump_offset >> 8) & 0x0F); + *ptr++ = (byte)((jump_offset >> 4) & 0x0F); + *ptr++ = (byte)(jump_offset & 0x0F); + memcpy(ptr, "\x00\xf7", 2); ptr += 2; // sysex end marker + } + } else { + + /* This is a sfx resource. First parse it quickly to find the parallel + * tracks. + */ + ptr = writeMIDIHeader(ptr, "ASFX", ppqn, total_size); + + byte current_instr[3][14]; + int current_note[3]; + int track_time[3]; + byte *track_data[3]; + + int track_ctr = 0; + byte chunk_type = 0; + int delay, delay2, olddelay; + + // Write a tempo change Meta event + // 473 / 4 Hz, convert to micro seconds. + dw = 1000000 * ppqn * 4 / 473; + memcpy(ptr, "\x00\xFF\x51\x03", 4); ptr += 4; + *ptr++ = (byte)((dw >> 16) & 0xFF); + *ptr++ = (byte)((dw >> 8) & 0xFF); + *ptr++ = (byte)(dw & 0xFF); + + for (i = 0; i < 3; i++) { + track_time[i] = -1; + current_note[i] = -1; + } + while (size > 0) { + assert(track_ctr < 3); + track_data[track_ctr] = src_ptr; + track_time[track_ctr] = 0; + track_ctr++; + while (size > 0) { + chunk_type = *(src_ptr); + if (chunk_type == 1) { + src_ptr += 15; + size -= 15; + } else if (chunk_type == 2) { + src_ptr += 11; + size -= 11; + } else if (chunk_type == 0x80) { + src_ptr ++; + size --; + } else { + break; + } + } + if (chunk_type == 0xff) + break; + src_ptr++; + } + + int curtime = 0; + for (;;) { + int mintime = -1; + ch = -1; + for (i = 0; i < 3; i++) { + if (track_time[i] >= 0 && + (mintime == -1 || mintime > track_time[i])) { + mintime = track_time[i]; + ch = i; + } + } + if (mintime < 0) + break; + + src_ptr = track_data[ch]; + chunk_type = *src_ptr; + + if (current_note[ch] >= 0) { + delay = mintime - curtime; + curtime = mintime; + ptr = writeVLQ(ptr, delay); + *ptr++ = 0x80 + ch; // key off channel; + *ptr++ = current_note[ch]; + *ptr++ = 0; + current_note[ch] = -1; + } + + switch (chunk_type) { + case 1: + /* Instrument definition */ + memcpy(current_instr[ch], src_ptr + 1, 14); + src_ptr += 15; + break; + + case 2: + /* tone/parammodulation */ + memcpy(ptr, ADLIB_INSTR_MIDI_HACK, + sizeof(ADLIB_INSTR_MIDI_HACK)); + + ptr[5] += ch; + ptr[28] += ch; + ptr[92] += ch; + + /* flags_1 */ + ptr[30 + 0] = (current_instr[ch][3] >> 4) & 0xf; + ptr[30 + 1] = current_instr[ch][3] & 0xf; + + /* oplvl_1 */ + ptr[30 + 2] = (current_instr[ch][4] >> 4) & 0xf; + ptr[30 + 3] = current_instr[ch][4] & 0xf; + + /* atdec_1 */ + ptr[30 + 4] = ((~current_instr[ch][5]) >> 4) & 0xf; + ptr[30 + 5] = (~current_instr[ch][5]) & 0xf; + + /* sustrel_1 */ + ptr[30 + 6] = ((~current_instr[ch][6]) >> 4) & 0xf; + ptr[30 + 7] = (~current_instr[ch][6]) & 0xf; + + /* waveform_1 */ + ptr[30 + 8] = (current_instr[ch][7] >> 4) & 0xf; + ptr[30 + 9] = current_instr[ch][7] & 0xf; + + /* flags_2 */ + ptr[30 + 10] = (current_instr[ch][8] >> 4) & 0xf; + ptr[30 + 11] = current_instr[ch][8] & 0xf; + + /* oplvl_2 */ + ptr[30 + 12] = ((current_instr[ch][9]) >> 4) & 0xf; + ptr[30 + 13] = (current_instr[ch][9]) & 0xf; + + /* atdec_2 */ + ptr[30 + 14] = ((~current_instr[ch][10]) >> 4) & 0xf; + ptr[30 + 15] = (~current_instr[ch][10]) & 0xf; + + /* sustrel_2 */ + ptr[30 + 16] = ((~current_instr[ch][11]) >> 4) & 0xf; + ptr[30 + 17] = (~current_instr[ch][11]) & 0xf; + + /* waveform_2 */ + ptr[30 + 18] = (current_instr[ch][12] >> 4) & 0xf; + ptr[30 + 19] = current_instr[ch][12] & 0xf; + + /* feedback */ + ptr[30 + 20] = (current_instr[ch][2] >> 4) & 0xf; + ptr[30 + 21] = current_instr[ch][2] & 0xf; + + delay = mintime - curtime; + curtime = mintime; + + { + delay = convert_extraflags(ptr + 30 + 22, src_ptr + 1); + delay2 = convert_extraflags(ptr + 30 + 40, src_ptr + 6); + debugC(DEBUG_SOUND, "delays: %d / %d", delay, delay2); + if (delay2 >= 0 && delay2 < delay) + delay = delay2; + if (delay == -1) + delay = 0; + } + + /* duration */ + ptr[30 + 58] = 0; // ((delay * 17 / 63) >> 4) & 0xf; + ptr[30 + 59] = 0; // (delay * 17 / 63) & 0xf; + + ptr += sizeof(ADLIB_INSTR_MIDI_HACK); + + olddelay = mintime - curtime; + curtime = mintime; + ptr = writeVLQ(ptr, olddelay); + + { + int freq = ((current_instr[ch][1] & 3) << 8) + | current_instr[ch][0]; + if (!freq) + freq = 0x80; + freq <<= ((current_instr[ch][1] >> 2) & 7) + 1; + int note = -11; + while (freq >= 0x100) { + note += 12; + freq >>= 1; + } + debugC(DEBUG_SOUND, "Freq: %d (%x) Note: %d", freq, freq, note); + if (freq < 0x80) + note = 0; + else + note += freq2note[freq - 0x80]; + + debugC(DEBUG_SOUND, "Note: %d", note); + if (note <= 0) + note = 1; + else if (note > 127) + note = 127; + + // Insert a note on event + *ptr++ = 0x90 + ch; // key on channel + *ptr++ = note; + *ptr++ = 63; + current_note[ch] = note; + track_time[ch] = curtime + delay; + } + src_ptr += 11; + break; + + case 0x80: + track_time[ch] = -1; + src_ptr ++; + break; + + default: + track_time[ch] = -1; + } + track_data[ch] = src_ptr; + } + } + + // Insert end of song sysex + memcpy(ptr, "\x00\xff\x2f\x00\x00", 5); ptr += 5; +} + + +int ScummEngine::readSoundResourceSmallHeader(int type, int idx) { + uint32 pos, total_size, size, tag; + uint32 ad_size = 0, ad_offs = 0; + uint32 ro_size = 0, ro_offs = 0; + uint32 wa_size = 0, wa_offs = 0; + + debug(4, "readSoundResourceSmallHeader(%d)", idx); + + if ((_gameId == GID_LOOM) && (_features & GF_PC) && VAR(VAR_SOUNDCARD) == 4) { + // Roland resources in Loom are tagless + // So we add an RO tag to allow imuse to detect format + byte *ptr, *src_ptr; + ro_offs = _fileHandle.pos(); + ro_size = _fileHandle.readUint16LE(); + + src_ptr = (byte *) calloc(ro_size - 4, 1); + _fileHandle.seek(ro_offs + 4, SEEK_SET); + _fileHandle.read(src_ptr, ro_size -4); + + ptr = createResource(type, idx, ro_size + 2); + memcpy(ptr, "RO", 2); ptr += 2; + memcpy(ptr, src_ptr, ro_size - 4); ptr += ro_size - 4; + return 1; + } else if (_features & GF_OLD_BUNDLE) { + wa_offs = _fileHandle.pos(); + wa_size = _fileHandle.readUint16LE(); + _fileHandle.seek(wa_size - 2, SEEK_CUR); + + if (!(_features & GF_ATARI_ST || _features & GF_MACINTOSH)) { + ad_offs = _fileHandle.pos(); + ad_size = _fileHandle.readUint16LE(); + } + _fileHandle.seek(4, SEEK_CUR); + total_size = wa_size + ad_size; + } else { + total_size = size = _fileHandle.readUint32LE(); + tag = _fileHandle.readUint16LE(); + debug(4, " tag='%c%c', size=%d", (char) (tag & 0xff), + (char) ((tag >> 8) & 0xff), size); + + if (tag == 0x4F52) { // RO + ro_offs = _fileHandle.pos(); + ro_size = size; + } else { + pos = 6; + while (pos < total_size) { + size = _fileHandle.readUint32LE(); + tag = _fileHandle.readUint16LE(); + debug(4, " tag='%c%c', size=%d", (char) (tag & 0xff), + (char) ((tag >> 8) & 0xff), size); + pos += size; + + // MI1 and Indy3 uses one or more nested SO resources, which contains AD and WA + // resources. + if ((tag == 0x4441) && !(ad_offs)) { // AD + ad_size = size; + ad_offs = _fileHandle.pos(); + } else if ((tag == 0x4157) && !(wa_offs)) { // WA + wa_size = size; + wa_offs = _fileHandle.pos(); + } else { // other AD, WA and nested SO resources + if (tag == 0x4F53) { // SO + pos -= size; + size = 6; + pos += 6; + } + } + _fileHandle.seek(size - 6, SEEK_CUR); + } + } + } + + if ((_midiDriver == MD_ADLIB) && ad_offs != 0) { + // AD resources have a header, instrument definitions and one MIDI track. + // We build an 'ADL ' resource from that: + // 8 bytes resource header + // 16 bytes MDhd header + // 14 bytes MThd header + // 8 bytes MTrk header + // 7 bytes MIDI tempo sysex + // + some default instruments + byte *ptr; + if (_features & GF_OLD_BUNDLE) { + ptr = (byte *) calloc(ad_size - 4, 1); + _fileHandle.seek(ad_offs + 4, SEEK_SET); + _fileHandle.read(ptr, ad_size - 4); + convertADResource(type, idx, ptr, ad_size - 4); + free(ptr); + return 1; + } else { + ptr = (byte *) calloc(ad_size - 6, 1); + _fileHandle.seek(ad_offs, SEEK_SET); + _fileHandle.read(ptr, ad_size - 6); + convertADResource(type, idx, ptr, ad_size - 6); + free(ptr); + return 1; + } + } else if (((_midiDriver == MD_PCJR) || (_midiDriver == MD_PCSPK)) && wa_offs != 0) { + if (_features & GF_OLD_BUNDLE) { + _fileHandle.seek(wa_offs, SEEK_SET); + _fileHandle.read(createResource(type, idx, wa_size), wa_size); + } else { + _fileHandle.seek(wa_offs - 6, SEEK_SET); + _fileHandle.read(createResource(type, idx, wa_size + 6), wa_size + 6); + } + return 1; + } else if (ro_offs != 0) { + _fileHandle.seek(ro_offs - 2, SEEK_SET); + _fileHandle.read(createResource(type, idx, ro_size - 4), ro_size - 4); + return 1; + } + res.roomoffs[type][idx] = 0xFFFFFFFF; + return 0; +} + + } // End of namespace Scumm diff --git a/scumm/util.cpp b/scumm/util.cpp new file mode 100644 index 0000000000..e1710d24c8 --- /dev/null +++ b/scumm/util.cpp @@ -0,0 +1,272 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2002-2004 The ScummVM project + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * $Header$ + * + */ + +#include "scumm/util.h" + +namespace Scumm { + +#pragma mark - +#pragma mark --- ScummFile --- +#pragma mark - + + +ScummFile::ScummFile() : _encbyte(0), _subFileStart(0), _subFileLen(0) { +} + +void ScummFile::setEnc(byte value) { + _encbyte = value; +} + +void ScummFile::setSubfileRange(uint32 start, uint32 len) { + // TODO: Add sanity checks + const uint32 fileSize = File::size(); + assert(start <= fileSize); + assert(start + len <= fileSize); + _subFileStart = start; + _subFileLen = len; + seek(0, SEEK_SET); +} + +void ScummFile::resetSubfile() { + _subFileStart = 0; + _subFileLen = 0; + seek(0, SEEK_SET); +} + +bool ScummFile::open(const char *filename, AccessMode mode) { + if (File::open(filename, mode)) { + resetSubfile(); + return true; + } else { + return false; + } +} + +bool ScummFile::openSubFile(const char *filename) { + assert(isOpen()); + + // Disable the XOR encryption and reset any current subfile range + setEnc(0); + resetSubfile(); + + // Read in the filename table and look for the specified file + + unsigned long file_off, file_len; + char file_name[0x20+1]; + unsigned long i; + + // Get the length of the data file to use for consistency checks + const uint32 data_file_len = size(); + + // Read offset and length to the file records */ + const uint32 file_record_off = readUint32BE(); + const uint32 file_record_len = readUint32BE(); + + // Do a quick check to make sure the offset and length are good + if (file_record_off + file_record_len > data_file_len) { + return false; + } + + // Do a little consistancy check on file_record_length + if (file_record_len % 0x28) { + return false; + } + + // Scan through the files + for (i = 0; i < file_record_len; i += 0x28) { + // read a file record + seek(file_record_off + i, SEEK_SET); + file_off = readUint32BE(); + file_len = readUint32BE(); + read(file_name, 0x20); + file_name[0x20] = 0; + + assert(file_name[0]); + //debug(7, " extracting \'%s\'", file_name); + + // Consistency check. make sure the file data is in the file + if (file_off + file_len > data_file_len) { + return false; + } + + if (scumm_stricmp(file_name, filename) == 0) { + // We got a match! + setSubfileRange(file_off, file_len); + return true; + } + } + + return false; +} + + +bool ScummFile::eof() { + return _subFileLen ? (pos() >= _subFileLen) : File::eof(); +} + +uint32 ScummFile::pos() { + return File::pos() - _subFileStart; +} + +uint32 ScummFile::size() { + return _subFileLen ? _subFileLen : File::size(); +} + +void ScummFile::seek(int32 offs, int whence) { + if (_subFileLen) { + // Constrain the seek to the subfile + switch (whence) { + case SEEK_END: + offs = _subFileStart + _subFileLen - offs; + break; + case SEEK_SET: + offs += _subFileStart; + break; + case SEEK_CUR: + offs += File::pos(); + break; + } + assert((int32)_subFileStart <= offs && offs <= (int32)(_subFileStart + _subFileLen)); + whence = SEEK_SET; + } + File::seek(offs, whence); +} + +uint32 ScummFile::read(void *ptr, uint32 len) { + uint32 realLen; + + if (_subFileLen) { + // Limit the amount we read by the subfile boundaries. + const uint32 curPos = pos(); + assert(_subFileLen >= curPos); + uint32 newPos = curPos + len; + if (newPos > _subFileLen) { + len = _subFileLen - curPos; + _ioFailed = true; + } + } + + realLen = File::read(ptr, len); + + + // If an encryption byte was specified, XOR the data we just read by it. + // This simple kind of "encryption" was used by some of the older SCUMM + // games. + if (_encbyte) { + byte *p = (byte *)ptr; + byte *end = p + realLen; + while (p < end) + *p++ ^= _encbyte; + } + + return realLen; +} + +uint32 ScummFile::write(const void *, uint32) { + error("ScummFile does not support writing!"); +} + +#pragma mark - +#pragma mark --- Utilities --- +#pragma mark - + +void checkRange(int max, int min, int no, const char *str) { + if (no < min || no > max) { + char buf[256]; + snprintf(buf, sizeof(buf), str, no); + error("Value %d is out of bounds (%d,%d) (%s)", no, min, max, buf); + } +} + +/** + * Convert an old style direction to a new style one (angle), + */ +int newDirToOldDir(int dir) { + if (dir >= 71 && dir <= 109) + return 1; + if (dir >= 109 && dir <= 251) + return 2; + if (dir >= 251 && dir <= 289) + return 0; + return 3; +} + +/** + * Convert an new style (angle) direction to an old style one. + */ +int oldDirToNewDir(int dir) { + assert(0 <= dir && dir <= 3); + const int new_dir_table[4] = { 270, 90, 180, 0 }; + return new_dir_table[dir]; +} + +/** + * Convert an angle to a simple direction. + */ +int toSimpleDir(int dirType, int dir) { + if (dirType) { + const int16 directions[] = { 22, 72, 107, 157, 202, 252, 287, 337 }; + for (int i = 0; i < 7; i++) + if (dir >= directions[i] && dir <= directions[i+1]) + return i+1; + } else { + const int16 directions[] = { 71, 109, 251, 289 }; + for (int i = 0; i < 3; i++) + if (dir >= directions[i] && dir <= directions[i+1]) + return i+1; + } + + return 0; +} + +/** + * Convert a simple direction to an angle. + */ +int fromSimpleDir(int dirType, int dir) { + if (dirType) + return dir * 45; + else + return dir * 90; +} + +/** + * Normalize the given angle - that means, ensure it is positive, and + * change it to the closest multiple of 45 degree by abusing toSimpleDir. + */ +int normalizeAngle(int angle) { + int temp; + + temp = (angle + 360) % 360; + + return toSimpleDir(1, temp) * 45; +} + +const char *tag2str(uint32 tag) { + static char str[5]; + str[0] = (char)(tag >> 24); + str[1] = (char)(tag >> 16); + str[2] = (char)(tag >> 8); + str[3] = (char)tag; + str[4] = '\0'; + return str; +} + +} // End of namespace Scumm diff --git a/scumm/util.h b/scumm/util.h new file mode 100644 index 0000000000..4b6c0bd974 --- /dev/null +++ b/scumm/util.h @@ -0,0 +1,71 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2002-2004 The ScummVM project + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * $Header$ + * + */ + +#ifndef SCUMM_UTIL_H +#define SCUMM_UTIL_H + +#include "common/file.h" +#include "common/util.h" + +namespace Scumm { + +class ScummFile : public File { +private: + byte _encbyte; + uint32 _subFileStart; + uint32 _subFileLen; +public: + ScummFile(); + void setEnc(byte value); + + void setSubfileRange(uint32 start, uint32 len); + void resetSubfile(); + + bool open(const char *filename, AccessMode mode = kFileReadMode); + bool openSubFile(const char *filename); + + bool eof(); + uint32 pos(); + uint32 size(); + void seek(int32 offs, int whence = SEEK_SET); + uint32 read(void *ptr, uint32 size); + uint32 write(const void *ptr, uint32 size); +}; + + +// This is a constant lookup table of reverse bit masks +extern const byte revBitMask[8]; + +/* Direction conversion functions (between old dir and new dir format) */ +int newDirToOldDir(int dir); +int oldDirToNewDir(int dir); + +int normalizeAngle(int angle); +int fromSimpleDir(int dirtype, int dir); +int toSimpleDir(int dirtype, int dir); + +void checkRange(int max, int min, int no, const char *str); + +const char *tag2str(uint32 tag); + +} // End of namespace Scumm + +#endif -- cgit v1.2.3