aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--engines/agi/module.mk1
-rw-r--r--engines/agi/sound.cpp718
-rw-r--r--engines/agi/sound.h282
-rw-r--r--engines/agi/sound_2gs.cpp736
-rw-r--r--engines/agi/sound_2gs.h311
5 files changed, 1063 insertions, 985 deletions
diff --git a/engines/agi/module.mk b/engines/agi/module.mk
index f031834c9d..3b7741443d 100644
--- a/engines/agi/module.mk
+++ b/engines/agi/module.mk
@@ -30,6 +30,7 @@ MODULE_OBJS := \
predictive.o \
saveload.o \
sound.o \
+ sound_2gs.o \
sprite.o \
text.o \
view.o \
diff --git a/engines/agi/sound.cpp b/engines/agi/sound.cpp
index 1b76b1ee40..014a3dba1a 100644
--- a/engines/agi/sound.cpp
+++ b/engines/agi/sound.cpp
@@ -31,6 +31,8 @@
#include "agi/agi.h"
+#include "agi/sound_2gs.h"
+
namespace Agi {
#define USE_INTERPOLATION
@@ -57,18 +59,6 @@ AgiSound *AgiSound::createFromRawResource(uint8 *data, uint32 len, int resnum, S
return NULL;
}
-IIgsMidi::IIgsMidi(uint8 *data, uint32 len, int resnum, SoundMgr &manager) : AgiSound(manager) {
- _data = data; // Save the resource pointer
- _ptr = _data + 2; // Set current position to just after the header
- _len = len; // Save the resource's length
- _type = READ_LE_UINT16(data); // Read sound resource's type
- _midiTicks = _soundBufTicks = 0;
- _isValid = (_type == AGI_SOUND_MIDI) && (_data != NULL) && (_len >= 2);
-
- if (!_isValid) // Check for errors
- warning("Error creating Apple IIGS midi sound from resource %d (Type %d, length %d)", resnum, _type, len);
-}
-
PCjrSound::PCjrSound(uint8 *data, uint32 len, int resnum, SoundMgr &manager) : AgiSound(manager) {
_data = data; // Save the resource pointer
_len = len; // Save the resource's length
@@ -86,208 +76,6 @@ const uint8 *PCjrSound::getVoicePointer(uint voiceNum) {
return _data + voiceStartOffset;
}
-IIgsSample::IIgsSample(uint8 *data, uint32 len, int resnum, SoundMgr &manager) : AgiSound(manager) {
- Common::MemoryReadStream stream(data, len, DisposeAfterUse::YES);
-
- // Check that the header was read ok and that it's of the correct type
- if (_header.read(stream) && _header.type == AGI_SOUND_SAMPLE) { // An Apple IIGS AGI sample resource
- uint32 sampleStartPos = stream.pos();
- uint32 tailLen = stream.size() - sampleStartPos;
-
- if (tailLen < _header.sampleSize) { // Check if there's no room for the sample data in the stream
- // Apple IIGS Manhunter I: Sound resource 16 has only 16074 bytes
- // of sample data although header says it should have 16384 bytes.
- warning("Apple IIGS sample (%d) too short (%d bytes. Should be %d bytes). Using the part that's left",
- resnum, tailLen, _header.sampleSize);
-
- _header.sampleSize = (uint16) tailLen; // Use the part that's left
- }
-
- if (_header.pitch > 0x7F) { // Check if the pitch is invalid
- warning("Apple IIGS sample (%d) has too high pitch (0x%02x)", resnum, _header.pitch);
-
- _header.pitch &= 0x7F; // Apple IIGS AGI probably did it this way too
- }
-
- // Finalize the header info using the 8-bit unsigned sample data
- _header.finalize(stream);
-
- // Convert sample data from 8-bit unsigned to 8-bit signed format
- stream.seek(sampleStartPos);
- _sample = new int8[_header.sampleSize];
-
- if (_sample != NULL)
- _isValid = SoundMgr::convertWave(stream, _sample, _header.sampleSize);
- }
-
- if (!_isValid) // Check for errors
- warning("Error creating Apple IIGS sample from resource %d (Type %d, length %d)", resnum, _header.type, len);
-}
-
-/** Reads an Apple IIGS envelope from then given stream. */
-bool IIgsEnvelope::read(Common::SeekableReadStream &stream) {
- for (int segNum = 0; segNum < ENVELOPE_SEGMENT_COUNT; segNum++) {
- seg[segNum].bp = stream.readByte();
- seg[segNum].inc = stream.readUint16LE();
- }
-
- return !(stream.eos() || stream.err());
-}
-
-/** Reads an Apple IIGS wave information structure from the given stream. */
-bool IIgsWaveInfo::read(Common::SeekableReadStream &stream, bool ignoreAddr) {
- 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.readSint16LE();
-
- // Zero the wave address if we want to ignore the wave address info
- if (ignoreAddr)
- addr = 0;
-
- return !(stream.eos() || stream.err());
-}
-
-bool IIgsWaveInfo::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;
- // A zero in the sample stream turns off looping
- // (At least that's what MESS 0.117 and KEGS32 0.91 seem to do)
- if (mode == OSC_MODE_LOOP)
- mode = OSC_MODE_ONESHOT;
- break;
- }
- }
- size = trueSize; // Set the true sample size
-
- uint8Wave.seek(startPos); // Seek back to the stream's starting position
-
- return true;
-}
-
-bool IIgsOscillator::finalize(Common::SeekableReadStream &uint8Wave) {
- for (uint i = 0; i < WAVES_PER_OSCILLATOR; i++)
- if (!waves[i].finalize(uint8Wave))
- return false;
-
- return true;
-}
-
-bool IIgsOscillatorList::read(Common::SeekableReadStream &stream, uint oscillatorCount, bool ignoreAddr) {
- // 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 IIgsOscillatorList::finalize(Common::SeekableReadStream &uint8Wave) {
- for (uint i = 0; i < count; i++)
- if (!osc[i].finalize(uint8Wave))
- return false;
-
- return true;
-}
-
-bool IIgsInstrumentHeader::read(Common::SeekableReadStream &stream, bool ignoreAddr) {
- 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.eos() || stream.err()); // A and B wave counts must match
-}
-
-bool IIgsInstrumentHeader::finalize(Common::SeekableReadStream &uint8Wave) {
- return oscList.finalize(uint8Wave);
-}
-
-bool IIgsSampleHeader::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 IIgsSampleHeader::finalize(Common::SeekableReadStream &uint8Wave) {
- return instrument.finalize(uint8Wave);
-}
-
-/** Older Apple IIGS AGI MIDI program change to instrument number mapping. */
-static const MidiProgramChangeMapping progToInstMappingV1 = {
- {19, 20, 22, 23, 21, 24, 5, 5, 5, 5,
- 6, 7, 10, 9, 11, 9, 15, 8, 5, 5,
- 17, 16, 18, 12, 14, 5, 5, 5, 5, 5,
- 0, 1, 2, 9, 3, 4, 15, 2, 2, 2,
- 25, 13, 13, 25},
- 5
-};
-
-/** Newer Apple IIGS AGI MIDI program change to instrument number mapping. */
-static const MidiProgramChangeMapping progToInstMappingV2 = {
- {21, 22, 24, 25, 23, 26, 6, 6, 6, 6,
- 7, 9, 12, 8, 13, 11, 17, 10, 6, 6,
- 19, 18, 20, 14, 16, 6, 6, 6, 6, 6,
- 0, 1, 2, 4, 3, 5, 17, 2, 2, 2,
- 27, 15, 15, 27},
- 6
-};
-
-/** Older Apple IIGS AGI instrument set. Used only by Space Quest I (AGI v1.002). */
-static const InstrumentSetInfo instSetV1 = {
- 1192, 26, "7ee16bbc135171ffd6b9120cc7ff1af2", "edd3bf8905d9c238e02832b732fb2e18", progToInstMappingV1
-};
-
-/** Newer Apple IIGS AGI instrument set (AGI v1.003+). Used by all others than Space Quest I. */
-static const InstrumentSetInfo instSetV2 = {
- 1292, 28, "b7d428955bb90721996de1cbca25e768", "c05fb0b0e11deefab58bc68fbd2a3d07", progToInstMappingV2
-};
-
-/** Information about different Apple IIGS AGI executables. */
-static const IIgsExeInfo IIgsExeInfos[] = {
- {GID_SQ1, "SQ", 0x1002, 138496, 0x80AD, instSetV1},
- {GID_LSL1, "LL", 0x1003, 141003, 0x844E, instSetV2},
- {GID_AGIDEMO, "DEMO", 0x1005, 141884, 0x8469, instSetV2},
- {GID_KQ1, "KQ", 0x1006, 141894, 0x8469, instSetV2},
- {GID_PQ1, "PQ", 0x1007, 141882, 0x8469, instSetV2},
- {GID_MIXEDUP, "MG", 0x1013, 142552, 0x84B7, instSetV2},
- {GID_KQ2, "KQ2", 0x1013, 143775, 0x84B7, instSetV2},
- {GID_KQ3, "KQ3", 0x1014, 144312, 0x84B7, instSetV2},
- {GID_SQ2, "SQ2", 0x1014, 107882, 0x6563, instSetV2},
- {GID_MH1, "MH", 0x2004, 147678, 0x8979, instSetV2},
- {GID_KQ4, "KQ4", 0x2006, 147652, 0x8979, instSetV2},
- {GID_BC, "BC", 0x3001, 148192, 0x8979, instSetV2},
- {GID_GOLDRUSH, "GR", 0x3003, 148268, 0x8979, instSetV2}
-};
-
static const int16 waveformRamp[WAVEFORM_SIZE] = {
0, 8, 16, 24, 32, 40, 48, 56,
64, 72, 80, 88, 96, 104, 112, 120,
@@ -370,7 +158,7 @@ void SoundMgr::startSound(int resnum, int flag) {
switch (type) {
case AGI_SOUND_SAMPLE: {
IIgsSample *sampleRes = (IIgsSample *) _vm->_game.sounds[_playingSound];
- _gsSound.playSampleSound(sampleRes->getHeader(), sampleRes->getSample());
+ _gsSound->playSampleSound(sampleRes->getHeader(), sampleRes->getSample());
break;
}
case AGI_SOUND_MIDI:
@@ -411,8 +199,6 @@ void SoundMgr::stopSound() {
debugC(3, kDebugLevelSound, "stopSound() --> %d", _playingSound);
- _vm->setflag(_endflag, true);
-
_endflag = -1;
if (_vm->_soundemu != SOUND_EMU_APPLE2GS) {
for (i = 0; i < NUM_CHANNELS; i++)
@@ -424,38 +210,13 @@ void SoundMgr::stopSound() {
_vm->_game.sounds[_playingSound]->stop();
if (_vm->_soundemu == SOUND_EMU_APPLE2GS) {
- _gsSound.stopSounds();
+ _gsSound->stopSounds();
}
_playingSound = -1;
}
}
-void IIgsSoundMgr::stopSounds() {
- // Stops all sounds on all MIDI channels
- for (iterator iter = _midiChannels.begin(); iter != _midiChannels.end(); ++iter)
- iter->stopSounds();
-}
-
-bool IIgsSoundMgr::playSampleSound(const IIgsSampleHeader &sampleHeader, const int8 *sample) {
- stopSounds();
- IIgsMidiChannel &channel = _midiChannels[kSfxMidiChannel];
-
- channel.setInstrument(&sampleHeader.instrument, sample);
- channel.setVolume(sampleHeader.volume);
- channel.noteOn(sampleHeader.pitch, 64); // Use default velocity (i.e. 64)
-
- return true;
-}
-
-void IIgsMidiChannel::stopSounds() {
- // Stops all sounds on this single MIDI channel
- for (iterator iter = _gsChannels.begin(); iter != _gsChannels.end(); ++iter)
- iter->stop();
-
- _gsChannels.clear();
-}
-
int SoundMgr::initSound() {
int r = -1;
@@ -540,290 +301,6 @@ void SoundMgr::playNote(int i, int freq, int vol) {
}
}
-void SoundMgr::playMidiSound() {
- if (_disabledMidi)
- return;
-
- const uint8 *p;
- uint8 parm1, parm2;
- static uint8 cmd, ch;
-
- if (_playingSound == -1 || _vm->_game.sounds[_playingSound] == NULL) {
- warning("Error playing Apple IIGS MIDI sound resource");
- _playing = false;
-
- return;
- }
-
- IIgsMidi *midiObj = (IIgsMidi *) _vm->_game.sounds[_playingSound];
-
- _playing = true;
- p = midiObj->getPtr();
-
- midiObj->_soundBufTicks++;
-
- while (true) {
- uint8 readByte = *p;
-
- // Check for end of MIDI sequence marker (Can also be here before delta-time)
- if (readByte == MIDI_BYTE_STOP_SEQUENCE) {
- debugC(3, kDebugLevelSound, "End of MIDI sequence (Before reading delta-time)");
- _playing = false;
-
- midiObj->rewind();
-
- return;
- } else if (readByte == MIDI_BYTE_TIMER_SYNC) {
- debugC(3, kDebugLevelSound, "Timer sync");
- p++; // Jump over the timer sync byte as it's not needed
-
- continue;
- }
-
- uint8 deltaTime = readByte;
- if (midiObj->_midiTicks + deltaTime > midiObj->_soundBufTicks) {
- break;
- }
- midiObj->_midiTicks += deltaTime;
- p++; // Jump over the delta-time byte as it was already taken care of
-
- // Check for end of MIDI sequence marker (This time it after reading delta-time)
- if (*p == MIDI_BYTE_STOP_SEQUENCE) {
- debugC(3, kDebugLevelSound, "End of MIDI sequence (After reading delta-time)");
- _playing = false;
-
- midiObj->rewind();
-
- return;
- }
-
- // Separate byte into command and channel if it's a command byte.
- // Otherwise use running status (i.e. previously set command and channel).
- if (*p & 0x80) {
- cmd = *p++;
- ch = cmd & 0x0f;
- cmd >>= 4;
- }
-
- switch (cmd) {
- case MIDI_CMD_NOTE_OFF:
- parm1 = *p++;
- parm2 = *p++;
- _gsSound.midiNoteOff(ch, parm1, parm2);
- break;
- case MIDI_CMD_NOTE_ON:
- parm1 = *p++;
- parm2 = *p++;
- _gsSound.midiNoteOn(ch, parm1, parm2);
- break;
- case MIDI_CMD_CONTROLLER:
- parm1 = *p++;
- parm2 = *p++;
- _gsSound.midiController(ch, parm1, parm2);
- break;
- case MIDI_CMD_PROGRAM_CHANGE:
- parm1 = *p++;
- _gsSound.midiProgramChange(ch, parm1);
- break;
- case MIDI_CMD_PITCH_WHEEL:
- parm1 = *p++;
- parm2 = *p++;
-
- uint16 wheelPos = ((parm2 & 0x7F) << 7) | (parm1 & 0x7F); // 14-bit value
- _gsSound.midiPitchWheel(wheelPos);
- break;
- }
- }
-
- midiObj->setPtr(p);
-}
-
-void IIgsSoundMgr::midiNoteOff(uint8 channel, uint8 note, uint8 velocity) {
- _midiChannels[channel].noteOff(note, velocity);
- debugC(3, kDebugLevelSound, "note off, channel %02x, note %02x, velocity %02x", channel, note, velocity);
-}
-
-void IIgsSoundMgr::midiNoteOn(uint8 channel, uint8 note, uint8 velocity) {
- _midiChannels[channel].noteOn(note, velocity);
- debugC(3, kDebugLevelSound, "note on, channel %02x, note %02x, velocity %02x", channel, note, velocity);
-}
-
-// TODO: Check if controllers behave differently on different MIDI channels
-// TODO: Doublecheck what other controllers than the volume controller do
-void IIgsSoundMgr::midiController(uint8 channel, uint8 controller, uint8 value) {
- IIgsMidiChannel &midiChannel = _midiChannels[channel];
-
- // The tested Apple IIGS AGI MIDI resources only used
- // controllers 0 (Bank select?), 7 (Volume) and 64 (Sustain On/Off).
- // Controller 0's parameter was in range 94-127,
- // controller 7's parameter was in range 0-127 and
- // controller 64's parameter was always 0 (i.e. sustain off).
- bool unimplemented = false;
- switch (controller) {
- case 7: // Volume
- midiChannel.setVolume(value);
- break;
- default:
- unimplemented = true;
- break;
- }
- debugC(3, kDebugLevelSound, "controller %02x, ch %02x, val %02x%s", controller, channel, value, unimplemented ? " (Unimplemented)" : "");
-}
-
-void IIgsSoundMgr::midiProgramChange(uint8 channel, uint8 program) {
- _midiChannels[channel].setInstrument(getInstrument(program), _wave.begin());
- debugC(3, kDebugLevelSound, "program change %02x, channel %02x", program, channel);
-}
-
-void IIgsSoundMgr::midiPitchWheel(uint8 wheelPos) {
- // In all the tested Apple IIGS AGI MIDI resources
- // pitch wheel commands always used 0x2000 (Center position).
- // Therefore it should be quite safe to ignore this command.
- debugC(3, kDebugLevelSound, "pitch wheel position %04x (Unimplemented)", wheelPos);
-}
-
-IIgsSoundMgr::IIgsSoundMgr() {
- _midiChannels.resize(16); // Set the amount of available MIDI channels
-}
-
-const IIgsInstrumentHeader* IIgsSoundMgr::getInstrument(uint8 program) const {
- return &_instruments[_midiProgToInst->map(program)];
-}
-
-void IIgsSoundMgr::setProgramChangeMapping(const MidiProgramChangeMapping *mapping) {
- _midiProgToInst = mapping;
-}
-
-void IIgsSoundMgr::removeStoppedSounds() {
- for (Common::Array<IIgsMidiChannel>::iterator iter = _midiChannels.begin(); iter != _midiChannels.end(); ++iter)
- iter->removeStoppedSounds();
-}
-
-void IIgsMidiChannel::removeStoppedSounds() {
- for (int i = _gsChannels.size() - 1; i >= 0; i--)
- if (!_gsChannels[i].playing())
- _gsChannels.remove_at(i);
-}
-
-uint IIgsSoundMgr::activeSounds() const {
- uint result = 0;
-
- for (Common::Array<IIgsMidiChannel>::const_iterator iter = _midiChannels.begin(); iter != _midiChannels.end(); ++iter)
- result += iter->activeSounds();
-
- return result;
-}
-
-uint IIgsMidiChannel::activeSounds() const {
- uint result = 0;
-
- for (const_iterator iter = _gsChannels.begin(); iter != _gsChannels.end(); ++iter)
- if (!iter->end)
- result++;
-
- return result;
-}
-
-void IIgsMidiChannel::setInstrument(const IIgsInstrumentHeader *instrument, const int8 *sample) {
- _instrument = instrument;
- _sample = sample;
-
- // Set program on each Apple IIGS channel playing on this MIDI channel
- for (iterator iter = _gsChannels.begin(); iter != _gsChannels.end(); ++iter)
- iter->setInstrument(instrument, sample);
-}
-
-void IIgsMidiChannel::setVolume(uint8 volume) {
- _volume = volume;
-
- // Set volume on each Apple IIGS channel playing on this MIDI channel
- for (iterator iter = _gsChannels.begin(); iter != _gsChannels.end(); ++iter)
- iter->setChannelVolume(volume);
-}
-
-void IIgsMidiChannel::noteOff(uint8 note, uint8 velocity) {
- // Go through all the notes playing on this MIDI channel
- // and turn off the ones that are playing the given note
- for (iterator iter = _gsChannels.begin(); iter != _gsChannels.end(); ++iter)
- if (iter->origNote == note)
- iter->noteOff(velocity);
-}
-
-void IIgsMidiChannel::noteOn(uint8 note, uint8 velocity) {
- IIgsChannelInfo channel;
-
- // Use the default channel volume and instrument
- channel.setChannelVolume(_volume);
- channel.setInstrument(_instrument, _sample);
-
- // Set the note on and save the channel
- channel.noteOn(note, velocity);
- _gsChannels.push_back(channel);
-}
-
-void IIgsChannelInfo::rewind() {
- this->envVol = this->startEnvVol;
- this->envSeg = 0;
- this->pos = intToFrac(0);
-}
-
-void IIgsChannelInfo::setChannelVolume(uint8 volume) {
- this->chanVol = intToFrac(volume);
-}
-
-void IIgsChannelInfo::setInstrument(const IIgsInstrumentHeader *instrument, const int8 *sample) {
- assert(instrument != NULL && sample != NULL);
- this->ins = instrument;
- this->unrelocatedSample = sample;
-}
-
-// TODO/FIXME: Implement correctly and fully (Take velocity into account etc)
-void IIgsChannelInfo::noteOn(uint8 noteParam, uint8 velocity) {
- this->origNote = noteParam;
- this->startEnvVol = intToFrac(0);
- rewind();
-
- const IIgsWaveInfo *waveInfo = NULL;
-
- for (uint i = 0; i < ins->oscList.count; i++)
- if (ins->oscList(i).waves[0].top >= noteParam)
- waveInfo = &ins->oscList(i).waves[0];
-
- assert(waveInfo != NULL);
-
- this->relocatedSample = this->unrelocatedSample + waveInfo->addr;
- this->posAdd = intToFrac(0);
- this->note = intToFrac(noteParam) + doubleToFrac(waveInfo->relPitch/256.0);
- this->vol = doubleToFrac(fracToDouble(this->envVol) * fracToDouble(this->chanVol) / 127.0);
- this->loop = (waveInfo->mode == OSC_MODE_LOOP);
- this->size = waveInfo->size - waveInfo->addr;
- this->end = waveInfo->halt;
-}
-
-// TODO/FIXME: Implement correctly and fully (Take release time and velocity into account etc)
-void IIgsChannelInfo::noteOff(uint8 velocity) {
- this->loop = false;
- this->envSeg = ins->relseg;
-}
-
-void IIgsChannelInfo::stop() {
- this->end = true;
-}
-
-bool IIgsChannelInfo::playing() {
- return !this->end;
-}
-
-void SoundMgr::playSampleSound() {
- if (_vm->_soundemu != SOUND_EMU_APPLE2GS) {
- warning("Trying to play a sample but not using Apple IIGS sound emulation mode");
- return;
- }
-
- if (_playingSound != -1)
- _playing = _gsSound.activeSounds() > 0;
-}
-
static int cocoFrequencies[] = {
130, 138, 146, 155, 164, 174, 184, 195, 207, 220, 233, 246,
261, 277, 293, 311, 329, 349, 369, 391, 415, 440, 466, 493,
@@ -946,9 +423,9 @@ uint32 SoundMgr::mixSound() {
// TODO: Implement playing both waves in an oscillator
// TODO: Implement swap-mode in an oscillator
if (_vm->_soundemu == SOUND_EMU_APPLE2GS) {
- for (uint midiChan = 0; midiChan < _gsSound._midiChannels.size(); midiChan++) {
- for (uint gsChan = 0; gsChan < _gsSound._midiChannels[midiChan]._gsChannels.size(); gsChan++) {
- IIgsChannelInfo &channel = _gsSound._midiChannels[midiChan]._gsChannels[gsChan];
+ for (uint midiChan = 0; midiChan < _gsSound->_midiChannels.size(); midiChan++) {
+ for (uint gsChan = 0; gsChan < _gsSound->_midiChannels[midiChan]._gsChannels.size(); gsChan++) {
+ IIgsChannelInfo &channel = _gsSound->_midiChannels[midiChan]._gsChannels[gsChan];
if (channel.playing()) { // Only mix in actively playing channels
// Frequency multiplier was 1076.0 based on tests made with MESS 0.117.
// Tests made with KEGS32 averaged the multiplier to around 1045.
@@ -1002,7 +479,7 @@ uint32 SoundMgr::mixSound() {
}
}
}
- _gsSound.removeStoppedSounds();
+ _gsSound->removeStoppedSounds();
return IIGS_BUFFER_SIZE;
} // else ...
@@ -1078,77 +555,6 @@ uint32 SoundMgr::mixSound() {
}
/**
- * Finds information about an Apple IIGS AGI executable based on the game ID.
- * @return A non-null IIgsExeInfo pointer if successful, otherwise NULL.
- */
-const IIgsExeInfo *SoundMgr::getIIgsExeInfo(enum AgiGameID gameid) const {
- for (int i = 0; i < ARRAYSIZE(IIgsExeInfos); i++)
- if (IIgsExeInfos[i].gameid == gameid)
- return &IIgsExeInfos[i];
- return NULL;
-}
-
-bool IIgsSoundMgr::loadInstrumentHeaders(const Common::FSNode &exePath, const IIgsExeInfo &exeInfo) {
- bool loadedOk = false; // Was loading successful?
- Common::File file;
-
- // Open the executable file and check that it has correct size
- file.open(exePath);
- if (file.size() != (int32)exeInfo.exeSize) {
- debugC(3, kDebugLevelSound, "Apple IIGS executable (%s) has wrong size (Is %d, should be %d)",
- exePath.getPath().c_str(), file.size(), exeInfo.exeSize);
- }
-
- // Read the whole executable file into memory
- Common::SharedPtr<Common::MemoryReadStream> data(file.readStream(file.size()));
- file.close();
-
- // Check that we got enough data to be able to parse the instruments
- if (data && data->size() >= (int32)(exeInfo.instSetStart + exeInfo.instSet.byteCount)) {
- // Check instrument set's length (The info's saved in the executable)
- data->seek(exeInfo.instSetStart - 4);
- uint16 instSetByteCount = data->readUint16LE();
- if (instSetByteCount != exeInfo.instSet.byteCount) {
- debugC(3, kDebugLevelSound, "Wrong instrument set size (Is %d, should be %d) in Apple IIGS executable (%s)",
- instSetByteCount, exeInfo.instSet.byteCount, exePath.getPath().c_str());
- }
-
- // Check instrument set's md5sum
- data->seek(exeInfo.instSetStart);
-
- char md5str[32+1];
- Common::md5_file_string(*data, md5str, exeInfo.instSet.byteCount);
- if (scumm_stricmp(md5str, exeInfo.instSet.md5)) {
- warning("Unknown Apple IIGS instrument set (md5: %s) in %s, trying to use it nonetheless",
- md5str, exePath.getPath().c_str());
- }
-
- // Read in the instrument set one instrument at a time
- data->seek(exeInfo.instSetStart);
-
- // Load the instruments
- _instruments.clear();
- _instruments.reserve(exeInfo.instSet.instCount);
-
- IIgsInstrumentHeader instrument;
- for (uint i = 0; i < exeInfo.instSet.instCount; i++) {
- if (!instrument.read(*data)) {
- warning("Error loading Apple IIGS instrument (%d. of %d) from %s, not loading more instruments",
- i + 1, exeInfo.instSet.instCount, exePath.getPath().c_str());
- break;
- }
- _instruments.push_back(instrument); // Add the successfully loaded instrument to the instruments array
- }
-
- // Loading was successful only if all instruments were loaded successfully
- loadedOk = (_instruments.size() == exeInfo.instSet.instCount);
- } else // Couldn't read enough data from the executable file
- warning("Error loading instruments from Apple IIGS executable (%s)", exePath.getPath().c_str());
-
- return loadedOk;
-}
-
-/**
* Convert sample from 8-bit unsigned to 8-bit signed format.
* @param source Source stream containing the 8-bit unsigned sample data.
* @param dest Destination buffer for the 8-bit signed sample data.
@@ -1161,111 +567,6 @@ bool SoundMgr::convertWave(Common::SeekableReadStream &source, int8 *dest, uint
return !(source.eos() || source.err());
}
-bool IIgsSoundMgr::loadWaveFile(const Common::FSNode &wavePath, const IIgsExeInfo &exeInfo) {
- Common::File file;
-
- // Open the wave file and read it into memory
- file.open(wavePath);
- Common::SharedPtr<Common::MemoryReadStream> uint8Wave(file.readStream(file.size()));
- file.close();
-
- // Check that we got the whole wave file
- if (uint8Wave && uint8Wave->size() == SIERRASTANDARD_SIZE) {
- // Check wave file's md5sum
- char md5str[32+1];
- Common::md5_file_string(*uint8Wave, md5str, SIERRASTANDARD_SIZE);
- if (scumm_stricmp(md5str, exeInfo.instSet.waveFileMd5)) {
- warning("Unknown Apple IIGS wave file (md5: %s, game: %s).\n" \
- "Please report the information on the previous line to the ScummVM team.\n" \
- "Using the wave file as it is - music may sound weird", md5str, exeInfo.exePrefix);
- }
-
- uint8Wave->seek(0); // Seek wave to its start
- // Convert the wave file from 8-bit unsigned to 8-bit signed and save the result
- _wave.resize(uint8Wave->size());
- return SoundMgr::convertWave(*uint8Wave, _wave.begin(), uint8Wave->size());
- } else { // Couldn't read the wave file or it had incorrect size
- warning("Error loading Apple IIGS wave file (%s), not loading instruments", wavePath.getPath().c_str());
- return false;
- }
-}
-
-/**
- * A function object (i.e. a functor) for testing if a Common::FSNode
- * object's name is equal (Ignoring case) to a string or to at least
- * one of the strings in a list of strings. Can be used e.g. with find_if().
- */
-struct fsnodeNameEqualsIgnoreCase : public Common::UnaryFunction<const Common::FSNode&, bool> {
-// FIXME: This should be replaced; use SearchMan instead
- fsnodeNameEqualsIgnoreCase(const Common::StringArray &str) : _str(str) {}
- fsnodeNameEqualsIgnoreCase(const Common::String str) { _str.push_back(str); }
- bool operator()(const Common::FSNode &param) const {
- for (Common::StringArray::const_iterator iter = _str.begin(); iter != _str.end(); ++iter)
- if (param.getName().equalsIgnoreCase(*iter))
- return true;
- return false;
- }
-private:
- Common::StringArray _str;
-};
-
-bool SoundMgr::loadInstruments() {
- // Check that the platform is Apple IIGS, as only it uses custom instruments
- if (_vm->getPlatform() != Common::kPlatformApple2GS) {
- debugC(3, kDebugLevelSound, "Platform isn't Apple IIGS so not loading any instruments");
- return true;
- }
-
- // Get info on the particular Apple IIGS AGI game's executable
- const IIgsExeInfo *exeInfo = getIIgsExeInfo((enum AgiGameID) _vm->getGameID());
- if (exeInfo == NULL) {
- warning("Unsupported Apple IIGS game, not loading instruments");
- return false;
- }
-
- // List files in the game path
- Common::FSList fslist;
- Common::FSNode dir(ConfMan.get("path"));
- if (!dir.getChildren(fslist, Common::FSNode::kListFilesOnly)) {
- warning("Invalid game path (\"%s\"), not loading Apple IIGS instruments", dir.getPath().c_str());
- return false;
- }
-
- // Populate executable filenames list (Long filename and short filename) for searching
- Common::StringArray exeNames;
- exeNames.push_back(Common::String(exeInfo->exePrefix) + ".SYS16");
- exeNames.push_back(Common::String(exeInfo->exePrefix) + ".SYS");
-
- // Populate wave filenames list (Long filename and short filename) for searching
- Common::StringArray waveNames;
- waveNames.push_back("SIERRASTANDARD");
- waveNames.push_back("SIERRAST");
-
- // Search for the executable file and the wave file (i.e. check if any of the filenames match)
- Common::FSList::const_iterator exeFsnode, waveFsnode;
- exeFsnode = Common::find_if(fslist.begin(), fslist.end(), fsnodeNameEqualsIgnoreCase(exeNames));
- waveFsnode = Common::find_if(fslist.begin(), fslist.end(), fsnodeNameEqualsIgnoreCase(waveNames));
-
- // Make sure that we found the executable file
- if (exeFsnode == fslist.end()) {
- warning("Couldn't find Apple IIGS game executable (%s), not loading instruments", exeNames.begin()->c_str());
- return false;
- }
-
- // Make sure that we found the wave file
- if (waveFsnode == fslist.end()) {
- warning("Couldn't find Apple IIGS wave file (%s), not loading instruments", waveNames.begin()->c_str());
- return false;
- }
-
- // Set the MIDI program change to instrument number mapping and
- // load the instrument headers and their sample data.
- // None of the tested SIERRASTANDARD-files have zeroes in them so
- // there's no need to check for prematurely ending samples here.
- _gsSound.setProgramChangeMapping(&exeInfo->instSet.progToInst);
- return _gsSound.loadWaveFile(*waveFsnode, *exeInfo) && _gsSound.loadInstrumentHeaders(*exeFsnode, *exeInfo);
-}
-
void SoundMgr::fillAudio(void *udata, int16 *stream, uint len) {
SoundMgr *soundMgr = (SoundMgr *)udata;
uint32 p = 0;
@@ -1306,6 +607,8 @@ SoundMgr::SoundMgr(AgiBase *agi, Audio::Mixer *pMixer) : _chn() {
_waveform = 0;
_disabledMidi = false;
_useChorus = true; // FIXME: Currently always true?
+
+ _gsSound = new IIgsSoundMgr;
}
void SoundMgr::premixerCall(int16 *data, uint len) {
@@ -1318,6 +621,7 @@ void SoundMgr::setVolume(uint8 volume) {
SoundMgr::~SoundMgr() {
free(_sndBuffer);
+ delete _gsSound;
}
} // End of namespace Agi
diff --git a/engines/agi/sound.h b/engines/agi/sound.h
index 881e3efd56..ab0c9e20e5 100644
--- a/engines/agi/sound.h
+++ b/engines/agi/sound.h
@@ -28,20 +28,11 @@
#include "sound/audiostream.h"
#include "sound/mixer.h"
-#include "common/frac.h"
namespace Agi {
#define BUFFER_SIZE 410
-// Apple IIGS MIDI uses 60 ticks per second (Based on tests with Apple IIGS
-// KQ1 and SQ1 under MESS 0.124a). So we make the audio buffer size to be a
-// 1/60th of a second in length. That should be getSampleRate() / 60 samples
-// in length but as getSampleRate() is always 22050 at the moment we just use
-// the hardcoded value of 368 (22050/60 = 367.5 which rounds up to 368).
-// FIXME: Use getSampleRate() / 60 rather than a hardcoded value
-#define IIGS_BUFFER_SIZE 368
-
#define SOUND_EMU_NONE 0
#define SOUND_EMU_PC 1
#define SOUND_EMU_TANDY 2
@@ -57,130 +48,6 @@ namespace Agi {
#define ENV_RELEASE 7500 /**< envelope release rate */
#define NUM_CHANNELS 7 /**< number of sound channels */
-// MIDI command values (Shifted right by 4 so they're in the lower nibble)
-#define MIDI_CMD_NOTE_OFF 0x08
-#define MIDI_CMD_NOTE_ON 0x09
-#define MIDI_CMD_CONTROLLER 0x0B
-#define MIDI_CMD_PROGRAM_CHANGE 0x0C
-#define MIDI_CMD_PITCH_WHEEL 0x0E
-// Whole MIDI byte values (Command and channel info together)
-#define MIDI_BYTE_STOP_SEQUENCE 0xFC
-#define MIDI_BYTE_TIMER_SYNC 0xF8
-
-struct IIgsEnvelopeSegment {
- uint8 bp;
- uint16 inc; ///< 8b.8b fixed point, very probably little 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);
-};
-
-// 2**(1/12) i.e. the 12th root of 2
-#define SEMITONE 1.059463094359295
-
-// C6's frequency is A4's (440 Hz) frequency but one full octave and three semitones higher
-// i.e. C6_FREQ = 440 * pow(2.0, 15/12.0)
-#define C6_FREQ 1046.502261202395
-
-// 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;
- int16 relPitch; ///< Relative pitch in semitones (Signed 8b.8b fixed point)
-
- /** Reads an Apple IIGS wave information structure from the given stream. */
- bool read(Common::SeekableReadStream &stream, bool ignoreAddr = false);
- bool finalize(Common::SeekableReadStream &uint8Wave);
-};
-
-// 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);
-};
-
-// 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
-
- /** Indexing operators for easier access to the oscillators. */
- const IIgsOscillator &operator()(uint index) const { return osc[index]; }
- IIgsOscillator &operator()(uint index) { return osc[index]; }
-
- /** Reads an Apple IIGS oscillator list from the given stream. */
- bool read(Common::SeekableReadStream &stream, uint oscillatorCount, bool ignoreAddr = false);
- bool finalize(Common::SeekableReadStream &uint8Wave);
-};
-
-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);
- bool finalize(Common::SeekableReadStream &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);
- bool finalize(Common::SeekableReadStream &uint8Wave);
-};
-
/**
* AGI sound note structure.
*/
@@ -200,32 +67,6 @@ struct AgiNote {
}
};
-struct IIgsChannelInfo {
- const IIgsInstrumentHeader *ins; ///< Instrument info
- const int8 *relocatedSample; ///< Source sample data (8-bit signed format) using relocation
- const int8 *unrelocatedSample; ///< Source sample data (8-bit signed format) without relocation
- frac_t pos; ///< Current sample position
- frac_t posAdd; ///< Current sample position adder (Calculated using note, vibrato etc)
- uint8 origNote; ///< The original note without the added relative pitch
- frac_t note; ///< Note (With the added relative pitch)
- frac_t vol; ///< Current volume (Takes both channel volume and enveloping into account)
- frac_t chanVol; ///< Channel volume
- frac_t startEnvVol; ///< Starting envelope volume
- frac_t envVol; ///< Current envelope volume
- uint envSeg; ///< Current envelope segment
- uint size; ///< Sample size
- bool loop; ///< Should we loop the sample?
- bool end; ///< Has the playing ended?
-
- void rewind(); ///< Rewinds the sound playing on this channel to its start
- void setChannelVolume(uint8 volume); ///< Sets the channel volume
- void setInstrument(const IIgsInstrumentHeader *instrument, const int8 *sample); ///< Sets the instrument to be used on this channel
- void noteOn(uint8 noteParam, uint8 velocity); ///< Starts playing a note on this channel
- void noteOff(uint8 velocity); ///< Releases the note on this channel
- void stop(); ///< Stops the note playing on this channel instantly
- bool playing(); ///< Is there a note playing on this channel?
-};
-
struct CoCoNote {
uint8 freq;
uint8 volume;
@@ -322,126 +163,11 @@ protected:
uint16 _type; ///< Sound resource type
};
-class IIgsMidi : public AgiSound {
-public:
- IIgsMidi(uint8 *data, uint32 len, int resnum, SoundMgr &manager);
- ~IIgsMidi() { if (_data != NULL) free(_data); }
- virtual uint16 type() { return _type; }
- virtual const uint8 *getPtr() { return _ptr; }
- virtual void setPtr(const uint8 *ptr) { _ptr = ptr; }
- virtual void rewind() { _ptr = _data + 2; _midiTicks = _soundBufTicks = 0; }
-protected:
- uint8 *_data; ///< Raw sound resource data
- const uint8 *_ptr; ///< Pointer to the current position in the MIDI data
- uint32 _len; ///< Length of the raw sound resource
- uint16 _type; ///< Sound resource type
-public:
- uint _midiTicks; ///< MIDI song position in ticks (1/60ths of a second)
- uint _soundBufTicks; ///< Sound buffer position in ticks (1/60ths of a second)
-};
-
-class IIgsSample : public AgiSound {
-public:
- IIgsSample(uint8 *data, uint32 len, int resnum, SoundMgr &manager);
- ~IIgsSample() { delete[] _sample; }
- virtual uint16 type() { return _header.type; }
- const IIgsSampleHeader &getHeader() const { return _header; }
- const int8 *getSample() const { return _sample; }
-protected:
- IIgsSampleHeader _header; ///< Apple IIGS AGI sample header
- int8 *_sample; ///< Sample data (8-bit signed format)
-};
-
-/** Apple IIGS MIDI program change to instrument number mapping. */
-struct MidiProgramChangeMapping {
- byte midiProgToInst[44]; ///< Lookup table for the MIDI program number to instrument number mapping
- byte undefinedInst; ///< The undefined instrument number
-
- // Maps the MIDI program number to an instrument number
- byte map(uint midiProg) const {
- return midiProg < ARRAYSIZE(midiProgToInst) ? midiProgToInst[midiProg] : undefinedInst;
- }
-};
-
-/** 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)
- const MidiProgramChangeMapping &progToInst; ///< Program change to instrument number mapping
-};
-
-/** 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 IIgsMidiChannel {
-public:
- IIgsMidiChannel() : _instrument(0), _sample(0), _volume(0) {}
- uint activeSounds() const; ///< How many active sounds are playing?
- void setInstrument(const IIgsInstrumentHeader *instrument, const int8 *sample);
- void setVolume(uint8 volume);
- void noteOff(uint8 note, uint8 velocity);
- void noteOn(uint8 note, uint8 velocity);
- void stopSounds(); ///< Clears the channel of any sounds
- void removeStoppedSounds(); ///< Removes all stopped sounds from this MIDI channel
-public:
- typedef Common::Array<IIgsChannelInfo>::const_iterator const_iterator;
- typedef Common::Array<IIgsChannelInfo>::iterator iterator;
- Common::Array<IIgsChannelInfo> _gsChannels; ///< Apple IIGS channels playing on this MIDI channel
-protected:
- const IIgsInstrumentHeader *_instrument; ///< Instrument used on this MIDI channel
- const int8 *_sample; ///< Sample data used on this MIDI channel
- uint8 _volume; ///< MIDI controller number 7 (Volume)
-};
-
-/**
- * Class for managing Apple IIGS sound channels.
- * TODO: Check what instruments are used by default on the MIDI channels
- * FIXME: Some instrument choices sound wrong
- */
-class IIgsSoundMgr {
-public:
- typedef Common::Array<IIgsMidiChannel>::const_iterator const_iterator;
- typedef Common::Array<IIgsMidiChannel>::iterator iterator;
- static const uint kSfxMidiChannel = 0; ///< The MIDI channel used for playing sound effects
-public:
- // For initializing
- IIgsSoundMgr();
- void setProgramChangeMapping(const MidiProgramChangeMapping *mapping);
- bool loadInstrumentHeaders(const Common::FSNode &exePath, const IIgsExeInfo &exeInfo);
- bool loadWaveFile(const Common::FSNode &wavePath, const IIgsExeInfo &exeInfo);
- // Miscellaneous methods
- uint activeSounds() const; ///< How many active sounds are playing?
- void stopSounds(); ///< Stops all sounds
- void removeStoppedSounds(); ///< Removes all stopped sounds from the MIDI channels
- // For playing Apple IIGS AGI samples (Sound effects etc)
- bool playSampleSound(const IIgsSampleHeader &sampleHeader, const int8 *sample);
- // MIDI commands
- void midiNoteOff(uint8 channel, uint8 note, uint8 velocity);
- void midiNoteOn(uint8 channel, uint8 note, uint8 velocity);
- void midiController(uint8 channel, uint8 controller, uint8 value);
- void midiProgramChange(uint8 channel, uint8 program);
- void midiPitchWheel(uint8 wheelPos);
-protected:
- const IIgsInstrumentHeader* getInstrument(uint8 program) const;
-public:
- Common::Array<IIgsMidiChannel> _midiChannels; ///< Information about each MIDI channel
-protected:
- Common::Array<int8> _wave; ///< Sample data used by the Apple IIGS MIDI instruments
- const MidiProgramChangeMapping *_midiProgToInst; ///< MIDI program change to instrument number mapping
- Common::Array<IIgsInstrumentHeader> _instruments; ///< Instruments used by the Apple IIGS AGI
-};
-
class AgiEngine;
class AgiBase;
+class IIgsSoundMgr;
+
+struct IIgsExeInfo;
class SoundMgr : public Audio::AudioStream {
AgiBase *_vm;
@@ -477,7 +203,7 @@ private:
bool _playing;
ChannelInfo _chn[NUM_CHANNELS];
- IIgsSoundMgr _gsSound;
+ IIgsSoundMgr *_gsSound;
int _endflag;
int _playingSound;
uint8 _env;
diff --git a/engines/agi/sound_2gs.cpp b/engines/agi/sound_2gs.cpp
new file mode 100644
index 0000000000..ce4aa160a0
--- /dev/null
+++ b/engines/agi/sound_2gs.cpp
@@ -0,0 +1,736 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "common/md5.h"
+#include "common/config-manager.h"
+#include "common/fs.h"
+#include "common/random.h"
+#include "common/str-array.h"
+
+#include "agi/agi.h"
+#include "agi/sound_2gs.h"
+
+namespace Agi {
+
+IIgsMidi::IIgsMidi(uint8 *data, uint32 len, int resnum, SoundMgr &manager) : AgiSound(manager) {
+ _data = data; // Save the resource pointer
+ _ptr = _data + 2; // Set current position to just after the header
+ _len = len; // Save the resource's length
+ _type = READ_LE_UINT16(data); // Read sound resource's type
+ _midiTicks = _soundBufTicks = 0;
+ _isValid = (_type == AGI_SOUND_MIDI) && (_data != NULL) && (_len >= 2);
+
+ if (!_isValid) // Check for errors
+ warning("Error creating Apple IIGS midi sound from resource %d (Type %d, length %d)", resnum, _type, len);
+}
+
+IIgsSample::IIgsSample(uint8 *data, uint32 len, int resnum, SoundMgr &manager) : AgiSound(manager) {
+ Common::MemoryReadStream stream(data, len, DisposeAfterUse::YES);
+
+ // Check that the header was read ok and that it's of the correct type
+ if (_header.read(stream) && _header.type == AGI_SOUND_SAMPLE) { // An Apple IIGS AGI sample resource
+ uint32 sampleStartPos = stream.pos();
+ uint32 tailLen = stream.size() - sampleStartPos;
+
+ if (tailLen < _header.sampleSize) { // Check if there's no room for the sample data in the stream
+ // Apple IIGS Manhunter I: Sound resource 16 has only 16074 bytes
+ // of sample data although header says it should have 16384 bytes.
+ warning("Apple IIGS sample (%d) too short (%d bytes. Should be %d bytes). Using the part that's left",
+ resnum, tailLen, _header.sampleSize);
+
+ _header.sampleSize = (uint16) tailLen; // Use the part that's left
+ }
+
+ if (_header.pitch > 0x7F) { // Check if the pitch is invalid
+ warning("Apple IIGS sample (%d) has too high pitch (0x%02x)", resnum, _header.pitch);
+
+ _header.pitch &= 0x7F; // Apple IIGS AGI probably did it this way too
+ }
+
+ // Finalize the header info using the 8-bit unsigned sample data
+ _header.finalize(stream);
+
+ // Convert sample data from 8-bit unsigned to 8-bit signed format
+ stream.seek(sampleStartPos);
+ _sample = new int8[_header.sampleSize];
+
+ if (_sample != NULL)
+ _isValid = SoundMgr::convertWave(stream, _sample, _header.sampleSize);
+ }
+
+ if (!_isValid) // Check for errors
+ warning("Error creating Apple IIGS sample from resource %d (Type %d, length %d)", resnum, _header.type, len);
+}
+
+/** Reads an Apple IIGS envelope from then given stream. */
+bool IIgsEnvelope::read(Common::SeekableReadStream &stream) {
+ for (int segNum = 0; segNum < ENVELOPE_SEGMENT_COUNT; segNum++) {
+ seg[segNum].bp = stream.readByte();
+ seg[segNum].inc = stream.readUint16LE();
+ }
+
+ return !(stream.eos() || stream.err());
+}
+
+/** Reads an Apple IIGS wave information structure from the given stream. */
+bool IIgsWaveInfo::read(Common::SeekableReadStream &stream, bool ignoreAddr) {
+ 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.readSint16LE();
+
+ // Zero the wave address if we want to ignore the wave address info
+ if (ignoreAddr)
+ addr = 0;
+
+ return !(stream.eos() || stream.err());
+}
+
+bool IIgsWaveInfo::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;
+ // A zero in the sample stream turns off looping
+ // (At least that's what MESS 0.117 and KEGS32 0.91 seem to do)
+ if (mode == OSC_MODE_LOOP)
+ mode = OSC_MODE_ONESHOT;
+ break;
+ }
+ }
+ size = trueSize; // Set the true sample size
+
+ uint8Wave.seek(startPos); // Seek back to the stream's starting position
+
+ return true;
+}
+
+bool IIgsOscillator::finalize(Common::SeekableReadStream &uint8Wave) {
+ for (uint i = 0; i < WAVES_PER_OSCILLATOR; i++)
+ if (!waves[i].finalize(uint8Wave))
+ return false;
+
+ return true;
+}
+
+bool IIgsOscillatorList::read(Common::SeekableReadStream &stream, uint oscillatorCount, bool ignoreAddr) {
+ // 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 IIgsOscillatorList::finalize(Common::SeekableReadStream &uint8Wave) {
+ for (uint i = 0; i < count; i++)
+ if (!osc[i].finalize(uint8Wave))
+ return false;
+
+ return true;
+}
+
+bool IIgsInstrumentHeader::read(Common::SeekableReadStream &stream, bool ignoreAddr) {
+ 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.eos() || stream.err()); // A and B wave counts must match
+}
+
+bool IIgsInstrumentHeader::finalize(Common::SeekableReadStream &uint8Wave) {
+ return oscList.finalize(uint8Wave);
+}
+
+bool IIgsSampleHeader::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 IIgsSampleHeader::finalize(Common::SeekableReadStream &uint8Wave) {
+ return instrument.finalize(uint8Wave);
+}
+
+/** Older Apple IIGS AGI MIDI program change to instrument number mapping. */
+static const MidiProgramChangeMapping progToInstMappingV1 = {
+ {19, 20, 22, 23, 21, 24, 5, 5, 5, 5,
+ 6, 7, 10, 9, 11, 9, 15, 8, 5, 5,
+ 17, 16, 18, 12, 14, 5, 5, 5, 5, 5,
+ 0, 1, 2, 9, 3, 4, 15, 2, 2, 2,
+ 25, 13, 13, 25},
+ 5
+};
+
+/** Newer Apple IIGS AGI MIDI program change to instrument number mapping. */
+static const MidiProgramChangeMapping progToInstMappingV2 = {
+ {21, 22, 24, 25, 23, 26, 6, 6, 6, 6,
+ 7, 9, 12, 8, 13, 11, 17, 10, 6, 6,
+ 19, 18, 20, 14, 16, 6, 6, 6, 6, 6,
+ 0, 1, 2, 4, 3, 5, 17, 2, 2, 2,
+ 27, 15, 15, 27},
+ 6
+};
+
+/** Older Apple IIGS AGI instrument set. Used only by Space Quest I (AGI v1.002). */
+static const InstrumentSetInfo instSetV1 = {
+ 1192, 26, "7ee16bbc135171ffd6b9120cc7ff1af2", "edd3bf8905d9c238e02832b732fb2e18", progToInstMappingV1
+};
+
+/** Newer Apple IIGS AGI instrument set (AGI v1.003+). Used by all others than Space Quest I. */
+static const InstrumentSetInfo instSetV2 = {
+ 1292, 28, "b7d428955bb90721996de1cbca25e768", "c05fb0b0e11deefab58bc68fbd2a3d07", progToInstMappingV2
+};
+
+/** Information about different Apple IIGS AGI executables. */
+static const IIgsExeInfo IIgsExeInfos[] = {
+ {GID_SQ1, "SQ", 0x1002, 138496, 0x80AD, instSetV1},
+ {GID_LSL1, "LL", 0x1003, 141003, 0x844E, instSetV2},
+ {GID_AGIDEMO, "DEMO", 0x1005, 141884, 0x8469, instSetV2},
+ {GID_KQ1, "KQ", 0x1006, 141894, 0x8469, instSetV2},
+ {GID_PQ1, "PQ", 0x1007, 141882, 0x8469, instSetV2},
+ {GID_MIXEDUP, "MG", 0x1013, 142552, 0x84B7, instSetV2},
+ {GID_KQ2, "KQ2", 0x1013, 143775, 0x84B7, instSetV2},
+ {GID_KQ3, "KQ3", 0x1014, 144312, 0x84B7, instSetV2},
+ {GID_SQ2, "SQ2", 0x1014, 107882, 0x6563, instSetV2},
+ {GID_MH1, "MH", 0x2004, 147678, 0x8979, instSetV2},
+ {GID_KQ4, "KQ4", 0x2006, 147652, 0x8979, instSetV2},
+ {GID_BC, "BC", 0x3001, 148192, 0x8979, instSetV2},
+ {GID_GOLDRUSH, "GR", 0x3003, 148268, 0x8979, instSetV2}
+};
+
+void IIgsSoundMgr::stopSounds() {
+ // Stops all sounds on all MIDI channels
+ for (iterator iter = _midiChannels.begin(); iter != _midiChannels.end(); ++iter)
+ iter->stopSounds();
+}
+
+void SoundMgr::playSampleSound() {
+ if (_vm->_soundemu != SOUND_EMU_APPLE2GS) {
+ warning("Trying to play a sample but not using Apple IIGS sound emulation mode");
+ return;
+ }
+
+ if (_playingSound != -1)
+ _playing = _gsSound->activeSounds() > 0;
+}
+
+bool IIgsSoundMgr::playSampleSound(const IIgsSampleHeader &sampleHeader, const int8 *sample) {
+ stopSounds();
+ IIgsMidiChannel &channel = _midiChannels[kSfxMidiChannel];
+
+ channel.setInstrument(&sampleHeader.instrument, sample);
+ channel.setVolume(sampleHeader.volume);
+ channel.noteOn(sampleHeader.pitch, 64); // Use default velocity (i.e. 64)
+
+ return true;
+}
+
+void IIgsMidiChannel::stopSounds() {
+ // Stops all sounds on this single MIDI channel
+ for (iterator iter = _gsChannels.begin(); iter != _gsChannels.end(); ++iter)
+ iter->stop();
+
+ _gsChannels.clear();
+}
+
+void SoundMgr::playMidiSound() {
+ if (_disabledMidi)
+ return;
+
+ const uint8 *p;
+ uint8 parm1, parm2;
+ static uint8 cmd, ch;
+
+ if (_playingSound == -1 || _vm->_game.sounds[_playingSound] == NULL) {
+ warning("Error playing Apple IIGS MIDI sound resource");
+ _playing = false;
+
+ return;
+ }
+
+ IIgsMidi *midiObj = (IIgsMidi *) _vm->_game.sounds[_playingSound];
+
+ _playing = true;
+ p = midiObj->getPtr();
+
+ midiObj->_soundBufTicks++;
+
+ while (true) {
+ uint8 readByte = *p;
+
+ // Check for end of MIDI sequence marker (Can also be here before delta-time)
+ if (readByte == MIDI_BYTE_STOP_SEQUENCE) {
+ debugC(3, kDebugLevelSound, "End of MIDI sequence (Before reading delta-time)");
+ _playing = false;
+
+ midiObj->rewind();
+
+ return;
+ } else if (readByte == MIDI_BYTE_TIMER_SYNC) {
+ debugC(3, kDebugLevelSound, "Timer sync");
+ p++; // Jump over the timer sync byte as it's not needed
+
+ continue;
+ }
+
+ uint8 deltaTime = readByte;
+ if (midiObj->_midiTicks + deltaTime > midiObj->_soundBufTicks) {
+ break;
+ }
+ midiObj->_midiTicks += deltaTime;
+ p++; // Jump over the delta-time byte as it was already taken care of
+
+ // Check for end of MIDI sequence marker (This time it after reading delta-time)
+ if (*p == MIDI_BYTE_STOP_SEQUENCE) {
+ debugC(3, kDebugLevelSound, "End of MIDI sequence (After reading delta-time)");
+ _playing = false;
+
+ midiObj->rewind();
+
+ return;
+ }
+
+ // Separate byte into command and channel if it's a command byte.
+ // Otherwise use running status (i.e. previously set command and channel).
+ if (*p & 0x80) {
+ cmd = *p++;
+ ch = cmd & 0x0f;
+ cmd >>= 4;
+ }
+
+ switch (cmd) {
+ case MIDI_CMD_NOTE_OFF:
+ parm1 = *p++;
+ parm2 = *p++;
+ _gsSound->midiNoteOff(ch, parm1, parm2);
+ break;
+ case MIDI_CMD_NOTE_ON:
+ parm1 = *p++;
+ parm2 = *p++;
+ _gsSound->midiNoteOn(ch, parm1, parm2);
+ break;
+ case MIDI_CMD_CONTROLLER:
+ parm1 = *p++;
+ parm2 = *p++;
+ _gsSound->midiController(ch, parm1, parm2);
+ break;
+ case MIDI_CMD_PROGRAM_CHANGE:
+ parm1 = *p++;
+ _gsSound->midiProgramChange(ch, parm1);
+ break;
+ case MIDI_CMD_PITCH_WHEEL:
+ parm1 = *p++;
+ parm2 = *p++;
+
+ uint16 wheelPos = ((parm2 & 0x7F) << 7) | (parm1 & 0x7F); // 14-bit value
+ _gsSound->midiPitchWheel(wheelPos);
+ break;
+ }
+ }
+
+ midiObj->setPtr(p);
+}
+
+void IIgsSoundMgr::midiNoteOff(uint8 channel, uint8 note, uint8 velocity) {
+ _midiChannels[channel].noteOff(note, velocity);
+ debugC(3, kDebugLevelSound, "note off, channel %02x, note %02x, velocity %02x", channel, note, velocity);
+}
+
+void IIgsSoundMgr::midiNoteOn(uint8 channel, uint8 note, uint8 velocity) {
+ _midiChannels[channel].noteOn(note, velocity);
+ debugC(3, kDebugLevelSound, "note on, channel %02x, note %02x, velocity %02x", channel, note, velocity);
+}
+
+// TODO: Check if controllers behave differently on different MIDI channels
+// TODO: Doublecheck what other controllers than the volume controller do
+void IIgsSoundMgr::midiController(uint8 channel, uint8 controller, uint8 value) {
+ IIgsMidiChannel &midiChannel = _midiChannels[channel];
+
+ // The tested Apple IIGS AGI MIDI resources only used
+ // controllers 0 (Bank select?), 7 (Volume) and 64 (Sustain On/Off).
+ // Controller 0's parameter was in range 94-127,
+ // controller 7's parameter was in range 0-127 and
+ // controller 64's parameter was always 0 (i.e. sustain off).
+ bool unimplemented = false;
+ switch (controller) {
+ case 7: // Volume
+ midiChannel.setVolume(value);
+ break;
+ default:
+ unimplemented = true;
+ break;
+ }
+ debugC(3, kDebugLevelSound, "controller %02x, ch %02x, val %02x%s", controller, channel, value, unimplemented ? " (Unimplemented)" : "");
+}
+
+void IIgsSoundMgr::midiProgramChange(uint8 channel, uint8 program) {
+ _midiChannels[channel].setInstrument(getInstrument(program), _wave.begin());
+ debugC(3, kDebugLevelSound, "program change %02x, channel %02x", program, channel);
+}
+
+void IIgsSoundMgr::midiPitchWheel(uint8 wheelPos) {
+ // In all the tested Apple IIGS AGI MIDI resources
+ // pitch wheel commands always used 0x2000 (Center position).
+ // Therefore it should be quite safe to ignore this command.
+ debugC(3, kDebugLevelSound, "pitch wheel position %04x (Unimplemented)", wheelPos);
+}
+
+IIgsSoundMgr::IIgsSoundMgr() {
+ _midiChannels.resize(16); // Set the amount of available MIDI channels
+}
+
+const IIgsInstrumentHeader* IIgsSoundMgr::getInstrument(uint8 program) const {
+ return &_instruments[_midiProgToInst->map(program)];
+}
+
+void IIgsSoundMgr::setProgramChangeMapping(const MidiProgramChangeMapping *mapping) {
+ _midiProgToInst = mapping;
+}
+
+void IIgsSoundMgr::removeStoppedSounds() {
+ for (Common::Array<IIgsMidiChannel>::iterator iter = _midiChannels.begin(); iter != _midiChannels.end(); ++iter)
+ iter->removeStoppedSounds();
+}
+
+void IIgsMidiChannel::removeStoppedSounds() {
+ for (int i = _gsChannels.size() - 1; i >= 0; i--)
+ if (!_gsChannels[i].playing())
+ _gsChannels.remove_at(i);
+}
+
+uint IIgsSoundMgr::activeSounds() const {
+ uint result = 0;
+
+ for (Common::Array<IIgsMidiChannel>::const_iterator iter = _midiChannels.begin(); iter != _midiChannels.end(); ++iter)
+ result += iter->activeSounds();
+
+ return result;
+}
+
+uint IIgsMidiChannel::activeSounds() const {
+ uint result = 0;
+
+ for (const_iterator iter = _gsChannels.begin(); iter != _gsChannels.end(); ++iter)
+ if (!iter->end)
+ result++;
+
+ return result;
+}
+
+void IIgsMidiChannel::setInstrument(const IIgsInstrumentHeader *instrument, const int8 *sample) {
+ _instrument = instrument;
+ _sample = sample;
+
+ // Set program on each Apple IIGS channel playing on this MIDI channel
+ for (iterator iter = _gsChannels.begin(); iter != _gsChannels.end(); ++iter)
+ iter->setInstrument(instrument, sample);
+}
+
+void IIgsMidiChannel::setVolume(uint8 volume) {
+ _volume = volume;
+
+ // Set volume on each Apple IIGS channel playing on this MIDI channel
+ for (iterator iter = _gsChannels.begin(); iter != _gsChannels.end(); ++iter)
+ iter->setChannelVolume(volume);
+}
+
+void IIgsMidiChannel::noteOff(uint8 note, uint8 velocity) {
+ // Go through all the notes playing on this MIDI channel
+ // and turn off the ones that are playing the given note
+ for (iterator iter = _gsChannels.begin(); iter != _gsChannels.end(); ++iter)
+ if (iter->origNote == note)
+ iter->noteOff(velocity);
+}
+
+void IIgsMidiChannel::noteOn(uint8 note, uint8 velocity) {
+ IIgsChannelInfo channel;
+
+ // Use the default channel volume and instrument
+ channel.setChannelVolume(_volume);
+ channel.setInstrument(_instrument, _sample);
+
+ // Set the note on and save the channel
+ channel.noteOn(note, velocity);
+ _gsChannels.push_back(channel);
+}
+
+void IIgsChannelInfo::rewind() {
+ this->envVol = this->startEnvVol;
+ this->envSeg = 0;
+ this->pos = intToFrac(0);
+}
+
+void IIgsChannelInfo::setChannelVolume(uint8 volume) {
+ this->chanVol = intToFrac(volume);
+}
+
+void IIgsChannelInfo::setInstrument(const IIgsInstrumentHeader *instrument, const int8 *sample) {
+ assert(instrument != NULL && sample != NULL);
+ this->ins = instrument;
+ this->unrelocatedSample = sample;
+}
+
+// TODO/FIXME: Implement correctly and fully (Take velocity into account etc)
+void IIgsChannelInfo::noteOn(uint8 noteParam, uint8 velocity) {
+ this->origNote = noteParam;
+ this->startEnvVol = intToFrac(0);
+ rewind();
+
+ const IIgsWaveInfo *waveInfo = NULL;
+
+ for (uint i = 0; i < ins->oscList.count; i++)
+ if (ins->oscList(i).waves[0].top >= noteParam)
+ waveInfo = &ins->oscList(i).waves[0];
+
+ assert(waveInfo != NULL);
+
+ this->relocatedSample = this->unrelocatedSample + waveInfo->addr;
+ this->posAdd = intToFrac(0);
+ this->note = intToFrac(noteParam) + doubleToFrac(waveInfo->relPitch/256.0);
+ this->vol = doubleToFrac(fracToDouble(this->envVol) * fracToDouble(this->chanVol) / 127.0);
+ this->loop = (waveInfo->mode == OSC_MODE_LOOP);
+ this->size = waveInfo->size - waveInfo->addr;
+ this->end = waveInfo->halt;
+}
+
+// TODO/FIXME: Implement correctly and fully (Take release time and velocity into account etc)
+void IIgsChannelInfo::noteOff(uint8 velocity) {
+ this->loop = false;
+ this->envSeg = ins->relseg;
+}
+
+void IIgsChannelInfo::stop() {
+ this->end = true;
+}
+
+bool IIgsChannelInfo::playing() {
+ return !this->end;
+}
+
+/**
+ * Finds information about an Apple IIGS AGI executable based on the game ID.
+ * @return A non-null IIgsExeInfo pointer if successful, otherwise NULL.
+ */
+const IIgsExeInfo *SoundMgr::getIIgsExeInfo(enum AgiGameID gameid) const {
+ for (int i = 0; i < ARRAYSIZE(IIgsExeInfos); i++)
+ if (IIgsExeInfos[i].gameid == gameid)
+ return &IIgsExeInfos[i];
+ return NULL;
+}
+
+bool IIgsSoundMgr::loadInstrumentHeaders(const Common::FSNode &exePath, const IIgsExeInfo &exeInfo) {
+ bool loadedOk = false; // Was loading successful?
+ Common::File file;
+
+ // Open the executable file and check that it has correct size
+ file.open(exePath);
+ if (file.size() != (int32)exeInfo.exeSize) {
+ debugC(3, kDebugLevelSound, "Apple IIGS executable (%s) has wrong size (Is %d, should be %d)",
+ exePath.getPath().c_str(), file.size(), exeInfo.exeSize);
+ }
+
+ // Read the whole executable file into memory
+ Common::SharedPtr<Common::MemoryReadStream> data(file.readStream(file.size()));
+ file.close();
+
+ // Check that we got enough data to be able to parse the instruments
+ if (data && data->size() >= (int32)(exeInfo.instSetStart + exeInfo.instSet.byteCount)) {
+ // Check instrument set's length (The info's saved in the executable)
+ data->seek(exeInfo.instSetStart - 4);
+ uint16 instSetByteCount = data->readUint16LE();
+ if (instSetByteCount != exeInfo.instSet.byteCount) {
+ debugC(3, kDebugLevelSound, "Wrong instrument set size (Is %d, should be %d) in Apple IIGS executable (%s)",
+ instSetByteCount, exeInfo.instSet.byteCount, exePath.getPath().c_str());
+ }
+
+ // Check instrument set's md5sum
+ data->seek(exeInfo.instSetStart);
+
+ char md5str[32+1];
+ Common::md5_file_string(*data, md5str, exeInfo.instSet.byteCount);
+ if (scumm_stricmp(md5str, exeInfo.instSet.md5)) {
+ warning("Unknown Apple IIGS instrument set (md5: %s) in %s, trying to use it nonetheless",
+ md5str, exePath.getPath().c_str());
+ }
+
+ // Read in the instrument set one instrument at a time
+ data->seek(exeInfo.instSetStart);
+
+ // Load the instruments
+ _instruments.clear();
+ _instruments.reserve(exeInfo.instSet.instCount);
+
+ IIgsInstrumentHeader instrument;
+ for (uint i = 0; i < exeInfo.instSet.instCount; i++) {
+ if (!instrument.read(*data)) {
+ warning("Error loading Apple IIGS instrument (%d. of %d) from %s, not loading more instruments",
+ i + 1, exeInfo.instSet.instCount, exePath.getPath().c_str());
+ break;
+ }
+ _instruments.push_back(instrument); // Add the successfully loaded instrument to the instruments array
+ }
+
+ // Loading was successful only if all instruments were loaded successfully
+ loadedOk = (_instruments.size() == exeInfo.instSet.instCount);
+ } else // Couldn't read enough data from the executable file
+ warning("Error loading instruments from Apple IIGS executable (%s)", exePath.getPath().c_str());
+
+ return loadedOk;
+}
+
+bool IIgsSoundMgr::loadWaveFile(const Common::FSNode &wavePath, const IIgsExeInfo &exeInfo) {
+ Common::File file;
+
+ // Open the wave file and read it into memory
+ file.open(wavePath);
+ Common::SharedPtr<Common::MemoryReadStream> uint8Wave(file.readStream(file.size()));
+ file.close();
+
+ // Check that we got the whole wave file
+ if (uint8Wave && uint8Wave->size() == SIERRASTANDARD_SIZE) {
+ // Check wave file's md5sum
+ char md5str[32+1];
+ Common::md5_file_string(*uint8Wave, md5str, SIERRASTANDARD_SIZE);
+ if (scumm_stricmp(md5str, exeInfo.instSet.waveFileMd5)) {
+ warning("Unknown Apple IIGS wave file (md5: %s, game: %s).\n" \
+ "Please report the information on the previous line to the ScummVM team.\n" \
+ "Using the wave file as it is - music may sound weird", md5str, exeInfo.exePrefix);
+ }
+
+ uint8Wave->seek(0); // Seek wave to its start
+ // Convert the wave file from 8-bit unsigned to 8-bit signed and save the result
+ _wave.resize(uint8Wave->size());
+ return SoundMgr::convertWave(*uint8Wave, _wave.begin(), uint8Wave->size());
+ } else { // Couldn't read the wave file or it had incorrect size
+ warning("Error loading Apple IIGS wave file (%s), not loading instruments", wavePath.getPath().c_str());
+ return false;
+ }
+}
+
+/**
+ * A function object (i.e. a functor) for testing if a Common::FSNode
+ * object's name is equal (Ignoring case) to a string or to at least
+ * one of the strings in a list of strings. Can be used e.g. with find_if().
+ */
+struct fsnodeNameEqualsIgnoreCase : public Common::UnaryFunction<const Common::FSNode&, bool> {
+// FIXME: This should be replaced; use SearchMan instead
+ fsnodeNameEqualsIgnoreCase(const Common::StringArray &str) : _str(str) {}
+ fsnodeNameEqualsIgnoreCase(const Common::String str) { _str.push_back(str); }
+ bool operator()(const Common::FSNode &param) const {
+ for (Common::StringArray::const_iterator iter = _str.begin(); iter != _str.end(); ++iter)
+ if (param.getName().equalsIgnoreCase(*iter))
+ return true;
+ return false;
+ }
+private:
+ Common::StringArray _str;
+};
+
+bool SoundMgr::loadInstruments() {
+ // Check that the platform is Apple IIGS, as only it uses custom instruments
+ if (_vm->getPlatform() != Common::kPlatformApple2GS) {
+ debugC(3, kDebugLevelSound, "Platform isn't Apple IIGS so not loading any instruments");
+ return true;
+ }
+
+ // Get info on the particular Apple IIGS AGI game's executable
+ const IIgsExeInfo *exeInfo = getIIgsExeInfo((enum AgiGameID) _vm->getGameID());
+ if (exeInfo == NULL) {
+ warning("Unsupported Apple IIGS game, not loading instruments");
+ return false;
+ }
+
+ // List files in the game path
+ Common::FSList fslist;
+ Common::FSNode dir(ConfMan.get("path"));
+ if (!dir.getChildren(fslist, Common::FSNode::kListFilesOnly)) {
+ warning("Invalid game path (\"%s\"), not loading Apple IIGS instruments", dir.getPath().c_str());
+ return false;
+ }
+
+ // Populate executable filenames list (Long filename and short filename) for searching
+ Common::StringArray exeNames;
+ exeNames.push_back(Common::String(exeInfo->exePrefix) + ".SYS16");
+ exeNames.push_back(Common::String(exeInfo->exePrefix) + ".SYS");
+
+ // Populate wave filenames list (Long filename and short filename) for searching
+ Common::StringArray waveNames;
+ waveNames.push_back("SIERRASTANDARD");
+ waveNames.push_back("SIERRAST");
+
+ // Search for the executable file and the wave file (i.e. check if any of the filenames match)
+ Common::FSList::const_iterator exeFsnode, waveFsnode;
+ exeFsnode = Common::find_if(fslist.begin(), fslist.end(), fsnodeNameEqualsIgnoreCase(exeNames));
+ waveFsnode = Common::find_if(fslist.begin(), fslist.end(), fsnodeNameEqualsIgnoreCase(waveNames));
+
+ // Make sure that we found the executable file
+ if (exeFsnode == fslist.end()) {
+ warning("Couldn't find Apple IIGS game executable (%s), not loading instruments", exeNames.begin()->c_str());
+ return false;
+ }
+
+ // Make sure that we found the wave file
+ if (waveFsnode == fslist.end()) {
+ warning("Couldn't find Apple IIGS wave file (%s), not loading instruments", waveNames.begin()->c_str());
+ return false;
+ }
+
+ // Set the MIDI program change to instrument number mapping and
+ // load the instrument headers and their sample data.
+ // None of the tested SIERRASTANDARD-files have zeroes in them so
+ // there's no need to check for prematurely ending samples here.
+ _gsSound->setProgramChangeMapping(&exeInfo->instSet.progToInst);
+ return _gsSound->loadWaveFile(*waveFsnode, *exeInfo) && _gsSound->loadInstrumentHeaders(*exeFsnode, *exeInfo);
+}
+
+} // End of namespace Agi
diff --git a/engines/agi/sound_2gs.h b/engines/agi/sound_2gs.h
new file mode 100644
index 0000000000..3669d29955
--- /dev/null
+++ b/engines/agi/sound_2gs.h
@@ -0,0 +1,311 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef AGI_SOUND_2GS_H
+#define AGI_SOUND_2GS_H
+
+#include "common/frac.h"
+
+namespace Agi {
+
+// Apple IIGS MIDI uses 60 ticks per second (Based on tests with Apple IIGS
+// KQ1 and SQ1 under MESS 0.124a). So we make the audio buffer size to be a
+// 1/60th of a second in length. That should be getSampleRate() / 60 samples
+// in length but as getSampleRate() is always 22050 at the moment we just use
+// the hardcoded value of 368 (22050/60 = 367.5 which rounds up to 368).
+// FIXME: Use getSampleRate() / 60 rather than a hardcoded value
+#define IIGS_BUFFER_SIZE 368
+
+// MIDI command values (Shifted right by 4 so they're in the lower nibble)
+#define MIDI_CMD_NOTE_OFF 0x08
+#define MIDI_CMD_NOTE_ON 0x09
+#define MIDI_CMD_CONTROLLER 0x0B
+#define MIDI_CMD_PROGRAM_CHANGE 0x0C
+#define MIDI_CMD_PITCH_WHEEL 0x0E
+// Whole MIDI byte values (Command and channel info together)
+#define MIDI_BYTE_STOP_SEQUENCE 0xFC
+#define MIDI_BYTE_TIMER_SYNC 0xF8
+
+struct IIgsEnvelopeSegment {
+ uint8 bp;
+ uint16 inc; ///< 8b.8b fixed point, very probably little 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);
+};
+
+// 2**(1/12) i.e. the 12th root of 2
+#define SEMITONE 1.059463094359295
+
+// C6's frequency is A4's (440 Hz) frequency but one full octave and three semitones higher
+// i.e. C6_FREQ = 440 * pow(2.0, 15/12.0)
+#define C6_FREQ 1046.502261202395
+
+// 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;
+ int16 relPitch; ///< Relative pitch in semitones (Signed 8b.8b fixed point)
+
+ /** Reads an Apple IIGS wave information structure from the given stream. */
+ bool read(Common::SeekableReadStream &stream, bool ignoreAddr = false);
+ bool finalize(Common::SeekableReadStream &uint8Wave);
+};
+
+// 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);
+};
+
+// 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
+
+ /** Indexing operators for easier access to the oscillators. */
+ const IIgsOscillator &operator()(uint index) const { return osc[index]; }
+ IIgsOscillator &operator()(uint index) { return osc[index]; }
+
+ /** Reads an Apple IIGS oscillator list from the given stream. */
+ bool read(Common::SeekableReadStream &stream, uint oscillatorCount, bool ignoreAddr = false);
+ bool finalize(Common::SeekableReadStream &uint8Wave);
+};
+
+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);
+ bool finalize(Common::SeekableReadStream &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);
+ bool finalize(Common::SeekableReadStream &uint8Wave);
+};
+
+struct IIgsChannelInfo {
+ const IIgsInstrumentHeader *ins; ///< Instrument info
+ const int8 *relocatedSample; ///< Source sample data (8-bit signed format) using relocation
+ const int8 *unrelocatedSample; ///< Source sample data (8-bit signed format) without relocation
+ frac_t pos; ///< Current sample position
+ frac_t posAdd; ///< Current sample position adder (Calculated using note, vibrato etc)
+ uint8 origNote; ///< The original note without the added relative pitch
+ frac_t note; ///< Note (With the added relative pitch)
+ frac_t vol; ///< Current volume (Takes both channel volume and enveloping into account)
+ frac_t chanVol; ///< Channel volume
+ frac_t startEnvVol; ///< Starting envelope volume
+ frac_t envVol; ///< Current envelope volume
+ uint envSeg; ///< Current envelope segment
+ uint size; ///< Sample size
+ bool loop; ///< Should we loop the sample?
+ bool end; ///< Has the playing ended?
+
+ void rewind(); ///< Rewinds the sound playing on this channel to its start
+ void setChannelVolume(uint8 volume); ///< Sets the channel volume
+ void setInstrument(const IIgsInstrumentHeader *instrument, const int8 *sample); ///< Sets the instrument to be used on this channel
+ void noteOn(uint8 noteParam, uint8 velocity); ///< Starts playing a note on this channel
+ void noteOff(uint8 velocity); ///< Releases the note on this channel
+ void stop(); ///< Stops the note playing on this channel instantly
+ bool playing(); ///< Is there a note playing on this channel?
+};
+
+class IIgsMidi : public AgiSound {
+public:
+ IIgsMidi(uint8 *data, uint32 len, int resnum, SoundMgr &manager);
+ ~IIgsMidi() { if (_data != NULL) free(_data); }
+ virtual uint16 type() { return _type; }
+ virtual const uint8 *getPtr() { return _ptr; }
+ virtual void setPtr(const uint8 *ptr) { _ptr = ptr; }
+ virtual void rewind() { _ptr = _data + 2; _midiTicks = _soundBufTicks = 0; }
+protected:
+ uint8 *_data; ///< Raw sound resource data
+ const uint8 *_ptr; ///< Pointer to the current position in the MIDI data
+ uint32 _len; ///< Length of the raw sound resource
+ uint16 _type; ///< Sound resource type
+public:
+ uint _midiTicks; ///< MIDI song position in ticks (1/60ths of a second)
+ uint _soundBufTicks; ///< Sound buffer position in ticks (1/60ths of a second)
+};
+
+class IIgsSample : public AgiSound {
+public:
+ IIgsSample(uint8 *data, uint32 len, int resnum, SoundMgr &manager);
+ ~IIgsSample() { delete[] _sample; }
+ virtual uint16 type() { return _header.type; }
+ const IIgsSampleHeader &getHeader() const { return _header; }
+ const int8 *getSample() const { return _sample; }
+protected:
+ IIgsSampleHeader _header; ///< Apple IIGS AGI sample header
+ int8 *_sample; ///< Sample data (8-bit signed format)
+};
+
+/** Apple IIGS MIDI program change to instrument number mapping. */
+struct MidiProgramChangeMapping {
+ byte midiProgToInst[44]; ///< Lookup table for the MIDI program number to instrument number mapping
+ byte undefinedInst; ///< The undefined instrument number
+
+ // Maps the MIDI program number to an instrument number
+ byte map(uint midiProg) const {
+ return midiProg < ARRAYSIZE(midiProgToInst) ? midiProgToInst[midiProg] : undefinedInst;
+ }
+};
+
+/** 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)
+ const MidiProgramChangeMapping &progToInst; ///< Program change to instrument number mapping
+};
+
+/** 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 IIgsMidiChannel {
+public:
+ IIgsMidiChannel() : _instrument(0), _sample(0), _volume(0) {}
+ uint activeSounds() const; ///< How many active sounds are playing?
+ void setInstrument(const IIgsInstrumentHeader *instrument, const int8 *sample);
+ void setVolume(uint8 volume);
+ void noteOff(uint8 note, uint8 velocity);
+ void noteOn(uint8 note, uint8 velocity);
+ void stopSounds(); ///< Clears the channel of any sounds
+ void removeStoppedSounds(); ///< Removes all stopped sounds from this MIDI channel
+public:
+ typedef Common::Array<IIgsChannelInfo>::const_iterator const_iterator;
+ typedef Common::Array<IIgsChannelInfo>::iterator iterator;
+ Common::Array<IIgsChannelInfo> _gsChannels; ///< Apple IIGS channels playing on this MIDI channel
+protected:
+ const IIgsInstrumentHeader *_instrument; ///< Instrument used on this MIDI channel
+ const int8 *_sample; ///< Sample data used on this MIDI channel
+ uint8 _volume; ///< MIDI controller number 7 (Volume)
+};
+
+/**
+ * Class for managing Apple IIGS sound channels.
+ * TODO: Check what instruments are used by default on the MIDI channels
+ * FIXME: Some instrument choices sound wrong
+ */
+class IIgsSoundMgr {
+public:
+ typedef Common::Array<IIgsMidiChannel>::const_iterator const_iterator;
+ typedef Common::Array<IIgsMidiChannel>::iterator iterator;
+ static const uint kSfxMidiChannel = 0; ///< The MIDI channel used for playing sound effects
+public:
+ // For initializing
+ IIgsSoundMgr();
+ void setProgramChangeMapping(const MidiProgramChangeMapping *mapping);
+ bool loadInstrumentHeaders(const Common::FSNode &exePath, const IIgsExeInfo &exeInfo);
+ bool loadWaveFile(const Common::FSNode &wavePath, const IIgsExeInfo &exeInfo);
+ // Miscellaneous methods
+ uint activeSounds() const; ///< How many active sounds are playing?
+ void stopSounds(); ///< Stops all sounds
+ void removeStoppedSounds(); ///< Removes all stopped sounds from the MIDI channels
+ // For playing Apple IIGS AGI samples (Sound effects etc)
+ bool playSampleSound(const IIgsSampleHeader &sampleHeader, const int8 *sample);
+ // MIDI commands
+ void midiNoteOff(uint8 channel, uint8 note, uint8 velocity);
+ void midiNoteOn(uint8 channel, uint8 note, uint8 velocity);
+ void midiController(uint8 channel, uint8 controller, uint8 value);
+ void midiProgramChange(uint8 channel, uint8 program);
+ void midiPitchWheel(uint8 wheelPos);
+protected:
+ const IIgsInstrumentHeader* getInstrument(uint8 program) const;
+public:
+ Common::Array<IIgsMidiChannel> _midiChannels; ///< Information about each MIDI channel
+protected:
+ Common::Array<int8> _wave; ///< Sample data used by the Apple IIGS MIDI instruments
+ const MidiProgramChangeMapping *_midiProgToInst; ///< MIDI program change to instrument number mapping
+ Common::Array<IIgsInstrumentHeader> _instruments; ///< Instruments used by the Apple IIGS AGI
+};
+
+} // End of namespace Agi
+
+#endif /* AGI_SOUND_2GS_H */