[Scummvm-git-logs] scummvm master -> 6558578f5423b3e1f072a4a06e12a51f48e91102

sev- sev at scummvm.org
Mon Sep 11 08:10:13 CEST 2017


This automated email contains information about 1 new commit which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .

Summary:
6558578f54 AUDIO: Import micromod code, xm/s3m/mod decoder


Commit: 6558578f5423b3e1f072a4a06e12a51f48e91102
    https://github.com/scummvm/scummvm/commit/6558578f5423b3e1f072a4a06e12a51f48e91102
Author: Simei Yin (roseline.yin at gmail.com)
Date: 2017-09-11T08:10:09+02:00

Commit Message:
AUDIO: Import micromod code, xm/s3m/mod decoder

Changed paths:
  A audio/mods/mod_xm_s3m.cpp
  A audio/mods/mod_xm_s3m.h
  A audio/mods/module_mod_xm_s3m.cpp
  A audio/mods/module_mod_xm_s3m.h
    audio/module.mk


diff --git a/audio/mods/mod_xm_s3m.cpp b/audio/mods/mod_xm_s3m.cpp
new file mode 100644
index 0000000..fdacbbb
--- /dev/null
+++ b/audio/mods/mod_xm_s3m.cpp
@@ -0,0 +1,1350 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+/*
+ * This code is based on IBXM mod player
+ *
+ * Copyright (c) 2015, Martin Cameron
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the
+ * following conditions are met:
+ *
+ * * Redistributions of source code must retain the above
+ * copyright notice, this list of conditions and the
+ * following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the
+ * above copyright notice, this list of conditions and the
+ * following disclaimer in the documentation and/or other
+ * materials provided with the distribution.
+ *
+ * * Neither the name of the organization nor the names of
+ *  its contributors may be used to endorse or promote
+ *  products derived from this software without specific
+ *  prior written permission.
+
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include "common/debug.h"
+#include "common/file.h"
+#include "common/memstream.h"
+
+#include "audio/audiostream.h"
+#include "audio/mods/mod_xm_s3m.h"
+#include "audio/mods/module_mod_xm_s3m.h"
+
+namespace Modules {
+
+class ModXmS3mStream : public Audio::AudioStream {
+private:
+	struct Channel {
+		Instrument *instrument;
+		Sample *sample;
+		Note note;
+		int keyOn, randomSeed, plRow;
+		int sampleOff, sampleIdx, sampleFra, freq, ampl, pann;
+		int volume, panning, fadeoutVol, volEnvTick, panEnvTick;
+		int period, portaPeriod, retrigCount, fxCount, avCount;
+		int portaUpParam, portaDownParam, tonePortaParam, offsetParam;
+		int finePortaUpParam, finePortaDownParam, xfinePortaParam;
+		int arpeggioParam, volSlideParam, gvolSlideParam, panSlideParam;
+		int fineVslideUpParam, fineVslideDownParam;
+		int retrigVolume, retrigTicks, tremorOnTicks, tremorOffTicks;
+		int vibratoType, vibratoPhase, vibratoSpeed, vibratoDepth;
+		int tremoloType, tremoloPhase, tremoloSpeed, tremoloDepth;
+		int tremoloAdd, vibratoAdd, arpeggioAdd;
+	};
+
+	ModuleModXmS3m _module;
+	bool _loadSuccess;
+	int _sampleRate, _interpolation, _globalVol;
+	int _seqPos, _breakPos, _row, _nextRow, _tick;
+	int _speed, _tempo, _plCount, _plChan;
+	int *_rampBuf;
+	int8 **_playCount;
+	Channel *_channels;
+	int _dataLeft;
+
+	// mix buffer to keep a partially consumed decoded tick.
+	int *_mixBuffer;
+	int _mixBufferSamples;	// number of samples kept in _mixBuffer
+
+	static const int FP_SHIFT;
+	static const int FP_ONE;
+	static const int FP_MASK;
+	static const int16 sinetable[];
+
+	int calculateDuration();
+	int calculateTickLength() { return (_sampleRate * 5) / (_tempo * 2); }
+	int calculateMixBufLength() { return (calculateTickLength() + 65) * 4; }
+
+	int initPlayCount(int8 **playCount);
+	void setSequencePos(int pos);
+	int tick();
+	void updateRow();
+	int seek(int samplePos);
+
+	// Sample
+	void downsample(int *buf, int count);
+	void resample(Channel &channel, int *mixBuf, int offset, int count, int sampleRate);
+	void updateSampleIdx(Channel &channel, int count, int sampleRate);
+
+	// Channel
+	void initChannel(int idx);
+	void tickChannel(Channel &channel);
+	void updateChannelRow(Channel &channel, Note note);
+
+	// Effects
+	int waveform(Channel &channel, int phase, int type);
+	void vibrato(Channel &channel, int fine);
+	void autoVibrato(Channel &channel);
+	void portaUp(Channel &channel, int param);
+	void portaDown(Channel &channel, int param);
+	void tonePorta(Channel &channel);
+	void volumeSlide(Channel &channel);
+	void retrigVolSlide(Channel &channel);
+	void tremolo(Channel &channel);
+	void tremor(Channel &channel);
+	void trigger(Channel &channel);
+	void calculateFreq(Channel &channel);
+	void calculateAmpl(Channel &channel);
+
+	// Envelopes
+	void updateEnvelopes(Channel &channel);
+	int envelopeNextTick(Envelope &envelope, int tick, int keyOn);
+	int calculateAmpl(Envelope &envelope, int tick);
+
+	// Read stream
+	int getAudio(int *mixBuf);
+	void volumeRamp(int *mixBuf, int tickLen);
+
+public:
+	// Check if module loading succeeds
+	bool loadSuccess() const { return _loadSuccess; }
+
+	// Implement virtual functions
+	virtual int readBuffer(int16 *buffer, const int numSamples);
+	virtual bool isStereo() const { return true; }
+	virtual int getRate() const { return _sampleRate; }
+	virtual bool endOfData() const { return _dataLeft <= 0; }
+
+	ModXmS3mStream(Common::SeekableReadStream *stream, int rate, int interpolation);
+	~ModXmS3mStream();
+};
+
+const int ModXmS3mStream::FP_SHIFT = 0xF;
+const int ModXmS3mStream::FP_ONE = 0x8000;
+const int ModXmS3mStream::FP_MASK = 0x7FFF;
+const short ModXmS3mStream::sinetable[] = {
+		  0,  24,  49,  74,  97, 120, 141, 161, 180, 197, 212, 224, 235, 244, 250, 253,
+		255, 253, 250, 244, 235, 224, 212, 197, 180, 161, 141, 120,  97,  74,  49,  24
+	};
+
+ModXmS3mStream::ModXmS3mStream(Common::SeekableReadStream *stream, int rate, int interpolation) {
+	_rampBuf = nullptr;
+	_playCount = nullptr;
+	_channels = nullptr;
+
+	if (!_module.load(*stream)) {
+		warning("It's not a valid Mod/S3m/Xm sound file");
+		_loadSuccess = false;
+		return;
+	}
+
+	// assign values
+	_loadSuccess = true;
+	_mixBufferSamples = 0;
+	_sampleRate = rate;
+	_interpolation = interpolation;
+	_rampBuf = new int[128];
+	_channels = new Channel[_module.numChannels];
+	_dataLeft = calculateDuration() * 4; // stereo and uint16
+	_mixBuffer = nullptr;
+}
+
+ModXmS3mStream::~ModXmS3mStream() {
+	if (_rampBuf) {
+		delete[] _rampBuf;
+		_rampBuf = nullptr;
+	}
+
+	if (_playCount) {
+		delete[] _playCount;
+		_playCount = nullptr;
+	}
+
+	if (_channels) {
+		delete[] _channels;
+		_channels = nullptr;
+	}
+
+	if (_mixBuffer) {
+		delete []_mixBuffer;
+		_mixBuffer = nullptr;
+	}
+}
+
+int ModXmS3mStream::initPlayCount(int8 **playCount) {
+	int len = 0;
+	for (int idx = 0; idx < _module.sequenceLen; ++idx) {
+		int pat = _module.sequence[idx];
+		int rows = (pat < _module.numPatterns) ? _module.patterns[pat].numRows : 0;
+		if (playCount) {
+			playCount[idx] = playCount[0] ? &playCount[0][len] : nullptr;
+		}
+		len += rows;
+	}
+	return len;
+}
+
+void ModXmS3mStream::initChannel(int idx) {
+	memset(&_channels[idx], 0, sizeof(Channel));
+	_channels[idx].panning = _module.defaultPanning[idx];
+	_channels[idx].instrument = &_module.instruments[0];
+	_channels[idx].sample = &_module.instruments[0].samples[0];
+	_channels[idx].randomSeed = (idx + 1) * 0xABCDEF;
+}
+
+void ModXmS3mStream::tickChannel(Channel &channel) {
+	channel.vibratoAdd = 0;
+	channel.fxCount++;
+	channel.retrigCount++;
+	if (!(channel.note.effect == 0x7D && channel.fxCount <= channel.note.param)) {
+		switch (channel.note.volume & 0xF0) {
+			case 0x60: /* Vol Slide Down.*/
+				channel.volume -= channel.note.volume & 0xF;
+				if (channel.volume < 0) {
+					channel.volume = 0;
+				}
+				break;
+			case 0x70: /* Vol Slide Up.*/
+				channel.volume += channel.note.volume & 0xF;
+				if (channel.volume > 64) {
+					channel.volume = 64;
+				}
+				break;
+			case 0xB0: /* Vibrato.*/
+				channel.vibratoPhase += channel.vibratoSpeed;
+				vibrato(channel, 0);
+				break;
+			case 0xD0: /* Pan Slide Left.*/
+				channel.panning -= channel.note.volume & 0xF;
+				if (channel.panning < 0) {
+					channel.panning = 0;
+				}
+				break;
+			case 0xE0: /* Pan Slide Right.*/
+				channel.panning += channel.note.volume & 0xF;
+				if (channel.panning > 255) {
+					channel.panning = 255;
+				}
+				break;
+			case 0xF0: /* Tone Porta.*/
+				tonePorta(channel);
+				break;
+		}
+	}
+	switch (channel.note.effect) {
+		case 0x01:
+		case 0x86: /* Porta Up. */
+			portaUp(channel, channel.portaUpParam);
+			break;
+		case 0x02:
+		case 0x85: /* Porta Down. */
+			portaDown(channel, channel.portaDownParam);
+			break;
+		case 0x03:
+		case 0x87: /* Tone Porta. */
+			tonePorta(channel);
+			break;
+		case 0x04:
+		case 0x88: /* Vibrato. */
+			channel.vibratoPhase += channel.vibratoSpeed;
+			vibrato(channel, 0);
+			break;
+		case 0x05:
+		case 0x8C: /* Tone Porta + Vol Slide. */
+			tonePorta(channel);
+			volumeSlide(channel);
+			break;
+		case 0x06:
+		case 0x8B: /* Vibrato + Vol Slide. */
+			channel.vibratoPhase += channel.vibratoSpeed;
+			vibrato(channel, 0);
+			volumeSlide(channel);
+			break;
+		case 0x07:
+		case 0x92: /* Tremolo. */
+			channel.tremoloPhase += channel.tremoloSpeed;
+			tremolo(channel);
+			break;
+		case 0x0A:
+		case 0x84: /* Vol Slide. */
+			volumeSlide(channel);
+			break;
+		case 0x11: /* Global Volume Slide. */
+			_globalVol = _globalVol + (channel.gvolSlideParam >> 4) - (channel.gvolSlideParam & 0xF);
+			if (_globalVol < 0) {
+				_globalVol = 0;
+			}
+			if (_globalVol > 64) {
+				_globalVol = 64;
+			}
+			break;
+		case 0x19: /* Panning Slide. */
+			channel.panning = channel.panning + (channel.panSlideParam >> 4) - (channel.panSlideParam & 0xF);
+			if (channel.panning < 0) {
+				channel.panning = 0;
+			}
+			if (channel.panning > 255) {
+				channel.panning = 255;
+			}
+			break;
+		case 0x1B:
+		case 0x91: /* Retrig + Vol Slide. */
+			retrigVolSlide(channel);
+			break;
+		case 0x1D:
+		case 0x89: /* Tremor. */
+			tremor(channel);
+			break;
+		case 0x79: /* Retrig. */
+			if (channel.fxCount >= channel.note.param) {
+				channel.fxCount = 0;
+				channel.sampleIdx = channel.sampleFra = 0;
+			}
+			break;
+		case 0x7C:
+		case 0xFC: /* Note Cut. */
+			if (channel.note.param == channel.fxCount) {
+				channel.volume = 0;
+			}
+			break;
+		case 0x7D:
+		case 0xFD: /* Note Delay. */
+			if (channel.note.param == channel.fxCount) {
+				trigger(channel);
+			}
+			break;
+		case 0x8A: /* Arpeggio. */
+			if (channel.fxCount == 1) {
+				channel.arpeggioAdd = channel.arpeggioParam >> 4;
+			} else if (channel.fxCount == 2) {
+				channel.arpeggioAdd = channel.arpeggioParam & 0xF;
+			} else {
+				channel.arpeggioAdd = channel.fxCount = 0;
+			}
+			break;
+		case 0x95: /* Fine Vibrato. */
+			channel.vibratoPhase += channel.vibratoSpeed;
+			vibrato(channel, 1);
+			break;
+	}
+	autoVibrato(channel);
+	calculateFreq(channel);
+	calculateAmpl(channel);
+	updateEnvelopes(channel);
+}
+
+void ModXmS3mStream::volumeSlide(Channel &channel) {
+	int up = channel.volSlideParam >> 4;
+	int down = channel.volSlideParam & 0xF;
+	if (down == 0xF && up > 0) {
+		/* Fine slide up.*/
+		if (channel.fxCount == 0) {
+			channel.volume += up;
+		}
+	} else if (up == 0xF && down > 0) {
+		/* Fine slide down.*/
+		if (channel.fxCount == 0) {
+			channel.volume -= down;
+		}
+	} else if (channel.fxCount > 0 || _module.fastVolSlides) {
+		/* Normal.*/
+		channel.volume += up - down;
+	}
+	if (channel.volume > 64) {
+		channel.volume = 64;
+	}
+	if (channel.volume < 0) {
+		channel.volume = 0;
+	}
+}
+
+void ModXmS3mStream::portaUp(Channel &channel, int param) {
+	switch (param & 0xF0) {
+		case 0xE0: /* Extra-fine porta.*/
+			if (channel.fxCount == 0) {
+				channel.period -= param & 0xF;
+			}
+			break;
+		case 0xF0: /* Fine porta.*/
+			if (channel.fxCount == 0) {
+				channel.period -= (param & 0xF) << 2;
+			}
+			break;
+		default:/* Normal porta.*/
+			if (channel.fxCount > 0) {
+				channel.period -= param << 2;
+			}
+			break;
+	}
+	if (channel.period < 0) {
+		channel.period = 0;
+	}
+}
+
+void ModXmS3mStream::portaDown(Channel &channel, int param) {
+	if (channel.period > 0) {
+		switch (param & 0xF0) {
+			case 0xE0: /* Extra-fine porta.*/
+				if (channel.fxCount == 0) {
+					channel.period += param & 0xF;
+				}
+				break;
+			case 0xF0: /* Fine porta.*/
+				if (channel.fxCount == 0) {
+					channel.period += (param & 0xF) << 2;
+				}
+				break;
+			default:/* Normal porta.*/
+				if (channel.fxCount > 0) {
+					channel.period += param << 2;
+				}
+				break;
+		}
+		if (channel.period > 65535) {
+			channel.period = 65535;
+		}
+	}
+}
+
+void ModXmS3mStream::tonePorta(Channel &channel) {
+	if (channel.period > 0) {
+		if (channel.period < channel.portaPeriod) {
+			channel.period += channel.tonePortaParam << 2;
+			if (channel.period > channel.portaPeriod) {
+				channel.period = channel.portaPeriod;
+			}
+		} else {
+			channel.period -= channel.tonePortaParam << 2;
+			if (channel.period < channel.portaPeriod) {
+				channel.period = channel.portaPeriod;
+			}
+		}
+	}
+}
+
+int ModXmS3mStream::waveform(Channel &channel, int phase, int type) {
+	int amplitude = 0;
+	switch (type) {
+		default: /* Sine. */
+			amplitude = sinetable[phase & 0x1F];
+			if ((phase & 0x20) > 0) {
+				amplitude = -amplitude;
+			}
+			break;
+		case 6: /* Saw Up.*/
+			amplitude = (((phase + 0x20) & 0x3F) << 3) - 255;
+			break;
+		case 1:
+		case 7: /* Saw Down. */
+			amplitude = 255 - (((phase + 0x20) & 0x3F) << 3);
+			break;
+		case 2:
+		case 5: /* Square. */
+			amplitude = (phase & 0x20) > 0 ? 255 : -255;
+			break;
+		case 3:
+		case 8: /* Random. */
+			amplitude = (channel.randomSeed >> 20) - 255;
+			channel.randomSeed = (channel.randomSeed * 65 + 17) & 0x1FFFFFFF;
+			break;
+	}
+	return amplitude;
+}
+
+void ModXmS3mStream::vibrato(Channel &channel, int fine) {
+	int wave = waveform(channel, channel.vibratoPhase, channel.vibratoType & 0x3);
+	channel.vibratoAdd = wave * channel.vibratoDepth >> (fine ? 7 : 5);
+}
+
+void ModXmS3mStream::tremolo(Channel &channel) {
+	int wave = waveform(channel, channel.tremoloPhase, channel.tremoloType & 0x3);
+	channel.tremoloAdd = wave * channel.tremoloDepth >> 6;
+}
+
+void ModXmS3mStream::tremor(Channel &channel) {
+	if (channel.retrigCount >= channel.tremorOnTicks) {
+		channel.tremoloAdd = -64;
+	}
+	if (channel.retrigCount >= (channel.tremorOnTicks + channel.tremorOffTicks)) {
+		channel.tremoloAdd = channel.retrigCount = 0;
+	}
+}
+
+void ModXmS3mStream::retrigVolSlide(Channel &channel) {
+	if (channel.retrigCount >= channel.retrigTicks) {
+		channel.retrigCount = channel.sampleIdx = channel.sampleFra = 0;
+		switch (channel.retrigVolume) {
+			case 0x1:
+				channel.volume = channel.volume - 1;
+				break;
+			case 0x2:
+				channel.volume = channel.volume - 2;
+				break;
+			case 0x3:
+				channel.volume = channel.volume - 4;
+				break;
+			case 0x4:
+				channel.volume = channel.volume - 8;
+				break;
+			case 0x5:
+				channel.volume = channel.volume - 16;
+				break;
+			case 0x6:
+				channel.volume = channel.volume * 2 / 3;
+				break;
+			case 0x7:
+				channel.volume = channel.volume >> 1;
+				break;
+			case 0x8: /* ? */
+				break;
+			case 0x9:
+				channel.volume = channel.volume + 1;
+				break;
+			case 0xA:
+				channel.volume = channel.volume + 2;
+				break;
+			case 0xB:
+				channel.volume = channel.volume + 4;
+				break;
+			case 0xC:
+				channel.volume = channel.volume + 8;
+				break;
+			case 0xD:
+				channel.volume = channel.volume + 16;
+				break;
+			case 0xE:
+				channel.volume = channel.volume * 3 / 2;
+				break;
+			case 0xF:
+				channel.volume = channel.volume << 1;
+				break;
+		}
+		if (channel.volume < 0) {
+			channel.volume = 0;
+		}
+		if (channel.volume > 64) {
+			channel.volume = 64;
+		}
+	}
+}
+
+void ModXmS3mStream::trigger(Channel &channel) {
+	int ins = channel.note.instrument;
+	if (ins > 0 && ins <= _module.numInstruments) {
+		channel.instrument = &_module.instruments[ins];
+		int key = channel.note.key < 97 ? channel.note.key : 0;
+		int sam = channel.instrument->keyToSample[key];
+		Sample *sample = &channel.instrument->samples[sam];
+		channel.volume = sample->volume >= 64 ? 64 : sample->volume & 0x3F;
+		if (sample->panning > 0) {
+			channel.panning = (sample->panning - 1) & 0xFF;
+		}
+		if (channel.period > 0 && sample->loopLength > 1) {
+			/* Amiga trigger.*/
+			channel.sample = sample;
+		}
+		channel.sampleOff = 0;
+		channel.volEnvTick = channel.panEnvTick = 0;
+		channel.fadeoutVol = 32768;
+		channel.keyOn = 1;
+	}
+	if (channel.note.effect == 0x09 || channel.note.effect == 0x8F) {
+		/* Set Sample Offset. */
+		if (channel.note.param > 0) {
+			channel.offsetParam = channel.note.param;
+		}
+		channel.sampleOff = channel.offsetParam << 8;
+	}
+	if (channel.note.volume >= 0x10 && channel.note.volume < 0x60) {
+		channel.volume = channel.note.volume < 0x50 ? channel.note.volume - 0x10 : 64;
+	}
+	switch (channel.note.volume & 0xF0) {
+		case 0x80: /* Fine Vol Down.*/
+			channel.volume -= channel.note.volume & 0xF;
+			if (channel.volume < 0) {
+				channel.volume = 0;
+			}
+			break;
+		case 0x90: /* Fine Vol Up.*/
+			channel.volume += channel.note.volume & 0xF;
+			if (channel.volume > 64) {
+				channel.volume = 64;
+			}
+			break;
+		case 0xA0: /* Set Vibrato Speed.*/
+			if ((channel.note.volume & 0xF) > 0) {
+				channel.vibratoSpeed = channel.note.volume & 0xF;
+			}
+			break;
+		case 0xB0: /* Vibrato.*/
+			if ((channel.note.volume & 0xF) > 0) {
+				channel.vibratoDepth = channel.note.volume & 0xF;
+			}
+			vibrato(channel, 0);
+			break;
+		case 0xC0: /* Set Panning.*/
+			channel.panning = (channel.note.volume & 0xF) * 17;
+			break;
+		case 0xF0: /* Tone Porta.*/
+			if ((channel.note.volume & 0xF) > 0) {
+				channel.tonePortaParam = channel.note.volume & 0xF;
+			}
+			break;
+	}
+	if (channel.note.key > 0) {
+		if (channel.note.key > 96) {
+			channel.keyOn = 0;
+		} else {
+			int porta = (channel.note.volume & 0xF0) == 0xF0 || channel.note.effect == 0x03 || channel.note.effect == 0x05 || channel.note.effect == 0x87 || channel.note.effect == 0x8C;
+			if (!porta) {
+				ins = channel.instrument->keyToSample[channel.note.key];
+				channel.sample = &channel.instrument->samples[ins];
+			}
+			int finetune = channel.sample->finetune;
+			if (channel.note.effect == 0x75 || channel.note.effect == 0xF2) {
+				/* Set Fine Tune. */
+				finetune = ((channel.note.param & 0xF) << 4) - 128;
+			}
+			int key = channel.note.key + channel.sample->relNote;
+			if (key < 1) {
+				key = 1;
+			}
+			if (key > 120) {
+				key = 120;
+			}
+			int period = (key << 6) + (finetune >> 1);
+			if (_module.linearPeriods) {
+				channel.portaPeriod = 7744 - period;
+			} else {
+				channel.portaPeriod = 29021 * ModuleModXmS3m::exp2((period << FP_SHIFT) / -768) >> FP_SHIFT;
+			}
+			if (!porta) {
+				channel.period = channel.portaPeriod;
+				channel.sampleIdx = channel.sampleOff;
+				channel.sampleFra = 0;
+				if (channel.vibratoType < 4) {
+					channel.vibratoPhase = 0;
+				}
+				if (channel.tremoloType < 4) {
+					channel.tremoloPhase = 0;
+				}
+				channel.retrigCount = channel.avCount = 0;
+			}
+		}
+	}
+}
+
+void ModXmS3mStream::updateEnvelopes(Channel &channel) {
+	if (channel.instrument->volEnv.enabled) {
+		if (!channel.keyOn) {
+			channel.fadeoutVol -= channel.instrument->volFadeout;
+			if (channel.fadeoutVol < 0) {
+				channel.fadeoutVol = 0;
+			}
+		}
+		channel.volEnvTick = envelopeNextTick(channel.instrument->volEnv, channel.volEnvTick, channel.keyOn);
+	}
+	if (channel.instrument->panEnv.enabled) {
+		channel.panEnvTick = envelopeNextTick(channel.instrument->panEnv, channel.panEnvTick, channel.keyOn);
+	}
+}
+
+void ModXmS3mStream::autoVibrato(Channel &channel) {
+	int depth = channel.instrument->vibDepth & 0x7F;
+	if (depth > 0) {
+		int sweep = channel.instrument->vibSweep & 0x7F;
+		int rate = channel.instrument->vibRate & 0x7F;
+		int type = channel.instrument->vibType;
+		if (channel.avCount < sweep) {
+			depth = depth * channel.avCount / sweep;
+		}
+		int wave = waveform(channel, channel.avCount * rate >> 2, type + 4);
+		channel.vibratoAdd += wave * depth >> 8;
+		channel.avCount++;
+	}
+}
+
+void ModXmS3mStream::calculateFreq(Channel &channel) {
+	int per = channel.period + channel.vibratoAdd;
+	if (_module.linearPeriods) {
+		per = per - (channel.arpeggioAdd << 6);
+		if (per < 28 || per > 7680) {
+			per = 7680;
+		}
+		channel.freq = ((_module.c2Rate >> 4) * ModuleModXmS3m::exp2(((4608 - per) << FP_SHIFT) / 768)) >> (FP_SHIFT - 4);
+	} else {
+		if (per > 29021) {
+			per = 29021;
+		}
+		per = (per << FP_SHIFT) / ModuleModXmS3m::exp2((channel.arpeggioAdd << FP_SHIFT) / 12);
+		if (per < 28) {
+			per = 29021;
+		}
+		channel.freq = _module.c2Rate * 1712 / per;
+	}
+}
+
+void ModXmS3mStream::calculateAmpl(Channel &channel) {
+	int envPan = 32, envVol = channel.keyOn ? 64 : 0;
+	if (channel.instrument->volEnv.enabled) {
+		envVol = calculateAmpl(channel.instrument->volEnv, channel.volEnvTick);
+	}
+	int vol = channel.volume + channel.tremoloAdd;
+	if (vol > 64) {
+		vol = 64;
+	}
+	if (vol < 0) {
+		vol = 0;
+	}
+	vol = (vol * _module.gain * FP_ONE) >> 13;
+	vol = (vol * channel.fadeoutVol) >> 15;
+	channel.ampl = (vol * _globalVol * envVol) >> 12;
+	if (channel.instrument->panEnv.enabled) {
+		envPan = calculateAmpl(channel.instrument->panEnv, channel.panEnvTick);
+	}
+	int range = (channel.panning < 128) ? channel.panning : (255 - channel.panning);
+	channel.pann = channel.panning + (range * (envPan - 32) >> 5);
+}
+
+void ModXmS3mStream::updateChannelRow(Channel &channel, Note note) {
+	channel.note = note;
+	channel.retrigCount++;
+	channel.vibratoAdd = channel.tremoloAdd = channel.arpeggioAdd = channel.fxCount = 0;
+	if (!((note.effect == 0x7D || note.effect == 0xFD) && note.param > 0)) {
+		/* Not note delay.*/
+		trigger(channel);
+	}
+	switch (channel.note.effect) {
+		case 0x01:
+		case 0x86: /* Porta Up. */
+			if (channel.note.param > 0) {
+				channel.portaUpParam = channel.note.param;
+			}
+			portaUp(channel, channel.portaUpParam);
+			break;
+		case 0x02:
+		case 0x85: /* Porta Down. */
+			if (channel.note.param > 0) {
+				channel.portaDownParam = channel.note.param;
+			}
+			portaDown(channel, channel.portaDownParam);
+			break;
+		case 0x03:
+		case 0x87: /* Tone Porta. */
+			if (channel.note.param > 0) {
+				channel.tonePortaParam = channel.note.param;
+			}
+			break;
+		case 0x04:
+		case 0x88: /* Vibrato. */
+			if ((channel.note.param >> 4) > 0) {
+				channel.vibratoSpeed = channel.note.param >> 4;
+			}
+			if ((channel.note.param & 0xF) > 0) {
+				channel.vibratoDepth = channel.note.param & 0xF;
+			}
+			vibrato(channel, 0);
+			break;
+		case 0x05:
+		case 0x8C: /* Tone Porta + Vol Slide. */
+			if (channel.note.param > 0) {
+				channel.volSlideParam = channel.note.param;
+			}
+			volumeSlide(channel);
+			break;
+		case 0x06:
+		case 0x8B: /* Vibrato + Vol Slide. */
+			if (channel.note.param > 0) {
+				channel.volSlideParam = channel.note.param;
+			}
+			vibrato(channel, 0);
+			volumeSlide(channel);
+			break;
+		case 0x07:
+		case 0x92: /* Tremolo. */
+			if ((channel.note.param >> 4) > 0) {
+				channel.tremoloSpeed = channel.note.param >> 4;
+			}
+			if ((channel.note.param & 0xF) > 0) {
+				channel.tremoloDepth = channel.note.param & 0xF;
+			}
+			tremolo(channel);
+			break;
+		case 0x08: /* Set Panning.*/
+			channel.panning = (channel.note.param < 128) ? (channel.note.param << 1) : 255;
+			break;
+		case 0x0A:
+		case 0x84: /* Vol Slide. */
+			if (channel.note.param > 0) {
+				channel.volSlideParam = channel.note.param;
+			}
+			volumeSlide(channel);
+			break;
+		case 0x0C: /* Set Volume. */
+			channel.volume = channel.note.param >= 64 ? 64 : channel.note.param & 0x3F;
+			break;
+		case 0x10:
+		case 0x96: /* Set Global Volume. */
+			_globalVol = channel.note.param >= 64 ? 64 : channel.note.param & 0x3F;
+			break;
+		case 0x11: /* Global Volume Slide. */
+			if (channel.note.param > 0) {
+				channel.gvolSlideParam = channel.note.param;
+			}
+			break;
+		case 0x14: /* Key Off. */
+			channel.keyOn = 0;
+			break;
+		case 0x15: /* Set Envelope Tick. */
+			channel.volEnvTick = channel.panEnvTick = channel.note.param & 0xFF;
+			break;
+		case 0x19: /* Panning Slide. */
+			if (channel.note.param > 0) {
+				channel.panSlideParam = channel.note.param;
+			}
+			break;
+		case 0x1B:
+		case 0x91: /* Retrig + Vol Slide. */
+			if ((channel.note.param >> 4) > 0) {
+				channel.retrigVolume = channel.note.param >> 4;
+			}
+			if ((channel.note.param & 0xF) > 0) {
+				channel.retrigTicks = channel.note.param & 0xF;
+			}
+			retrigVolSlide(channel);
+			break;
+		case 0x1D:
+		case 0x89: /* Tremor. */
+			if ((channel.note.param >> 4) > 0) {
+				channel.tremorOnTicks = channel.note.param >> 4;
+			}
+			if ((channel.note.param & 0xF) > 0) {
+				channel.tremorOffTicks = channel.note.param & 0xF;
+			}
+			tremor(channel);
+			break;
+		case 0x21: /* Extra Fine Porta. */
+			if (channel.note.param > 0) {
+				channel.xfinePortaParam = channel.note.param;
+			}
+			switch (channel.xfinePortaParam & 0xF0) {
+				case 0x10:
+					portaUp(channel, 0xE0 | (channel.xfinePortaParam & 0xF));
+					break;
+				case 0x20:
+					portaDown(channel, 0xE0 | (channel.xfinePortaParam & 0xF));
+					break;
+			}
+			break;
+		case 0x71: /* Fine Porta Up. */
+			if (channel.note.param > 0) {
+				channel.finePortaUpParam = channel.note.param;
+			}
+			portaUp(channel, 0xF0 | (channel.finePortaUpParam & 0xF));
+			break;
+		case 0x72: /* Fine Porta Down. */
+			if (channel.note.param > 0) {
+				channel.finePortaDownParam = channel.note.param;
+			}
+			portaDown(channel, 0xF0 | (channel.finePortaDownParam & 0xF));
+			break;
+		case 0x74:
+		case 0xF3: /* Set Vibrato Waveform. */
+			if (channel.note.param < 8) {
+				channel.vibratoType = channel.note.param;
+			}
+			break;
+		case 0x77:
+		case 0xF4: /* Set Tremolo Waveform. */
+			if (channel.note.param < 8) {
+				channel.tremoloType = channel.note.param;
+			}
+			break;
+		case 0x7A: /* Fine Vol Slide Up. */
+			if (channel.note.param > 0) {
+				channel.fineVslideUpParam = channel.note.param;
+			}
+			channel.volume += channel.fineVslideUpParam;
+			if (channel.volume > 64) {
+				channel.volume = 64;
+			}
+			break;
+		case 0x7B: /* Fine Vol Slide Down. */
+			if (channel.note.param > 0) {
+				channel.fineVslideDownParam = channel.note.param;
+			}
+			channel.volume -= channel.fineVslideDownParam;
+			if (channel.volume < 0) {
+				channel.volume = 0;
+			}
+			break;
+		case 0x7C:
+		case 0xFC: /* Note Cut. */
+			if (channel.note.param <= 0) {
+				channel.volume = 0;
+			}
+			break;
+		case 0x8A: /* Arpeggio. */
+			if (channel.note.param > 0) {
+				channel.arpeggioParam = channel.note.param;
+			}
+			break;
+		case 0x95: /* Fine Vibrato.*/
+			if ((channel.note.param >> 4) > 0) {
+				channel.vibratoSpeed = channel.note.param >> 4;
+			}
+			if ((channel.note.param & 0xF) > 0) {
+				channel.vibratoDepth = channel.note.param & 0xF;
+			}
+			vibrato(channel, 1);
+			break;
+		case 0xF8: /* Set Panning. */
+			channel.panning = channel.note.param * 17;
+			break;
+	}
+	autoVibrato(channel);
+	calculateFreq(channel);
+	calculateAmpl(channel);
+	updateEnvelopes(channel);
+}
+
+int ModXmS3mStream::tick() {
+	int count = 1;
+	if (--_tick <= 0) {
+		_tick = _speed;
+		updateRow();
+	} else {
+		for (int idx = 0; idx < _module.numChannels; idx++) {
+			tickChannel(_channels[idx]);
+		}
+	}
+	if (_playCount && _playCount[0]) {
+		count = _playCount[_seqPos][_row] - 1;
+	}
+	return count;
+}
+
+void ModXmS3mStream::updateRow() {
+	if (_nextRow < 0) {
+		_breakPos = _seqPos + 1;
+		_nextRow = 0;
+	}
+	if (_breakPos >= 0) {
+		if (_breakPos >= _module.sequenceLen) {
+			_breakPos = _nextRow = 0;
+		}
+		while (_module.sequence[_breakPos] >= _module.numPatterns) {
+			_breakPos++;
+			if (_breakPos >= _module.sequenceLen) {
+				_breakPos = _nextRow = 0;
+			}
+		}
+		_seqPos = _breakPos;
+		for (int idx = 0; idx < _module.numChannels; idx++) {
+			_channels[idx].plRow = 0;
+		}
+		_breakPos = -1;
+	}
+	Pattern &pattern = _module.patterns[_module.sequence[_seqPos]];
+	_row = _nextRow;
+	if (_row >= pattern.numRows) {
+		_row = 0;
+	}
+	if (_playCount && _playCount[0]) {
+		int count = _playCount[_seqPos][_row];
+		if (_plCount < 0 && count < 127) {
+			_playCount[_seqPos][_row] = count + 1;
+		}
+	}
+	_nextRow = _row + 1;
+	if (_nextRow >= pattern.numRows) {
+		_nextRow = -1;
+	}
+	for (int idx = 0; idx < _module.numChannels; ++idx) {
+		Note note = pattern.getNote(_row, idx);
+		if (note.effect == 0xE) {
+			note.effect = 0x70 | (note.param >> 4);
+			note.param &= 0xF;
+		}
+		if (note.effect == 0x93) {
+			note.effect = 0xF0 | (note.param >> 4);
+			note.param &= 0xF;
+		}
+		if (note.effect == 0 && note.param > 0) {
+			note.effect = 0x8A;
+		}
+
+		Channel &channel = _channels[idx];
+		updateChannelRow(channel, note);
+		switch (note.effect) {
+			case 0x81: /* Set Speed. */
+				if (note.param > 0) {
+					_tick = _speed = note.param;
+				}
+				break;
+			case 0xB:
+			case 0x82: /* Pattern Jump.*/
+				if (_plCount < 0) {
+					_breakPos = note.param;
+					_nextRow = 0;
+				}
+				break;
+			case 0xD:
+			case 0x83: /* Pattern Break.*/
+				if (_plCount < 0) {
+					if (_breakPos < 0) {
+						_breakPos = _seqPos + 1;
+					}
+					_nextRow = (note.param >> 4) * 10 + (note.param & 0xF);
+				}
+				break;
+			case 0xF: /* Set Speed/Tempo.*/
+				if (note.param > 0) {
+					if (note.param < 32) {
+						_tick = _speed = note.param;
+					} else {
+						_tempo = note.param;
+					}
+				}
+				break;
+			case 0x94: /* Set Tempo.*/
+				if (note.param > 32) {
+					_tempo = note.param;
+				}
+				break;
+			case 0x76:
+			case 0xFB: /* Pattern Loop.*/
+				if (note.param == 0) {
+					/* Set loop marker on this channel. */
+					channel.plRow = _row;
+				}
+				if (channel.plRow < _row && _breakPos < 0) {
+					/* Marker valid. */
+					if (_plCount < 0) {
+						/* Not already looping, begin. */
+						_plCount = note.param;
+						_plChan = idx;
+					}
+					if (_plChan == idx) {
+						/* Next Loop.*/
+						if (_plCount == 0) {
+							/* Loop finished. Invalidate current marker. */
+							channel.plRow = _row + 1;
+						} else {
+							/* Loop. */
+							_nextRow = channel.plRow;
+						}
+						_plCount--;
+					}
+				}
+				break;
+			case 0x7E:
+			case 0xFE: /* Pattern Delay.*/
+				_tick = _speed + _speed * note.param;
+				break;
+		}
+	}
+}
+
+int ModXmS3mStream::envelopeNextTick(Envelope &envelope, int tick, int keyOn) {
+	tick++;
+	if (envelope.looped && tick >= envelope.loopEndTick) {
+		tick = envelope.loopStartTick;
+	}
+	if (envelope.sustain && keyOn && tick >= envelope.sustainTick) {
+		tick = envelope.sustainTick;
+	}
+	return tick;
+}
+
+int ModXmS3mStream::calculateAmpl(Envelope &envelope, int tick) {
+	int idx, point, dt, da;
+	int ampl = envelope.pointsAmpl[envelope.numPoints - 1];
+	if (tick < envelope.pointsTick[envelope.numPoints - 1]) {
+		point = 0;
+		for (idx = 1; idx < envelope.numPoints; idx++) {
+			if (envelope.pointsTick[idx] <= tick) {
+				point = idx;
+			}
+		}
+		dt = envelope.pointsTick[point + 1] - envelope.pointsTick[point];
+		da = envelope.pointsAmpl[point + 1] - envelope.pointsAmpl[point];
+		ampl = envelope.pointsAmpl[point];
+		ampl += ((da << 24) / dt) * (tick - envelope.pointsTick[point]) >> 24;
+	}
+	return ampl;
+}
+
+int ModXmS3mStream::calculateDuration() {
+	int count = 0, duration = 0;
+	setSequencePos(0);
+	while (count < 1) {
+		duration += calculateTickLength();
+		count = tick();
+	}
+	setSequencePos(0);
+	return duration;
+}
+
+/* Seek to approximately the specified sample position.
+ The actual sample position reached is returned. */
+int ModXmS3mStream::seek(int samplePos) {
+	int currentPos = 0;
+	setSequencePos(0);
+	int tickLen = calculateTickLength();
+	while ((samplePos - currentPos) >= tickLen) {
+		for (int idx = 0; idx < _module.numChannels; ++idx) {
+			updateSampleIdx(_channels[idx], tickLen * 2, _sampleRate * 2);
+		}
+		currentPos += tickLen;
+		tick();
+		tickLen = calculateTickLength();
+	}
+	return currentPos;
+}
+
+void ModXmS3mStream::resample(Channel &channel, int *mixBuf, int offset, int count, int sampleRate) {
+	Sample *sample = channel.sample;
+	int lGain = 0, rGain = 0, samIdx = 0, samFra = 0, step = 0;
+	int loopLen = 0, loopEnd = 0, outIdx = 0, outEnd = 0, y = 0, m = 0, c = 0;
+	int16 *sampleData = channel.sample->data;
+	if (channel.ampl > 0) {
+		lGain = channel.ampl * (255 - channel.pann) >> 8;
+		rGain = channel.ampl * channel.pann >> 8;
+		samIdx = channel.sampleIdx;
+		samFra = channel.sampleFra;
+		step = (channel.freq << (FP_SHIFT - 3)) / (sampleRate >> 3);
+		loopLen = sample->loopLength;
+		loopEnd = sample->loopStart + loopLen;
+		outIdx = offset * 2;
+		outEnd = (offset + count) * 2;
+		if (_interpolation) {
+			while (outIdx < outEnd) {
+				if (samIdx >= loopEnd) {
+					if (loopLen > 1) {
+						while (samIdx >= loopEnd) {
+							samIdx -= loopLen;
+						}
+					} else {
+						break;
+					}
+				}
+				c = sampleData[samIdx];
+				m = sampleData[samIdx + 1] - c;
+				y = ((m * samFra) >> FP_SHIFT) + c;
+				mixBuf[outIdx++] += (y * lGain) >> FP_SHIFT;
+				mixBuf[outIdx++] += (y * rGain) >> FP_SHIFT;
+				samFra += step;
+				samIdx += samFra >> FP_SHIFT;
+				samFra &= FP_MASK;
+			}
+		} else {
+			while (outIdx < outEnd) {
+				if (samIdx >= loopEnd) {
+					if (loopLen > 1) {
+						while (samIdx >= loopEnd) {
+							samIdx -= loopLen;
+						}
+					} else {
+						break;
+					}
+				}
+				if (samIdx < 0)
+					samIdx = 0;
+				y = sampleData[samIdx];
+				mixBuf[outIdx++] += (y * lGain) >> FP_SHIFT;
+				mixBuf[outIdx++] += (y * rGain) >> FP_SHIFT;
+				samFra += step;
+				samIdx += samFra >> FP_SHIFT;
+				samFra &= FP_MASK;
+			}
+		}
+	}
+}
+
+void ModXmS3mStream::updateSampleIdx(Channel &channel, int count, int sampleRate) {
+	Sample *sample = channel.sample;
+	int step = (channel.freq << (FP_SHIFT - 3)) / (sampleRate >> 3);
+	channel.sampleFra += step * count;
+	channel.sampleIdx += channel.sampleFra >> FP_SHIFT;
+	if (channel.sampleIdx > (int)sample->loopStart) {
+		if (sample->loopLength > 1) {
+			channel.sampleIdx = sample->loopStart + (channel.sampleIdx - sample->loopStart) % sample->loopLength;
+		} else {
+			channel.sampleIdx = sample->loopStart;
+		}
+	}
+	channel.sampleFra &= FP_MASK;
+}
+
+void ModXmS3mStream::volumeRamp(int *mixBuf, int tickLen) {
+	int rampRate = 256 * 2048 / _sampleRate;
+	for (int idx = 0, a1 = 0; a1 < 256; idx += 2, a1 += rampRate) {
+		int a2 = 256 - a1;
+		mixBuf[idx] = (mixBuf[idx] * a1 + _rampBuf[idx] * a2) >> 8;
+		mixBuf[idx + 1] = (mixBuf[idx + 1] * a1 + _rampBuf[idx + 1] * a2) >> 8;
+	}
+	memcpy(_rampBuf, &mixBuf[tickLen * 2], 128 * sizeof(int));
+}
+
+/* 2:1 downsampling with simple but effective anti-aliasing. Buf must contain count * 2 + 1 stereo samples. */
+void ModXmS3mStream::downsample(int *buf, int count) {
+	int outLen = count * 2;
+	for (int idx = 0, outIdx = 0; outIdx < outLen; idx += 4, outIdx += 2) {
+		buf[outIdx] = (buf[idx] >> 2) + (buf[idx + 2] >> 1) + (buf[idx + 4] >> 2);
+		buf[outIdx + 1] = (buf[idx + 1] >> 2) + (buf[idx + 3] >> 1) + (buf[idx + 5] >> 2);
+	}
+}
+
+/* Generates audio and returns the number of stereo samples written into mixBuf. */
+int ModXmS3mStream::getAudio(int *mixBuf) {
+	if (_mixBuffer) {
+		memcpy(mixBuf, _mixBuffer, _mixBufferSamples * sizeof(int));
+		delete []_mixBuffer;
+		_mixBuffer = nullptr;
+		return _mixBufferSamples;
+	}
+
+	int tickLen = calculateTickLength();
+	/* Clear output buffer. */
+	memset(mixBuf, 0, (tickLen + 65) * 4 * sizeof(int));
+	/* Resample. */
+	for (int idx = 0; idx < _module.numChannels; idx++) {
+		Channel &channel = _channels[idx];
+		resample(channel, mixBuf, 0, (tickLen + 65) * 2, _sampleRate * 2);
+		updateSampleIdx(channel, tickLen * 2, _sampleRate * 2);
+	}
+	downsample(mixBuf, tickLen + 64);
+	volumeRamp(mixBuf, tickLen);
+	tick();
+	return tickLen * 2;  // stereo samples
+}
+
+int ModXmS3mStream::readBuffer(int16 *buffer, const int numSamples) {
+	int samplesRead = 0;
+	while (samplesRead < numSamples && _dataLeft >= 0) {
+		int *mixBuf = new int[calculateMixBufLength()];
+		int samples = getAudio(mixBuf);
+		if (samplesRead + samples > numSamples) {
+			_mixBufferSamples = samplesRead + samples - numSamples;
+			samples -= _mixBufferSamples;
+			_mixBuffer = new int[_mixBufferSamples];
+			memcpy(_mixBuffer, mixBuf + samples, _mixBufferSamples * sizeof(int));
+		}
+		for (int idx = 0; idx < samples; ++idx) {
+			int ampl = mixBuf[idx];
+			if (ampl > 32767) {
+				ampl = 32767;
+			}
+			if (ampl < -32768) {
+				ampl = -32768;
+			}
+			*buffer++ = ampl;
+		}
+		samplesRead += samples;
+		delete []mixBuf; // free
+	}
+	_dataLeft -= samplesRead * 2;
+
+	return samplesRead;
+}
+
+void ModXmS3mStream::setSequencePos(int pos) {
+	if (pos >= _module.sequenceLen) {
+		pos = 0;
+	}
+	_breakPos = pos;
+	_nextRow = 0;
+	_tick = 1;
+	_globalVol = _module.defaultGvol;
+	_speed = _module.defaultSpeed > 0 ? _module.defaultSpeed : 6;
+	_tempo = _module.defaultTempo > 0 ? _module.defaultTempo : 125;
+	_plCount = _plChan = -1;
+
+	// play count
+	if (_playCount) {
+		delete[] _playCount[0];
+		delete[] _playCount;
+	}
+	_playCount = new int8 *[_module.sequenceLen];
+	memset(_playCount, 0, _module.sequenceLen * sizeof(int8 *));
+	int len = initPlayCount(_playCount);
+	_playCount[0] = new int8[len];
+	memset(_playCount[0], 0, len * sizeof(int8));
+	initPlayCount(_playCount);
+
+	for (int idx = 0; idx < _module.numChannels; ++idx) {
+		initChannel(idx);
+	}
+	memset(_rampBuf, 0, 128 * sizeof(int));
+	tick();
+}
+
+} // End of namespace Modules
+
+namespace Audio {
+
+AudioStream *makeModXmS3mStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, int rate, int interpolation) {
+	Modules::ModXmS3mStream *soundStream = new Modules::ModXmS3mStream(stream, rate, interpolation);
+
+	if (disposeAfterUse == DisposeAfterUse::YES)
+		delete stream;
+
+	if (!soundStream->loadSuccess()) {
+		delete soundStream;
+		return nullptr;
+	}
+
+	return (AudioStream *)soundStream;
+}
+
+} // End of namespace Audio
diff --git a/audio/mods/mod_xm_s3m.h b/audio/mods/mod_xm_s3m.h
new file mode 100644
index 0000000..904adae
--- /dev/null
+++ b/audio/mods/mod_xm_s3m.h
@@ -0,0 +1,92 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+/*
+ * This code is based on IBXM mod player
+ *
+ * Copyright (c) 2015, Martin Cameron
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the
+ * following conditions are met:
+ *
+ * * Redistributions of source code must retain the above
+ * copyright notice, this list of conditions and the
+ * following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the
+ * above copyright notice, this list of conditions and the
+ * following disclaimer in the documentation and/or other
+ * materials provided with the distribution.
+ *
+ * * Neither the name of the organization nor the names of
+ *  its contributors may be used to endorse or promote
+ *  products derived from this software without specific
+ *  prior written permission.
+
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#ifndef AUDIO_MODS_MOD_XM_S3M_H
+#define AUDIO_MODS_MOD_XM_S3M_H
+
+namespace Common {
+class SeekableReadStream;
+}
+
+namespace Audio {
+
+class AudioStream;
+
+/*
+ * Factory function for ModXmS3mStream streams. Reads all data from the
+ * given ReadStream and creates an AudioStream from this. No reference
+ * to the 'stream' object is kept, so you can safely delete it after
+ * invoking this factory.
+ *
+ * @param stream			the ReadStream from which to read the tracker sound data
+ * @param disposeAfterUse	whether to delete the stream after use
+ * @param rate				sample rate
+ * @param interpolation		interpolation effect level
+ */
+AudioStream *makeModXmS3mStream(Common::SeekableReadStream *stream,
+		DisposeAfterUse::Flag disposeAfterUse,
+		int rate = 48000, int interpolation = 0);
+
+} // End of namespace Audio
+
+#endif
diff --git a/audio/mods/module_mod_xm_s3m.cpp b/audio/mods/module_mod_xm_s3m.cpp
new file mode 100644
index 0000000..6283dbe
--- /dev/null
+++ b/audio/mods/module_mod_xm_s3m.cpp
@@ -0,0 +1,841 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+/*
+ * This code is based on IBXM mod player
+ *
+ * Copyright (c) 2015, Martin Cameron
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the
+ * following conditions are met:
+ *
+ * * Redistributions of source code must retain the above
+ * copyright notice, this list of conditions and the
+ * following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the
+ * above copyright notice, this list of conditions and the
+ * following disclaimer in the documentation and/or other
+ * materials provided with the distribution.
+ *
+ * * Neither the name of the organization nor the names of
+ *  its contributors may be used to endorse or promote
+ *  products derived from this software without specific
+ *  prior written permission.
+
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include "common/debug.h"
+#include "common/endian.h"
+#include "common/stream.h"
+#include "common/textconsole.h"
+#include "common/util.h"
+#include "module_mod_xm_s3m.h"
+
+namespace Modules {
+
+const int ModuleModXmS3m::FP_SHIFT = 0xF;
+const int ModuleModXmS3m::FP_ONE = 0x8000;
+const int ModuleModXmS3m::FP_MASK = 0x7FFF;
+
+const int ModuleModXmS3m::exp2table[] = {
+		32768, 32946, 33125, 33305, 33486, 33667, 33850, 34034,
+		34219, 34405, 34591, 34779, 34968, 35158, 35349, 35541,
+		35734, 35928, 36123, 36319, 36516, 36715, 36914, 37114,
+		37316, 37518, 37722, 37927, 38133, 38340, 38548, 38757,
+		38968, 39180, 39392, 39606, 39821, 40037, 40255, 40473,
+		40693, 40914, 41136, 41360, 41584, 41810, 42037, 42265,
+		42495, 42726, 42958, 43191, 43425, 43661, 43898, 44137,
+		44376, 44617, 44859, 45103, 45348, 45594, 45842, 46091,
+		46341, 46593, 46846, 47100, 47356, 47613, 47871, 48131,
+		48393, 48655, 48920, 49185, 49452, 49721, 49991, 50262,
+		50535, 50810, 51085, 51363, 51642, 51922, 52204, 52488,
+		52773, 53059, 53347, 53637, 53928, 54221, 54515, 54811,
+		55109, 55408, 55709, 56012, 56316, 56622, 56929, 57238,
+		57549, 57861, 58176, 58491, 58809, 59128, 59449, 59772,
+		60097, 60423, 60751, 61081, 61413, 61746, 62081, 62419,
+		62757, 63098, 63441, 63785, 64132, 64480, 64830, 65182,
+		65536
+};
+
+int ModuleModXmS3m::exp2(int x) {
+	int c, m, y;
+	int x0 = (x & FP_MASK) >> (FP_SHIFT - 7);
+	c = exp2table[x0];
+	m = exp2table[x0 + 1] - c;
+	y = (m * (x & (FP_MASK >> 7)) >> 8) + c;
+	return (y << FP_SHIFT) >> (FP_SHIFT - (x >> FP_SHIFT));
+}
+
+int ModuleModXmS3m::log2(int x) {
+	int y = 16 << FP_SHIFT;
+	for (int step = y; step > 0; step >>= 1) {
+		if (exp2(y - step) >= x) {
+			y -= step;
+		}
+	}
+	return y;
+}
+
+bool ModuleModXmS3m::load(Common::SeekableReadStream &st) {
+	int32 setPos = st.pos();
+
+	// xm file
+	char sigXm[18] = { 0 };
+	st.read(sigXm, 17);
+	if (!memcmp(sigXm, "Extended Module: ", 17)) {
+		return loadXm(st);
+	}
+	st.seek(setPos);
+
+	// s3m file
+	char sigS3m[4];
+	st.skip(44);
+	st.read(sigS3m, 4);
+	if (!memcmp(sigS3m, "SCRM", 4)) {
+		st.seek(setPos);
+		return loadS3m(st);
+	}
+	st.seek(setPos);
+
+	// mod file
+	return loadMod(st);
+}
+
+ModuleModXmS3m::ModuleModXmS3m() {
+	sequenceLen = 1;
+	sequence = nullptr;
+	restartPos = 0;
+
+	// patterns
+	numChannels = 4;
+	numPatterns = 1;
+	patterns = nullptr;
+
+	// instruments
+	numInstruments = 1;
+	instruments = nullptr;
+
+	// others
+	defaultGvol = 64;
+	defaultSpeed = 6;
+	defaultTempo = 125;
+	c2Rate = 8287;
+	gain = 64;
+	linearPeriods = false;
+	fastVolSlides = false;
+	defaultPanning = nullptr; //{ 51, 204, 204, 51 };
+}
+
+ModuleModXmS3m::~ModuleModXmS3m() {
+	// free song position
+	if (sequence) {
+		delete[] sequence;
+		sequence = nullptr;
+	}
+
+	// free instruments
+	if (instruments) {
+		for (int i = 0; i <= numInstruments; ++i) {
+			// free samples
+			for (int j = 0; j < instruments[i].numSamples; ++j) {
+				if (instruments[i].samples[j].data) {
+					delete[] instruments[i].samples[j].data;
+					instruments[i].samples[j].data = nullptr;
+				}
+			}
+			delete[] instruments[i].samples;
+			instruments[i].samples = nullptr;
+		}
+		delete[] instruments;
+		instruments = nullptr;
+	}
+
+	// free patterns
+	if (patterns) {
+		for (int i = 0; i < numPatterns; ++i) {
+			delete []patterns[i].notes;
+		}
+		delete[] patterns;
+		patterns = nullptr;
+	}
+
+	// free default values
+	if (defaultPanning) {
+		delete[] defaultPanning;
+		defaultPanning = nullptr;
+	}
+}
+
+bool ModuleModXmS3m::loadMod(Common::SeekableReadStream &st) {
+	// load song name
+	st.read(name, 20);
+	name[20] = '\0';
+
+	// load instruments
+	numInstruments = 31;
+	instruments = new Instrument[numInstruments + 1];
+	memset(instruments, 0, sizeof(Instrument) * (numInstruments + 1));
+	instruments[0].numSamples = 1;
+	instruments[0].samples = new Sample[1];
+	memset(&instruments[0].samples[0], 0, sizeof(Sample));
+
+	for (int i = 1; i <= numInstruments; ++i) {
+		instruments[i].numSamples = 1;
+		instruments[i].samples = new Sample[1];
+		memset(&instruments[i].samples[0], 0, sizeof(Sample));
+
+		// load sample
+		Sample &sample = instruments[i].samples[0];
+		st.read((byte *)sample.name, 22);
+		sample.name[22] = '\0';
+		sample.length = 2 * st.readUint16BE();
+
+		sample.finetune = st.readByte();
+		assert(sample.finetune < 0x10);
+
+		sample.volume = st.readByte();
+		sample.loopStart = 2 * st.readUint16BE();
+		sample.loopLength = 2 * st.readUint16BE();
+
+		if (sample.loopStart + sample.loopLength > sample.length) {
+			sample.loopLength = sample.length - sample.loopStart;
+		}
+		if (sample.loopLength < 4) {
+			sample.loopStart = sample.length;
+			sample.loopLength = 0;
+		}
+	}
+
+	sequenceLen = st.readByte();
+	if (sequenceLen > 128)
+		sequenceLen = 128;
+
+	restartPos = 0;
+	st.readByte(); // undefined byte, should be 127
+
+	sequence = new byte[128];
+	st.read(sequence, 128);
+
+	// check signature
+	byte xx[2];
+	st.read(xx, 2); // first 2 bytes of the signature
+	switch (st.readUint16BE()) {
+		case MKTAG16('K', '.'): /* M.K. */
+			// Fall Through intended
+		case MKTAG16('K', '!'): /* M!K! */
+			// Fall Through intended
+		case MKTAG16('T', '4'): /* FLT4 */
+			// Fall Through intended
+			numChannels = 4;
+			c2Rate = 8287;
+			gain = 64;
+			break;
+
+		case MKTAG16('H', 'N'): /* xCHN */
+			numChannels = xx[0] - '0';
+			c2Rate = 8363;
+			gain = 32;
+			break;
+
+		case MKTAG16('C', 'H'): /* xxCH */
+			numChannels = (xx[0] - '0') * 10 + xx[1] - '0';
+			c2Rate = 8363;
+			gain = 32;
+			break;
+
+		default:
+			warning("No known signature found in micromod module");
+			return false;
+
+	}
+
+	// default values
+	defaultGvol = 64;
+	defaultSpeed = 6;
+	defaultTempo = 125;
+	defaultPanning = new byte[numChannels];
+	for (int i = 0; i < numChannels; ++i) {
+		defaultPanning[i] = 51;
+		if ((i & 3) == 1 || (i & 3) == 2) {
+			defaultPanning[i] = 204;
+		}
+	}
+
+	// load patterns
+	numPatterns = 0;
+	for (int i = 0; i < 128; ++i)
+		if (numPatterns < sequence[i])
+			numPatterns = sequence[i];
+	++numPatterns;
+
+	// load patterns
+	patterns = new Pattern[numPatterns];
+	for (int i = 0; i < numPatterns; ++i) {
+		patterns[i].numChannels = numChannels;
+		patterns[i].numRows = 64;
+
+		// load notes
+		/*
+		 Old (amiga) noteinfo:
+
+		 _____byte 1_____   byte2_    _____byte 3_____   byte4_
+		 /                \ /      \  /                \ /      \
+		0000          0000-00000000  0000          0000-00000000
+
+		 Upper four    12 bits for    Lower four    Effect command.
+		 bits of sam-  note period.   bits of sam-
+		 ple number.                  ple number.
+		 */
+
+		int numNotes = patterns[i].numChannels * patterns[i].numRows;
+		patterns[i].notes = new Note[numNotes];
+		memset(patterns[i].notes, 0, numNotes * sizeof(Note));
+		for (int idx = 0; idx < numNotes; ++idx) {
+			byte first = st.readByte();
+			byte second = st.readByte();
+			byte third = st.readByte();
+			byte fourth = st.readByte();
+
+			// period, key
+			uint period = (first & 0xF) << 8;
+			period = (period | second) * 4;
+			if (period >= 112 && period <= 6848) {
+				int key = -12 * log2((period << FP_SHIFT) / 29021);
+				key = (key + (key & (FP_ONE >> 1))) >> FP_SHIFT;
+				patterns[i].notes[idx].key = key;
+			}
+
+			// instrument
+			uint ins = (third & 0xF0) >> 4;
+			ins = ins | (first & 0x10);
+			patterns[i].notes[idx].instrument = ins;
+
+			// effect, param
+			byte effect = third & 0x0F;
+			byte param = fourth & 0xff;
+			if(param == 0 && (effect < 3 || effect == 0xA)) {
+				effect = 0;
+			}
+			if(param == 0 && (effect == 5 || effect == 6)) {
+				effect -= 2;
+			}
+			if(effect == 8 && numChannels == 4) {
+				effect = param = 0;
+			}
+			patterns[i].notes[idx].effect = effect;
+			patterns[i].notes[idx].param = param;
+		}
+	}
+
+	// load data for the sample of instruments
+	for (int i = 1; i <= numInstruments; ++i) {
+		Sample &sample = instruments[i].samples[0];
+		if (!sample.length) {
+			sample.data = 0;
+		} else {
+			sample.data = new int16[sample.length + 1];
+			readSampleSint8(st, sample.length, sample.data);
+			sample.data[sample.loopStart + sample.loopLength] = sample.data[sample.loopStart];
+		}
+	}
+
+	return true;
+}
+
+bool ModuleModXmS3m::loadXm(Common::SeekableReadStream &st) {
+	st.read(name, 20);
+	name[20] = '\0';
+	st.readByte(); // reserved byte
+
+	byte trackername[20];
+	st.read(trackername, 20);
+	bool deltaEnv = !memcmp(trackername, "DigiBooster Pro", 15);
+
+	uint16 version = st.readUint16LE();
+	if (version != 0x0104) {
+		warning("XM format version must be 0x0104!");
+		return false;
+	}
+
+	uint offset = st.pos() + st.readUint32LE();
+
+	sequenceLen = st.readUint16LE();
+	restartPos = st.readUint16LE();
+
+	numChannels = st.readUint16LE();
+	numPatterns = st.readUint16LE();
+	numInstruments = st.readUint16LE();
+	linearPeriods = st.readUint16LE() & 0x1;
+	defaultGvol = 64;
+	defaultSpeed = st.readUint16LE();
+	defaultTempo = st.readUint16LE();
+	c2Rate = 8363;
+	gain = 64;
+
+	defaultPanning = new byte[numChannels];
+	for (int i = 0; i < numChannels; ++i) {
+		defaultPanning[i] = 128;
+	}
+
+	sequence = new byte[sequenceLen];
+	for (int i = 0; i < sequenceLen; ++i) {
+		int entry = st.readByte();
+		sequence[i] = entry < numPatterns ? entry : 0;
+	}
+
+	// load patterns
+	patterns = new Pattern[numPatterns];
+	for (int i = 0; i < numPatterns; ++i) {
+		st.seek(offset, SEEK_SET);
+		offset += st.readUint32LE();
+		if (st.readByte()) {
+			warning("Unknown pattern packing type!");
+			return false;
+		}
+		patterns[i].numRows = st.readUint16LE();
+		if (patterns[i].numRows < 1)
+			patterns[i].numRows = 1;
+		uint16 patDataLength = st.readUint16LE();
+		offset += patDataLength;
+
+		// load notes
+		patterns[i].numChannels = numChannels;
+		int numNotes = patterns[i].numRows * numChannels;
+		patterns[i].notes = new Note[numNotes];
+		memset(patterns[i].notes, 0, numNotes * sizeof(Note));
+
+		if (patDataLength > 0) {
+			for (int j = 0; j < numNotes; ++j) {
+				Note &note = patterns[i].notes[j];
+				byte cmp = st.readByte();
+				if (cmp & 0x80) {
+					if (cmp & 1)
+						note.key = st.readByte();
+					if (cmp & 2)
+						note.instrument = st.readByte();
+					if (cmp & 4)
+						note.volume = st.readByte();
+					if (cmp & 8)
+						note.effect = st.readByte();
+					if (cmp & 16)
+						note.param = st.readByte();
+				} else {
+					note.key = cmp;
+					note.instrument = st.readByte();
+					note.volume = st.readByte();
+					note.effect = st.readByte();
+					note.param = st.readByte();
+				}
+				if( note.effect >= 0x40 ) {
+					note.effect = note.param = 0;
+				}
+			}
+		}
+	}
+
+	// load instruments
+	instruments = new Instrument[numInstruments + 1];
+	memset(instruments, 0, (numInstruments + 1) * sizeof(Instrument));
+	instruments[0].samples = new Sample[1];
+	memset(instruments[0].samples, 0, sizeof(Sample));
+	for (int i = 1; i <= numInstruments; ++i) {
+		st.seek(offset, SEEK_SET);
+		offset += st.readUint32LE();
+
+		Instrument &ins = instruments[i];
+		st.read(ins.name, 22);
+		ins.name[22] = '\0';
+
+		st.readByte(); // Instrument type (always 0)
+
+		// load sample number
+		int nSamples = st.readUint16LE();
+		ins.numSamples = nSamples > 0 ? nSamples : 1;
+		ins.samples = new Sample[ins.numSamples];
+		memset(ins.samples, 0, ins.numSamples * sizeof(Sample));
+		st.readUint32LE(); // skip 4 byte
+
+		// load instrument informations
+		if (nSamples > 0) {
+			for (int k = 0; k < 96; ++k) {
+				ins.keyToSample[k + 1] = st.readByte();
+			}
+			int pointTick = 0;
+			for (int p = 0; p < 12; ++p) {
+				pointTick = (deltaEnv ? pointTick : 0) + st.readUint16LE();
+				ins.volEnv.pointsTick[p] = pointTick;
+				ins.volEnv.pointsAmpl[p] = st.readUint16LE();
+			}
+			pointTick = 0;
+			for (int p = 0; p < 12; ++p) {
+				pointTick = (deltaEnv ? pointTick : 0) + st.readUint16LE();
+				ins.panEnv.pointsTick[p] = pointTick;
+				ins.panEnv.pointsAmpl[p] = st.readUint16LE();
+			}
+			ins.volEnv.numPoints = st.readByte();
+			if (ins.volEnv.numPoints > 12)
+				ins.volEnv.numPoints = 0;
+			ins.panEnv.numPoints = st.readByte();
+			if (ins.panEnv.numPoints > 12)
+				ins.panEnv.numPoints = 0;
+			ins.volEnv.sustainTick = ins.volEnv.pointsTick[st.readByte() & 0xF];
+			ins.volEnv.loopStartTick = ins.volEnv.pointsTick[st.readByte() & 0xF];
+			ins.volEnv.loopEndTick = ins.volEnv.pointsTick[st.readByte() & 0xF];
+			ins.panEnv.sustainTick = ins.panEnv.pointsTick[st.readByte() & 0xF];
+			ins.panEnv.loopStartTick = ins.panEnv.pointsTick[st.readByte() & 0xF];
+			ins.panEnv.loopEndTick = ins.panEnv.pointsTick[st.readByte() & 0xF];
+			byte volParam = st.readByte();
+			ins.volEnv.enabled = ins.volEnv.numPoints > 0 && (volParam & 0x1);
+			ins.volEnv.sustain = (volParam & 0x2) > 0;
+			ins.volEnv.looped = (volParam & 0x4) > 0;
+			byte panParam = st.readByte();
+			ins.panEnv.enabled = ins.panEnv.numPoints > 0 && (panParam & 0x1);
+			ins.panEnv.sustain = (panParam & 0x2) > 0;
+			ins.panEnv.looped = (panParam & 0x4) > 0;
+			ins.vibType = st.readByte();
+			ins.vibSweep = st.readByte();
+			ins.vibDepth = st.readByte();
+			ins.vibRate = st.readByte();
+			ins.volFadeout = st.readUint16LE();
+		}
+
+		// load samples
+		uint samHeadOffset = offset;
+		offset += nSamples * 40; // offset for sample data
+		for (int j = 0; j < nSamples; ++j) {
+			// load sample head
+			st.seek(samHeadOffset, SEEK_SET);
+			samHeadOffset += 40; // increment
+			Sample &sample = ins.samples[j];
+			uint samDataBytes = st.readUint32LE();
+			uint samLoopStart = st.readUint32LE();
+			uint samLoopLength = st.readUint32LE();
+			sample.volume = st.readByte();
+			sample.finetune = st.readSByte();
+			byte loopType = st.readByte();
+			bool looped = (loopType & 0x3) > 0;
+			bool pingPong = (loopType & 0x2) > 0;
+			bool sixteenBit = (loopType & 0x10) > 0;
+			sample.panning = st.readByte() + 1;
+			sample.relNote = st.readSByte();
+			st.readByte(); // reserved byte
+			st.read(sample.name, 22);
+			sample.name[22] = '\0';
+
+			uint samDataSamples = samDataBytes;
+			if (sixteenBit) {
+				samDataSamples = samDataSamples >> 1;
+				samLoopStart = samLoopStart >> 1;
+				samLoopLength = samLoopLength >> 1;
+			}
+			if (!looped || (samLoopStart + samLoopLength) > samDataSamples) {
+				samLoopStart = samDataSamples;
+				samLoopLength = 0;
+			}
+			sample.loopStart = samLoopStart;
+			sample.loopLength = samLoopLength;
+
+			// load sample data
+			st.seek(offset, SEEK_SET);
+			offset += samDataBytes; // increment
+			sample.data = new int16[samDataSamples + 1];
+			if (sixteenBit) {
+				readSampleSint16LE(st, samDataSamples, sample.data);
+			} else {
+				readSampleSint8(st, samDataSamples, sample.data);
+			}
+			int amp = 0;
+			for (uint idx = 0; idx < samDataSamples; idx++) {
+				amp = amp + sample.data[idx];
+				amp = (amp & 0x7FFF) - (amp & 0x8000);
+				sample.data[idx] = amp;
+			}
+			sample.data[samLoopStart + samLoopLength] = sample.data[samLoopStart];
+			if (pingPong) {
+				SamplePingPong(sample);
+			}
+		}
+	}
+	return true;
+}
+
+bool ModuleModXmS3m::loadS3m(Common::SeekableReadStream &st) {
+	st.read(name, 28);
+	name[28] = '\0';
+	st.skip(4); // skip 4 bytes
+
+	sequenceLen = st.readUint16LE();
+	numInstruments = st.readUint16LE();
+	numPatterns = st.readUint16LE();
+	uint16 flags = st.readUint16LE();
+	uint16 version = st.readUint16LE();
+	fastVolSlides = ((flags & 0x40) == 0x40) || version == 0x1300;
+	bool signedSamples = st.readUint16LE() == 1;
+
+	// check signature
+	if (st.readUint32BE() != MKTAG('S', 'C', 'R', 'M')) {
+		warning("Not an S3M file!");
+		return false;
+	}
+
+	defaultGvol = st.readByte();
+	defaultSpeed = st.readByte();
+	defaultTempo = st.readByte();
+	c2Rate = 8363;
+	byte mastermult = st.readByte();
+	gain = mastermult & 0x7F;
+	bool stereoMode = (mastermult & 0x80) == 0x80;
+	st.readByte(); // skip ultra-click
+	bool defaultPan = st.readByte() == 0xFC;
+	st.skip(10); // skip 10 bytes
+
+	// load channel map
+	numChannels = 0;
+	int channelMap[32];
+	for (int i = 0; i < 32; ++i) {
+		channelMap[i] = -1;
+		if (st.readByte() < 16) {
+			channelMap[i] = numChannels++;
+		}
+	}
+
+	// load sequence
+	sequence = new byte[sequenceLen];
+	st.read(sequence, sequenceLen);
+
+	int moduleDataIndex = st.pos();
+
+	// load instruments
+	instruments = new Instrument[numInstruments + 1];
+	memset(instruments, 0, sizeof(Instrument) * (numInstruments + 1));
+	instruments[0].numSamples = 1;
+	instruments[0].samples = new Sample[1];
+	memset(instruments[0].samples, 0, sizeof(Sample));
+	for (int i = 1; i <= numInstruments; ++i) {
+		Instrument &instrum = instruments[i];
+		instrum.numSamples = 1;
+		instrum.samples = new Sample[1];
+		memset(instrum.samples, 0, sizeof(Sample));
+		Sample &sample = instrum.samples[0];
+
+		// get instrument offset
+		st.seek(moduleDataIndex, SEEK_SET);
+		int instOffset = st.readUint16LE() << 4;
+		moduleDataIndex += 2;
+		st.seek(instOffset, SEEK_SET);
+
+		// load instrument, sample
+		if (st.readByte() == 1) { // type
+			st.skip(12); // skip file name
+			int sampleOffset = (st.readByte() << 20) + (st.readUint16LE() << 4);
+			uint sampleLength = st.readUint32LE();
+			uint loopStart = st.readUint32LE();
+			uint loopLength = st.readUint32LE() - loopStart;
+			sample.volume = st.readByte();
+			st.skip(1); // skip dsk
+			if (st.readByte() != 0) {
+				warning("Packed samples not supported for S3M files");
+				return false;
+			}
+			byte samParam = st.readByte();
+
+			if (loopStart + loopLength > sampleLength) {
+				loopLength = sampleLength - loopStart;
+			}
+			if (loopLength < 1 || !(samParam & 0x1)) {
+				loopStart = sampleLength;
+				loopLength = 0;
+			}
+
+			sample.loopStart = loopStart;
+			sample.loopLength = loopLength;
+
+			bool sixteenBit = samParam & 0x4;
+			int tune = (log2(st.readUint32LE()) - log2(c2Rate)) * 12;
+			sample.relNote = tune >> FP_SHIFT;
+			sample.finetune = (tune & FP_MASK) >> (FP_SHIFT - 7);
+			st.skip(12); // skip unused bytes
+			st.read(instrum.name, 28);
+
+			// load sample data
+			sample.data = new int16[sampleLength + 1];
+			st.seek(sampleOffset, SEEK_SET);
+			if (sixteenBit) {
+				readSampleSint16LE(st, sampleLength, sample.data);
+			} else {
+				readSampleSint8(st, sampleLength, sample.data);
+			}
+			if (!signedSamples) {
+				for (uint idx = 0; idx < sampleLength; ++idx) {
+					sample.data[idx] = (sample.data[idx] & 0xFFFF) - 32768;
+				}
+			}
+			sample.data[loopStart + loopLength] = sample.data[loopStart];
+		}
+	}
+
+	// load patterns
+	patterns = new Pattern[numPatterns];
+	memset(patterns, 0, numPatterns * sizeof(Pattern));
+	for (int i = 0; i < numPatterns; ++i) {
+		patterns[i].numChannels = numChannels;
+		patterns[i].numRows = 64;
+
+		// get pattern data offset
+		st.seek(moduleDataIndex, SEEK_SET);
+		int patOffset = (st.readUint16LE() << 4) + 2;
+		st.seek(patOffset, SEEK_SET);
+
+		// load notes
+		patterns[i].notes = new Note[numChannels * 64];
+		memset(patterns[i].notes, 0, numChannels * 64 * sizeof(Note));
+		int row = 0;
+		while (row < 64) {
+			byte token = st.readByte();
+			if (token) {
+				byte key = 0;
+				byte ins = 0;
+				if ((token & 0x20) == 0x20) {
+					/* Key + Instrument.*/
+					key = st.readByte();
+					ins = st.readByte();
+					if (key < 0xFE) {
+						key = (key >> 4) * 12 + (key & 0xF) + 1;
+					} else if (key == 0xFF) {
+						key = 0;
+					}
+				}
+				byte volume = 0;
+				if ((token & 0x40) == 0x40) {
+					/* Volume Column.*/
+					volume = (st.readByte() & 0x7F) + 0x10;
+					if (volume > 0x50) {
+						volume = 0;
+					}
+				}
+				byte effect = 0;
+				byte param = 0;
+				if ((token & 0x80) == 0x80) {
+					/* Effect + Param.*/
+					effect = st.readByte();
+					param = st.readByte();
+					if (effect < 1 || effect >= 0x40) {
+						effect = param = 0;
+					} else if (effect > 0) {
+						effect += 0x80;
+					}
+				}
+				int chan = channelMap[token & 0x1F];
+				if (chan >= 0) {
+					int noteIndex = row * numChannels + chan;
+					patterns[i].notes[noteIndex].key = key;
+					patterns[i].notes[noteIndex].instrument = ins;
+					patterns[i].notes[noteIndex].volume = volume;
+					patterns[i].notes[noteIndex].effect = effect;
+					patterns[i].notes[noteIndex].param = param;
+				}
+			} else {
+				row++;
+			}
+		}
+
+		// increment index
+		moduleDataIndex += 2;
+	}
+
+	// load default panning
+	defaultPanning = new byte[numChannels];
+	memset(defaultPanning, 0, numChannels);
+	for (int chan = 0; chan < 32; ++chan) {
+		if (channelMap[chan] >= 0) {
+			byte panning = 7;
+			if (stereoMode) {
+				panning = 12;
+				st.seek(64 + chan, SEEK_SET);
+				if (st.readByte() < 8) {
+					panning = 3;
+				}
+			}
+			if (defaultPan) {
+				st.seek(moduleDataIndex + chan, SEEK_SET);
+				flags = st.readByte();
+				if ((flags & 0x20) == 0x20) {
+					panning = flags & 0xF;
+				}
+			}
+			defaultPanning[channelMap[chan]] = panning * 17;
+		}
+	}
+	return true;
+}
+
+void ModuleModXmS3m::readSampleSint8(Common::SeekableReadStream &stream, int length, int16 *dest) {
+	for (int i = 0; i < length; ++i) {
+		dest[i] = (stream.readSByte() << 8);
+		dest[i] = (dest[i] & 0x7FFF) - (dest[i] & 0x8000);
+	}
+}
+
+void ModuleModXmS3m::readSampleSint16LE(Common::SeekableReadStream &stream, int length, int16 *dest) {
+	for (int i = 0; i < length; ++i) {
+		dest[i] = stream.readSint16LE();
+		dest[i] = (dest[i] & 0x7FFF) - (dest[i] & 0x8000);
+	}
+}
+
+void ModuleModXmS3m::SamplePingPong(Sample &sample) {
+	int loopStart = sample.loopStart;
+	int loopLength = sample.loopLength;
+	int loopEnd = loopStart + loopLength;
+	int16 *sampleData = sample.data;
+	int16 *newData = new int16[loopEnd + loopLength + 1];
+	if (newData) {
+		memcpy(newData, sampleData, loopEnd * sizeof(int16));
+		for (int idx = 0; idx < loopLength; idx++) {
+			newData[loopEnd + idx] = sampleData[loopEnd - idx - 1];
+		}
+		delete []sample.data;
+		sample.data = newData;
+		sample.loopLength *= 2;
+		sample.data[loopStart + sample.loopLength] = sample.data[loopStart];
+	}
+}
+
+} // End of namespace Modules
diff --git a/audio/mods/module_mod_xm_s3m.h b/audio/mods/module_mod_xm_s3m.h
new file mode 100644
index 0000000..d92ac66
--- /dev/null
+++ b/audio/mods/module_mod_xm_s3m.h
@@ -0,0 +1,174 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+/*
+ * This code is based on IBXM mod player
+ *
+ * Copyright (c) 2015, Martin Cameron
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the
+ * following conditions are met:
+ *
+ * * Redistributions of source code must retain the above
+ * copyright notice, this list of conditions and the
+ * following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the
+ * above copyright notice, this list of conditions and the
+ * following disclaimer in the documentation and/or other
+ * materials provided with the distribution.
+ *
+ * * Neither the name of the organization nor the names of
+ *  its contributors may be used to endorse or promote
+ *  products derived from this software without specific
+ *  prior written permission.
+
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#ifndef AUDIO_MODS_MODULE_MOD_XM_S3M_H
+#define AUDIO_MODS_MODULE_MOD_XM_S3M_H
+
+#include "common/scummsys.h"
+
+namespace Common {
+class SeekableReadStream;
+}
+
+namespace Modules {
+
+struct Note {
+	byte key;
+	byte instrument;
+	byte volume;
+	byte effect;	// effect type
+	byte param;		// parameter of effect
+};
+
+struct Pattern {
+	int numChannels, numRows;
+	Note *notes;
+
+	Note getNote(int row, int chan) {
+		Note res;
+		if (row >= 0 && chan >= 0 && row < numRows && chan < numChannels)
+			res = notes[row * numChannels + chan];
+		else
+			memset(&res, 0, sizeof(struct Note));
+		return res;
+	}
+};
+
+struct Sample {
+	char name[32];		// sample name
+	int16 finetune;		// fine tune
+	int16 volume;		// volume
+	int length;		// loop start
+	int loopStart;		// loop start
+	int loopLength;	// loop length
+	int16 panning;
+	int16 relNote;
+	int16 *data;
+};
+
+struct Envelope {
+	byte enabled, sustain, looped, numPoints;
+	uint16 sustainTick, loopStartTick, loopEndTick;
+	uint16 pointsTick[16], pointsAmpl[16];
+};
+
+struct Instrument {
+	int numSamples, volFadeout;
+	char name[32], keyToSample[97];
+	int8 vibType, vibSweep, vibDepth, vibRate;
+	Envelope volEnv, panEnv;
+	Sample *samples;
+};
+
+struct ModuleModXmS3m {
+
+private:
+	static const int FP_SHIFT;
+	static const int FP_ONE;
+	static const int FP_MASK;
+	static const int exp2table[];
+
+public:
+	// sound properties
+	byte name[32];
+	int sequenceLen;
+	int restartPos;
+	byte *sequence;
+
+	// patterns
+	int numChannels;
+	int numPatterns;
+	Pattern *patterns;
+
+	// instruments
+	int numInstruments;
+	Instrument *instruments;
+
+	// others
+	int defaultGvol, defaultSpeed, defaultTempo, c2Rate, gain;
+	bool linearPeriods, fastVolSlides;
+	byte *defaultPanning;
+
+	ModuleModXmS3m();
+	~ModuleModXmS3m();
+
+	bool load(Common::SeekableReadStream &stream);
+
+	// math functions
+	static int log2(int x);
+	static int exp2(int x);
+
+private:
+	bool loadMod(Common::SeekableReadStream &stream);
+	bool loadXm(Common::SeekableReadStream &stream);
+	bool loadS3m(Common::SeekableReadStream &stream);
+
+	void readSampleSint8(Common::SeekableReadStream &stream, int length, int16 *dest);
+	void readSampleSint16LE(Common::SeekableReadStream &stream, int length, int16 *dest);
+
+	void SamplePingPong(Sample &sample);
+};
+
+} // End of namespace Modules
+
+#endif
diff --git a/audio/module.mk b/audio/module.mk
index 9e002d5..0d95ecc 100644
--- a/audio/module.mk
+++ b/audio/module.mk
@@ -34,7 +34,9 @@ MODULE_OBJS := \
 	decoders/xa.o \
 	mods/infogrames.o \
 	mods/maxtrax.o \
+	mods/mod_xm_s3m.o \
 	mods/module.o \
+	mods/module_mod_xm_s3m.o \
 	mods/protracker.o \
 	mods/paula.o \
 	mods/rjp1.o \





More information about the Scummvm-git-logs mailing list