/* 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::moduleExp2(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::moduleLog2(int x) {
	int y = 16 << FP_SHIFT;
	for (int step = y; step > 0; step >>= 1) {
		if (moduleExp2(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 * moduleLog2((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 = (moduleLog2(st.readUint32LE()) - moduleLog2(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
