diff --git OpenMPT/libopenmpt/foo_openmpt.cpp OpenMPT/libopenmpt/foo_openmpt.cpp
index ac7ce34..8947cdf 100644
--- OpenMPT/libopenmpt/foo_openmpt.cpp
+++ OpenMPT/libopenmpt/foo_openmpt.cpp
@@ -319,6 +319,7 @@ DECLARE_FILE_TYPE("OpenMPT compatible module files",
 	"*.imf" ";"
 	"*.j2b" ";"
 	"*.plm" ";"
+	"*.stp" ";"
 	"*.gdm" ";"
 	"*.umx" ";"
 	"*.mo3" ";"
diff --git OpenMPT/soundlib/Load_stp.cpp OpenMPT/soundlib/Load_stp.cpp
new file mode 100644
index 0000000..457ecdf
--- /dev/null
+++ OpenMPT/soundlib/Load_stp.cpp
@@ -0,0 +1,716 @@
+/*
+ * Load_stp.cpp
+ * ------------
+ * Purpose: STP (Soundtracker Pro II) module loader
+ * Notes  : doesn't support multiple loops per sample, and various exotic effects.
+ *          fractional speed values and combined auto effects are handled whenever possible,
+ *          but some effects may be omitted (and there may be tempo accuracy issues).
+ * Authors: Devin Acker
+ *          OpenMPT Devs
+ * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
+ *
+ * Wisdom from the Soundtracker Pro II manual:
+ * "To create shorter patterns, simply create shorter patterns."
+ */
+ 
+#include "stdafx.h"
+#include "Loaders.h"
+#include "Tables.h"
+
+OPENMPT_NAMESPACE_BEGIN
+
+#ifdef NEEDS_PRAGMA_PACK
+#pragma pack(push, 1)
+#endif
+
+// File header (except for "STP3" magic)
+struct PACKED STPFileHeader {
+	uint16 version;
+	uint8  numOrders;
+	uint8  patternLength;
+	uint8  orderList[128];
+	uint16 speed;
+	uint16 speedFrac;
+	uint16 timerCount;
+	uint16 flags;
+	uint32 reserved;
+	uint16 midiCount; // always 50
+	uint8  midi[50];
+	uint16 numSamples;
+	uint16 sampleStructSize;
+	
+	void ConvertEndianness()
+	{
+		SwapBytesBE(version);
+		SwapBytesBE(speed);
+		SwapBytesBE(speedFrac);
+		SwapBytesBE(timerCount);
+		SwapBytesBE(flags);
+		SwapBytesBE(midiCount);
+		SwapBytesBE(numSamples);
+		SwapBytesBE(sampleStructSize);
+	}
+};
+
+STATIC_ASSERT(sizeof(STPFileHeader) == 200);
+
+// Sample header (versions 0 and 1)
+struct PACKED STPSampleHeaderOld {
+	char   pathName[31];
+	uint8  flags;
+	char   fileName[30];
+	uint32 length;
+	uint8  volume;
+	uint8  reserved1;
+	uint32 loopStart;
+	uint32 loopLength;
+	uint16 defaultCmd;
+	uint32 reserved2;
+	
+	void ConvertEndianness()
+	{
+		SwapBytesBE(length);
+		SwapBytesBE(loopStart);
+		SwapBytesBE(loopLength);
+		SwapBytesBE(defaultCmd);
+	}
+	
+	void ConvertToMPT(ModSample &mptSmp) const
+	{
+		mptSmp.Initialize(MOD_TYPE_MOD);
+		mptSmp.nLength = length;
+		mptSmp.nVolume = 4 * MIN(volume, 64);
+		
+		mptSmp.nLoopStart = loopStart;
+		mptSmp.nLoopEnd = loopStart + loopLength;
+
+		if(mptSmp.nLoopStart >= mptSmp.nLength)
+		{
+			mptSmp.nLoopStart = mptSmp.nLength - 1;
+		}
+		if(mptSmp.nLoopEnd > mptSmp.nLength)
+		{
+			mptSmp.nLoopEnd = mptSmp.nLength;
+		}
+		
+		if(mptSmp.nLoopStart > mptSmp.nLoopEnd)
+		{
+			mptSmp.nLoopStart = 0;
+			mptSmp.nLoopEnd = 0;
+		}
+		else if(mptSmp.nLoopEnd > mptSmp.nLoopStart)
+		{
+			mptSmp.uFlags.set(CHN_LOOP);
+		}
+	}
+	
+	void Read(FileReader &file) {
+		file.ReadConvertEndianness(*this);
+		pathName[30] = '\0';
+	}
+};
+
+STATIC_ASSERT(sizeof(STPSampleHeaderOld) == 82);
+
+// Sample header (version 2), not packed due to variable string length and alignment in file
+struct STPSampleHeader {
+	char   pathName[256];
+	uint8  flags;
+	char   fileName[30];
+	uint32 length;
+	uint8  volume;
+	uint8  reserved1;
+	uint32 loopStart;
+	uint32 loopLength;
+	uint16 defaultCmd;
+	uint16 defaultPeriod;
+	uint8  finetune;
+	uint8  reserved2;
+	
+	void ConvertToMPT(ModSample &mptSmp) const
+	{
+		mptSmp.Initialize(MOD_TYPE_MOD);
+		mptSmp.nLength = length;
+		mptSmp.nFineTune = static_cast<int8>(finetune << 3);
+		mptSmp.nVolume = 4 * MIN(volume, 64);
+		
+		mptSmp.nLoopStart = loopStart;
+		mptSmp.nLoopEnd = loopStart + loopLength;
+
+		if(mptSmp.nLoopStart >= mptSmp.nLength)
+		{
+			mptSmp.nLoopStart = mptSmp.nLength - 1;
+		}
+		if(mptSmp.nLoopEnd > mptSmp.nLength)
+		{
+			mptSmp.nLoopEnd = mptSmp.nLength;
+		}
+		
+		if(mptSmp.nLoopStart > mptSmp.nLoopEnd)
+		{
+			mptSmp.nLoopStart = 0;
+			mptSmp.nLoopEnd = 0;
+		}
+		else if(mptSmp.nLoopEnd > mptSmp.nLoopStart)
+		{
+			mptSmp.uFlags.set(CHN_LOOP);
+		}
+	}
+	
+	void Read(FileReader &file) {
+		std::string str;
+		
+		file.ReadNullString(str, 255);
+		std::strncpy(pathName, str.c_str(), 255);
+		pathName[255] = '\0';
+		
+		flags = file.ReadUint8();
+		
+		file.ReadNullString(str, 29);
+		std::strncpy(fileName, str.c_str(), 29);
+		pathName[29] = '\0';
+		
+		// seek to even boundary
+		if(file.GetPosition() & 1)
+			file.Skip(1);
+		
+		length = file.ReadUint32BE();
+		volume = file.ReadUint8();
+		reserved1 = file.ReadUint8();
+		
+		loopStart = file.ReadUint32BE();
+		loopLength = file.ReadUint32BE();
+		
+		defaultCmd = file.ReadUint16BE();
+		defaultPeriod = file.ReadUint16BE();
+		
+		finetune = file.ReadUint8();
+		reserved2 = file.ReadUint8();
+	}
+};
+
+#ifdef NEEDS_PRAGMA_PACK
+#pragma pack(pop)
+#endif
+
+template <typename T>
+static void ReadSample(FileReader &file, T &sampleHeader, ModSample &sample, char (&sampleName)[MAX_SAMPLENAME])
+//--------------------------------------------------------------------------------------------------------------
+{
+	sampleHeader.Read(file);
+	sampleHeader.ConvertToMPT(sample);
+
+	mpt::String::Read<mpt::String::spacePadded>(sampleName, sampleHeader.fileName);
+	// Get rid of weird characters in sample names.
+	for(size_t i = 0; i < CountOf(sampleName); i++)
+	{
+		if(sampleName[i] > 0 && sampleName[i] < ' ')
+		{
+			sampleName[i] = ' ';
+		}
+	}
+}
+
+static TEMPO ConvertTempo(uint16 ciaSpeed)
+//---------------------------------------
+{
+	// 3546 is the resulting CIA timer value when using 4F7D (tempo 125 bpm) command in STProII
+	return TEMPO(125.0 * (3546.0 / ciaSpeed));
+}
+
+bool CSoundFile::ReadSTP(FileReader &file, ModLoadingFlags loadFlags)
+//-------------------------------------------------------------------
+{
+	file.Rewind();
+	if(!file.ReadMagic("STP3"))
+		return false;
+
+	STPFileHeader fileHeader;
+	file.ReadConvertEndianness(fileHeader);
+	if(fileHeader.version > 2 || 
+	   fileHeader.numOrders > 128 || fileHeader.numSamples > MAX_SAMPLES ||
+	   fileHeader.midiCount != 50)
+		return false;
+	
+	if(loadFlags == onlyVerifyHeader)
+		return true;
+		
+	InitializeGlobals(MOD_TYPE_STP);
+	
+	m_nChannels = 4;
+	m_nSamples = 0;
+	m_nInstruments = 0;
+	
+	m_nDefaultSpeed = fileHeader.speed;
+	m_nDefaultTempo = ConvertTempo(fileHeader.timerCount);
+	
+	m_nMinPeriod = 14 * 4;
+	m_nMaxPeriod = 3424 * 4;
+	
+	Order.ReadFromArray(fileHeader.orderList, fileHeader.numOrders);
+
+	// Load sample headers
+	for(SAMPLEINDEX smp = 1; smp <= fileHeader.numSamples; smp++)
+	{
+		// this is 1-based the same as smp
+		SAMPLEINDEX actualSmp = file.ReadUint16BE();
+		if(actualSmp > MAX_SAMPLES)
+			return false;
+		
+		m_nSamples = MAX(m_nSamples, actualSmp);
+	
+		if(fileHeader.version == 2)
+		{
+			static STPSampleHeader sampleHeader;
+			uint32 headerSize = file.ReadUint32BE();
+			
+			ReadSample(file, sampleHeader, Samples[actualSmp], m_szNames[actualSmp]);
+			// TODO: verify string lengths against headerSize?
+			(void)headerSize;
+		}
+		else
+		{
+			static STPSampleHeaderOld sampleHeaderOld;
+			ReadSample(file, sampleHeaderOld, Samples[actualSmp], m_szNames[actualSmp]);
+		}
+		
+		if(fileHeader.version >= 1)
+		{
+			uint16 numLoops = file.ReadUint16BE();
+			// ignore these
+			file.Skip(numLoops*8);
+		}
+	}
+
+	// Load patterns
+	uint16 numPatterns = 128;
+	if (fileHeader.version == 0)
+		numPatterns = file.ReadUint16BE();
+	
+	uint16 patternLength = fileHeader.patternLength;
+	uint16 channels = 4;
+	
+	uint8 globalVolSlide = 0;
+	std::vector<uint8> autoFinePorta;
+	std::vector<uint8> autoPortaUp;
+	std::vector<uint8> autoPortaDown;
+	std::vector<uint8> autoVolSlide;
+	std::vector<uint8> autoVibrato;
+	std::vector<uint8> vibratoMem;
+	std::vector<uint8> autoTremolo;
+	std::vector<uint8> autoTonePorta;
+	std::vector<uint8> tonePortaMem;
+	
+	for(PATTERNINDEX pat = 0; pat < numPatterns; pat++)
+	{
+		PATTERNINDEX actualPat = pat;
+		
+		if(fileHeader.version > 0)
+		{
+			actualPat = file.ReadUint16BE();
+			if(actualPat == 0xFFFF)
+				break;
+			
+			patternLength = file.ReadUint16BE();
+			channels = file.ReadUint16BE();
+			
+			m_nChannels = MIN(m_nChannels, channels);
+		}
+		
+		if(!(loadFlags & loadPatternData) || !Patterns.Insert(actualPat, patternLength))
+		{
+			file.Skip(channels * patternLength * 4);
+			continue;
+		}
+		
+		autoFinePorta.resize(channels, 0);
+		autoPortaUp.resize(channels, 0);
+		autoPortaDown.resize(channels, 0);
+		autoVolSlide.resize(channels, 0);
+		autoVibrato.resize(channels, 0);
+		vibratoMem.resize(channels, 0);
+		autoTremolo.resize(channels, 0);
+		autoTonePorta.resize(channels, 0);
+		tonePortaMem.resize(channels, 0);
+		
+		for(ROWINDEX row = 0; row < patternLength; row++)
+		{
+			PatternRow rowBase = Patterns[actualPat].GetpModCommand(row, 0);
+
+			bool didGlobalVolSlide = false;
+			bool shouldDelay = false;
+			
+			// if a fractional speed value is in use then determine if we should stick a fine pattern delay somewhere
+			switch(fileHeader.speedFrac & 3)
+			{
+			// 1/4
+			case 1: shouldDelay = (row & 3) == 0; break;
+			// 1/2
+			case 2: shouldDelay = (row & 1) == 0; break;
+			// 3/4
+			case 3: shouldDelay = (row & 3) != 3; break;
+			}
+			
+			for(CHANNELINDEX chn = 0; chn < channels; chn++)
+			{
+				ModCommand &m = rowBase[chn];
+				
+				m.instr   = file.ReadUint8();
+				m.note    = file.ReadUint8();
+				m.command = file.ReadUint8();
+				m.param   = file.ReadUint8();
+				
+				if(m.note)
+				{
+					m.note += 25;
+					
+					autoFinePorta[chn] = 0;
+					autoPortaUp[chn] = 0;
+					autoPortaDown[chn] = 0;
+					autoVolSlide[chn] = 0;
+					autoVibrato[chn] = vibratoMem[chn] = 0;
+					autoTremolo[chn] = 0;
+					autoTonePorta[chn] = tonePortaMem[chn] = 0;
+				}
+				
+				uint8 swap = (m.param >> 4) | (m.param << 4);
+				
+				if((m.command & 0xF0) == 0xF0)
+				{
+					m.param = Util::Round<uint8>(ConvertTempo(m.param | (((uint16)m.command & 0xF) << 8)).ToDouble());
+					m.command = CMD_TEMPO;
+				} else switch(m.command)
+				{
+				case 0x00: // arpeggio
+					if(m.param)
+						m.command = CMD_ARPEGGIO; 
+					break;
+					
+				case 0x01: // portamento up
+					m.command = CMD_PORTAMENTOUP; 
+					break;
+					
+				case 0x02: // portamento down
+					m.command = CMD_PORTAMENTODOWN; 
+					break;
+				
+				case 0x03: // auto fine portamento up
+					autoFinePorta[chn] = 0x10 | MIN(m.param, 15);
+					autoPortaUp[chn] = 0;
+					autoPortaDown[chn] = 0;
+					autoTonePorta[chn] = 0;
+				
+					m.command = m.param = 0;
+					break;
+					
+				case 0x04: // auto fine portamento down
+					autoFinePorta[chn] = 0x20 | MIN(m.param, 15);
+					autoPortaUp[chn] = 0;
+					autoPortaDown[chn] = 0;
+					autoTonePorta[chn] = 0;
+				
+					m.command = m.param = 0;
+					break;
+				
+				case 0x05: // auto portamento up
+					autoFinePorta[chn] = 0;
+					autoPortaUp[chn] = m.param;
+					autoPortaDown[chn] = 0;
+					autoTonePorta[chn] = 0;
+				
+					m.command = m.param = 0;
+					break;
+				
+				case 0x06: // auto portamento down
+					autoFinePorta[chn] = 0;
+					autoPortaUp[chn] = 0;
+					autoPortaDown[chn] = m.param;
+					autoTonePorta[chn] = 0;
+				
+					m.command = m.param = 0;
+					break;
+				
+				case 0x07: // set global volume
+					m.command = CMD_GLOBALVOLUME;
+					globalVolSlide = 0;
+					break;
+				
+				case 0x08: // auto global volume slide
+					globalVolSlide = swap;
+					m.command = m.param = 0;
+					break;
+				
+				case 0x09: // fine portamento up
+					m.command = CMD_MODCMDEX;
+					m.param = 0x10 | MIN(m.param, 15);
+					break;
+					
+				case 0x0A: // fine portamento down
+					m.command = CMD_MODCMDEX;
+					m.param = 0x20 | MIN(m.param, 15);
+					break;
+				
+				case 0x0B: // auto volume slide
+					autoVolSlide[chn] = swap;
+					m.command = m.param = 0;
+					break;
+					
+				case 0x0C: // set volume
+					m.volcmd = VOLCMD_VOLUME;
+					m.vol = m.param;
+					autoVolSlide[chn] = 0;
+					m.command = m.param = 0;
+					break;
+					
+				case 0x0D: // volume slide (param is swapped compared to .mod)
+					if(m.param & 0xF0)
+					{
+						m.volcmd = VOLCMD_VOLSLIDEDOWN;
+						m.vol = m.param >> 4;
+					} else if(m.param & 0x0F)
+					{
+						m.volcmd = VOLCMD_VOLSLIDEUP;
+						m.vol = m.param & 0xF;
+					}
+					autoVolSlide[chn] = 0;
+					m.command = m.param = 0;
+					break;
+				
+				case 0x0E: // set filter (also uses opposite value compared to .mod)
+					m.command = CMD_MODCMDEX;
+					m.param = 1 ^ MIN(m.param, 1);
+					break;
+					
+				case 0x0F: // set speed
+					m.command = CMD_SPEED;
+					fileHeader.speedFrac = m.param & 0xF;
+					m.param >>= 4; 
+					break;
+				
+				case 0x10: // auto vibrato
+					autoVibrato[chn] = m.param;
+					vibratoMem[chn] = 0;
+					m.command = m.param = 0;
+					break;
+					
+				case 0x11: // auto tremolo
+					if(m.param & 0xF)
+						autoTremolo[chn] = m.param;
+					else
+						autoTremolo[chn] = 0;
+					m.command = m.param = 0;
+					break;
+				
+				case 0x12: // pattern break
+					m.command = CMD_PATTERNBREAK; 
+					break;
+				
+				case 0x13: // auto tone portamento
+					autoFinePorta[chn] = 0;
+					autoPortaUp[chn] = 0;
+					autoPortaDown[chn] = 0;
+					autoTonePorta[chn] = m.param;
+				
+					tonePortaMem[chn] = 0;
+					m.command = m.param = 0;
+					break;
+				
+				case 0x14: // position jump
+					m.command = CMD_POSITIONJUMP; 
+					break;
+				
+				case 0x1D: // fine volume slide
+					m.command = CMD_MODCMDEX;
+					if(m.param & 0xF0)
+						m.param = 0xB0 | (m.param >> 4);
+					else if(m.param & 0x0F)
+						m.param = 0xA0 | (m.param & 0xF);
+					break;
+				
+				case 0x20: // "delayed fade"
+					// just behave like either a normal fade or a notecut
+					// depending on the speed
+					if(m.param & 0xF0)
+					{
+						autoVolSlide[chn] = m.param >> 4;
+						m.command = m.param = 0;
+					} else {
+						m.command = CMD_MODCMDEX;
+						m.param = 0xC0 | (m.param & 0xF);
+					}
+					break;
+				
+				case 0x21: // note delay
+					m.command = CMD_MODCMDEX;
+					m.param = 0xD0 | MIN(m.param, 15);
+					break;
+				
+				case 0x22: // retrigger note
+					m.command = CMD_RETRIG;
+					break;
+				
+				case 0x49: // set sample offset
+					m.command = CMD_OFFSET; 
+					break;
+				
+				case 0x4E: // other protracker commands (pattern loop / delay)
+					if((m.param & 0xF0) == 0x60 || (m.param & 0xF0) == 0xE0)
+						m.command = CMD_MODCMDEX;
+					else
+						m.command = m.param = 0;
+					break;
+				
+				case 0x4F: // set speed/tempo
+					if(m.param < 0x20)
+					{
+						m.command = CMD_SPEED;
+						fileHeader.speedFrac = 0;
+					} else
+					{
+						m.command = CMD_TEMPO;
+					}
+					break;
+				
+				default:
+					m.command = m.param = 0; 
+					break;
+				}
+				
+				bool didVolSlide = false;
+				
+				// try to put volume slide in volume command
+				if(autoVolSlide[chn] && !m.volcmd)
+				{
+					if(autoVolSlide[chn] & 0xF0)
+					{
+						m.volcmd = VOLCMD_VOLSLIDEDOWN;
+						m.vol = autoVolSlide[chn] >> 4;
+					} else
+					{
+						m.volcmd = VOLCMD_VOLSLIDEUP;
+						m.vol = autoVolSlide[chn] & 0xF;
+					}
+					didVolSlide = true;
+				}
+				
+				// try to place/combine all remaining running effects.
+				if(!m.command)
+				{
+					if(autoPortaUp[chn])
+					{
+						m.command = CMD_PORTAMENTOUP;
+						m.param = autoPortaUp[chn];
+						
+					} else if(autoPortaDown[chn])
+					{
+						m.command = CMD_PORTAMENTODOWN;
+						m.param = autoPortaDown[chn];
+						
+					} else if(autoFinePorta[chn])
+					{
+						m.command = CMD_MODCMDEX;
+						m.param = autoFinePorta[chn];
+						
+					} else if(autoTonePorta[chn])
+					{
+						// try a volslide+portamento
+						if (!didVolSlide && autoVolSlide[chn] && tonePortaMem[chn])
+						{
+							m.command = CMD_TONEPORTAVOL;
+							m.param = autoVolSlide[chn];
+							didVolSlide = true;
+						} else
+						{
+							m.command = CMD_TONEPORTAMENTO;
+							m.param = tonePortaMem[chn] = autoTonePorta[chn];
+						}
+						
+					} else if(autoVibrato[chn])
+					{
+						// try a volslide+vibrato
+						if (!didVolSlide && autoVolSlide[chn] && vibratoMem[chn])
+						{
+							m.command = CMD_VIBRATOVOL;
+							m.param = autoVolSlide[chn];
+							didVolSlide = true;
+						} else
+						{
+							m.command = CMD_VIBRATO;
+							m.param = vibratoMem[chn] = autoVibrato[chn];
+						}
+						
+					} else if(!didVolSlide && autoVolSlide[chn])
+					{
+						m.command = CMD_VOLUMESLIDE;
+						m.param = autoVolSlide[chn];
+						didVolSlide = true;
+						
+					} else if(autoTremolo[chn])
+					{
+						m.command = CMD_TREMOLO;
+						m.param = autoTremolo[chn];
+						
+					} else if(shouldDelay)
+					{
+						// insert a fine pattern delay here
+						m.command = CMD_S3MCMDEX;
+						m.param = 0x61;
+						shouldDelay = false;
+					
+					} else if(!didGlobalVolSlide && globalVolSlide)
+					{
+						m.command = CMD_GLOBALVOLSLIDE;
+						m.param = globalVolSlide;
+						didGlobalVolSlide = true;
+					}
+				}
+			}
+			
+			// TODO: create/use extra channels for global volslide/delay if needed
+		}
+	}
+
+	// after we know how many channels there really are...
+	m_nSamplePreAmp = 256 / m_nChannels;
+	// Setup channel pan positions and volume
+	SetupMODPanning(true);
+
+	// Skip over scripts and drumpad info
+	if(fileHeader.version > 0)
+	{
+		uint16 scriptNum;
+		uint32 length;
+		
+		while(file.CanRead(2))
+		{
+			scriptNum = file.ReadUint16BE();
+			if(scriptNum == 0xFFFF)
+				break;
+			
+			file.Skip(2);
+			length = file.ReadUint32BE();
+			file.Skip(length);
+		}
+		
+		// skip drumpad stuff
+		file.Skip(17*2);
+	}
+
+	// Reading samples
+	if(loadFlags & loadSampleData)
+	{
+		for(SAMPLEINDEX smp = 1; smp <= m_nSamples; smp++) if(Samples[smp].nLength)
+		{
+			SampleIO(
+				SampleIO::_8bit,
+				SampleIO::mono,
+				SampleIO::littleEndian,
+				SampleIO::signedPCM)
+				.ReadSample(Samples[smp], file);
+		}
+	}
+
+	return true;
+}
+
+OPENMPT_NAMESPACE_END
diff --git OpenMPT/soundlib/Snd_defs.h OpenMPT/soundlib/Snd_defs.h
index 4499f0b..23eeb62 100644
--- OpenMPT/soundlib/Snd_defs.h
+++ OpenMPT/soundlib/Snd_defs.h
@@ -97,6 +97,7 @@ enum MODTYPE
 	MOD_TYPE_DIGI	= 0x8000000,
 	MOD_TYPE_UAX	= 0x10000000, // sampleset as module
 	MOD_TYPE_PLM	= 0x20000000,
+	MOD_TYPE_STP	= 0x40000000,
 };
 DECLARE_FLAGSET(MODTYPE)
 
