From 29e2b19c923abb81eb50b61580b982edecd6af3d Mon Sep 17 00:00:00 2001 From: Kari Salminen Date: Thu, 17 Apr 2008 17:18:43 +0000 Subject: 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 --- engines/agi/sound.cpp | 410 +++++++++++++++++++++++++++++++++----------------- engines/agi/sound.h | 78 +++++++++- 2 files changed, 342 insertions(+), 146 deletions(-) (limited to 'engines') 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::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::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 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 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 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(); // 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 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::const_iterator const_iterator; + typedef Common::Array::iterator iterator; + Common::Array _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::const_iterator const_iterator; + typedef Common::Array::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 _midiChannels; ///< Information about each MIDI channel +protected: + Common::Array _wave; ///< Sample data used by the Apple IIGS MIDI instruments + const MidiProgramChangeMapping *_midiProgToInst; ///< MIDI program change to instrument number mapping + Common::Array _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 _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 loadWaveFile(const Common::String &wavePath, const IIgsExeInfo &exeInfo); }; } // End of namespace Agi -- cgit v1.2.3