[Scummvm-cvs-logs] SF.net SVN: scummvm:[33938] scummvm/trunk/engines/kyra

athrxx at users.sourceforge.net athrxx at users.sourceforge.net
Sat Aug 16 14:38:38 CEST 2008


Revision: 33938
          http://scummvm.svn.sourceforge.net/scummvm/?rev=33938&view=rev
Author:   athrxx
Date:     2008-08-16 12:38:37 +0000 (Sat, 16 Aug 2008)

Log Message:
-----------
- KYRA: FM-Towns/PC-98 Audio: fixed several bugs and got rid of some floating point arithmetic (or at least replaced some doubles with floats)
- Improved support for PC-98 music: HOF tracks seem to be fine, KYRA should be okay, too (I can't compare with an emulator since I am missing the floppy boot disk with the executable)
- There are still tempo issues. Some tracks play way too slow, others way too fast
- PC-98 sound effects in Kyra 1 don't work (I would need the floppy disk with the executable for that)

Modified Paths:
--------------
    scummvm/trunk/engines/kyra/kyra_hof.cpp
    scummvm/trunk/engines/kyra/sequences_hof.cpp
    scummvm/trunk/engines/kyra/sound.h
    scummvm/trunk/engines/kyra/sound_towns.cpp

Modified: scummvm/trunk/engines/kyra/kyra_hof.cpp
===================================================================
--- scummvm/trunk/engines/kyra/kyra_hof.cpp	2008-08-16 12:05:44 UTC (rev 33937)
+++ scummvm/trunk/engines/kyra/kyra_hof.cpp	2008-08-16 12:38:37 UTC (rev 33938)
@@ -306,8 +306,10 @@
 			_res->loadFileList(_ingamePakList, _ingamePakListSize);
 		}
 
-		if (_flags.platform == Common::kPlatformPC98)
+		if (_flags.platform == Common::kPlatformPC98) {
 			_res->loadPakFile("AUDIO.PAK");
+			_sound->loadSoundFile("sound.dat");
+		}
 	}
 
 	_menuDirectlyToLoad = (_menuChoice == 3) ? true : false;
@@ -1578,10 +1580,14 @@
 	int16 vocIndex = (int16)READ_LE_UINT16(&_ingameSoundIndex[track * 2]);
 	if (vocIndex != -1)
 		_sound->voicePlay(_ingameSoundList[vocIndex], true);
-	else if (_flags.platform != Common::kPlatformFMTowns)
+	else if (_flags.platform == Common::kPlatformPC)
+		KyraEngine_v1::snd_playSoundEffect(track);
+
 		// TODO ?? Maybe there is a way to let users select whether they want
 		// voc, midi or adl sfx (even though it makes no sense to choose anything but voc).
-		KyraEngine_v1::snd_playSoundEffect(track);
+		// The PC-98 version has support for non-pcm sound effects, but only for tracks 
+		// which also have voc files. The syntax would be:
+		// KyraEngine_v1::snd_playSoundEffect(vocIndex);
 }
 
 #pragma mark -

Modified: scummvm/trunk/engines/kyra/sequences_hof.cpp
===================================================================
--- scummvm/trunk/engines/kyra/sequences_hof.cpp	2008-08-16 12:05:44 UTC (rev 33937)
+++ scummvm/trunk/engines/kyra/sequences_hof.cpp	2008-08-16 12:38:37 UTC (rev 33938)
@@ -2830,6 +2830,9 @@
 	_res->unloadAllPakFiles();
 	_res->loadPakFile(StaticResource::staticDataFilename());
 	_res->loadFileList(_sequencePakList, _sequencePakListSize);
+	
+	if (_flags.platform == Common::kPlatformPC98)
+		_sound->loadSoundFile("sound.dat");
 
 	int numShp = -1;
 

Modified: scummvm/trunk/engines/kyra/sound.h
===================================================================
--- scummvm/trunk/engines/kyra/sound.h	2008-08-16 12:05:44 UTC (rev 33937)
+++ scummvm/trunk/engines/kyra/sound.h	2008-08-16 12:38:37 UTC (rev 33938)
@@ -429,7 +429,7 @@
 	MidiChannel *allocateChannel()		{ return 0; }
 	MidiChannel *getPercussionChannel()	{ return 0; }
 
-	static float semitoneAndSampleRate_to_sampleStep(int8 semiTone, int8 semiToneRootkey,
+	static float calculatePhaseStep(int8 semiTone, int8 semiToneRootkey,
 		uint32 sampleRate, uint32 outputRate, int32 pitchWheel);
 
 private:
@@ -492,7 +492,7 @@
 	void process();
 
 	void loadSoundFile(uint file) {}
-	void loadSoundFile(Common::String) {}
+	void loadSoundFile(Common::String file);
 
 	void playTrack(uint8 track);
 	void haltTrack();
@@ -507,6 +507,7 @@
 	bool _useFmSfx;
 
 	uint8 *_musicTrackData;
+	uint8 *_sfxTrackData;
 	TownsPC98_OpnDriver *_driver;	
 };
 

Modified: scummvm/trunk/engines/kyra/sound_towns.cpp
===================================================================
--- scummvm/trunk/engines/kyra/sound_towns.cpp	2008-08-16 12:05:44 UTC (rev 33937)
+++ scummvm/trunk/engines/kyra/sound_towns.cpp	2008-08-16 12:38:37 UTC (rev 33938)
@@ -427,7 +427,7 @@
 		return;
 	}
 
