diff options
Diffstat (limited to 'engines/agi/sound.h')
-rw-r--r-- | engines/agi/sound.h | 218 |
1 files changed, 218 insertions, 0 deletions
diff --git a/engines/agi/sound.h b/engines/agi/sound.h index adde156f75..03b243cd58 100644 --- a/engines/agi/sound.h +++ b/engines/agi/sound.h @@ -70,6 +70,200 @@ private: bool _isPlaying; ///< Is the sound playing? }; +struct IIgsEnvelopeSegment { + uint8 bp; + uint16 inc; ///< 8b.8b fixed point, big endian? +}; + +#define ENVELOPE_SEGMENT_COUNT 8 +struct IIgsEnvelope { + IIgsEnvelopeSegment seg[ENVELOPE_SEGMENT_COUNT]; + + /** Reads an Apple IIGS envelope from then given stream. */ + bool read(Common::SeekableReadStream &stream) { + for (int segNum = 0; segNum < ENVELOPE_SEGMENT_COUNT; segNum++) { + seg[segNum].bp = stream.readByte(); + seg[segNum].inc = stream.readUint16BE(); + } + return !stream.ioFailed(); + } +}; + +// 2**(1/12) i.e. the 12th root of 2 +#define SEMITONE 1.059463094359295 + +// Size of the SIERRASTANDARD file (i.e. the wave file i.e. the sample data used by the instruments). +#define SIERRASTANDARD_SIZE 65536 + +// Maximum number of instruments in an Apple IIGS instrument set. +// Chosen empirically based on Apple IIGS AGI game data, increase if needed. +#define MAX_INSTRUMENTS 28 + +struct IIgsWaveInfo { + uint8 top; + uint addr; + uint size; +// Oscillator channel +#define OSC_CHANNEL_RIGHT 0 +#define OSC_CHANNEL_LEFT 1 + uint channel; +// Oscillator mode +#define OSC_MODE_LOOP 0 +#define OSC_MODE_ONESHOT 1 +#define OSC_MODE_SYNC_AM 2 +#define OSC_MODE_SWAP 3 + uint mode; + bool halt; + uint16 relPitch; ///< 8b.8b fixed point, big endian? + + /** Reads an Apple IIGS wave information structure from the given stream. */ + bool read(Common::SeekableReadStream &stream, bool ignoreAddr = false) { + top = stream.readByte(); + addr = stream.readByte() * 256; + size = (1 << (stream.readByte() & 7)) * 256; + + // Read packed mode byte and parse it into parts + byte packedModeByte = stream.readByte(); + channel = (packedModeByte >> 4) & 1; // Bit 4 + mode = (packedModeByte >> 1) & 3; // Bits 1-2 + halt = (packedModeByte & 1) != 0; // Bit 0 (Converted to boolean) + + relPitch = stream.readUint16BE(); + + // Zero the wave address if we want to ignore the wave address info + if (ignoreAddr) + addr = 0; + + return !stream.ioFailed(); + } + + bool finalize(Common::SeekableReadStream &uint8Wave) { + uint32 startPos = uint8Wave.pos(); // Save stream's starting position + uint8Wave.seek(addr, SEEK_CUR); // Seek to wave's address + + // Calculate the true sample size (A zero ends the sample prematurely) + uint trueSize = size; // Set a default value for the result + for (uint i = 0; i < size; i++) { + if (uint8Wave.readByte() == 0) { + trueSize = i; + break; + } + } + size = trueSize; // Set the true sample size + + uint8Wave.seek(startPos); // Seek back to the stream's starting position + return true; + } +}; + +// Number of waves per Apple IIGS sound oscillator +#define WAVES_PER_OSCILLATOR 2 + +/** An Apple IIGS sound oscillator. Consists always of two waves. */ +struct IIgsOscillator { + IIgsWaveInfo waves[WAVES_PER_OSCILLATOR]; + + bool finalize(Common::SeekableReadStream &uint8Wave) { + for (uint i = 0; i < WAVES_PER_OSCILLATOR; i++) + if (!waves[i].finalize(uint8Wave)) + return false; + return true; + } +}; + +// Maximum number of oscillators in an Apple IIGS instrument. +// Chosen empirically based on Apple IIGS AGI game data, increase if needed. +#define MAX_OSCILLATORS 4 + +/** An Apple IIGS sound oscillator list. */ +struct IIgsOscillatorList { + uint count; ///< Oscillator count + IIgsOscillator osc[MAX_OSCILLATORS]; ///< The oscillators + + bool read(Common::SeekableReadStream &stream, uint oscillatorCount, bool ignoreAddr = false) { + // First read the A waves and then the B waves for the oscillators + for (uint waveNum = 0; waveNum < WAVES_PER_OSCILLATOR; waveNum++) + for (uint oscNum = 0; oscNum < oscillatorCount; oscNum++) + if (!osc[oscNum].waves[waveNum].read(stream, ignoreAddr)) + return false; + + count = oscillatorCount; // Set the oscillator count + return true; + } + + bool finalize(Common::SeekableReadStream &uint8Wave) { + for (uint i = 0; i < count; i++) + if (!osc[i].finalize(uint8Wave)) + return false; + return true; + } +}; + +struct IIgsInstrumentHeader { + IIgsEnvelope env; + uint8 relseg; + uint8 bendrange; + uint8 vibdepth; + uint8 vibspeed; + IIgsOscillatorList oscList; + + /** + * Read an Apple IIGS instrument header from the given stream. + * @param stream The source stream from which to read the data. + * @param ignoreAddr Should we ignore wave infos' wave address variable's value? + * @return True if successful, false otherwise. + */ + bool read(Common::SeekableReadStream &stream, bool ignoreAddr = false) { + env.read(stream); + relseg = stream.readByte(); + byte priority = stream.readByte(); // Not needed? 32 in all tested data. + bendrange = stream.readByte(); + vibdepth = stream.readByte(); + vibspeed = stream.readByte(); + byte spare = stream.readByte(); // Not needed? 0 in all tested data. + byte wac = stream.readByte(); // Read A wave count + byte wbc = stream.readByte(); // Read B wave count + oscList.read(stream, wac, ignoreAddr); // Read the oscillators + return (wac == wbc) && !stream.ioFailed(); // A and B wave counts must match + } + + bool finalize(Common::SeekableReadStream &uint8Wave) { + return oscList.finalize(uint8Wave); + } +}; + +struct IIgsSampleHeader { + uint16 type; + uint8 pitch; ///< Logarithmic, base is 2**(1/12), unknown multiplier (Possibly in range 1040-1080) + uint8 unknownByte_Ofs3; // 0x7F in Gold Rush's sound resource 60, 0 in all others. + uint8 volume; ///< Current guess: Logarithmic in 6 dB steps + uint8 unknownByte_Ofs5; ///< 0 in all tested samples. + uint16 instrumentSize; ///< Little endian. 44 in all tested samples. A guess. + uint16 sampleSize; ///< Little endian. Accurate in all tested samples excluding Manhunter I's sound resource 16. + IIgsInstrumentHeader instrument; + + /** + * Read an Apple IIGS AGI sample header from the given stream. + * @param stream The source stream from which to read the data. + * @return True if successful, false otherwise. + */ + bool read(Common::SeekableReadStream &stream) { + type = stream.readUint16LE(); + pitch = stream.readByte(); + unknownByte_Ofs3 = stream.readByte(); + volume = stream.readByte(); + unknownByte_Ofs5 = stream.readByte(); + instrumentSize = stream.readUint16LE(); + sampleSize = stream.readUint16LE(); + // Read the instrument header *ignoring* its wave address info + return instrument.read(stream, true); + } + + bool finalize(Common::SeekableReadStream &uint8Wave) { + return instrument.finalize(uint8Wave); + } +}; + #include "common/pack-start.h" /** @@ -112,6 +306,24 @@ struct ChannelInfo { uint32 env; }; +/** Apple IIGS AGI instrument set information. */ +struct instrumentSetInfo { + uint byteCount; ///< Length of the whole instrument set in bytes + uint instCount; ///< Amount of instrument in the set + const char *md5; ///< MD5 hex digest of the whole instrument set + const char *waveFileMd5; ///< MD5 hex digest of the wave file (i.e. the sample data used by the instruments) +}; + +/** Apple IIGS AGI executable file information. */ +struct IIgsExeInfo { + enum AgiGameID gameid; ///< Game ID + const char *exePrefix; ///< Prefix of the Apple IIGS AGI executable (e.g. "SQ", "PQ", "KQ4" etc) + uint agiVer; ///< Apple IIGS AGI version number, not strictly needed + uint exeSize; ///< Size of the Apple IIGS AGI executable file in bytes + uint instSetStart; ///< Starting offset of the instrument set inside the executable file + const instrumentSetInfo &instSet; ///< Information about the used instrument set +}; + class AgiEngine; class SoundMgr : public Audio::AudioStream { @@ -165,6 +377,12 @@ public: #ifdef USE_IIGS_SOUND void playMidiSound(); void playSampleSound(); + bool finalizeInstruments(Common::SeekableReadStream &uint8Wave); + Audio::AudioStream *makeIIgsSampleStream(Common::SeekableReadStream &stream, int resnum = -1); + const IIgsExeInfo *getIIgsExeInfo(enum AgiGameID gameid) const; + bool loadInstrumentHeaders(const Common::String &exePath, const IIgsExeInfo &exeInfo); + bool convertWave(Common::SeekableReadStream &source, int16 *dest, uint length); + Common::MemoryReadStream *loadWaveFile(const Common::String &wavePath, const IIgsExeInfo &exeInfo); #endif }; |