diff options
| -rw-r--r-- | engines/agi/sound_2gs.cpp | 950 | ||||
| -rw-r--r-- | engines/agi/sound_2gs.h | 327 | 
2 files changed, 519 insertions, 758 deletions
| diff --git a/engines/agi/sound_2gs.cpp b/engines/agi/sound_2gs.cpp index 11bf5a9034..239290716a 100644 --- a/engines/agi/sound_2gs.cpp +++ b/engines/agi/sound_2gs.cpp @@ -25,6 +25,7 @@  #include "common/config-manager.h"  #include "common/fs.h" +#include "common/archive.h"  #include "common/md5.h"  #include "common/memstream.h"  #include "common/str-array.h" @@ -35,47 +36,91 @@  namespace Agi {  SoundGen2GS::SoundGen2GS(AgiEngine *vm, Audio::Mixer *pMixer) : SoundGen(vm, pMixer) { -	_disabledMidi = !loadInstruments(); +	// Allocate memory for the wavetable +	_wavetable = new int8[SIERRASTANDARD_SIZE]; +	// Apple IIGS AGI MIDI player advances 60 ticks per second. Strategy +	// here is to first generate audio for a 1/60th of a second and then +	// advance the MIDI player by one tick. Thus, make the output buffer +	// to be a 1/60th of a second in length. +	_outSize = _sampleRate/60; +	_out = new int16[2*_outSize]; // stereo + +	// Initialize player variables +	_nextGen = 0; +	_ticks = 0; + +	// Not playing anything yet  	_playingSound = -1;  	_playing = false; -	_sndBuffer = (int16 *)calloc(2, BUFFER_SIZE); - -	_midiChannels.resize(16); // Set the amount of available MIDI channels +	// Load instruments +	_disableMidi = !loadInstruments();  	_mixer->playStream(Audio::Mixer::kMusicSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);  }  SoundGen2GS::~SoundGen2GS() {  	_mixer->stopHandle(_soundHandle); - -	free(_sndBuffer); +	delete _wavetable; +	delete _out;  }  int SoundGen2GS::readBuffer(int16 *buffer, const int numSamples) { -	fillAudio(buffer, numSamples / 2); -	 +	static uint data_available = 0; +	static uint data_offset = 0; +	uint n = numSamples << 1; +	uint8 *p = (uint8*)buffer; + +	while (n > data_available) { +		memcpy(p, (uint8*)_out + data_offset, data_available); +		p += data_available; +		n -= data_available; + +		advancePlayer(); + +		data_available = generateOutput() << 1; +		data_offset = 0; +	} + +	memcpy(p, (uint8*)_out + data_offset, n); +	data_offset += n; +	data_available -= n; +  	return numSamples;  } +/** + * Initiate the playing of a sound resource. + * @param resnum Resource number + */  void SoundGen2GS::play(int resnum) {  	AgiSoundEmuType type;  	_playingSound = resnum;  	type = (AgiSoundEmuType)_vm->_game.sounds[resnum]->type(); -  	assert (type == AGI_SOUND_SAMPLE || type == AGI_SOUND_MIDI); +	if (_vm->_soundemu != SOUND_EMU_APPLE2GS) { +		warning("Trying to play sample or MIDI resource but not using Apple IIGS sound emulation mode"); +		return; +	} + +	haltGenerators(); +  	switch (type) {  	case AGI_SOUND_SAMPLE: {  		IIgsSample *sampleRes = (IIgsSample *) _vm->_game.sounds[_playingSound]; -		playSampleSound(sampleRes->getHeader(), sampleRes->getSample()); +		const IIgsSampleHeader &header = sampleRes->getHeader(); +		_channels[kSfxMidiChannel].setInstrument(&header.instrument); +		_channels[kSfxMidiChannel].setVolume(header.volume); +		midiNoteOn(kSfxMidiChannel, header.pitch, 127);  		break;  	}  	case AGI_SOUND_MIDI:  		((IIgsMidi *) _vm->_game.sounds[_playingSound])->rewind(); +		_ticks = 0;  		break;  	default:  		break; @@ -83,213 +128,168 @@ void SoundGen2GS::play(int resnum) {  }  void SoundGen2GS::stop() { +	haltGenerators();  	_playingSound = -1; - -	// Stops all sounds on all MIDI channels -	for (iterator iter = _midiChannels.begin(); iter != _midiChannels.end(); ++iter) -		iter->stopSounds(); +	_playing = 0;  } -void SoundGen2GS::playSound() { -	if (_playingSound == -1) -		return; - -	if (_vm->_game.sounds[_playingSound]->type() == AGI_SOUND_MIDI) { -		playMidiSound(); -		//warning("playSound: Trying to play an Apple IIGS MIDI sound. Not yet implemented"); -	} else if (_vm->_game.sounds[_playingSound]->type() == AGI_SOUND_SAMPLE) { -		//debugC(3, kDebugLevelSound, "playSound: Trying to play an Apple IIGS sample"); -		playSampleSound(); -	} - -	if (!_playing) { -		_vm->_sound->soundIsFinished(); - -		_playingSound = -1; -	} -} - -uint32 SoundGen2GS::mixSound() { -	int i, b; - -	memset(_sndBuffer, 0, BUFFER_SIZE << 1); +/** + * Fill output buffer by advancing the generators for a 1/60th of a second. + * @return Number of generated samples + */ +uint32 SoundGen2GS::generateOutput() { +	memset(_out, 0, _outSize*2*2);  	if (!_playing || _playingSound == -1) -		return BUFFER_SIZE; - -	// Handle Apple IIGS sound mixing here -	// TODO: Implement playing both waves in an oscillator -	// TODO: Implement swap-mode in an oscillator -	for (uint midiChan = 0; midiChan < _midiChannels.size(); midiChan++) { -		for (uint gsChan = 0; gsChan < _midiChannels[midiChan]._gsChannels.size(); gsChan++) { -			IIgsChannelInfo &channel = _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; -						} +		return _outSize*2; + +	int16 *p = _out; +	int n = _outSize; +	while (n--) { +		int outl = 0; +		int outr = 0; +		for (int k = 0; k < MAX_GENERATORS; k++) { +			IIgsGenerator *g = &_generators[k]; +			if (!g->ins) +				continue; +			const IIgsInstrumentHeader *i = g->ins; + +			// Advance envelope +			int vol = fracToInt(g->a); +			if (g->a <= i->env[g->seg].bp) { +				g->a += i->env[g->seg].inc * ENVELOPE_COEF; +				if (g->a > i->env[g->seg].bp) { +					g->a = i->env[g->seg].bp; +					g->seg++; +				} +			} else { +				g->a -= i->env[g->seg].inc * ENVELOPE_COEF; +				if (g->a < i->env[g->seg].bp) { +					g->a = i->env[g->seg].bp; +					g->seg++; +				} +			} +			 +			// TODO: Advance vibrato here. Apple IIGS uses a LFO with +			// a triangle wave for vibrato. None of the instruments in the +			// current mappings from MIDI program number to instrument use +			// vibrato, but some of the choices are possibly still wrong. +			 +			// Advance oscillators +			int s0 = 0; +			int s1 = 0; +			if (!g->osc[0].halt) { +				s0 = g->osc[0].base[fracToInt(g->osc[0].p)]; +				g->osc[0].p += g->osc[0].pd; +				if ((uint)fracToInt(g->osc[0].p) >= g->osc[0].size) { +					g->osc[0].p -= intToFrac(g->osc[0].size); +					if (!g->osc[0].loop) +						g->osc[0].halt = 1; +					if (g->osc[0].swap) { +						g->osc[0].halt = 1; +						g->osc[1].halt = 0;  					}  				} - -				if (channel.envSeg < ENVELOPE_SEGMENT_COUNT) { -					const IIgsEnvelopeSegment &seg = channel.ins->env.seg[channel.envSeg]; -					// I currently assume enveloping works with the same speed as the MIDI -					// (i.e. with 1/60ths of a second ticks). -					// TODO: Check if enveloping really works with the same speed as MIDI -					frac_t envVolDelta = doubleToFrac(seg.inc/256.0); -					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; -						} +			} +			if (!g->osc[1].halt) { +				s1 = g->osc[1].base[fracToInt(g->osc[1].p)]; +				g->osc[1].p += g->osc[1].pd; +				if ((uint)fracToInt(g->osc[1].p) >= g->osc[1].size) { +					g->osc[1].p -= intToFrac(g->osc[1].size); +					if (!g->osc[1].loop) +						g->osc[1].halt = 1; +					if (g->osc[1].swap) { +						g->osc[0].halt = 0; +						g->osc[1].halt = 1;  					}  				}  			} +			 +			// Multiply sample with envelope and MIDI volume information. +			// Also amplify. +			s0 *= vol * g->vel/127 * 80/256; +			s1 *= vol * g->vel/127 * 80/256; +			 +			if (g->osc[0].chn) outl += s0; +			else               outr += s0; +			if (g->osc[1].chn) outl += s1; +			else               outr += s1;  		} -	} - -	removeStoppedSounds(); -	return IIGS_BUFFER_SIZE; -} - -void SoundGen2GS::fillAudio(int16 *stream, uint len) { -	uint32 p = 0; - -	// current number of audio bytes in _sndBuffer -	static uint32 data_available = 0; -	// offset of start of audio bytes in _sndBuffer -	static uint32 data_offset = 0; - -	len <<= 2; - -	debugC(5, kDebugLevelSound, "(%p, %d)", (void *)stream, len); - -	while (len > data_available) { -		memcpy((uint8 *)stream + p, (uint8*)_sndBuffer + data_offset, data_available); -		p += data_available; -		len -= data_available; +		if (outl > 32768) outl = 32768; +		if (outl <-32767) outl =-32767; +		if (outr > 32768) outr = 32768; +		if (outr <-32767) outr =-32767; -		playSound(); -		data_available = mixSound() << 1; -		data_offset = 0; +		*p++ = outl; +		*p++ = outr;  	} -	memcpy((uint8 *)stream + p, (uint8*)_sndBuffer + data_offset, len); -	data_offset += len; -	data_available -= len; +	return _outSize*2;  } -void SoundGen2GS::playSampleSound() { -	if (_vm->_soundemu != SOUND_EMU_APPLE2GS) { -		warning("Trying to play a sample but not using Apple IIGS sound emulation mode"); +void SoundGen2GS::advancePlayer() { +	if (_playingSound == -1)  		return; -	} -	if (_playingSound != -1) -		_playing = activeSounds() > 0; -} - -void SoundGen2GS::stopSounds() { -	// Stops all sounds on all MIDI channels -	for (iterator iter = _midiChannels.begin(); iter != _midiChannels.end(); ++iter) -		iter->stopSounds(); -} - -bool SoundGen2GS::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) +	if (_vm->_game.sounds[_playingSound]->type() == AGI_SOUND_MIDI) { +		advanceMidiPlayer(); +	} else if (_vm->_game.sounds[_playingSound]->type() == AGI_SOUND_SAMPLE) { +		_playing = activeGenerators() > 0; +	} -	return true; +	if (!_playing) { +		_vm->_sound->soundIsFinished(); +		_playingSound = -1; +	}  } -void SoundGen2GS::playMidiSound() { -	if (_disabledMidi) +void SoundGen2GS::advanceMidiPlayer() { +	if (_disableMidi)  		return;  	const uint8 *p;  	uint8 parm1, parm2; -	static uint8 cmd, ch; +	static uint8 cmd, chn;  	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]; +	_ticks++;  	_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) { +		if (*p == MIDI_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) { +		} +		if (*p == MIDI_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) { +		// Check for delta time +		uint8 delta = *p; +		if (midiObj->_ticks + delta > _ticks)  			break; -		} -		midiObj->_midiTicks += deltaTime; -		p++; // Jump over the delta-time byte as it was already taken care of +		midiObj->_ticks += delta; +		p++;  		// Check for end of MIDI sequence marker (This time it after reading delta-time) -		if (*p == MIDI_BYTE_STOP_SEQUENCE) { +		if (*p == MIDI_STOP_SEQUENCE) {  			debugC(3, kDebugLevelSound, "End of MIDI sequence (After reading delta-time)");  			_playing = false; -  			midiObj->rewind(); -  			return;  		} @@ -297,36 +297,51 @@ void SoundGen2GS::playMidiSound() {  		// Otherwise use running status (i.e. previously set command and channel).  		if (*p & 0x80) {  			cmd = *p++; -			ch = cmd & 0x0f; +			chn = cmd & 0x0f;  			cmd >>= 4;  		}  		switch (cmd) { -		case MIDI_CMD_NOTE_OFF: +		case MIDI_NOTE_OFF:  			parm1 = *p++;  			parm2 = *p++; -			midiNoteOff(ch, parm1, parm2); +			debugC(3, kDebugLevelSound, "channel %X: note off (key = %d, velocity = %d)", chn, parm1, parm2); +			midiNoteOff(chn, parm1, parm2);  			break; -		case MIDI_CMD_NOTE_ON: +		case MIDI_NOTE_ON:  			parm1 = *p++;  			parm2 = *p++; -			midiNoteOn(ch, parm1, parm2); +			debugC(3, kDebugLevelSound, "channel %X: note on (key = %d, velocity = %d)", chn, parm1, parm2); +			midiNoteOn(chn, parm1, parm2);  			break; -		case MIDI_CMD_CONTROLLER: +		case MIDI_CONTROLLER:  			parm1 = *p++;  			parm2 = *p++; -			midiController(ch, parm1, parm2); +			debugC(3, kDebugLevelSound, "channel %X: controller %02X = %02X", chn, parm1, parm2); +			// 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). +			switch (parm1) { +			case 7: +				_channels[chn].setVolume(parm2); +				break; +			}  			break; -		case MIDI_CMD_PROGRAM_CHANGE: +		case MIDI_PROGRAM_CHANGE:  			parm1 = *p++; -			midiProgramChange(ch, parm1); +			debugC(3, kDebugLevelSound, "channel %X: program change %02X", chn, parm1); +			_channels[chn].setInstrument(getInstrument(parm1));  			break; -		case MIDI_CMD_PITCH_WHEEL: +		case MIDI_PITCH_WHEEL:  			parm1 = *p++;  			parm2 = *p++; +			debugC(3, kDebugLevelSound, "channel %X: pitch wheel (unimplemented)", chn); +			break; -			uint16 wheelPos = ((parm2 & 0x7F) << 7) | (parm1 & 0x7F); // 14-bit value -			midiPitchWheel(wheelPos); +		default: +			debugC(3, kDebugLevelSound, "channel %X: unimplemented command %02X", chn, cmd);  			break;  		}  	} @@ -334,70 +349,88 @@ void SoundGen2GS::playMidiSound() {  	midiObj->setPtr(p);  } -void SoundGen2GS::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 SoundGen2GS::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 SoundGen2GS::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; +void SoundGen2GS::midiNoteOff(int channel, int note, int velocity) { +	// Release keys within the given MIDI channel +	for (int i = 0; i < MAX_GENERATORS; i++) { +		if (_generators[i].chn == channel && _generators[i].key == note) +			_generators[i].seg = _generators[i].ins->seg;  	} -	debugC(3, kDebugLevelSound, "controller %02x, ch %02x, val %02x%s", controller, channel, value, unimplemented ? " (Unimplemented)" : ""); -} - -void SoundGen2GS::midiProgramChange(uint8 channel, uint8 program) { -	_midiChannels[channel].setInstrument(getInstrument(program), _wave.begin()); -	debugC(3, kDebugLevelSound, "program change %02x, channel %02x", program, channel);  } -void SoundGen2GS::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); -} - -const IIgsInstrumentHeader* SoundGen2GS::getInstrument(uint8 program) const { -	return &_instruments[_midiProgToInst->map(program)]; -} +void SoundGen2GS::midiNoteOn(int channel, int note, int velocity) { +	if (!_channels[channel].getInstrument()) { +		debugC(3, kDebugLevelSound, "midiNoteOn(): no instrument specified for channel %d", channel); +		return; +	} -void SoundGen2GS::setProgramChangeMapping(const MidiProgramChangeMapping *mapping) { -	_midiProgToInst = mapping; +	// Allocate a generator for the note. +	IIgsGenerator* g = allocateGenerator(); +	g->ins = _channels[channel].getInstrument(); +	const IIgsInstrumentHeader* i = g->ins; +	 +	// Pass information from the MIDI channel to the generator. Take +	// velocity into account, although simplistically. +	velocity *= 5 / 3; +	if (velocity > 127) +		velocity = 127; + +	g->key = note; +	g->vel = velocity * _channels[channel].getVolume() / 127; +	g->chn = channel; +	 +	// Instruments can define different samples to be used based on +	// what the key is. Find the correct sample for our key. +	int wa = 0; +	int wb = 0; +	while (wa < i->waveCount[0] - 1 && note > i->wave[0][wa].key) +		wa++; +	while (wb < i->waveCount[1] - 1 && note > i->wave[1][wb].key) +		wb++; + +	// Prepare the generator. +	g->osc[0].base = i->wave[0][wa].base; +	g->osc[0].size = i->wave[0][wa].size; +	g->osc[0].pd   = doubleToFrac(midiKeyToFreq(note, (double)i->wave[0][wa].tune / 256.0) / (double)_sampleRate); +	g->osc[0].p    = 0; +	g->osc[0].halt = i->wave[0][wa].halt; +	g->osc[0].loop = i->wave[0][wa].loop; +	g->osc[0].swap = i->wave[0][wa].swap; +	g->osc[0].chn  = i->wave[0][wa].chn; + +	g->osc[1].base = i->wave[1][wb].base; +	g->osc[1].size = i->wave[1][wb].size; +	g->osc[1].pd   = doubleToFrac(midiKeyToFreq(note, (double)i->wave[1][wb].tune / 256.0) / (double)_sampleRate); +	g->osc[1].p    = 0; +	g->osc[1].halt = i->wave[1][wb].halt; +	g->osc[1].loop = i->wave[1][wb].loop; +	g->osc[1].swap = i->wave[1][wb].swap; +	g->osc[1].chn  = i->wave[1][wb].chn; + +	g->seg = 0; +	g->a   = 0; +} + +double SoundGen2GS::midiKeyToFreq(int key, double finetune) { +	return 440.0 * pow(2.0, (15.0 + (double)key + finetune) / 12.0); +} + +void SoundGen2GS::haltGenerators() { +	for (int i = 0; i < MAX_GENERATORS; i++) { +		_generators[i].osc[0].halt = true; +		_generators[i].osc[1].halt = true; +	}  } -void SoundGen2GS::removeStoppedSounds() { -	for (Common::Array<IIgsMidiChannel>::iterator iter = _midiChannels.begin(); iter != _midiChannels.end(); ++iter) -		iter->removeStoppedSounds(); +uint SoundGen2GS::activeGenerators() { +	int n = 0; +	for (int i = 0; i < MAX_GENERATORS; i++) +		if (!_generators[i].osc[0].halt || !_generators[i].osc[1].halt) +			n++; +	return n;  } -uint SoundGen2GS::activeSounds() const { -	uint result = 0; - -	for (Common::Array<IIgsMidiChannel>::const_iterator iter = _midiChannels.begin(); iter != _midiChannels.end(); ++iter) -		result += iter->activeSounds(); - -	return result; +void SoundGen2GS::setProgramChangeMapping(const IIgsMidiProgramMapping *mapping) { +	_progToInst = mapping;  }  IIgsMidi::IIgsMidi(uint8 *data, uint32 len, int resnum, SoundMgr &manager) : AgiSound(manager) { @@ -405,7 +438,7 @@ IIgsMidi::IIgsMidi(uint8 *data, uint32 len, int resnum, SoundMgr &manager) : Agi  	_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; +	_ticks = 0;  	_isValid = (_type == AGI_SOUND_MIDI) && (_data != NULL) && (_len >= 2);  	if (!_isValid) // Check for errors @@ -421,7 +454,7 @@ IIgsMidi::IIgsMidi(uint8 *data, uint32 len, int resnum, SoundMgr &manager) : Agi  static bool convertWave(Common::SeekableReadStream &source, int8 *dest, uint length) {  	// Convert the wave from 8-bit unsigned to 8-bit signed format  	for (uint i = 0; i < length; i++) -		dest[i] = (int8) ((int) source.readByte() - 128); +		dest[i] = (int8) ((int) source.readByte() - ZERO_OFFSET);  	return !(source.eos() || source.err());  } @@ -448,121 +481,82 @@ IIgsSample::IIgsSample(uint8 *data, uint32 len, int resnum, SoundMgr &manager) :  			_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) +		if (_sample != NULL) {  			_isValid = convertWave(stream, _sample, _header.sampleSize); +			// Finalize header info using sample data +			_header.finalize(_sample); +		}  	}  	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; -		} +bool IIgsInstrumentHeader::read(Common::SeekableReadStream &stream, bool ignoreAddr) { +	for (int i = 0; i < ENVELOPE_SEGMENT_COUNT; i++) { +		env[i].bp  = intToFrac(stream.readByte()); +		env[i].inc = intToFrac(stream.readUint16LE()) >> 8;  	} -	size = trueSize; // Set the true sample size - -	uint8Wave.seek(startPos); // Seek back to the stream's starting position - -	return true; -} +	seg          = stream.readByte(); +	/*priority   =*/ stream.readByte(); // Not needed. 32 in all tested data. +	bend         = stream.readByte(); +	vibDepth     = stream.readByte(); +	vibSpeed     = stream.readByte(); +	stream.readByte(); // Not needed? 0 in all tested data. + +	waveCount[0] = stream.readByte(); +	waveCount[1] = stream.readByte(); +	for (int i = 0; i < 2; i++) +	for (int k = 0; k < waveCount[i]; k++) { +		wave[i][k].key  = stream.readByte(); +		wave[i][k].base = (int8*)(stream.readByte() << 8); +		wave[i][k].size = 0x100 << (stream.readByte() & 7); +		uint8 b = stream.readByte(); +		wave[i][k].tune = stream.readUint16LE(); + +		// For sample resources we ignore the address. +		if (ignoreAddr) +			wave[i][k].base = 0; + +		// Check for samples that extend out of the wavetable. +		if ((int)wave[i][k].base + wave[i][k].size >= SIERRASTANDARD_SIZE) { +			warning("Invalid data detected in the instrument set of Apple IIGS AGI. Continuing anyway..."); +			wave[i][k].size = SIERRASTANDARD_SIZE - (int)wave[i][k].base; +		} -bool IIgsOscillator::finalize(Common::SeekableReadStream &uint8Wave) { -	for (uint i = 0; i < WAVES_PER_OSCILLATOR; i++) -		if (!waves[i].finalize(uint8Wave)) -			return false; +		// Parse the generator mode byte to separate fields. +		wave[i][k].halt =   b & 0x1;         // Bit 0     = HALT +		wave[i][k].loop = !(b & 0x2);        // Bit 1     = LOOP +		wave[i][k].swap =  (b & 0x3) == 0x3; // HALT|LOOP = SWAP +		wave[k][k].chn  = (b >> 4) > 0; // Output channel (left or right) +	} -	return true; +	return !(stream.eos() || stream.err());  } -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 IIgsInstrumentHeader::finalize(int8 *wavetable) { +	// Calculate final pointers to sample data and detect true sample size +	// in case the sample ends prematurely. +	for (int i = 0; i < 2; i++) +	for (int k = 0; k < waveCount[i]; k++) { +		wave[i][k].base += (uint)wavetable; -bool IIgsOscillatorList::finalize(Common::SeekableReadStream &uint8Wave) { -	for (uint i = 0; i < count; i++) -		if (!osc[i].finalize(uint8Wave)) -			return false; +		int8 *p = wave[i][k].base; +		uint trueSize; +		for (trueSize = 0; trueSize < wave[i][k].size; trueSize++) +			if (p[trueSize] == -ZERO_OFFSET) +				break; +		wave[i][k].size = trueSize; +	}  	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(); @@ -572,206 +566,52 @@ bool IIgsSampleHeader::read(Common::SeekableReadStream &stream) {  	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); -} - -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 IIgsMidiChannel::removeStoppedSounds() { -	for (int i = _gsChannels.size() - 1; i >= 0; i--) -		if (!_gsChannels[i].playing()) -			_gsChannels.remove_at(i); -} - -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; +bool IIgsSampleHeader::finalize(int8 *sample) { +	return instrument.finalize(sample);  } -void IIgsChannelInfo::stop() { -	this->end = true; -} - -bool IIgsChannelInfo::playing() { -	return !this->end; -} - -/** - * 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 ¶m) 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; -}; +//### +//### LOADER METHODS +//###  bool SoundGen2GS::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()); +	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"); +	// Find the executable file and the wavetable file +	Common::ArchiveMemberList exeNames, waveNames; +	SearchMan.listMatchingMembers(exeNames, "*.SYS16"); +	SearchMan.listMatchingMembers(exeNames, "*.SYS"); +	SearchMan.listMatchingMembers(waveNames, "SIERRASTANDARD"); +	SearchMan.listMatchingMembers(waveNames, "SIERRAST"); -	// 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()); +	if (exeNames.empty()) { +		warning("Couldn't find Apple IIGS game executable (*.SYS16 or *.SYS), not loading instruments");  		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()); +	if (waveNames.empty()) { +		warning("Couldn't find Apple IIGS wave file (SIERRASTANDARD or SIERRAST), not loading instruments");  		return false;  	} +	Common::String exeName  = exeNames.front()->getName(); +	Common::String waveName = waveNames.front()->getName(); +  	// 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.  	setProgramChangeMapping(exeInfo->instSet->progToInst); -	return loadWaveFile(*waveFsnode, *exeInfo) && loadInstrumentHeaders(*exeFsnode, *exeInfo); +	return loadWaveFile(waveName, *exeInfo) && loadInstrumentHeaders(exeName, *exeInfo);  }  /** Older Apple IIGS AGI MIDI program change to instrument number mapping. */ -static const MidiProgramChangeMapping progToInstMappingV1 = { +static const IIgsMidiProgramMapping 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, @@ -780,8 +620,9 @@ static const MidiProgramChangeMapping progToInstMappingV1 = {  	5  }; -/** Newer Apple IIGS AGI MIDI program change to instrument number mapping. */ -static const MidiProgramChangeMapping progToInstMappingV2 = { +/** Newer Apple IIGS AGI MIDI program change to instrument number mapping. +    FIXME: Some instrument choices sound wrong. */ +static const IIgsMidiProgramMapping 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, @@ -791,12 +632,12 @@ static const MidiProgramChangeMapping progToInstMappingV2 = {  };  /** Older Apple IIGS AGI instrument set. Used only by Space Quest I (AGI v1.002). */ -static const InstrumentSetInfo instSetV1 = { +static const IIgsInstrumentSetInfo 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 = { +static const IIgsInstrumentSetInfo instSetV2 = {  	1292, 28, "b7d428955bb90721996de1cbca25e768", "c05fb0b0e11deefab58bc68fbd2a3d07", &progToInstMappingV2  }; @@ -828,15 +669,14 @@ const IIgsExeInfo *SoundGen2GS::getIIgsExeInfo(enum AgiGameID gameid) const {  	return NULL;  } -bool SoundGen2GS::loadInstrumentHeaders(const Common::FSNode &exePath, const IIgsExeInfo &exeInfo) { -	bool loadedOk = false; // Was loading successful? +bool SoundGen2GS::loadInstrumentHeaders(Common::String &exePath, const IIgsExeInfo &exeInfo) {  	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); +			exePath.c_str(), file.size(), exeInfo.exeSize);  	}  	// Read the whole executable file into memory @@ -844,50 +684,49 @@ bool SoundGen2GS::loadInstrumentHeaders(const Common::FSNode &exePath, const IIg  	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()); -		} +	if (!data || data->size() < (int32)(exeInfo.instSetStart + exeInfo.instSet->byteCount)) { +		warning("Error loading instruments from Apple IIGS executable (%s)", exePath.c_str()); +		return false; +	} -		// Check instrument set's md5sum -		data->seek(exeInfo.instSetStart); +	// 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.c_str()); +	} -		Common::String md5str = Common::computeStreamMD5AsString(*data, exeInfo.instSet->byteCount); -		if (md5str != exeInfo.instSet->md5) { -			warning("Unknown Apple IIGS instrument set (md5: %s) in %s, trying to use it nonetheless", -				md5str.c_str(), exePath.getPath().c_str()); -		} +	// Check instrument set's md5sum +	data->seek(exeInfo.instSetStart); +	Common::String md5str = Common::computeStreamMD5AsString(*data, exeInfo.instSet->byteCount); +	if (md5str != exeInfo.instSet->md5) { +		warning("Unknown Apple IIGS instrument set (md5: %s) in %s, trying to use it nonetheless", +			md5str.c_str(), exePath.c_str()); +	} -		// Read in the instrument set one instrument at a time -		data->seek(exeInfo.instSetStart); +	// Read in the instrument set one instrument at a time +	data->seek(exeInfo.instSetStart); -		// Load the instruments -		_instruments.clear(); -		_instruments.reserve(exeInfo.instSet->instCount); +	_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 +	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.c_str()); +			break;  		} +		instrument.finalize(_wavetable); +		_instruments.push_back(instrument); +	} -		// 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; +	// Loading was successful only if all instruments were loaded successfully +	return (_instruments.size() == exeInfo.instSet->instCount);  } -bool SoundGen2GS::loadWaveFile(const Common::FSNode &wavePath, const IIgsExeInfo &exeInfo) { +bool SoundGen2GS::loadWaveFile(Common::String &wavePath, const IIgsExeInfo &exeInfo) {  	Common::File file;  	// Open the wave file and read it into memory @@ -896,23 +735,22 @@ bool SoundGen2GS::loadWaveFile(const Common::FSNode &wavePath, const IIgsExeInfo  	file.close();  	// Check that we got the whole wave file -	if (uint8Wave && uint8Wave->size() == SIERRASTANDARD_SIZE) { -		// Check wave file's md5sum -		Common::String md5str = Common::computeStreamMD5AsString(*uint8Wave, SIERRASTANDARD_SIZE); -		if (md5str != exeInfo.instSet->waveFileMd5) { -			warning("Unknown Apple IIGS wave file (md5: %s, game: %s).\n" \ +	if (!uint8Wave || (uint8Wave->size() != SIERRASTANDARD_SIZE)) { +		warning("Error loading Apple IIGS wave file (%s), not loading instruments", wavePath.c_str()); +		return false; +	} + +	// Check wave file's md5sum +	Common::String md5str = Common::computeStreamMD5AsString(*uint8Wave, SIERRASTANDARD_SIZE); +	if (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.c_str(), 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 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;  	} + +	// Convert the wave file to 8-bit signed and save the result +	uint8Wave->seek(0); +	return convertWave(*uint8Wave, _wavetable, SIERRASTANDARD_SIZE);  }  } // End of namespace Agi diff --git a/engines/agi/sound_2gs.h b/engines/agi/sound_2gs.h index 76f0642b7b..e729699bae 100644 --- a/engines/agi/sound_2gs.h +++ b/engines/agi/sound_2gs.h @@ -31,45 +31,30 @@  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 - -// 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 +// Sample data in SIERRASTANDARD files is in unsigned 8-bit format. A zero +// occurring in the sample data causes the ES5503 wavetable sound chip in +// Apple IIGS to halt the corresponding oscillator immediately. We preprocess +// the sample data by converting it to signed values and the instruments by +// detecting prematurely stopping samples beforehand. +//  +// Note: None of the tested SIERRASTANDARD files have zeroes in them. So in +// practice there is no need to check for them. However, they still do exist +// in the sample resources. +#define ZERO_OFFSET 0x80 + +// Apple IIGS envelope update frequency defaults to 100Hz. It can be changed, +// so there might be differences per game, for example. +#define ENVELOPE_COEF 100 / _sampleRate + +// MIDI player commands +#define MIDI_NOTE_OFF       0x8 +#define MIDI_NOTE_ON        0x9 +#define MIDI_CONTROLLER     0xB +#define MIDI_PROGRAM_CHANGE 0xC +#define MIDI_PITCH_WHEEL    0xE + +#define MIDI_STOP_SEQUENCE  0xFC +#define MIDI_TIMER_SYNC     0xF8  // Size of the SIERRASTANDARD file (i.e. the wave file i.e. the sample data used by the instruments).  #define SIERRASTANDARD_SIZE 65536 @@ -78,63 +63,34 @@ struct IIgsEnvelope {  // 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]; } +// The MIDI player allocates one generator for each note it starts to play. +// Here the maximum number of generators is defined. Feel free to increase +// this if it does not seem to be enough. +#define MAX_GENERATORS 16 -	/** 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); -}; +#define ENVELOPE_SEGMENT_COUNT 8 +#define MAX_OSCILLATOR_WAVES 127  // Maximum is one for every MIDI key  struct IIgsInstrumentHeader { -	IIgsEnvelope env; -	uint8 relseg; -	uint8 bendrange; -	uint8 vibdepth; -	uint8 vibspeed; -	IIgsOscillatorList oscList; +	struct { +		frac_t bp;      // Envelope segment breakpoint +		frac_t inc;     // Envelope segment velocity +	} env[ENVELOPE_SEGMENT_COUNT]; +	uint8 seg;          // Envelope release segment +	uint8 bend;         // Maximum range for pitch bend +	uint8 vibDepth;     // Vibrato depth +	uint8 vibSpeed;     // Vibrato speed +	uint8 waveCount[2]; // Wave count for both generators +	struct { +		uint8 key;      // Highest MIDI key to use this wave +		int8* base;     // Pointer to wave data +		uint size;      // Wave size +		bool halt;      // Oscillator halted? +		bool loop;      // Loop mode? +		bool swap;      // Swap mode? +		bool chn;       // Output channel (left / right) +		int16 tune;     // Fine tune in semitones (8.8 fixed point) +	} wave[2][MAX_OSCILLATOR_WAVES];  	/**  	 * Read an Apple IIGS instrument header from the given stream. @@ -143,7 +99,7 @@ struct IIgsInstrumentHeader {  	 * @return True if successful, false otherwise.  	 */  	bool read(Common::SeekableReadStream &stream, bool ignoreAddr = false); -	bool finalize(Common::SeekableReadStream &uint8Wave); +	bool finalize(int8 *);  };  struct IIgsSampleHeader { @@ -162,33 +118,32 @@ struct IIgsSampleHeader {  	 * @return True if successful, false otherwise.  	 */  	bool read(Common::SeekableReadStream &stream); -	bool finalize(Common::SeekableReadStream &uint8Wave); +	bool finalize(int8 *sample);  }; -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 IIgsGenerator { +public: +	IIgsGenerator() : ins(NULL), key(-1), chn(-1) {} +	void setInstrument(IIgsInstrumentHeader *ins); +	void noteOn(uint8 key, uint8 velocity); +	void noteOff(); + +	const IIgsInstrumentHeader *ins; ///< Currently used instrument +	int key;                   ///< MIDI key +	int vel;                   ///< MIDI velocity (& channel volume) +	int chn;                   ///< MIDI channel +	struct { +		int8 *base;             ///< Sample base pointer +		uint size;              ///< Sample size +		frac_t p;               ///< Sample pointer +		frac_t pd;              ///< Sample pointer delta +		bool halt;              ///< Is oscillator halted? +		bool loop;              ///< Is looping enabled? +		bool swap;              ///< Is swapping enabled? +		bool chn;               ///< Output channel (left / right) +	} osc[2]; +	int seg;                   ///< Current envelope segment +	frac_t a;                  ///< Current envelope amplitude  };  class IIgsMidi : public AgiSound { @@ -198,15 +153,14 @@ public:  	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; } +	virtual void rewind() { _ptr = _data + 2; _ticks = 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) +	uint _ticks; ///< MIDI song position in ticks (1/60ths of a second)  };  class IIgsSample : public AgiSound { @@ -222,7 +176,7 @@ protected:  };  /** Apple IIGS MIDI program change to instrument number mapping. */ -struct MidiProgramChangeMapping { +struct IIgsMidiProgramMapping {  	byte midiProgToInst[44]; ///< Lookup table for the MIDI program number to instrument number mapping  	byte undefinedInst; ///< The undefined instrument number @@ -233,12 +187,12 @@ struct MidiProgramChangeMapping {  };  /** Apple IIGS AGI instrument set information. */ -struct InstrumentSetInfo { +struct IIgsInstrumentSetInfo {  	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 +	const IIgsMidiProgramMapping *progToInst; ///< Program change to instrument number mapping  };  /** Apple IIGS AGI executable file information. */ @@ -248,27 +202,19 @@ struct IIgsExeInfo {  	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 +	const IIgsInstrumentSetInfo *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: +	IIgsMidiChannel() : _instrument(NULL), _volume(127) {} +	void setInstrument(const IIgsInstrumentHeader *instrument) { _instrument = instrument; } +	const IIgsInstrumentHeader* getInstrument() { return _instrument; } +	void setVolume(int volume) { _volume = volume; } +	int getVolume() { return _volume; } +private:  	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) +	int _volume;								///< MIDI controller number 7 (Volume)  };  class SoundGen2GS : public SoundGen, public Audio::AudioStream { @@ -279,73 +225,50 @@ public:  	void play(int resnum);  	void stop(void); -	// AudioStream API  	int readBuffer(int16 *buffer, const int numSamples); -	bool isStereo() const { -		return false; -	} - -	bool endOfData() const { -		return false; -	} - -	int getRate() const { -		// FIXME: Ideally, we should use _sampleRate. -		return 22050; -	} +	bool isStereo() const { return true; } +	bool endOfData() const { return false; } +	int getRate() const { return _sampleRate; }  private: -	bool _disabledMidi; -	int _playingSound; -	bool _playing; - -	int16 *_sndBuffer; - -/** - * 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 - */ -private: -	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 - +	// Loader methods  	bool loadInstruments(); -	const IIgsExeInfo *getIIgsExeInfo(enum AgiGameID gameid) const; +	bool loadInstrumentHeaders(Common::String &exePath, const IIgsExeInfo &exeInfo); +	bool loadWaveFile(Common::String &wavePath, const IIgsExeInfo &exeInfo); -	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 -	void fillAudio(int16 *stream, uint len); -	uint32 mixSound(); -	void playSound(); -	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); -	void playMidiSound(); -	void playSampleSound(); - -	// 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 +	const IIgsExeInfo *getIIgsExeInfo(enum AgiGameID gameid) const; +	void setProgramChangeMapping(const IIgsMidiProgramMapping *mapping); + +	// Player methods +	void advancePlayer();      ///< Advance the player +	void advanceMidiPlayer();  ///< Advance MIDI player +	uint generateOutput();     ///< Fill the output buffer + +	void haltGenerators();     ///< Halt all generators +	uint activeGenerators();   ///< How many generators are active? + +	void midiNoteOff(int channel, int note, int velocity); +	void midiNoteOn(int channel, int note, int velocity); +	double midiKeyToFreq(int key, double finetune); +	IIgsInstrumentHeader* getInstrument(uint8 program) { return &_instruments[_progToInst->map(program)]; }; +	IIgsGenerator* allocateGenerator() { IIgsGenerator* g = &_generators[_nextGen++]; _nextGen %= 16; return g; } + +	bool _disableMidi;   ///< Disable MIDI if loading instruments fail +	int _playingSound;   ///< Resource number for the currently playing sound +	bool _playing;       ///< True when the resource is still playing + +	IIgsGenerator _generators[MAX_GENERATORS];         ///< IIGS sound generators that are used to play single notes +	uint _nextGen;                                     ///< Next generator available for allocation +	IIgsMidiChannel _channels[16];                     ///< MIDI channels +	Common::Array<IIgsInstrumentHeader> _instruments;  ///< Instrument data +	const IIgsMidiProgramMapping *_progToInst;         ///< MIDI program number to instrument mapping +	int8 *_wavetable;                                  ///< Sample data used by the instruments +	uint _ticks;                                       ///< MIDI ticks (60Hz) +	int16 *_out;                                       ///< Output buffer +	uint _outSize;                                     ///< Output buffer size +	 +	static const int kSfxMidiChannel = 15;  ///< MIDI channel used for playing sample resources  };  } // End of namespace Agi | 