diff --git OpenMPT/soundlib/Snd_fx.cpp OpenMPT/soundlib/Snd_fx.cpp
index 1631620..80bcb87 100644
--- OpenMPT/soundlib/Snd_fx.cpp
+++ OpenMPT/soundlib/Snd_fx.cpp
@@ -3358,7 +3358,7 @@ void CSoundFile::PortamentoUp(CHANNELINDEX nChn, ModCommand::PARAM param, const
 	else
 		param = pChn->nOldPortaUpDown;
 
-	const bool doFineSlides = !doFinePortamentoAsRegular && !(GetType() & (MOD_TYPE_MOD | MOD_TYPE_XM | MOD_TYPE_MT2 | MOD_TYPE_MED | MOD_TYPE_AMF0 | MOD_TYPE_DIGI));
+	const bool doFineSlides = !doFinePortamentoAsRegular && !(GetType() & (MOD_TYPE_MOD | MOD_TYPE_XM | MOD_TYPE_MT2 | MOD_TYPE_MED | MOD_TYPE_AMF0 | MOD_TYPE_DIGI | MOD_TYPE_STP));
 
 	// Process MIDI pitch bend for instrument plugins
 	MidiPortamento(nChn, param, doFineSlides);
@@ -3417,7 +3417,7 @@ void CSoundFile::PortamentoDown(CHANNELINDEX nChn, ModCommand::PARAM param, cons
 	else
 		param = pChn->nOldPortaUpDown;
 
-	const bool doFineSlides = !doFinePortamentoAsRegular && !(GetType() & (MOD_TYPE_MOD | MOD_TYPE_XM | MOD_TYPE_MT2 | MOD_TYPE_MED | MOD_TYPE_AMF0 | MOD_TYPE_DIGI));
+	const bool doFineSlides = !doFinePortamentoAsRegular && !(GetType() & (MOD_TYPE_MOD | MOD_TYPE_XM | MOD_TYPE_MT2 | MOD_TYPE_MED | MOD_TYPE_AMF0 | MOD_TYPE_DIGI | MOD_TYPE_STP));
 
 	// Process MIDI pitch bend for instrument plugins
 	MidiPortamento(nChn, -static_cast<int>(param), doFineSlides);
@@ -3889,7 +3889,7 @@ void CSoundFile::VolumeSlide(ModChannel *pChn, ModCommand::PARAM param)
 	else
 		param = pChn->nOldVolumeSlide;
 
-	if((GetType() & (MOD_TYPE_MOD | MOD_TYPE_XM | MOD_TYPE_MT2 | MOD_TYPE_MED | MOD_TYPE_DIGI)))
+	if((GetType() & (MOD_TYPE_MOD | MOD_TYPE_XM | MOD_TYPE_MT2 | MOD_TYPE_MED | MOD_TYPE_DIGI | MOD_TYPE_STP)))
 	{
 		// MOD / XM nibble priority
 		if((param & 0xF0) != 0)
@@ -3902,7 +3902,7 @@ void CSoundFile::VolumeSlide(ModChannel *pChn, ModCommand::PARAM param)
 	}
 
 	int newvolume = pChn->nVolume;
-	if(!(GetType() & (MOD_TYPE_MOD | MOD_TYPE_XM | MOD_TYPE_AMF0 | MOD_TYPE_DIGI | MOD_TYPE_MED)))
+	if(!(GetType() & (MOD_TYPE_MOD | MOD_TYPE_XM | MOD_TYPE_AMF0 | MOD_TYPE_DIGI | MOD_TYPE_MED | MOD_TYPE_STP)))
 	{
 		if ((param & 0x0F) == 0x0F) //Fine upslide or slide -15
 		{
@@ -5445,7 +5445,7 @@ uint32 CSoundFile::GetFreqFromPeriod(uint32 period, uint32 c5speed, int32 nPerio
 //------------------------------------------------------------------------------------------
 {
 	if (!period) return 0;
-	if (GetType() & (MOD_TYPE_MED|MOD_TYPE_MOD|MOD_TYPE_DIGI|MOD_TYPE_MTM|MOD_TYPE_AMF0))
+	if (GetType() & (MOD_TYPE_MED|MOD_TYPE_MOD|MOD_TYPE_DIGI|MOD_TYPE_MTM|MOD_TYPE_AMF0|MOD_TYPE_STP))
 	{
 		return ((3546895L * 4) << FREQ_FRACBITS) / period;
 	} else if (GetType() == MOD_TYPE_XM)
diff --git OpenMPT/soundlib/Sndfile.cpp OpenMPT/soundlib/Sndfile.cpp
index 006d046..39e4a03 100644
--- OpenMPT/soundlib/Sndfile.cpp
+++ OpenMPT/soundlib/Sndfile.cpp
@@ -317,7 +317,8 @@ bool CSoundFile::Create(FileReader file, ModLoadingFlags loadFlags)
 		 && !ReadMod(file, loadFlags)
 		 && !Read669(file, loadFlags)
 		 && !ReadICE(file, loadFlags)
-		 && !ReadM15(file, loadFlags))
+		 && !ReadM15(file, loadFlags)
+		 && !ReadSTP(file, loadFlags))
 		{
 			m_nType = MOD_TYPE_NONE;
 			m_ContainerType = MOD_CONTAINERTYPE_NONE;
@@ -1046,6 +1047,8 @@ MODTYPE CSoundFile::GetBestSaveFormat() const
 	case MOD_TYPE_AMF0:
 	case MOD_TYPE_DIGI:
 		return MOD_TYPE_MOD;
+	case MOD_TYPE_STP:
+		return MOD_TYPE_XM;
 	case MOD_TYPE_MED:
 		if(m_nDefaultTempo == TEMPO(125, 0) && m_nDefaultSpeed == 6 && !m_nInstruments)
 		{
diff --git OpenMPT/soundlib/Sndfile.h OpenMPT/soundlib/Sndfile.h
index 1ea882a..1c03f2a 100644
--- OpenMPT/soundlib/Sndfile.h
+++ OpenMPT/soundlib/Sndfile.h
@@ -659,6 +659,7 @@ public:
 	bool ReadDIGI(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule);
 	bool ReadPLM(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule);
 	bool ReadMID(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule);
+	bool ReadSTP(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule);
 
 	static std::vector<const char *> GetSupportedExtensions(bool otherFormats);
 	static mpt::Charset GetCharsetFromModType(MODTYPE modtype);
diff --git OpenMPT/soundlib/Tables.cpp OpenMPT/soundlib/Tables.cpp
index abf91a9..06250bd 100644
--- OpenMPT/soundlib/Tables.cpp
+++ OpenMPT/soundlib/Tables.cpp
@@ -87,6 +87,7 @@ static const ModFormatInfo modFormatInfo[] =
 	{ MOD_TYPE_IMF,		"Imago Orpheus",			"imf" },
 	{ MOD_TYPE_J2B,		"Galaxy Sound System",		"j2b" },
 	{ MOD_TYPE_PLM,		"Disorder Tracker 2",		"plm" },
+	{ MOD_TYPE_STP,		"Soundtracker Pro II",		"stp" },
 
 #ifndef NO_ARCHIVE_SUPPORT
 	// Compressed modules
@@ -147,6 +148,7 @@ static const ModCharsetInfo ModCharsetInfos[] =
 	{ MOD_TYPE_OKT , mpt::CharsetISO8859_1  },
 	{ MOD_TYPE_DBM , mpt::CharsetISO8859_1  },
 	{ MOD_TYPE_DIGI, mpt::CharsetISO8859_1  },
+	{ MOD_TYPE_STP,  mpt::CharsetISO8859_1  },
 	// Amiga // DOS
 	{ MOD_TYPE_MOD , mpt::CharsetISO8859_1  },
 	{ MOD_TYPE_MED , mpt::CharsetISO8859_1  },
