aboutsummaryrefslogtreecommitdiff
path: root/engines
diff options
context:
space:
mode:
authorKari Salminen2008-04-17 17:18:43 +0000
committerKari Salminen2008-04-17 17:18:43 +0000
commit29e2b19c923abb81eb50b61580b982edecd6af3d (patch)
treeb5d4fce7c96efc0be1c763e147a58c2973cfadf1 /engines
parent75641c405e01df1dbceaca2038fe3c4c95c39dc5 (diff)
downloadscummvm-rg350-29e2b19c923abb81eb50b61580b982edecd6af3d.tar.gz
scummvm-rg350-29e2b19c923abb81eb50b61580b982edecd6af3d.tar.bz2
scummvm-rg350-29e2b19c923abb81eb50b61580b982edecd6af3d.zip
Implemented Apple IIGS MIDI playing with software mixing.
(NOTE: As you can hear this is a work in progress ;-)) - Added IIgsSoundMgr class for handling Apple IIGS sounds and moved functionality inside it - Added IIgsMidiChannel class for handling Apple IIGS MIDI channels - Made sound effects playing use the IIgsSoundMgr class too - Modified software mixing routine to mix multiple sounds svn-id: r31544
Diffstat (limited to 'engines')
-rw-r--r--engines/agi/sound.cpp410
-rw-r--r--engines/agi/sound.h78
2 files changed, 342 insertions, 146 deletions
diff --git a/engines/agi/sound.cpp b/engines/agi/sound.cpp
index 31c722fe59..5cf7047ba8 100644
--- a/engines/agi/sound.cpp
+++ b/engines/agi/sound.cpp
@@ -276,8 +276,6 @@ static const IIgsExeInfo IIgsExeInfos[] = {
{GID_GOLDRUSH, "GR", 0x3003, 148268, 0x8979, instSetV2}
};
-static IIgsInstrumentHeader g_instruments[MAX_INSTRUMENTS];
-static uint g_numInstruments = 0;
// Time (In milliseconds) in Apple IIGS mixing buffer time granularity
// (i.e. in IIGS_BUFFER_SIZE / getRate() seconds granularity)
static uint32 g_IIgsBufGranMillis = 0;
@@ -365,22 +363,7 @@ void SoundMgr::startSound(int resnum, int flag) {
switch (type) {
case AGI_SOUND_SAMPLE: {
IIgsSample *sampleRes = (IIgsSample *) _vm->_game.sounds[_playingSound];
- const IIgsWaveInfo &waveInfo = _IIgsChannel.ins.oscList(0).waves[0];
- const IIgsSampleHeader &header = sampleRes->getHeader();
-
- _IIgsChannel.ins = header.instrument;
- _IIgsChannel.sample = sampleRes->getSample() + waveInfo.addr;
- _IIgsChannel.pos = intToFrac(0);
- _IIgsChannel.posAdd = intToFrac(0);
- _IIgsChannel.note = intToFrac(header.pitch) + doubleToFrac(waveInfo.relPitch/256.0);
- _IIgsChannel.startEnvVol = intToFrac(0);
- _IIgsChannel.chanVol = intToFrac(header.volume);
- _IIgsChannel.envVol = _IIgsChannel.startEnvVol;
- _IIgsChannel.vol = doubleToFrac(fracToDouble(_IIgsChannel.envVol) * fracToDouble(_IIgsChannel.chanVol) / 127.0);
- _IIgsChannel.envSeg = intToFrac(0);
- _IIgsChannel.loop = (waveInfo.mode == OSC_MODE_LOOP);
- _IIgsChannel.size = waveInfo.size - waveInfo.addr;
- _IIgsChannel.end = false;
+ _gsSound.playSampleSound(sampleRes->getHeader(), sampleRes->getSample());
break;
}
case AGI_SOUND_MIDI:
@@ -442,14 +425,35 @@ void SoundMgr::stopSound() {
_vm->_game.sounds[_playingSound]->stop();
if (_vm->_soundemu == SOUND_EMU_APPLE2GS) {
- _IIgsChannel.end = true;
- _IIgsChannel.chanVol = intToFrac(0);
+ _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();
+}
+
static int16 *buffer;
int SoundMgr::initSound() {
@@ -590,57 +594,27 @@ void SoundMgr::playMidiSound() {
case MIDI_CMD_NOTE_OFF:
parm1 = *p++;
parm2 = *p++;
-#if 0
- if (ch < NUM_CHANNELS)
- stopNote(ch);
-#endif
- debugC(3, kDebugLevelSound, "note off, channel %02x, note %02x, velocity %02x", ch, parm1, parm2);
+ _gsSound.midiNoteOff(ch, parm1, parm2);
break;
case MIDI_CMD_NOTE_ON:
parm1 = *p++;
parm2 = *p++;
-#if 0
- if (ch < NUM_CHANNELS)
- playNote(ch, noteToPeriod(parm1), 127);
-#endif
- debugC(3, kDebugLevelSound, "note on, channel %02x, note %02x, velocity %02x", ch, parm1, parm2);
+ _gsSound.midiNoteOn(ch, parm1, parm2);
break;
case MIDI_CMD_CONTROLLER:
- // 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).
- // TODO: Find out what controller 0 does and implement volume changes.
parm1 = *p++;
parm2 = *p++;
- debugC(3, kDebugLevelSound, "controller %02x, ch %02x, val %02x", parm1, ch, parm2);
+ _gsSound.midiController(ch, parm1, parm2);
break;
case MIDI_CMD_PROGRAM_CHANGE:
- // In all the tested Apple IIGS AGI MIDI resources
- // program change's parameter was in range 0-43.
- // This doesn't map directly to instrument numbers as all of
- // the tested Apple IIGS AGI games only use 26 or 28 instruments.
- // TODO: Find out the mapping to instruments and implement it.
parm1 = *p++;
- debugC(3, kDebugLevelSound, "program change %02x, channel %02x", parm1, ch);
-#if 0
- if (ch < NUM_CHANNELS) {
- chn[ch].ins = (uint16 *)&wave[waveaddr[parm1]];
- chn[ch].size = wavesize[parm1];
- }
- debugC(3, kDebugLevelSound, "set patch %02x (%d,%d), ch %02x",
- parm1, waveaddr[parm1], wavesize[parm1], ch);
-#endif
+ _gsSound.midiProgramChange(ch, parm1);
break;
case MIDI_CMD_PITCH_WHEEL:
parm1 = *p++;
parm2 = *p++;
- // In all the tested Apple IIGS AGI MIDI resources
- // pitch wheel commands always had 0x2000 (Center position)
- // as the combined 14-bit value for the position.
uint16 wheelPos = ((parm2 & 0x7F) << 7) | (parm1 & 0x7F); // 14-bit value
- debugC(3, kDebugLevelSound, "Pitch wheel position %04x (Not implemented yet)", wheelPos);
+ _gsSound.midiPitchWheel(wheelPos);
break;
}
}
@@ -648,6 +622,170 @@ void SoundMgr::playMidiSound() {
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 note, uint8 velocity) {
+ this->origNote = note;
+ 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 >= note)
+ waveInfo = &ins->oscList(i).waves[0];
+ assert(waveInfo != NULL);
+ this->relocatedSample = this->unrelocatedSample + waveInfo->addr;
+ this->posAdd = intToFrac(0);
+ this->note = intToFrac(note) + 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");
@@ -655,7 +793,7 @@ void SoundMgr::playSampleSound() {
}
if (_playingSound != -1)
- _playing = !_IIgsChannel.end;
+ _playing = _gsSound.activeSounds() > 0;
}
void SoundMgr::playAgiSound() {
@@ -745,72 +883,66 @@ uint32 SoundMgr::mixSound(void) {
return BUFFER_SIZE;
// Handle Apple IIGS sound mixing here
+ // TODO: Implement playing both waves in an oscillator
+ // TODO: Implement swap-mode in an oscillator
if (_vm->_soundemu == SOUND_EMU_APPLE2GS) {
- AgiSoundType type = (AgiSoundType) _vm->_game.sounds[_playingSound]->type();
- // Currently we only support mixing a single sample in Apple IIGS mixing code.
- if (type != AGI_SOUND_SAMPLE)
- return IIGS_BUFFER_SIZE;
- //IIgsWaveInfo &waveInfo = _IIgsChannel.ins.oscList(0).waves[0];
-
- //uint period = noteToPeriod(fracToInt(_IIgsChannel.note + FRAC_HALF));
- //_IIgsChannel.posAdd = ((frac_t) (118600 * 4 / period)) << (FRAC_BITS - 8);
-
- // Hertz (number of vibrations a second) = 6.875 x 2 ^ ( ( 3 + MIDI_Pitch ) / 12 )
- // From http://www.musicmasterworks.com/WhereMathMeetsMusic.html
- //double hertz = 6.875 * pow(SEMITONE, 3 + fracToDouble(_IIgsChannel.note));
- //double hertz = 8.175798915644 * pow(SEMITONE, fracToDouble(_IIgsChannel.note));
- // double step = getRate() / hertz;
- // _IIgsChannel.posAdd = doubleToFrac(step);
-
- // Frequency multiplier was 1076.0 based on tests made with MESS 0.117.
- // Tests made with KEGS32 averaged the multiplier to around 1045.
- // So this is a guess but maybe it's 1046.5... i.e. C6's frequency?
- double hertz = C6_FREQ * pow(SEMITONE, fracToDouble(_IIgsChannel.note));
- _IIgsChannel.posAdd = doubleToFrac(hertz / getRate());
- _IIgsChannel.vol = doubleToFrac(fracToDouble(_IIgsChannel.envVol) * fracToDouble(_IIgsChannel.chanVol) / 127.0);
- double tempVol = fracToDouble(_IIgsChannel.vol)/127.0;
-
- for (i = 0; i < IIGS_BUFFER_SIZE; i++) {
- b = _IIgsChannel.sample[fracToInt(_IIgsChannel.pos)];
- // DOESN'T DO MIXING YET! ONLY ONE SAMPLE PER PLAYING!
- _sndBuffer[i] = (int16) (b * tempVol * 256);
- _IIgsChannel.pos += _IIgsChannel.posAdd;
-
- if (_IIgsChannel.pos >= intToFrac(_IIgsChannel.size)) {
- if (_IIgsChannel.loop) {
- _IIgsChannel.pos %= intToFrac(_IIgsChannel.size);
- // Probably we should loop the envelope too
- _IIgsChannel.envSeg = 0;
- _IIgsChannel.envVol = _IIgsChannel.startEnvVol;
- } else {
- _IIgsChannel.pos = _IIgsChannel.chanVol = 0;
- _IIgsChannel.end = true;
- break;
- }
- }
- }
+ 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.
+ // So this is a guess but maybe it's 1046.5... i.e. C6's frequency?
+ double hertz = C6_FREQ * pow(SEMITONE, fracToDouble(channel.note));
+ channel.posAdd = doubleToFrac(hertz / getRate());
+ channel.vol = doubleToFrac(fracToDouble(channel.envVol) * fracToDouble(channel.chanVol) / 127.0);
+ double tempVol = fracToDouble(channel.vol)/127.0;
+ for (i = 0; i < IIGS_BUFFER_SIZE; i++) {
+ b = channel.relocatedSample[fracToInt(channel.pos)];
+ // TODO: Find out what volume/amplification setting is loud enough
+ // but still doesn't clip when playing many channels on it.
+ _sndBuffer[i] += (int16) (b * tempVol * 256/4);
+ channel.pos += channel.posAdd;
+
+ if (channel.pos >= intToFrac(channel.size)) {
+ if (channel.loop) {
+ // Don't divide by zero on zero length samples
+ channel.pos %= intToFrac(channel.size + (channel.size == 0));
+ // Probably we should loop the envelope too
+ channel.envSeg = 0;
+ channel.envVol = channel.startEnvVol;
+ } else {
+ channel.pos = channel.chanVol = 0;
+ channel.end = true;
+ break;
+ }
+ }
+ }
- if (_IIgsChannel.envSeg <= _IIgsChannel.ins.relseg) {
- IIgsEnvelopeSegment &seg = _IIgsChannel.ins.env.seg[_IIgsChannel.envSeg];
- double bufSecLen = IIGS_BUFFER_SIZE / (double) getRate();
- double ticksPerSec = 100; // 1000 is way too much
- double bufTickLen = bufSecLen / (1.0/ticksPerSec);
- frac_t envVolDelta = doubleToFrac((seg.inc/256.0)*bufTickLen);
- if (intToFrac(seg.bp) >= _IIgsChannel.envVol) {
- _IIgsChannel.envVol += envVolDelta;
- if (_IIgsChannel.envVol >= intToFrac(seg.bp)) {
- _IIgsChannel.envVol = intToFrac(seg.bp);
- _IIgsChannel.envSeg += 1;
- }
- } else {
- _IIgsChannel.envVol -= envVolDelta;
- if (_IIgsChannel.envVol <= intToFrac(seg.bp)) {
- _IIgsChannel.envVol = intToFrac(seg.bp);
- _IIgsChannel.envSeg += 1;
+ if (channel.envSeg < ENVELOPE_SEGMENT_COUNT) {
+ const IIgsEnvelopeSegment &seg = channel.ins->env.seg[channel.envSeg];
+ double bufSecLen = IIGS_BUFFER_SIZE / (double) getRate();
+ double ticksPerSec = 60; // Currently a guess, 1000 would be way too much
+ double bufTickLen = bufSecLen / (1.0/ticksPerSec);
+ frac_t envVolDelta = doubleToFrac((seg.inc/256.0)*bufTickLen);
+ if (intToFrac(seg.bp) >= channel.envVol) {
+ channel.envVol += envVolDelta;
+ if (channel.envVol >= intToFrac(seg.bp)) {
+ channel.envVol = intToFrac(seg.bp);
+ channel.envSeg += 1;
+ }
+ } else {
+ channel.envVol -= envVolDelta;
+ if (channel.envVol <= intToFrac(seg.bp)) {
+ channel.envVol = intToFrac(seg.bp);
+ channel.envSeg += 1;
+ }
+ }
+ }
}
}
}
- //_IIgsChannel.envSeg += doubleToFrac(1/100.0);
+ _gsSound.removeStoppedSounds();
return IIGS_BUFFER_SIZE;
} /* else ... */
@@ -896,7 +1028,7 @@ const IIgsExeInfo *SoundMgr::getIIgsExeInfo(enum AgiGameID gameid) const {
return NULL;
}
-bool SoundMgr::loadInstrumentHeaders(const Common::String &exePath, const IIgsExeInfo &exeInfo) {
+bool IIgsSoundMgr::loadInstrumentHeaders(const Common::String &exePath, const IIgsExeInfo &exeInfo) {
bool loadedOk = false; // Was loading successful?
Common::File file;
@@ -908,11 +1040,11 @@ bool SoundMgr::loadInstrumentHeaders(const Common::String &exePath, const IIgsEx
}
// Read the whole executable file into memory
- Common::MemoryReadStream *data = file.readStream(file.size());
+ 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 != NULL && data->size() >= (exeInfo.instSetStart + exeInfo.instSet.byteCount)) {
+ if (data && data->size() >= (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();
@@ -932,22 +1064,25 @@ bool SoundMgr::loadInstrumentHeaders(const Common::String &exePath, const IIgsEx
// Read in the instrument set one instrument at a time
data->seek(exeInfo.instSetStart);
- g_numInstruments = 0; // Zero number of successfully loaded instruments
+
+ // Load the instruments
+ _instruments.clear();
+ _instruments.reserve(exeInfo.instSet.instCount);
+ IIgsInstrumentHeader instrument;
for (uint i = 0; i < exeInfo.instSet.instCount; i++) {
- if (!g_instruments[i].read(*data)) {
+ 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.c_str());
break;
}
- g_numInstruments++; // Increase number of successfully loaded instruments
+ _instruments.push_back(instrument); // Add the successfully loaded instrument to the instruments array
}
// Loading was successful only if all instruments were loaded successfully
- loadedOk = (g_numInstruments == exeInfo.instSet.instCount);
+ 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.c_str());
- delete data; // Free the memory buffer allocated for reading the executable file
return loadedOk;
}
@@ -964,7 +1099,7 @@ bool SoundMgr::convertWave(Common::SeekableReadStream &source, int8 *dest, uint
return !source.ioFailed();
}
-Common::SharedPtr<Common::MemoryReadStream> SoundMgr::loadWaveFile(const Common::String &wavePath, const IIgsExeInfo &exeInfo) {
+bool IIgsSoundMgr::loadWaveFile(const Common::String &wavePath, const IIgsExeInfo &exeInfo) {
Common::File file;
// Open the wave file and read it into memory
@@ -983,10 +1118,12 @@ Common::SharedPtr<Common::MemoryReadStream> SoundMgr::loadWaveFile(const Common:
"Using the wave file as it is - music may sound weird", md5str, exeInfo.exePrefix);
}
uint8Wave->seek(0); // Seek wave to its start
- return uint8Wave;
+ // 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.c_str());
- return Common::SharedPtr<Common::MemoryReadStream>(); // Return a NULL shared pointer
+ return false;
}
}
@@ -1057,17 +1194,12 @@ bool SoundMgr::loadInstruments() {
return false;
}
- // First load the wave file and then load the instrument headers.
- // Finally convert the wave file from 8-bit unsigned to 8-bit signed format.
- // As none of the tested SIERRASTANDARD-files have zeroes in them there's
- // no need to check for true sample size (A zero in the sample data would
- // end the sample prematurely).
- Common::SharedPtr<Common::MemoryReadStream> uint8Wave = loadWaveFile(waveFsnode->getPath(), *exeInfo);
- if (uint8Wave && loadInstrumentHeaders(exeFsnode->getPath(), *exeInfo)) {
- _gsWave.resize(uint8Wave->size()); // Allocate space for the 8-bit signed version of the SIERRASTANDARD-file
- return SoundMgr::convertWave(*uint8Wave, _gsWave.begin(), uint8Wave->size());
- } else // Error loading the wave file or the instrument headers
- 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->getPath(), *exeInfo) && _gsSound.loadInstrumentHeaders(exeFsnode->getPath(), *exeInfo);
}
static void fillAudio(void *udata, int16 *stream, uint len) {
diff --git a/engines/agi/sound.h b/engines/agi/sound.h
index 6ba222780a..faf8c11131 100644
--- a/engines/agi/sound.h
+++ b/engines/agi/sound.h
@@ -196,11 +196,13 @@ struct AgiNote {
};
struct IIgsChannelInfo {
- IIgsInstrumentHeader ins; ///< Instrument info
- const int8 *sample; ///< Source sample data (8-bit signed format)
+ 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)
- frac_t note; ///< Note
+ 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
@@ -209,6 +211,14 @@ struct IIgsChannelInfo {
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 note, 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?
};
/**
@@ -349,6 +359,63 @@ struct IIgsExeInfo {
const InstrumentSetInfo &instSet; ///< Information about the used instrument set
};
+class IIgsMidiChannel {
+public:
+ 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::String &exePath, const IIgsExeInfo &exeInfo);
+ bool loadWaveFile(const Common::String &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;
@@ -386,14 +453,13 @@ private:
bool _playing;
ChannelInfo _chn[NUM_CHANNELS];
- IIgsChannelInfo _IIgsChannel;
+ IIgsSoundMgr _gsSound;
int _endflag;
int _playingSound;
uint8 _env;
int16 *_sndBuffer;
const int16 *_waveform;
- Common::Array<int8> _gsWave;
void premixerCall(int16 *buf, uint len);
@@ -412,9 +478,7 @@ public:
void playMidiSound();
void playSampleSound();
const IIgsExeInfo *getIIgsExeInfo(enum AgiGameID gameid) const;
- bool loadInstrumentHeaders(const Common::String &exePath, const IIgsExeInfo &exeInfo);
static bool convertWave(Common::SeekableReadStream &source, int8 *dest, uint length);
- Common::SharedPtr<Common::MemoryReadStream> loadWaveFile(const Common::String &wavePath, const IIgsExeInfo &exeInfo);
};
} // End of namespace Agi