-	float phaseStep = SoundTowns::semitoneAndSampleRate_to_sampleStep(_note, _voice->_snd[_current]->keyNote -
+	float phaseStep = SoundTowns::calculatePhaseStep(_note, _voice->_snd[_current]->keyNote -
 		_voice->_env[_current]->rootKeyOffset, _voice->_snd[_current]->samplingRate, _rate, _frequencyOffs);
 
 	int32 looplength = _voice->_snd[_current]->loopLength;
@@ -1085,7 +1085,7 @@
 
 class TownsPC98_OpnOperator {
 public:
-	TownsPC98_OpnOperator(double rate, const uint8 *rateTable,
+	TownsPC98_OpnOperator(float rate, const uint8 *rateTable,
 		const uint8 *shiftTable, const uint8 *attackDecayTable, const uint32 *frqTable,
 		const uint32 *sineTable, const int32 *tlevelOut, const int32 *detuneTable);
 	~TownsPC98_OpnOperator() {}
@@ -1095,7 +1095,7 @@
 	void frequency(int freq);
 	void updatePhaseIncrement();
 	void recalculateRates();
-	void generateOutput(int phasebuf, int *_feedbuf, int &out);
+	void generateOutput(int phasebuf, int *feedbuf, int &out);
 
 	void feedbackLevel(int32 level) {_feedbackLevel = level ? level + 6 : 0; }
 	void detune(int value) { _detn = &_detnTbl[value << 5]; }
@@ -1137,8 +1137,8 @@
 	const int32 *_tLvlTbl;
 	const int32 *_detnTbl;
 
-	const double _tickLength;
-	double _tick;
+	const int _tickLength;
+	int _tick;
 	int32 _currentLevel;
 
 	struct EvpState {
@@ -1147,11 +1147,11 @@
 	} fs_a, fs_d, fs_s, fs_r;
 };
 
-TownsPC98_OpnOperator::TownsPC98_OpnOperator(double rate, const uint8 *rateTable, 
+TownsPC98_OpnOperator::TownsPC98_OpnOperator(float rate, const uint8 *rateTable, 
 	const uint8 *shiftTable, const uint8 *attackDecayTable,	const uint32 *frqTable,
 	const uint32 *sineTable, const int32 *tlevelOut, const int32 *detuneTable) :
 	_rateTbl(rateTable), _rshiftTbl(shiftTable), _adTbl(attackDecayTable), _fTbl(frqTable),
-	_sinTbl(sineTable), _tLvlTbl(tlevelOut), _detnTbl(detuneTable), _tickLength(rate * 65536.0),
+	_sinTbl(sineTable), _tLvlTbl(tlevelOut), _detnTbl(detuneTable), _tickLength((int)(rate * 65536.0f)),
 	_specifiedAttackRate(0), _specifiedDecayRate(0), _specifiedReleaseRate(0), _specifiedSustainRate(0),
 	_phase(0), _state(s_ready) {
 	
@@ -1172,6 +1172,7 @@
 	uint8 block = (freq >> 11);
 	uint16 pos = (freq & 0x7ff);
 	uint8 c = pos >> 7;
+
 	_kcode = (block << 2) | ((c < 7) ? 0 : ((c > 8) ? 3 : c - 6 ));
 	_frequency = _fTbl[pos << 1] >> (7 - block);
 }
@@ -1204,7 +1205,7 @@
 	fs_r.shift = _rshiftTbl[r + k];
 }
 
-void TownsPC98_OpnOperator::generateOutput(int phasebuf, int *_feedbuf, int &out) {
+void TownsPC98_OpnOperator::generateOutput(int phasebuf, int *feed, int &out) {
 	if (_state == s_ready)
 		return;
 
@@ -1216,32 +1217,32 @@
 		int32 levelIncrement = 0;
 		uint32 targetTime = 0;
 		int32 targetLevel = 0;
-		EnvelopeState next_state = s_ready;
+		EnvelopeState nextState = s_ready;
 
 		switch (_state) {
 			case s_ready:
 				return;
 			case s_attacking:
-				next_state = s_decaying;
+				nextState = s_decaying;
 				targetTime = (1 << fs_a.shift) - 1;
 				targetLevel = 0;
 				levelIncrement = (~_currentLevel * _adTbl[fs_a.rate + ((_tickCount >> fs_a.shift) & 7)]) >> 4;
 				break;
 			case s_decaying:
 				targetTime = (1 << fs_d.shift) - 1;
-				next_state = s_sustaining;
+				nextState = s_sustaining;
 				targetLevel = _sustainLevel;
 				levelIncrement = _adTbl[fs_d.rate + ((_tickCount >> fs_d.shift) & 7)];
 				break;
 			case s_sustaining:
 				targetTime = (1 << fs_s.shift) - 1;
-				next_state = s_ready;
+				nextState = s_sustaining;
 				targetLevel = 1023;
 				levelIncrement = _adTbl[fs_s.rate + ((_tickCount >> fs_s.shift) & 7)];
 				break;
 			case s_releasing:
 				targetTime = (1 << fs_r.shift) - 1;
-				next_state = s_ready;
+				nextState = s_ready;
 				targetLevel = 1023;
 				levelIncrement = _adTbl[fs_r.rate + ((_tickCount >> fs_r.shift) & 7)];
 				break;
@@ -1249,11 +1250,10 @@
 
 		if (!(_tickCount & targetTime)) {
 			_currentLevel += levelIncrement;
-			if ((!targetLevel && _currentLevel <= targetLevel) || (targetLevel && _currentLevel >= targetLevel)) {
+			if ((_state == s_attacking && _currentLevel <= targetLevel) || (_state != s_attacking && _currentLevel >= targetLevel)) {
 				if (_state != s_decaying)
 					_currentLevel = targetLevel;
-				if (_state != s_sustaining)
-					_state = next_state;
+				_state = nextState;
 			}
 		}
 	}
@@ -1264,10 +1264,10 @@
 	int *i = &outp, *o = &outp;
 	int phaseShift = 0;
 
-	if (_feedbuf) {
-		o = &_feedbuf[0];
-		i = &_feedbuf[1];
-		phaseShift = _feedbackLevel ? ((_feedbuf[0] + _feedbuf[1]) << _feedbackLevel) : 0;
+	if (feed) {
+		o = &feed[0];
+		i = &feed[1];
+		phaseShift = _feedbackLevel ? ((*o + *i) << _feedbackLevel) : 0;
 		if (phasebuf == -1)
 			*i = 0;
 		*o = *i;
@@ -1332,25 +1332,24 @@
 	virtual ~TownsPC98_OpnChannel();
 	virtual void init();
 
-	typedef bool (TownsPC98_OpnChannel::*ControlEventFunc)(uint8 para);
-
 	typedef enum channelState {
 		CHS_RECALCFREQ		=	0x01,
 		CHS_KEYOFF			=	0x02,
-		CHS_SSG				=	0x04,
+		CHS_SSGOFF			=	0x04,
 		CHS_PITCHWHEELOFF	=	0x08,
 		CHS_ALL_BUT_EOT		=	0x0f,
+		CHS_PROTECT			=	0x40,
 		CHS_EOT				=	0x80
 	} ChannelState;
 
 	virtual void loadData(uint8 *data);
 	virtual void processEvents();
 	virtual void processFrequency();
-	bool processControlEvent(uint8 cmd);
+	virtual bool processControlEvent(uint8 cmd);
 	void writeReg(uint8 regAdress, uint8 value);
 
 	virtual void keyOn();
-	virtual void keyOff();	
+	void keyOff();	
 	
 	void setOutputLevel();
 	void fadeStep();
@@ -1361,11 +1360,14 @@
 
 	bool _enableLeft;
 	bool _enableRight;
-	bool _updateEnvelopes;
+	bool _updateEnvelopeParameters;
 	const uint8 _idFlag;
 	int _feedbuf[3];
 
 protected:
+	void setupPitchWheel();
+	bool processPitchWheel();
+
 	bool control_dummy(uint8 para);
 	bool control_f0_setPatch(uint8 para);
 	bool control_f1_presetOutputLevel(uint8 para);
@@ -1377,40 +1379,36 @@
 	bool control_f7_setupPitchWheel(uint8 para);
 	bool control_f8_togglePitchWheel(uint8 para);
 	bool control_fa_writeReg(uint8 para);
-	bool control_fb_incOutLevel(uint8 para);
-	bool control_fc_decOutLevel(uint8 para);
+	virtual bool control_fb_incOutLevel(uint8 para);
+	virtual bool control_fc_decOutLevel(uint8 para);
 	bool control_fd_jump(uint8 para);
-	bool control_ff_endOfTrack(uint8 para);
+	virtual bool control_ff_endOfTrack(uint8 para);
 
-	bool control_f0_setPatchSSG(uint8 para);
-	bool control_f1_setTotalLevel(uint8 para);
-	bool control_f4_setAlgorithm(uint8 para);
-	bool control_f9_unkSSG(uint8 para);
-	bool control_fb_incOutLevelSSG(uint8 para);
-	bool control_fc_decOutLevelSSG(uint8 para);
-	bool control_ff_endOfTrackSSG(uint8 para);
-
 	uint8 _ticksLeft;
 	uint8 _algorithm;
-	uint8 _instrID;
+	uint8 _instr;
 	uint8 _totalLevel;
 	uint8 _frqBlockMSB;
 	int8 _frqLSB;
 	uint8 _keyOffTime;
-	bool _protect;
+	bool _hold;
 	uint8 *_dataPtr;
+	uint8 _ptchWhlInitDelayHi;
 	uint8 _ptchWhlInitDelayLo;
-	uint8 _ptchWhlInitDelayHi;
 	int16 _ptchWhlModInitVal;
 	uint8 _ptchWhlDuration;
 	uint8 _ptchWhlCurDelay;
 	int16 _ptchWhlModCurVal;
 	uint8 _ptchWhlDurLeft;
-	uint16 frequency;
+	uint16 _frequency;
+	uint8 _block;
 	uint8 _regOffset;
 	uint8 _flags;
-	uint8 _ssg1;
-	uint8 _ssg2;
+	uint8 _ssgTl;
+	uint8 _ssgStep;
+	uint8 _ssgTicksLeft;
+	uint8 _ssgTargetLvl;
+	uint8 _ssgStartLvl;
 
 	const uint8 _chanNum;
 	const uint8 _keyNum;
@@ -1420,6 +1418,7 @@
 	TownsPC98_OpnOperator **_opr;
 	uint16 _frqTemp;
 
+	typedef bool (TownsPC98_OpnChannel::*ControlEventFunc)(uint8 para);
 	const ControlEventFunc *controlEvents;
 };
 
@@ -1427,24 +1426,129 @@
 public:
 	TownsPC98_OpnChannelSSG(TownsPC98_OpnDriver *driver, uint8 regOffs,
 		uint8 flgs, uint8 num, uint8 key, uint8 prt, uint8 id);
-	~TownsPC98_OpnChannelSSG() {}
+	virtual ~TownsPC98_OpnChannelSSG() {}
 	void init();
 
+	virtual void loadData(uint8 *data);
 	void processEvents();
 	void processFrequency();
+	bool processControlEvent(uint8 cmd);
 
 	void keyOn();
-	void keyOff();
+	void nextShape();
+
+	void protect();
+	void restore();
+
+	void fadeStep();
+
+protected:
+	void setOutputLevel(uint8 lvl);
+
+	bool control_f0_setInstr(uint8 para);
+	bool control_f1_setTotalLevel(uint8 para);
+	bool control_f4_setAlgorithm(uint8 para);
+	bool control_f9_loadCustomPatch(uint8 para);
+	bool control_fb_incOutLevel(uint8 para);
+	bool control_fc_decOutLevel(uint8 para);
+	bool control_ff_endOfTrack(uint8 para);
+
+	typedef bool (TownsPC98_OpnChannelSSG::*ControlEventFunc)(uint8 para);
+	const ControlEventFunc *controlEvents;
+};
+
+class TownsPC98_OpnSfxChannel : public TownsPC98_OpnChannelSSG {
+public:
+	TownsPC98_OpnSfxChannel(TownsPC98_OpnDriver *driver, uint8 regOffs,
+		uint8 flgs, uint8 num, uint8 key, uint8 prt, uint8 id) : 
+		TownsPC98_OpnChannelSSG(driver, regOffs, flgs, num, key, prt, id) {}
+	~TownsPC98_OpnSfxChannel() {}
+
 	void loadData(uint8 *data);
+};
 
+class TownsPC98_OpnChannelPCM : public TownsPC98_OpnChannel {
+public:
+	TownsPC98_OpnChannelPCM(TownsPC98_OpnDriver *driver, uint8 regOffs,
+		uint8 flgs, uint8 num, uint8 key, uint8 prt, uint8 id);
+	~TownsPC98_OpnChannelPCM() {}
+	void init();
+
+	void loadData(uint8 *data);
+	void processEvents();
+	bool processControlEvent(uint8 cmd);
+
+	void fadeStep();
+
 private:
-	void opn_SSG_UNK(uint8 a);
+	bool control_f1_pcmStart(uint8 para);
+	bool control_ff_endOfTrack(uint8 para);
+
+	typedef bool (TownsPC98_OpnChannelPCM::*ControlEventFunc)(uint8 para);
+	const ControlEventFunc *controlEvents;
 };
 
+class TownsPC98_SSG : public Audio::AudioStream {
+public:
+	TownsPC98_SSG(TownsPC98_OpnDriver *driver, Audio::Mixer *mixer, float rate);
+	~TownsPC98_SSG();
 
+	void init(const int *rsTable, const int *rseTable);	
+	void reset();
+	uint8 readReg(uint8 address);
+	void writeReg(uint8 address, uint8 value, bool force = false);
+
+	// AudioStream interface
+	int inline readBuffer(int16 *buffer, const int numSamples);
+	bool isStereo() const { return true; }
+	bool endOfData() const { return false; }
+	int getRate() const { return _mixer->getOutputRate(); }
+
+private:
+	void updatesRegs();
+	void nextTick(int16 *buffer, uint32 bufferSize);
+
+	uint8 _reg[16];
+	uint8 _updateRequestBuf[32];
+	int _updateRequest;
+	uint8 *_regIndex;
+	int _rand;
+
+	int8 _evpTimer;
+	uint32 _pReslt;
+	uint8 _attack;
+	
+	bool _evpUpdate, _cont;
+
+	int _evpUpdateCnt;
+	uint8 _outN;	
+	int _nTick;	
+
+	int32 *_tlTable;
+	int32 *_tleTable;
+
+	const float _rate;
+	const int _tickLength;
+	int _timer;
+
+	struct Channel {
+		int tick;
+		uint8 smp;
+		uint8 out;
+	} _channels[3];
+
+	TownsPC98_OpnDriver *_drv;
+	Audio::Mixer *_mixer;
+	Audio::SoundHandle _soundHandle;
+
+	bool _ready;
+};
+
 class TownsPC98_OpnDriver : public Audio::AudioStream {
 friend class TownsPC98_OpnChannel;
 friend class TownsPC98_OpnChannelSSG;
+friend class TownsPC98_OpnSfxChannel;
+friend class TownsPC98_OpnChannelPCM;
 public:
 	enum OpnType {
 		OD_TOWNS,
@@ -1456,12 +1560,13 @@
 	~TownsPC98_OpnDriver();
 
 	bool init();
-	void loadData(uint8 *data, bool loadPaused = false);
+	void loadMusicData(uint8 *data, bool loadPaused = false);
+	void loadSoundEffectData(uint8 *data, uint8 trackNum);
 	void reset();
 	void fadeOut();
 	
-	void pause() { _playing = false; }
-	void cont() { _playing = true; }
+	void pause() { _musicPlaying = false; }
+	void cont() { _musicPlaying = true; }
 
 	void callback();
 	void nextTick(int16 *buffer, uint32 bufferSize);
@@ -1479,8 +1584,11 @@
 
 	TownsPC98_OpnChannel **_channels;
 	TownsPC98_OpnChannelSSG **_ssgChannels;
-	//TownsPC98_OpnChannel *_adpcmChannel;
+	TownsPC98_OpnSfxChannel **_sfxChannels;
+	TownsPC98_OpnChannelPCM *_pcmChannel;
+	TownsPC98_SSG *_ssg;
 
+	void startSoundEffect();	
 	void setTempo(uint8 tempo);
 
 	void lock() { _mutex.lock(); }
@@ -1492,6 +1600,7 @@
 
 	const uint8 *_opnCarrier;
 	const uint8 *_opnFreqTable;
+	const uint8 *_opnFreqTableSSG;
 	const uint8 *_opnFxCmdLen;
 	const uint8 *_opnLvlPresets;
 
@@ -1503,21 +1612,34 @@
 	int32 *_oprLevelOut;
 	int32 *_oprDetune;
 
-	uint8 *_trackData;
+	uint8 *_musicBuffer;
+	uint8 *_sfxBuffer;	
+	uint8 *_trackPtr;
 	uint8 *_patches;
+	uint8 *_ssgPatches;
 
 	uint8 _cbCounter;
 	uint8 _updateChannelsFlag;
+	uint8 _updateSSGFlag;
+	uint8 _updatePCMFlag;
 	uint8 _finishedChannelsFlag;
+	uint8 _finishedSSGFlag;
+	uint8 _finishedPCMFlag;
+
 	uint16 _tempo;
-	bool _playing;
+	bool _musicPlaying;
+	bool _sfxPlaying;
 	bool _fading;
 	uint8 _looping;
 	uint32 _tickCounter;
 
-	bool _updateEnvelopes;
-	int _ssgFlag;
+	bool _updateEnvelopeParameters;
 
+	bool _regProtectionFlag;
+	int _sfxOffs;
+	uint8 *_sfxData;
+	uint16 _sfxOffsets[2];
+
 	int32 _samplesTillCallback;
 	int32 _samplesTillCallbackRemainder;
 	int32 _samplesPerCallback;
@@ -1525,12 +1647,14 @@
 
 	const int _numChan;
 	const int _numSSG;
-	const bool _hasADPCM;
+	const bool _hasPCM;
 	const bool _hasStereo;
 
-	double _baserate;
 	static const uint8 _drvTables[];
 	static const uint32 _adtStat[];
+	static const int _ssgTables[];
+
+	const float _baserate;
 	bool _ready;
 };
 
@@ -1538,14 +1662,15 @@
 	uint8 key, uint8 prt, uint8 id) : _drv(driver), _regOffset(regOffs), _flags(flgs), _chanNum(num), _keyNum(key),
 	_part(prt), _idFlag(id) {
 
-	_ticksLeft = _algorithm = _instrID = _totalLevel = _frqBlockMSB = _keyOffTime = _ssg1 = _ssg2 = 0;
-	_ptchWhlInitDelayLo = _ptchWhlInitDelayHi = _ptchWhlDuration = _ptchWhlCurDelay = _ptchWhlDurLeft = 0;
+	_ticksLeft = _algorithm = _instr = _totalLevel = _frqBlockMSB = _keyOffTime = 0;
+	_ssgStartLvl = _ssgTl = _ssgStep = _ssgTicksLeft = _ssgTargetLvl = _block = 0;
+	_ptchWhlInitDelayHi = _ptchWhlInitDelayLo = _ptchWhlDuration = _ptchWhlCurDelay = _ptchWhlDurLeft = 0;
 	_frqLSB = 0;
-	_protect = _updateEnvelopes = false;
+	_hold = _updateEnvelopeParameters = false;
 	_enableLeft = _enableRight = true;
 	_dataPtr = 0;		
 	_ptchWhlModInitVal = _ptchWhlModCurVal = 0;
-	frequency = _frqTemp = 0;
+	_frequency = _frqTemp = 0;
 	memset(&_feedbuf, 0, sizeof(int) * 3);
 	_opr = 0;
 }
@@ -1558,8 +1683,7 @@
 	}
 }
 
-void TownsPC98_OpnChannel::init() {
-	
+void TownsPC98_OpnChannel::init() {	
 	_opr = new TownsPC98_OpnOperator*[4];
 	for (int i = 0; i < 4; i++)
 		_opr[i] = new TownsPC98_OpnOperator(_drv->_baserate, _drv->_oprRates, _drv->_oprRateshift,
@@ -1645,13 +1769,13 @@
 	if (_flags & CHS_EOT)
 		return;
 
-	if (_protect == false && _ticksLeft == _keyOffTime)
+	if (_hold == false && _ticksLeft == _keyOffTime)
 		keyOff();
 
 	if (--_ticksLeft)
 		return;
 
-	if (_protect == false)
+	if (_hold == false)
 		keyOff();
 
 	uint8 cmd = 0;
@@ -1669,14 +1793,14 @@
 
 	if (cmd == 0x80) {
 		keyOff();
-		_protect = false;
+		_hold = false;
 	} else {
 		keyOn();
 
-		if (_protect == false || cmd != _frqBlockMSB)
+		if (_hold == false || cmd != _frqBlockMSB)
 			_flags |= CHS_RECALCFREQ;
 	
-		_protect = (para & 0x80) ? true : false;
+		_hold = (para & 0x80) ? true : false;
 		_frqBlockMSB = cmd;
 	}
 
@@ -1687,37 +1811,48 @@
 	if (_flags & CHS_RECALCFREQ) {
 		uint8 block = (_frqBlockMSB & 0x70) >> 1;
 		uint16 bfreq = ((const uint16*)_drv->_opnFreqTable)[_frqBlockMSB & 0x0f];
-		frequency = (bfreq + _frqLSB) | (block << 8);
 
-		writeReg(_regOffset + 0xa4, (frequency >> 8));
-		writeReg(_regOffset + 0xa0, (frequency & 0xff));
+		_frequency = (bfreq + _frqLSB) | (block << 8);
+		writeReg(_regOffset + 0xa4, (_frequency >> 8));
+		writeReg(_regOffset + 0xa0, (_frequency & 0xff));
 
-		_ptchWhlCurDelay = _ptchWhlInitDelayHi;
-		if (_flags & CHS_KEYOFF) {
-			_ptchWhlModCurVal = _ptchWhlModInitVal;
-			_ptchWhlCurDelay += _ptchWhlInitDelayLo;
-		}
-
-		_ptchWhlDurLeft = (_ptchWhlDuration >> 1);
-		_flags &= ~(CHS_KEYOFF | CHS_RECALCFREQ);
+		setupPitchWheel();
 	}
 
 	if (!(_flags & CHS_PITCHWHEELOFF)) {
-		if (--_ptchWhlCurDelay)
+		if (!processPitchWheel())
 			return;
-		_ptchWhlCurDelay = _ptchWhlInitDelayHi;
-		frequency += _ptchWhlModCurVal;
 
-		writeReg(_regOffset + 0xa4, (frequency >> 8));
-		writeReg(_regOffset + 0xa0, (frequency & 0xff));
+		writeReg(_regOffset + 0xa4, (_frequency >> 8));
+		writeReg(_regOffset + 0xa0, (_frequency & 0xff));
+	}
+}
 
-		if(!--_ptchWhlDurLeft) {
-			_ptchWhlDurLeft = _ptchWhlDuration;
-			_ptchWhlModCurVal = -_ptchWhlModCurVal;
-		}
+void TownsPC98_OpnChannel::setupPitchWheel() {
+	_ptchWhlCurDelay = _ptchWhlInitDelayHi;
+	if (_flags & CHS_KEYOFF) {
+		_ptchWhlModCurVal = _ptchWhlModInitVal;
+		_ptchWhlCurDelay += _ptchWhlInitDelayLo;
 	}
+	_ptchWhlDurLeft = (_ptchWhlDuration >> 1);
+	_flags &= ~(CHS_KEYOFF | CHS_RECALCFREQ);
 }
 
+bool TownsPC98_OpnChannel::processPitchWheel() {
+	if (--_ptchWhlCurDelay)
+		return false;
+
+	_ptchWhlCurDelay = _ptchWhlInitDelayHi;
+	_frequency += _ptchWhlModCurVal;
+
+	if(!--_ptchWhlDurLeft) {
+		_ptchWhlDurLeft = _ptchWhlDuration;
+		_ptchWhlModCurVal = -_ptchWhlModCurVal;
+	}
+	
+	return true;
+}
+
 bool TownsPC98_OpnChannel::processControlEvent(uint8 cmd) {
 	uint8 para = *_dataPtr++;
 	return (this->*controlEvents[cmd & 0x0f])(para);
@@ -1743,10 +1878,23 @@
 }
 
 void TownsPC98_OpnChannel::reset() {
-	for (int i = 0; i < 4; i++)
-		_opr[i]->reset();
+	if (_opr) {
+		for (int i = 0; i < 4; i++)
+			_opr[i]->reset();
+	}
 
-	_updateEnvelopes = false;
+	_block = 0;
+	_frequency = 0;
+	_hold = false;
+	_frqTemp = 0;
+	_ssgTl = 0;
+	_ssgStartLvl = 0;
+	_ssgTargetLvl = 0;
+	_ssgStep = 0;
+	_ssgTicksLeft = 0;
+	_totalLevel = 0;
+
+	_updateEnvelopeParameters = false;
 	_enableLeft = _enableRight = true;
 	memset(&_feedbuf, 0, sizeof(int) * 3);
 }
@@ -1820,7 +1968,7 @@
 		};
 
 	if (_enableLeft) {
-		int l = output + leftSample;
+		int l = output + (int) leftSample;
 		if (l > 32767)
 			l = 32767;
 		if (l < -32767)
@@ -1829,7 +1977,7 @@
 	}
 
 	if (_enableRight) {
-		int r = output + rightSample;
+		int r = output + (int) rightSample;
 		if (r > 32767)
 			r = 32767;
 		if (r < -32767)
@@ -1839,19 +1987,29 @@
 }
 
 void TownsPC98_OpnChannel::writeReg(uint8 regAdress, uint8 value) {
+	if (_drv->_regProtectionFlag)
+		return;
+
 	uint8 h = regAdress & 0xf0;
 	uint8 l = (regAdress & 0x0f);
 	static const uint8 oprOrdr[] = { 0, 2, 1, 3 };
 	uint8 o = oprOrdr[(l - _regOffset) >> 2];
-	
+
 	switch (h) {
 		case 0x00:
 			// ssg
-			warning("TownsPC98_OpnDriver: UNKNOWN ADDRESS %d", regAdress);
+			if (_drv->_ssg)
+				_drv->_ssg->writeReg(regAdress, value);
 			break;
 		case 0x10:
-			// adpcm
-			warning("TownsPC98_OpnDriver: UNKNOWN ADDRESS %d", regAdress);
+			// pcm rhythm channel
+			if (!(regAdress -= 0x10) && !(value & 0x80)) {
+				static const char *names[] = { "bass drum", "snare drum", "top cymbal", "high hat", "tom tom", "rim shot" };
+				for (int i = 0; i < 6; i++)
+					if ((value >> i) & 1)
+						debugC(9, kDebugLevelMain | kDebugLevelSound, "TownsPC98_OpnDriver: TRYING TO USE UNSUPPORTED PCM INSTRUMENT: '%s'", names[i]);
+			}
+
 			break;
 		case 0x20:
 			if (l == 8) {
@@ -1884,7 +2042,7 @@
 			// detune, multiple
 			_opr[o]->detune((value >> 4) & 7);
 			_opr[o]->multiple(value & 0x0f);
-			_updateEnvelopes = true;
+			_updateEnvelopeParameters = true;
 			break;
 
 		case 0x40:
@@ -1896,7 +2054,7 @@
 			// rate scaling, attack rate
 			_opr[o]->attackRate(value & 0x1f);
 			if (_opr[o]->scaleRate(value >> 6))
-				_updateEnvelopes = true;
+				_updateEnvelopeParameters = true;
 			break;
 
 		case 0x60:
@@ -1904,7 +2062,6 @@
 			_opr[o]->decayRate(value & 0x1f);
 			if (value & 0x80)
 				warning("TownsPC98_OpnDriver: TRYING TO USE AMP MODULATION (NOT SUPPORTED)");
-
 			break;
 
 		case 0x70:
@@ -1919,7 +2076,6 @@
 			break;
 
 		case 0x90:
-			// ssg
 			warning("TownsPC98_OpnDriver: UNKNOWN ADDRESS %d", regAdress);
 			break;
 
@@ -1928,7 +2084,7 @@
 			l -= _regOffset;
 			if (l == 0) {
 				_frqTemp = (_frqTemp & 0xff00) | value;
-				_updateEnvelopes = true;
+				_updateEnvelopeParameters = true;
 				for (int i = 0; i < 4; i++)
 					_opr[i]->frequency(_frqTemp);
 			} else if (l == 4) {
@@ -1970,7 +2126,7 @@
 }
 
 bool TownsPC98_OpnChannel::control_f0_setPatch(uint8 para) {
-	_instrID = para;
+	_instr = para;
 	uint8 reg = _regOffset + 0x80;
 
 	for (int i = 0; i < 4; i++) {
@@ -1979,7 +2135,7 @@
 		reg += 4;
 	}
 
-	const uint8 *tptr = _drv->_patches + ((uint32)_instrID << 5);
+	const uint8 *tptr = _drv->_patches + ((uint32)_instr << 5);
 	reg = _regOffset + 0x30;
 
 	// write registers 0x30 to 0x8f
@@ -2043,7 +2199,7 @@
 
 	if (*_dataPtr) {
 		// repeat section until counter has reached zero
-		_dataPtr = _drv->_trackData + READ_LE_UINT16(_dataPtr + 2);
+		_dataPtr = _drv->_trackPtr + READ_LE_UINT16(_dataPtr + 2);
 	} else {
 		// reset counter, advance to next section
 		_dataPtr[0] = _dataPtr[1];
@@ -2053,8 +2209,8 @@
 }
 
 bool TownsPC98_OpnChannel::control_f7_setupPitchWheel(uint8 para) {
-	_ptchWhlInitDelayLo = _dataPtr[0];
-	_ptchWhlInitDelayHi = para;
+	_ptchWhlInitDelayHi = _dataPtr[0];
+	_ptchWhlInitDelayLo = para;
 	_ptchWhlModInitVal = (int16) READ_LE_UINT16(_dataPtr + 1);
 	_ptchWhlDuration = _dataPtr[3];
 	_dataPtr += 4;
@@ -2113,7 +2269,7 @@
 }
 
 bool TownsPC98_OpnChannel::control_fd_jump(uint8 para) {
-	uint8 *tmp = _drv->_trackData + READ_LE_UINT16(_dataPtr - 1);
+	uint8 *tmp = _drv->_trackPtr + READ_LE_UINT16(_dataPtr - 1);
 	_dataPtr = (tmp[1] == 1) ? tmp : ++_dataPtr;
 	return true;
 }
@@ -2127,7 +2283,7 @@
 	uint16 val = READ_LE_UINT16(--_dataPtr);
 	if (val) {
 		// loop
-		_dataPtr = _drv->_trackData + val;
+		_dataPtr = _drv->_trackPtr + val;
 		return true;
 	} else {
 		// quit parsing for active channel
@@ -2139,31 +2295,250 @@
 	}
 }
 
-bool TownsPC98_OpnChannel::control_f0_setPatchSSG(uint8 para) {
-	_instrID = para << 4;
+TownsPC98_OpnChannelSSG::TownsPC98_OpnChannelSSG(TownsPC98_OpnDriver *driver, uint8 regOffs, 
+		uint8 flgs, uint8 num, uint8 key, uint8 prt, uint8 id) :
+		TownsPC98_OpnChannel(driver, regOffs, flgs, num, key, prt, id) {
+}
+
+void TownsPC98_OpnChannelSSG::init() {
+	_algorithm = 0x80;
+
+	#define Control(x)	&TownsPC98_OpnChannelSSG::control_##x
+	static const ControlEventFunc ctrlEventsSSG[] = {
+		Control(f0_setInstr),
+		Control(f1_setTotalLevel),
+		Control(f2_setKeyOffTime),
+		Control(f3_setFreqLSB),
+		Control(f4_setAlgorithm),
+		Control(f5_setTempo),
+		Control(f6_repeatSection),
+		Control(f7_setupPitchWheel),
+		Control(f8_togglePitchWheel),
+		Control(f9_loadCustomPatch),
+		Control(fa_writeReg),
+		Control(fb_incOutLevel),
+		Control(fc_decOutLevel),
+		Control(fd_jump),
+		Control(dummy),
+		Control(ff_endOfTrack)
+	};
+	#undef Control
+
+	controlEvents = ctrlEventsSSG;
+}
+
+void TownsPC98_OpnChannelSSG::processEvents() {
+	if (_flags & CHS_EOT)
+		return;
+
+	_drv->_regProtectionFlag = (_flags & CHS_PROTECT) ? true : false;
+
+	if (!_hold && _ticksLeft == _keyOffTime)
+		nextShape();
+
+	if (!--_ticksLeft) {
+		
+		uint8 cmd = 0;
+		bool loop = true;
+
+		while (loop) {
+			cmd = *_dataPtr++;
+			if (cmd < 0xf0)
+				loop = false;
+			else if (!processControlEvent(cmd))
+				return;
+		}
+
+		uint8 para = *_dataPtr++;
+
+		if (cmd == 0x80) {
+			nextShape();
+			_hold = false;
+		} else {
+			if (!_hold) {
+				_instr &= 0xf0;
+				_ssgStep = _drv->_ssgPatches[_instr];
+				_ssgTicksLeft = _drv->_ssgPatches[_instr + 1] & 0x7f;
+				_ssgTargetLvl = _drv->_ssgPatches[_instr + 2];
+				_ssgStartLvl = _drv->_ssgPatches[_instr + 3];
+				_flags = (_flags & ~CHS_SSGOFF) | CHS_KEYOFF;
+			}
+
+			keyOn();
+
+			if (_hold == false || cmd != _frqBlockMSB)
+				_flags |= CHS_RECALCFREQ;
+	
+			_hold = (para & 0x80) ? true : false;
+			_frqBlockMSB = cmd;
+		}
+
+		_ticksLeft = para & 0x7f;
+	}
+
+	if (!(_flags & CHS_SSGOFF)) {
+		if (--_ssgTicksLeft) {
+			if (!_drv->_fading)
+				setOutputLevel(_ssgStartLvl);	
+			return;
+		}
+
+		_ssgTicksLeft = _drv->_ssgPatches[_instr + 1] & 0x7f;
+
+		if (_drv->_ssgPatches[_instr + 1] & 0x80) {
+			uint8 t = _ssgStartLvl - _ssgStep;
+
+			if (_ssgStep <= _ssgStartLvl && _ssgTargetLvl < t) {
+				if (!_drv->_fading)
+					setOutputLevel(t);	
+				return;
+			}
+		} else {
+			int t = _ssgStartLvl + _ssgStep;
+			uint8 p = (uint8) (t & 0xff);
+
+			if (t < 256 && _ssgTargetLvl > p) {
+				if (!_drv->_fading)
+					setOutputLevel(p);
+				return;
+			}
+		}
+
+		setOutputLevel(_ssgTargetLvl);
+		if (_ssgStartLvl && !(_instr & 8)){
+			_instr += 4;
+			_ssgStep = _drv->_ssgPatches[_instr];
+			_ssgTicksLeft = _drv->_ssgPatches[_instr + 1] & 0x7f;
+			_ssgTargetLvl = _drv->_ssgPatches[_instr + 2];
+		} else {
+			_flags |= CHS_SSGOFF;
+			setOutputLevel(0);
+		}
+	}
+}
+
+void TownsPC98_OpnChannelSSG::processFrequency() {
+	if (_algorithm & 0x40)
+		return;
+
+	if (_flags & CHS_RECALCFREQ) {
+		_block = _frqBlockMSB >> 4;
+		_frequency = ((const uint16*)_drv->_opnFreqTableSSG)[_frqBlockMSB & 0x0f] + _frqLSB;
+
+		uint16 f = _frequency >> _block;
+		writeReg(_regOffset << 1, f & 0xff);
+		writeReg((_regOffset << 1) + 1, f >> 8);
+
+		setupPitchWheel();
+	}
+
+	if (!(_flags & (CHS_EOT | CHS_PITCHWHEELOFF | CHS_SSGOFF))) {
+		if (!processPitchWheel())
+			return;
+		
+		processPitchWheel();
+
+		uint16 f = _frequency >> _block;
+		writeReg(_regOffset << 1, f & 0xff);
+		writeReg((_regOffset << 1) + 1, f >> 8);
+	}
+}
+
+bool TownsPC98_OpnChannelSSG::processControlEvent(uint8 cmd) {
+	uint8 para = *_dataPtr++;
+	return (this->*controlEvents[cmd & 0x0f])(para);
+}
+
+void TownsPC98_OpnChannelSSG::nextShape() {
+	_instr = (_instr & 0xf0) + 0x0c;
+	_ssgStep = _drv->_ssgPatches[_instr];
+	_ssgTicksLeft = _drv->_ssgPatches[_instr + 1] & 0x7f;
+	_ssgTargetLvl = _drv->_ssgPatches[_instr + 2];
+}
+
+void TownsPC98_OpnChannelSSG::keyOn() {
+	uint8 c = 0x7b;
+	uint8 t = (_algorithm & 0xC0) << 1;
+	if (_algorithm & 0x80)
+		t |= 4;
+
+	c = (c << (_regOffset + 1)) | (c >> (7 - _regOffset));
+	t = (t << (_regOffset + 1)) | (t >> (7 - _regOffset));
+
+	if (!(_algorithm & 0x80))
+		writeReg(6, _algorithm & 0x7f);
+
+	uint8 e = (_drv->_ssg->readReg(7) & c) | t;
+	writeReg(7, e);
+}
+
+void TownsPC98_OpnChannelSSG::protect() {
+	_flags |= CHS_PROTECT;
+}
+
+void TownsPC98_OpnChannelSSG::restore() {
+	_flags &= ~CHS_PROTECT;
+	keyOn();
+	writeReg(8 + _regOffset, _ssgTl);
+	uint16 f = _frequency >> _block;
+	writeReg(_regOffset << 1, f & 0xff);
+	writeReg((_regOffset << 1) + 1, f >> 8);
+}
+
+void TownsPC98_OpnChannelSSG::loadData(uint8 *data) {
+	_drv->_regProtectionFlag = (_flags & CHS_PROTECT) ? true : false;	
+	TownsPC98_OpnChannel::loadData(data);
+	setOutputLevel(0);
+	_algorithm = 0x80;
+}
+
+void TownsPC98_OpnChannelSSG::setOutputLevel(uint8 lvl) {
+	_ssgStartLvl = lvl;
+	uint16 newTl = (((uint16)_totalLevel + 1) * (uint16)lvl) >> 8;
+	if (newTl == _ssgTl)
+		return;
+	_ssgTl = newTl;
+	writeReg(8 + _regOffset, _ssgTl);
+}
+
+void TownsPC98_OpnChannelSSG::fadeStep() {
+	_totalLevel--;
+	if ((int8)_totalLevel < 0)
+		_totalLevel = 0;
+	setOutputLevel(_ssgStartLvl);
+}
+
+bool TownsPC98_OpnChannelSSG::control_f0_setInstr(uint8 para) {
+	_instr = para << 4;
 	para = (para >> 3) & 0x1e;
 	if (para)
 		return control_f4_setAlgorithm(para | 0x40);
 	return true;
 }
 
-bool TownsPC98_OpnChannel::control_f1_setTotalLevel(uint8 para) {
+bool TownsPC98_OpnChannelSSG::control_f1_setTotalLevel(uint8 para) {
 	if (!_drv->_fading)
 		_totalLevel = para;
 	return true;
 }
 
-bool TownsPC98_OpnChannel::control_f4_setAlgorithm(uint8 para) {
+bool TownsPC98_OpnChannelSSG::control_f4_setAlgorithm(uint8 para) {
 	_algorithm = para;
 	return true;
 }
 
-bool TownsPC98_OpnChannel::control_f9_unkSSG(uint8 para) {
-	_dataPtr += 5;
+bool TownsPC98_OpnChannelSSG::control_f9_loadCustomPatch(uint8 para) {
+	_instr = (_drv->_sfxOffs + 10 + _regOffset) << 4;
+	_drv->_ssgPatches[_instr] = *_dataPtr++;
+	_drv->_ssgPatches[_instr + 3] = para;
+	_drv->_ssgPatches[_instr + 4] = *_dataPtr++;
+	_drv->_ssgPatches[_instr + 6] = *_dataPtr++;
+	_drv->_ssgPatches[_instr + 8] = *_dataPtr++;
+	_drv->_ssgPatches[_instr + 12] = *_dataPtr++;
 	return true;
 }
 
-bool TownsPC98_OpnChannel::control_fb_incOutLevelSSG(uint8 para) {
+bool TownsPC98_OpnChannelSSG::control_fb_incOutLevel(uint8 para) {
 	_dataPtr--;
 	if (_drv->_fading)
 		return true;
@@ -2175,7 +2550,7 @@
 	return true;
 }
 
-bool TownsPC98_OpnChannel::control_fc_decOutLevelSSG(uint8 para) {
+bool TownsPC98_OpnChannelSSG::control_fc_decOutLevel(uint8 para) {
 	_dataPtr--;
 	if (_drv->_fading)
 		return true;
@@ -2186,184 +2561,331 @@
 	return true;
 }
 
-bool TownsPC98_OpnChannel::control_ff_endOfTrackSSG(uint8 para) {
-	uint16 val = READ_LE_UINT16(--_dataPtr);
-	if (val) {
-		// loop
-		_dataPtr = _drv->_trackData + val;
-		return true;
+bool TownsPC98_OpnChannelSSG::control_ff_endOfTrack(uint8 para) {
+	if (!_drv->_sfxOffs) {
+		uint16 val = READ_LE_UINT16(--_dataPtr);
+		if (val) {
+			// loop
+			_dataPtr = _drv->_trackPtr + val;
+			return true;
+		} else {
+			// stop parsing
+			if (!_drv->_fading)
+				setOutputLevel(0);	
+			--_dataPtr;
+			_flags |= CHS_EOT;
+			_drv->_finishedSSGFlag |= _idFlag;
+		}
 	} else {
-		// quit parsing for active channel
-		--_dataPtr;
+		// end of sfx track - restore ssg music channel
 		_flags |= CHS_EOT;
-		//_finishedChannelsFlag |= _idFlag;
-		keyOff();
-		return false;
+		_drv->_ssgChannels[_chanNum]->restore();
 	}
+	
+	return false;
 }
 
-TownsPC98_OpnChannelSSG::TownsPC98_OpnChannelSSG(TownsPC98_OpnDriver *driver, uint8 regOffs, 
+void TownsPC98_OpnSfxChannel::loadData(uint8 *data) {
+	_flags = CHS_ALL_BUT_EOT;
+	_ticksLeft = 1;
+	_dataPtr = data;
+	_ssgTl = 0xff;
+	_algorithm = 0x80;
+}
+
+TownsPC98_OpnChannelPCM::TownsPC98_OpnChannelPCM(TownsPC98_OpnDriver *driver, uint8 regOffs, 
 		uint8 flgs, uint8 num, uint8 key, uint8 prt, uint8 id) :
 		TownsPC98_OpnChannel(driver, regOffs, flgs, num, key, prt, id) {
 }
 
-void TownsPC98_OpnChannelSSG::init() {
+void TownsPC98_OpnChannelPCM::init() {
 	_algorithm = 0x80;
-	
-	_opr = new TownsPC98_OpnOperator*[4];
-	for (int i = 0; i < 4; i++)
-		_opr[i] = new TownsPC98_OpnOperator(_drv->_baserate, _drv->_oprRates, _drv->_oprRateshift,
-			_drv->_oprAttackDecay, _drv->_oprFrq, _drv->_oprSinTbl, _drv->_oprLevelOut, _drv->_oprDetune);
 
-	#define Control(x)	&TownsPC98_OpnChannelSSG::control_##x
-	static const ControlEventFunc ctrlEventsSSG[] = {
-		Control(f0_setPatchSSG),
-		Control(f1_setTotalLevel),
-		Control(f2_setKeyOffTime),
-		Control(f3_setFreqLSB),
-		Control(f4_setOutputLevel),
-		Control(f5_setTempo),
+	#define Control(x)	&TownsPC98_OpnChannelPCM::control_##x
+	static const ControlEventFunc ctrlEventsPCM[] = {
+		Control(dummy),
+		Control(f1_pcmStart),
+		Control(dummy),
+		Control(dummy),
+		Control(dummy),
+		Control(dummy),
 		Control(f6_repeatSection),
-		Control(f7_setupPitchWheel),
-		Control(f8_togglePitchWheel),
-		Control(f9_unkSSG),
+		Control(dummy),
+		Control(dummy),
+		Control(dummy),
 		Control(fa_writeReg),
-		Control(fb_incOutLevelSSG),
-		Control(fc_decOutLevelSSG),
-		Control(fd_jump),
 		Control(dummy),
-		Control(ff_endOfTrackSSG)
+		Control(dummy),
+		Control(dummy),
+		Control(dummy),
+		Control(ff_endOfTrack)
 	};
 	#undef Control
 
-	controlEvents = ctrlEventsSSG;
+	controlEvents = ctrlEventsPCM;
 }
 
-void TownsPC98_OpnChannelSSG::processEvents() {
+void TownsPC98_OpnChannelPCM::loadData(uint8 *data) {
+	_flags = (_flags & ~CHS_EOT) | CHS_ALL_BUT_EOT;
+	_ticksLeft = 1;
+	_dataPtr = data;
+	_totalLevel = 0x7F;
+}
+
+void TownsPC98_OpnChannelPCM::processEvents()  {
 	if (_flags & CHS_EOT)
 		return;
 
-	_drv->_ssgFlag = (_flags & CHS_SSG) ? -1 : 0;
-
-	if (_protect == false && _ticksLeft == _keyOffTime)
-		keyOff();
-
 	if (--_ticksLeft)
 		return;
 
-	if (_protect == false)
-		keyOff();
-
 	uint8 cmd = 0;
 	bool loop = true;
 
 	while (loop) {
 		cmd = *_dataPtr++;
-		if (cmd < 0xf0)
+		if (cmd == 0x80) {
 			loop = false;
-		else if (!processControlEvent(cmd))
+		} else if (cmd < 0xf0) {
+			writeReg(0x10, cmd);
+		} else if (!processControlEvent(cmd)) {
 			return;
+		}
 	}
 
+	_ticksLeft = *_dataPtr++;
+}
+
+bool TownsPC98_OpnChannelPCM::processControlEvent(uint8 cmd) {
 	uint8 para = *_dataPtr++;
+	return (this->*controlEvents[cmd & 0x0f])(para);
+}
 
-	if (cmd == 0x80) {
-		keyOff();
-		_protect = false;
+void TownsPC98_OpnChannelPCM::fadeStep() {
+
+}
+
+bool TownsPC98_OpnChannelPCM::control_f1_pcmStart(uint8 para) {
+	_totalLevel = para;
+	writeReg(0x11, para);
+	return true;
+}
+
+bool TownsPC98_OpnChannelPCM::control_ff_endOfTrack(uint8 para) {
+	uint16 val = READ_LE_UINT16(--_dataPtr);
+	if (val) {
+		// loop
+		_dataPtr = _drv->_trackPtr + val;
+		return true;
 	} else {
-		keyOn();
-
-		if (_protect == false || cmd != _frqBlockMSB)
-			_flags |= CHS_RECALCFREQ;
-	
-		_protect = (para & 0x80) ? true : false;
-		_frqBlockMSB = cmd;
+		// quit parsing for active channel
+		--_dataPtr;
+		_flags |= CHS_EOT;
+		_drv->_finishedPCMFlag |= _idFlag;
+		return false;
 	}
+}
 
-	_ticksLeft = para & 0x7f;
+TownsPC98_SSG::TownsPC98_SSG(TownsPC98_OpnDriver *driver, Audio::Mixer *mixer, float rate) : _drv(driver), _mixer(mixer), _rate(rate),
+	_tlTable(0), _tleTable(0), _regIndex(_reg), _updateRequest(-1), _tickLength((int)(rate * 32768.0f * 27.0f)), _ready(0) {
+	memset(_reg, 0, 16);
+	memset(_channels, 0, sizeof(Channel) * 3);
+	reset();
+}
 
-	if (!(_flags & CHS_SSG)) {
+TownsPC98_SSG::~TownsPC98_SSG() {
+	_mixer->stopHandle(_soundHandle);
 
-	}
+	delete [] _tlTable;
+	delete [] _tleTable;
 }
 
-void TownsPC98_OpnChannelSSG::processFrequency() {
-	if (_flags & CHS_RECALCFREQ) {
-		uint8 block = (_frqBlockMSB & 0x70) >> 1;
-		uint16 bfreq = ((const uint16*)_drv->_opnFreqTable)[_frqBlockMSB & 0x0f];
-		frequency = (bfreq + _frqLSB) | (block << 8);
+void TownsPC98_SSG::init(const int *rsTable, const int *rseTable) {
+	if (_ready) {
+		reset();
+		return;
+	}
 
-		writeReg(_regOffset + 0xa4, (frequency >> 8));
-		writeReg(_regOffset + 0xa0, (frequency & 0xff));
+	delete [] _tlTable;
+	delete [] _tleTable;
+	_tlTable = new int32[16];
+	_tleTable = new int32[32];
+	float a, b, d;
+	d = 801.0f;
 
-		_ptchWhlCurDelay = _ptchWhlInitDelayHi;
-		if (_flags & CHS_KEYOFF) {
-			_ptchWhlModCurVal = _ptchWhlModInitVal;
-			_ptchWhlCurDelay += _ptchWhlInitDelayLo;
-		}
+	for (int i = 0; i < 16; i++) {
+		b = 1.0f / rsTable[i];
+		a = 1.0f / d + b + 1.0f / 1000.0f;
+		float v = (b / a) * 32767.0f;
+		_tlTable[i] = (int32) v;
 
-		_ptchWhlDurLeft = (_ptchWhlDuration >> 1);
-		_flags &= ~(CHS_KEYOFF | CHS_RECALCFREQ);
+		b = 1.0f / rseTable[i];
+		a = 1.0f / d + b + 1.0f / 1000.0f;
+		v = (b / a) * 32767.0f;
+		_tleTable[i] = (int32) v;
 	}
 
-	if (!(_flags & CHS_PITCHWHEELOFF)) {
-		if (--_ptchWhlCurDelay)
-			return;
-		_ptchWhlCurDelay = _ptchWhlInitDelayHi;
-		frequency += _ptchWhlModCurVal;
+	for (int i = 16; i < 32; i++) {
+		b = 1.0f / rseTable[i];
+		a = 1.0f / d + b + 1.0f / 1000.0f;
+		float v = (b / a) * 32767.0f;
+		_tleTable[i] = (int32) v;
+	}
 
-		writeReg(_regOffset + 0xa4, (frequency >> 8));
-		writeReg(_regOffset + 0xa0, (frequency & 0xff));
+	_mixer->playInputStream(Audio::Mixer::kMusicSoundType,
+		&_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, false, true);
 
-		if(!--_ptchWhlDurLeft) {
-			_ptchWhlDurLeft = _ptchWhlDuration;
-			_ptchWhlModCurVal = -_ptchWhlModCurVal;
-		}
+	_ready = true;
+}
+
+void TownsPC98_SSG::reset() {
+	_rand = 1;
+	_outN = 1;
+	_updateRequest = -1;
+	_nTick = _evpUpdateCnt = 0;
+	_regIndex = _reg;
+	_evpTimer = 0x1f;
+	_pReslt = 0x1f;
+	_attack = 0;
+	_cont = false;
+	_evpUpdate = true;
+	_timer = 0;
+
+	for (int i = 0; i < 3; i++) {
+		_channels[i].tick = 0;
+		_channels[i].smp = _channels[i].out = 0;
 	}
+
+	for (int i = 0; i < 14; i++)
+		writeReg(i, 0, true);
+
+	writeReg(7, 0xbf, true);
 }
 
-void TownsPC98_OpnChannelSSG::keyOff() {
-	// all operators off
-	uint8 value = _keyNum & 0x0f;
-	uint8 regAdress = 0x28;
-	writeReg(regAdress, value);
-	_flags |= CHS_KEYOFF;
+uint8 TownsPC98_SSG::readReg(uint8 address) {
+	return _reg[address];
 }
 
-void TownsPC98_OpnChannelSSG::keyOn() {
-	// all operators on
-	uint8 value = _keyNum | 0xf0;
-	uint8 regAdress = 0x28;
-	writeReg(regAdress, value);
+void TownsPC98_SSG::writeReg(uint8 address, uint8 value, bool force) {
+	_regIndex = &_reg[address];
+	int o = _regIndex - _reg;
+	if (!force && (o == 13 || *_regIndex != value)) {
+		if (_updateRequest == 31) {
+			warning("TownsPC98_SSG: event buffer overflow");
+			_updateRequest = -1;
+		}
+		_updateRequestBuf[++_updateRequest] = value;
+		_updateRequestBuf[++_updateRequest] = o;
+		return;
+	}
+
+	*_regIndex = value;
 }
 
-void TownsPC98_OpnChannelSSG::loadData(uint8 *data) {
-	_drv->_ssgFlag = (_flags & CHS_SSG) ? -1 : 0;
-	opn_SSG_UNK(0);
-	TownsPC98_OpnChannel::loadData(data);
-	_algorithm = 0x80;
+void TownsPC98_SSG::updatesRegs() {
+	for (int i = 0; i < _updateRequest;) {
+		uint8 b = _updateRequestBuf[i++];
+		uint8 a = _updateRequestBuf[i++];
+		writeReg(a, b, true);
+	}
+	_updateRequest = -1;	
 }
 
-void TownsPC98_OpnChannelSSG::opn_SSG_UNK(uint8 a) {
-	_ssg1 = a;
-	uint16 h = (_totalLevel + 1) * a;
-	if ((h >> 8) == _ssg2)
+void TownsPC98_SSG::nextTick(int16 *buffer, uint32 bufferSize) {
+	if (!_ready)
 		return;
-	_ssg2 = (h >> 8);
-	writeReg(8 + _regOffset, _ssg2);
+
+	for (uint32 i = 0; i < bufferSize; i++) {
+		_timer += _tickLength;
+		while (_timer > 0x30000) {
+			_timer -= 0x30000;
+
+			if (++_nTick >= (_reg[6] & 0x1f)) {
+				if ((_rand + 1) & 2)
+					_outN ^= 1;
+
+				_rand = (((_rand & 1) ^ ((_rand >> 3) & 1)) << 16) | (_rand >> 1);
+				_nTick = 0;
+			}
+
+			for (int ii = 0; ii < 3; ii++) {
+				if (++_channels[ii].tick >= (((_reg[ii * 2 + 1] & 0x0f) << 8) | _reg[ii * 2])) {
+					_channels[ii].tick = 0;
+					_channels[ii].smp ^= 1;
+				}
+				_channels[ii].out = (_channels[ii].smp | ((_reg[7] >> ii) & 1)) & (_outN | ((_reg[7] >> (ii + 3)) & 1));
+			}
+
+			if (_evpUpdate) {
+				if (++_evpUpdateCnt >= ((_reg[12] << 8) | _reg[11])) {
+					_evpUpdateCnt = 0;
+
+					if (--_evpTimer < 0) {
+						if (_cont) {
+							_evpTimer &= 0x1f;
+						} else {
+							_evpUpdate = false;
+							_evpTimer = 0;
+						}
+					}
+				}
+			}
+			_pReslt = _evpTimer ^ _attack;
+			updatesRegs();
+		}
+
+		int16 t[3];
+		for (int ii = 0; ii < 3; ii++) {
+			if ((_reg[ii + 8] >> 4) & 1)
+				t[ii] = _tleTable[_channels[ii].out ? _pReslt : 0];
+			else
+				t[ii] = _tlTable[_channels[ii].out ? (_reg[ii + 8] & 0x0f) : 0];
+		}
+
+		int l = (int) buffer[i << 1];
+		int r = (int) buffer[(i << 1) + 1];
+
+		for (int ii = 0; ii < 3; ii++) {
+			l += t[ii];
+			r += t[ii];
+		}
+		
+		l /= 2;
+		r /= 2;
+
+		if (l > 32767)
+			l = 32767;
+		if (l < -32767)
+			l = -32767;
+		buffer[i << 1] = (int16)l;
+
+		if (r > 32767)
+			r = 32767;
+		if (r < -32767)
+			r = -32767;
+		buffer[(i << 1) + 1] = (int16)r;
+	}
 }
 
+int TownsPC98_SSG::readBuffer(int16 *buffer, const int numSamples) {
+	memset(buffer, 0, sizeof(int16) * numSamples);
+	nextTick(buffer, numSamples >> 1);
+	return numSamples;
+}
+	
 TownsPC98_OpnDriver::TownsPC98_OpnDriver(Audio::Mixer *mixer, OpnType type) :
-	_mixer(mixer), _trackData(0), _playing(false), _fading(false), _channels(0), _ssgChannels(0),
-	_looping(0), _opnCarrier(_drvTables + 76), _opnFreqTable(_drvTables + 84),
-	_opnFxCmdLen(_drvTables + 36), _opnLvlPresets(_drvTables + (type == OD_TOWNS ? 52 : 220)) ,
-	_oprRates(0), _oprRateshift(0), _oprAttackDecay(0), _oprFrq(0),	_oprSinTbl(0), _oprLevelOut(0),
-	_oprDetune(0), _cbCounter(4), _tickCounter(0), _updateChannelsFlag(type == OD_TYPE26 ? 0x07 : 0x3F),
-	_finishedChannelsFlag(0), _samplesTillCallback(0), _samplesTillCallbackRemainder(0), _ready(false),
-	_numSSG(type == OD_TOWNS ? 0 : 3), _hasADPCM(type == OD_TYPE86 ? true : false),
-	_numChan(type == OD_TYPE26 ? 3 : 6), _hasStereo(type == OD_TYPE26 ? false : true) {	
+	_mixer(mixer), _trackPtr(0), _musicPlaying(false), _sfxPlaying(false), _fading(false), _channels(0),
+	_ssgChannels(0), _sfxChannels(0), _pcmChannel(0),	_looping(0), _opnCarrier(_drvTables + 76),
+	_opnFreqTable(_drvTables + 84), _opnFreqTableSSG(_drvTables + 252),	_opnFxCmdLen(_drvTables + 36),
+	_opnLvlPresets(_drvTables + (type == OD_TOWNS ? 52 : 228)), _oprRates(0), _oprRateshift(0), _oprAttackDecay(0),
+	_oprFrq(0), _oprSinTbl(0), _oprLevelOut(0), _oprDetune(0), _cbCounter(4), _tickCounter(0),
+	_updateChannelsFlag(type == OD_TYPE26 ? 0x07 : 0x3F), _updateSSGFlag(type == OD_TOWNS ? 0 : 7),
+	_updatePCMFlag(type == OD_TYPE86 ? 1 : 0), _finishedChannelsFlag(0), _finishedSSGFlag(0),
+	_finishedPCMFlag(0), _samplesTillCallback(0), _samplesTillCallbackRemainder(0),
+	_sfxData(0), _ready(false), _numSSG(type == OD_TOWNS ? 0 : 3), _hasPCM(type == OD_TYPE86 ? true : false),
+	_sfxOffs(0), _numChan(type == OD_TYPE26 ? 3 : 6), _hasStereo(type == OD_TYPE26 ? false : true),
+	_ssgPatches(0), _ssg(0), _baserate(55125.0f / (float)getRate()) {	
 	setTempo(84);
-	_baserate = (486202500.0 / (double)getRate()) / 10368.0;
 }
 
 TownsPC98_OpnDriver::~TownsPC98_OpnDriver() {
@@ -2381,13 +2903,24 @@
 		delete [] _ssgChannels;
 	}
 
+	if (_sfxChannels) {
+		for (int i = 0; i < 2; i++)
+			delete _sfxChannels[i];
+		delete [] _sfxChannels;
+	}
+
+	if (_pcmChannel)
+		delete _pcmChannel;
+
+	delete _ssg;
 	delete [] _oprRates;
 	delete [] _oprRateshift;
 	delete [] _oprFrq;
 	delete [] _oprAttackDecay;
 	delete [] _oprSinTbl;
 	delete [] _oprLevelOut;
-	delete [] _oprDetune;	
+	delete [] _oprDetune;
+	delete [] _ssgPatches;
 }
 
 bool TownsPC98_OpnDriver::init() {
@@ -2420,7 +2953,21 @@
 		}
 		delete [] _ssgChannels;
 	}
+
+	if (_sfxChannels) {
+		for (int i = 0; i < 2; i++) {
+			if (_sfxChannels[i])
+				delete _sfxChannels[i];
+		}
+		delete [] _sfxChannels;
+	}
+
 	if (_numSSG) {
+		_ssg = new TownsPC98_SSG(this, _mixer, _baserate);
+		_ssg->init(&_ssgTables[0], &_ssgTables[16]);
+		_ssgPatches = new uint8[256];
+		memcpy(_ssgPatches, _drvTables + 244, 256);
+
 		_ssgChannels = new TownsPC98_OpnChannelSSG*[_numSSG];
 		for (int i = 0; i < _numSSG; i++) {
 			int ii = i * 6;
@@ -2428,8 +2975,22 @@
 				_drvTables[ii + 2],	_drvTables[ii + 3],	_drvTables[ii + 4], _drvTables[ii + 5]);
 			_ssgChannels[i]->init();
 		}
+
+		_sfxChannels = new TownsPC98_OpnSfxChannel*[2];
+		for (int i = 0; i < 2; i++) {
+			int ii = (i + 1) * 6;
+			_sfxChannels[i] = new TownsPC98_OpnSfxChannel(this, _drvTables[ii], _drvTables[ii + 1],
+				_drvTables[ii + 2],	_drvTables[ii + 3],	_drvTables[ii + 4], _drvTables[ii + 5]);
+			_sfxChannels[i]->init();
+		}
 	}
 
+	if (_hasPCM) {
+		delete _pcmChannel;
+		_pcmChannel = new TownsPC98_OpnChannelPCM(this, 0, 0, 0, 0, 0, 1);
+		_pcmChannel->init();
+	}
+
 	_mixer->playInputStream(Audio::Mixer::kMusicSoundType,
 		&_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, false, true);
 
@@ -2440,9 +3001,11 @@
 int inline TownsPC98_OpnDriver::readBuffer(int16 *buffer, const int numSamples) {
 	memset(buffer, 0, sizeof(int16) * numSamples);
 	int32 samplesLeft = numSamples >> 1;
+
 	while (samplesLeft) {
 		if (!_samplesTillCallback) {
 			callback();
+
 			_samplesTillCallback = _samplesPerCallback;
 			_samplesTillCallbackRemainder += _samplesPerCallbackRemainder;
 			if (_samplesTillCallbackRemainder >= _tempo) {
@@ -2458,8 +3021,19 @@
 		nextTick(buffer, render);
 
 		for (int i = 0; i < render; ++i) {
-			buffer[i << 1] <<= 2;
-			buffer[(i << 1) + 1] <<= 2;
+			int l = (int) (buffer[i << 1] * 7) / 2;
+			if (l > 32767)
+				l = 32767;
+			if (l < -32767)
+				l = -32767;
+			buffer[i << 1] = (int16) l;
+
+			int r = (int) (buffer[(i << 1) + 1] * 7) / 2;
+			if (r > 32767)
+				r = 32767;
+			if (r < -32767)
+				r = -32767;
+			buffer[(i << 1) + 1] = (int16) r;
 		}
 
 		buffer += (render << 1);
@@ -2468,7 +3042,7 @@
 	return numSamples;
 }
 
-void TownsPC98_OpnDriver::loadData(uint8 *data, bool loadPaused) {
+void TownsPC98_OpnDriver::loadMusicData(uint8 *data, bool loadPaused) {
 	if (!_ready) {
 		warning("TownsPC98_OpnDriver: Driver must be initialized before loading data");
 		return;
@@ -2479,12 +3053,11 @@
 		return;
 	}
 
+	reset();
+
 	lock();
-	_trackData = data;
-
-	reset();
 	
-	uint8 *src_a = data;
+	uint8 *src_a = _trackPtr = _musicBuffer = data;
 
 	for (uint8 i = 0; i < 3; i++) {
 		_channels[i]->loadData(data + READ_LE_UINT16(src_a));
@@ -2501,118 +3074,168 @@
 		src_a += 2;
 	}
 
-	if (_hasADPCM) {
-		//_adpcmChannel->loadData(data + READ_LE_UINT16(src_a));
+	if (_hasPCM) {
+		_pcmChannel->loadData(data + READ_LE_UINT16(src_a));
 		src_a += 2;
 	}
 
-	_ssgFlag = 0;
+	_regProtectionFlag = false;
 
 	_patches = src_a + 4;
 	_cbCounter = 4;
-	_finishedChannelsFlag = 0;
+	_finishedChannelsFlag = _finishedSSGFlag = 0;
 
-	// AH 0x17
+	_musicPlaying = (loadPaused ? false : true);
+
 	unlock();
-	_playing = (loadPaused ? false : true);
 }
 
+void TownsPC98_OpnDriver::loadSoundEffectData(uint8 *data, uint8 trackNum) {
+	if (!_ready) {
+		warning("TownsPC98_OpnDriver: Driver must be initialized before loading data");
+		return;
+	}
+
+	if (!_sfxChannels) {
+		warning("TownsPC98_OpnDriver: sound effects not supported by this configuration");
+		return;
+	}
+
+	if (!data) {
+		warning("TownsPC98_OpnDriver: Invalid sound effect file data");
+		return;
+	}
+
+	lock();
+	_sfxData = _sfxBuffer = data;
+	_sfxOffsets[0] = READ_LE_UINT16(&_sfxData[(trackNum << 2)]);
+	_sfxOffsets[1] = READ_LE_UINT16(&_sfxData[(trackNum << 2) + 2]);
+	_sfxPlaying = true;
+	unlock();
+}
+
 void TownsPC98_OpnDriver::reset() {
-	for (int i = 0; i < (_numChan); i++)
+	lock();
+
+	for (int i = 0; i < _numChan; i++)
 		_channels[i]->reset();
-	for (int i = 0; i < (_numSSG); i++)
+	for (int i = 0; i < _numSSG; i++)
 		_ssgChannels[i]->reset();
+	
+	if (_ssg) {
+		for (int i = 0; i < 2; i++)
+			_sfxChannels[i]->reset();
 
-	_playing = _fading = false;
+		memcpy(_ssgPatches, _drvTables + 276, 256);
+		_ssg->reset();
+	}
+
+	_musicPlaying = _sfxPlaying = _fading = false;
 	_looping = 0;
 	_tickCounter = 0;
+	_sfxData = 0;
+
+	unlock();
 }
 
 void TownsPC98_OpnDriver::fadeOut() {
-	if (!_playing)
+	if (!_musicPlaying)
 		return;
 
-	_fading = true;
-
 	for (int i = 0; i < 20; i++) {		
 		lock();
+
+		_fading = true;
+
 		uint32 dTime = _tickCounter + 2;
 		for (int j = 0; j < _numChan; j++) {
 			if (_updateChannelsFlag & _channels[j]->_idFlag)
 				_channels[j]->fadeStep();
 		}
-		for (int j = 0; j < _numSSG; j++)
-			_ssgChannels[j]->fadeStep();
+		for (int j = 0; j < _numSSG; j++) {
+			if (_updateSSGFlag & _channels[j]->_idFlag)
+				_ssgChannels[j]->fadeStep();
+		}
 
 		unlock();
 
-		while (_playing) {
+		while (_musicPlaying) {
 			if (_tickCounter >= dTime)
 				break;
 		}
 	}
 
-	_fading = false;
-
 	reset();
 }
 
 void TownsPC98_OpnDriver::callback() {
-	if (!_playing || --_cbCounter)
-		return;
+	lock();
 
-	_cbCounter = 4;
 	_tickCounter++;
 
-	lock();
+	if (_sfxChannels && _sfxPlaying) {
+		if (_sfxData)
+			startSoundEffect();
 
-	for (int i = 0; i < _numChan; i++) {
-		if (_updateChannelsFlag & _channels[i]->_idFlag) {
-			_channels[i]->processEvents();
-			_channels[i]->processFrequency();
+		_sfxOffs = 3;
+		_trackPtr = _sfxBuffer;
+
+		for (int i = 0; i < 2; i++) {
+			_sfxChannels[i]->processEvents();
+			_sfxChannels[i]->processFrequency();
 		}
+
+		_trackPtr = _musicBuffer;
 	}
 
-	if (_numSSG) {
-		for (int i = 0; i < _numSSG; i++) {
-			_ssgChannels[i]->processEvents();
-			_ssgChannels[i]->processFrequency();
+	_sfxOffs = 0;
+
+	if (!--_cbCounter && _musicPlaying) {
+		_cbCounter = 4;
+
+		for (int i = 0; i < _numChan; i++) {
+			if (_updateChannelsFlag & _channels[i]->_idFlag) {
+				_channels[i]->processEvents();
+				_channels[i]->processFrequency();
+			}
 		}
+
+		if (_numSSG) {
+			for (int i = 0; i < _numSSG; i++) {
+				if (_updateSSGFlag & _ssgChannels[i]->_idFlag) {
+					_ssgChannels[i]->processEvents();
+					_ssgChannels[i]->processFrequency();
+				}
+			}
+		}
+
+		if (_hasPCM)
+			if (_updatePCMFlag & _pcmChannel->_idFlag)
+				_pcmChannel->processEvents();
 	}
-	
-	_ssgFlag = 0;
 
+	_regProtectionFlag = false;
+
+	if (_finishedChannelsFlag == _updateChannelsFlag && _finishedSSGFlag == _updateSSGFlag && _finishedPCMFlag == _updatePCMFlag)
+		_musicPlaying = false;
+
 	unlock();
-
-	if (_finishedChannelsFlag == _updateChannelsFlag)
-		reset();
 }
 
 void TownsPC98_OpnDriver::nextTick(int16 *buffer, uint32 bufferSize) {
-	if (!_playing)
+	if (!_ready)
 		return;	
 
-	for (int i = 0; i < _numChan ; i++) {
-		if (_channels[i]->_updateEnvelopes) {
-			_channels[i]->_updateEnvelopes = false;
+	for (int i = 0; i < _numChan; i++) {
+		if (_channels[i]->_updateEnvelopeParameters) {
+			_channels[i]->_updateEnvelopeParameters = false;
 			_channels[i]->updateEnv();
 		}
 		
 		for (uint32 ii = 0; ii < bufferSize ; ii++)
 			_channels[i]->generateOutput(buffer[ii * 2],
-			buffer[ii * 2 + 1],	&_channels[i]->_feedbuf[2], _channels[i]->_feedbuf);
+				buffer[ii * 2 + 1],	&_channels[i]->_feedbuf[2], _channels[i]->_feedbuf);
 	}
-
-	for (int i = 0; i < _numSSG ; i++) {
-		if (_ssgChannels[i]->_updateEnvelopes) {
-			_ssgChannels[i]->_updateEnvelopes = false;
-			_ssgChannels[i]->updateEnv();
-		}
-		
-		for (uint32 ii = 0; ii < bufferSize ; ii++)
-			_ssgChannels[i]->generateOutput(buffer[ii * 2],
-			buffer[ii * 2 + 1],	&_ssgChannels[i]->_feedbuf[2], _ssgChannels[i]->_feedbuf);
-	}
 }
 
 void TownsPC98_OpnDriver::generateTables() {
@@ -2641,7 +3264,7 @@
 	delete [] _oprFrq;
 	_oprFrq = new uint32[0x1000];
 	for (uint32 i = 0; i < 0x1000; i++)
-		_oprFrq[i] = (uint32)(_baserate * (double)(i << 11));
+		_oprFrq[i] = (uint32)(_baserate * (float)(i << 11));
 
 	delete [] _oprAttackDecay;
 	_oprAttackDecay = new uint8[152];
@@ -2680,17 +3303,29 @@
 	delete [] _oprDetune;
 	_oprDetune = new int32[256];
 	for (int i = 0; i < 128; i++) {
-		_oprDetune[i] = (int32)	((double)dtt[i] * _baserate * 64.0);
+		_oprDetune[i] = (int32)	((float)dtt[i] * _baserate * 64.0);
 		_oprDetune[i + 128] = -_oprDetune[i];
 	}
 
 	delete [] dtt;
 }
 
+void TownsPC98_OpnDriver::startSoundEffect() {
+	for (int i = 0; i < 2; i++) {
+		if (_sfxOffsets[i]) {
+			_ssgChannels[i + 1]->protect();
+			_sfxChannels[i]->reset();
+			_sfxChannels[i]->loadData(_sfxData + _sfxOffsets[i]);
+		}
+	}
+
+	_sfxData = 0;
+}
+
 void TownsPC98_OpnDriver::setTempo(uint8 tempo) {
-	_tempo = tempo;
-	_samplesPerCallback = getRate() / _tempo;
-	_samplesPerCallbackRemainder = getRate() % _tempo;
+	_tempo = tempo - 0x10;
+	_samplesPerCallback = _tempo ? (getRate() / _tempo) : 0;
+	_samplesPerCallbackRemainder = _tempo ? (getRate() % _tempo) : 0;
 }
 
 const uint8 TownsPC98_OpnDriver::_drvTables[] = {
@@ -2742,7 +3377,47 @@
 	//	pc98 level presets 
 	0x40, 0x3B, 0x38, 0x34, 0x30, 0x2A, 0x28, 0x25,
 	0x22, 0x20, 0x1D, 0x1A, 0x18, 0x15, 0x12, 0x10,
-	0x0D, 0x0A, 0x08, 0x05, 0x02, 0x90, 0x90, 0x90
+	0x0D, 0x0A, 0x08, 0x05, 0x02, 0x90, 0x90, 0x90,
+
+	//	ssg frequencies
+	0xE8, 0x0E, 0x12, 0x0E, 0x48, 0x0D, 0x89, 0x0C,
+	0xD5, 0x0B, 0x2B, 0x0B, 0x8A, 0x0A, 0xF3, 0x09,
+	0x64, 0x09, 0xDD, 0x08, 0x5E, 0x08, 0xE6, 0x07,
+
+	// ssg patch data
+	0x00, 0x00, 0xFF, 0xFF, 0x00, 0x81, 0x00, 0x00,
+	0x00, 0x81, 0x00, 0x00, 0xFF, 0x81, 0x00, 0x00,
+	0x00, 0x01, 0xFF, 0xFF, 0x37, 0x81, 0xC8, 0x00,
+	0x00, 0x81, 0x00, 0x00, 0x0A, 0x81, 0x00, 0x00,
+	0x00, 0x01, 0xFF, 0xFF, 0x37, 0x81, 0xC8, 0x00,
+	0x01, 0x81, 0x00, 0x00, 0x0A, 0x81, 0x00, 0x00,
+	0x00, 0x01, 0xFF, 0xFF, 0xFF, 0x81, 0xBE, 0x00,
+	0x00, 0x81, 0x00, 0x00, 0x0A, 0x81, 0x00, 0x00,
+	0x00, 0x01, 0xFF, 0xFF, 0xFF, 0x81, 0xBE, 0x00,
+	0x01, 0x81, 0x00, 0x00, 0x0A, 0x81, 0x00, 0x00,
+	0x00, 0x01, 0xFF, 0xFF, 0xFF, 0x81, 0xBE, 0x00,
+	0x04, 0x81, 0x00, 0x00, 0x0A, 0x81, 0x00, 0x00,
+	0x00, 0x01, 0xFF, 0xFF, 0xFF, 0x81, 0xBE, 0x00,
+	0x0A, 0x81, 0x00, 0x00, 0x0A, 0x81, 0x00, 0x00,
+	0x00, 0x01, 0xFF, 0xFF, 0xFF, 0x81, 0x01, 0x00,
+	0xFF, 0x81, 0x00, 0x00, 0xFF, 0x81, 0x00, 0x00,
+	0xFF, 0x01, 0xFF, 0xFF, 0xFF, 0x81, 0xFF, 0x00,
+	0x01, 0x81, 0x00, 0x00, 0x0A, 0x81, 0x00, 0x00,
+	0x64, 0x01, 0xFF, 0x64, 0xFF, 0x81, 0xFF, 0x00,	
+	0x01, 0x81, 0x00, 0x00, 0x0A, 0x81, 0x00, 0x00,	
+	
+	0x02, 0x01, 0xFF, 0x28, 0xFF, 0x81, 0xF0, 0x00,
+	0x00, 0x81, 0x00, 0x00, 0x0A, 0x81, 0x00, 0x00,
+	0x00, 0x01, 0xFF, 0xFF, 0xFF, 0x81, 0xC8, 0x00,
+	0x01, 0x81, 0x00, 0x00, 0x28, 0x81, 0x00, 0x00,	
+	0x00, 0x01, 0xFF, 0x78, 0x5F, 0x81, 0xA0, 0x00,
+	0x05, 0x81, 0x00, 0x00, 0x28, 0x81, 0x00, 0x00,
+	0x00, 0x01, 0xFF, 0xFF, 0x00, 0x81, 0x00, 0x00,
+	0x00, 0x81, 0x00, 0x00, 0xFF, 0x81, 0x00, 0x00,	
+	0x00, 0x01, 0xFF, 0xFF, 0x00, 0x81, 0x00, 0x00,
+	0x00, 0x81, 0x00, 0x00, 0xFF, 0x81, 0x00, 0x00,
+	0x00, 0x01, 0xFF, 0xFF, 0x00, 0x81, 0x00, 0x00,
+	0x00, 0x81, 0x00, 0x00, 0xFF, 0x81, 0x00, 0x00
 };
 
 const uint32 TownsPC98_OpnDriver::_adtStat[] = {
@@ -2757,6 +3432,15 @@
 	0x08080808, 0x08080808, 0x10101010, 0x10101010
 };
 
+const int TownsPC98_OpnDriver::_ssgTables[] = {
+	0x01202A, 0x0092D2, 0x006B42, 0x0053CB, 0x003DF8, 0x003053, 0x0022DA, 0x001A8C,
+	0x00129B, 0x000DC1, 0x000963, 0x0006C9, 0x000463, 0x0002FA, 0x0001B6, 0x0000FB,
+	0x0193B6, 0x01202A, 0x00CDB1, 0x0092D2, 0x007D7D, 0x006B42, 0x005ECD, 0x0053CB,
+	0x00480F, 0x003DF8, 0x0036B9, 0x003053, 0x00290A, 0x0022DA, 0x001E6B, 0x001A8C,
+	0x001639, 0x00129B, 0x000FFF, 0x000DC1, 0x000B5D, 0x000963, 0x0007FB, 0x0006C9,
+	0x000575, 0x000463, 0x00039D, 0x0002FA, 0x000242, 0x0001B6, 0x00014C, 0x0000FB
+};
+
 SoundTowns::SoundTowns(KyraEngine_v1 *vm, Audio::Mixer *mixer)
 	: Sound(vm, mixer), _lastTrack(-1), _currentSFX(0), _sfxFileData(0),
 	_sfxFileIndex((uint)-1), _sfxWDTable(0), _sfxBTTable(0), _parser(0) {
@@ -2916,8 +3600,9 @@
 	}
 
 	playbackBufferSize -= 0x20;
-	uint32 outputRate = uint32(11025 * semitoneAndSampleRate_to_sampleStep(note, sfxRootNoteOffs, sfxRate, 11025, 0x2000));
 
+	uint32 outputRate = uint32(11025 * calculatePhaseStep(note, sfxRootNoteOffs, sfxRate, 11025, 0x2000));
+
 	_currentSFX = Audio::makeLinearInputStream(sfxPlaybackBuffer, playbackBufferSize,
 		outputRate, Audio::Mixer::FLAG_UNSIGNED | Audio::Mixer::FLAG_LITTLE_ENDIAN | Audio::Mixer::FLAG_AUTOFREE, 0, 0);
 	_mixer->playInputStream(Audio::Mixer::kSFXSoundType, &_sfxHandle, _currentSFX);
@@ -2995,7 +3680,7 @@
 		music->_parser->onTimer();
 }
 
-float SoundTowns::semitoneAndSampleRate_to_sampleStep(int8 semiTone, int8 semiToneRootkey,
+float SoundTowns::calculatePhaseStep(int8 semiTone, int8 semiToneRootkey,
 	uint32 sampleRate, uint32 outputRate, int32 pitchWheel) {
 	if (semiTone < 0)
 		semiTone = 0;
@@ -3054,14 +3739,14 @@
 	if (track == _lastTrack && _musicEnabled)
 		return;
 
-	haltTrack();
+	beginFadeOut();
 
 	char musicfile[13];
 	sprintf(musicfile, fileListEntry(0), track);
 	delete[] _musicTrackData;
 	_musicTrackData = _vm->resource()->fileData(musicfile, 0);
 	if (_musicEnabled)
-		_driver->loadData(_musicTrackData);
+		_driver->loadMusicData(_musicTrackData);
 
 	_lastTrack = track;
 }
@@ -3078,25 +3763,32 @@
 	haltTrack();
 }
 
-void SoundPC98::playSoundEffect(uint8) {
-	/// TODO ///
+void SoundPC98::playSoundEffect(uint8 track) {
+	if (!_sfxTrackData)
+		return;
+
+	//	This has been disabled for now since I don't know
+	//	how to make up the correct track number. It probably
+	//	needs a map.
+	//_driver->loadSoundEffectData(_sfxTrackData, track);
 }
 
 
 //	KYRA 2
 
 SoundTownsPC98_v2::SoundTownsPC98_v2(KyraEngine_v1 *vm, Audio::Mixer *mixer) :
-	Sound(vm, mixer), _currentSFX(0), _musicTrackData(0), _lastTrack(-1), _driver(0), _useFmSfx(false) {
+	Sound(vm, mixer), _currentSFX(0), _musicTrackData(0), _sfxTrackData(0), _lastTrack(-1), _driver(0), _useFmSfx(false) {
 }
 
 SoundTownsPC98_v2::~SoundTownsPC98_v2() {
 	delete[] _musicTrackData;
+	delete[] _sfxTrackData;
 	delete _driver;
 }
 
 bool SoundTownsPC98_v2::init() {
-	_driver = new TownsPC98_OpnDriver(_mixer, /*_vm->gameFlags().platform == Common::kPlatformPC98 ?
-		TownsPC98_OpnDriver::OD_TYPE86 :*/ TownsPC98_OpnDriver::OD_TOWNS);
+	_driver = new TownsPC98_OpnDriver(_mixer, _vm->gameFlags().platform == Common::kPlatformPC98 ?
+		TownsPC98_OpnDriver::OD_TYPE86 : TownsPC98_OpnDriver::OD_TOWNS);
 	_useFmSfx = _vm->gameFlags().platform == Common::kPlatformPC98 ? true : false;
 	_vm->checkCD();
 	// FIXME: While checking for 'track1.XXX(X)' looks like
@@ -3110,9 +3802,15 @@
 		(Common::File::exists("track1.mp3") || Common::File::exists("track1.ogg") ||
 		 Common::File::exists("track1.flac") || Common::File::exists("track1.fla")))
 			_musicEnabled = 2;
+
 	return _driver->init();
 }
 
+void SoundTownsPC98_v2::loadSoundFile(Common::String file) {
+	delete [] _sfxTrackData;
+	_sfxTrackData = _vm->resource()->fileData(file.c_str(), 0);
+}
+
 void SoundTownsPC98_v2::process() {
 	AudioCD.updateCD();
 }
@@ -3140,7 +3838,7 @@
 	delete[] _musicTrackData;
 	
 	_musicTrackData = _vm->resource()->fileData(musicfile, 0);
-	_driver->loadData(_musicTrackData, true);
+	_driver->loadMusicData(_musicTrackData, true);
 
 	if (_musicEnabled == 2 && trackNum != -1) {
 		AudioCD.play(trackNum+1, _driver->looping() ? -1 : 1, 0, 0);
@@ -3221,7 +3919,7 @@
 		sfx[i] = cmd;
 	}
 
-	uint32 outputRate = uint32(11025 * SoundTowns::semitoneAndSampleRate_to_sampleStep(0x3c, 0x3c, sfxRate, 11025, 0x2000));
+	uint32 outputRate = uint32(11025 * SoundTowns::calculatePhaseStep(0x3c, 0x3c, sfxRate, 11025, 0x2000));
 
 	_currentSFX = Audio::makeLinearInputStream(sfx, outsize, outputRate,
 		Audio::Mixer::FLAG_UNSIGNED | Audio::Mixer::FLAG_LITTLE_ENDIAN | Audio::Mixer::FLAG_AUTOFREE, 0, 0);
@@ -3233,18 +3931,12 @@
 }
 
 void SoundTownsPC98_v2::playSoundEffect(uint8 track) {
-	if (!_useFmSfx)
+	if (!_useFmSfx || !_sfxTrackData)
 		return;
-
-	uint8 *sd = _vm->resource()->fileData("sound.dat", 0);
-
-
-	//TODO
-
-	delete [] sd;
+	
+	_driver->loadSoundEffectData(_sfxTrackData, track);
 }
 
 } // end of namespace Kyra
 
 #undef EUPHONY_FADEOUT_TICKS
-


This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.




More information about the Scummvm-git-logs mailing list