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/sound.cpp | 1046 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1046 insertions(+) (limited to 'scumm/sound.cpp') 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 -- cgit v1.2.3