| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515 | 
							- /*
 
-  * Load_ult.cpp
 
-  * ------------
 
-  * Purpose: ULT (UltraTracker) module loader
 
-  * Notes  : (currently none)
 
-  * Authors: Storlek (Original author - http://schismtracker.org/ - code ported with permission)
 
-  *			Johannes Schultz (OpenMPT Port, tweaks)
 
-  * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
 
-  */
 
- #include "stdafx.h"
 
- #include "Loaders.h"
 
- OPENMPT_NAMESPACE_BEGIN
 
- struct UltFileHeader
 
- {
 
- 	char  signature[14];		// "MAS_UTrack_V00"
 
- 	uint8 version;				// '1'...'4'
 
- 	char  songName[32];			// Song Name, not guaranteed to be null-terminated
 
- 	uint8 messageLength;		// Number of Lines
 
- };
 
- MPT_BINARY_STRUCT(UltFileHeader, 48)
 
- struct UltSample
 
- {
 
- 	enum UltSampleFlags
 
- 	{
 
- 		ULT_16BIT = 4,
 
- 		ULT_LOOP  = 8,
 
- 		ULT_PINGPONGLOOP = 16,
 
- 	};
 
- 	char     name[32];
 
- 	char     filename[12];
 
- 	uint32le loopStart;
 
- 	uint32le loopEnd;
 
- 	uint32le sizeStart;
 
- 	uint32le sizeEnd;
 
- 	uint8le  volume;	// 0-255, apparently prior to 1.4 this was logarithmic?
 
- 	uint8le  flags;		// above
 
- 	uint16le speed;		// only exists for 1.4+
 
- 	int16le  finetune;
 
- 	// Convert an ULT sample header to OpenMPT's internal sample header.
 
- 	void ConvertToMPT(ModSample &mptSmp) const
 
- 	{
 
- 		mptSmp.Initialize();
 
- 		mptSmp.Set16BitCuePoints();
 
- 		mptSmp.filename = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, filename);
 
- 		if(sizeEnd <= sizeStart)
 
- 		{
 
- 			return;
 
- 		}
 
- 		mptSmp.nLength = sizeEnd - sizeStart;
 
- 		mptSmp.nSustainStart = loopStart;
 
- 		mptSmp.nSustainEnd = std::min(static_cast<SmpLength>(loopEnd), mptSmp.nLength);
 
- 		mptSmp.nVolume = volume;
 
- 		mptSmp.nC5Speed = speed;
 
- 		if(finetune)
 
- 		{
 
- 			mptSmp.Transpose(finetune / (12.0 * 32768.0));
 
- 		}
 
- 		if(flags & ULT_LOOP)
 
- 			mptSmp.uFlags.set(CHN_SUSTAINLOOP);
 
- 		if(flags & ULT_PINGPONGLOOP)
 
- 			mptSmp.uFlags.set(CHN_PINGPONGSUSTAIN);
 
- 		if(flags & ULT_16BIT)
 
- 		{
 
- 			mptSmp.uFlags.set(CHN_16BIT);
 
- 			mptSmp.nSustainStart /= 2;
 
- 			mptSmp.nSustainEnd /= 2;
 
- 		}
 
- 		
 
- 	}
 
- };
 
- MPT_BINARY_STRUCT(UltSample, 66)
 
- /* Unhandled effects:
 
- 5x1 - do not loop sample (x is unused)
 
- E0x - set vibrato strength (2 is normal)
 
- The logarithmic volume scale used in older format versions here, or pretty
 
- much anywhere for that matter. I don't even think Ultra Tracker tries to
 
- convert them. */
 
- static void TranslateULTCommands(uint8 &effect, uint8 ¶m, uint8 version)
 
- {
 
- 	static constexpr uint8 ultEffTrans[] =
 
- 	{
 
- 		CMD_ARPEGGIO,
 
- 		CMD_PORTAMENTOUP,
 
- 		CMD_PORTAMENTODOWN,
 
- 		CMD_TONEPORTAMENTO,
 
- 		CMD_VIBRATO,
 
- 		CMD_NONE,
 
- 		CMD_NONE,
 
- 		CMD_TREMOLO,
 
- 		CMD_NONE,
 
- 		CMD_OFFSET,
 
- 		CMD_VOLUMESLIDE,
 
- 		CMD_PANNING8,
 
- 		CMD_VOLUME,
 
- 		CMD_PATTERNBREAK,
 
- 		CMD_NONE, // extended effects, processed separately
 
- 		CMD_SPEED,
 
- 	};
 
- 	uint8 e = effect & 0x0F;
 
- 	effect = ultEffTrans[e];
 
- 	switch(e)
 
- 	{
 
- 	case 0x00:
 
- 		if(!param || version < '3')
 
- 			effect = CMD_NONE;
 
- 		break;
 
- 	case 0x05:
 
- 		// play backwards
 
- 		if((param & 0x0F) == 0x02 || (param & 0xF0) == 0x20)
 
- 		{
 
- 			effect = CMD_S3MCMDEX;
 
- 			param = 0x9F;
 
- 		}
 
- 		if(((param & 0x0F) == 0x0C || (param & 0xF0) == 0xC0) && version >= '3')
 
- 		{
 
- 			effect = CMD_KEYOFF;
 
- 			param = 0;
 
- 		}
 
- 		break;
 
- 	case 0x07:
 
- 		if(version < '4')
 
- 			effect = CMD_NONE;
 
- 		break;
 
- 	case 0x0A:
 
- 		if(param & 0xF0)
 
- 			param &= 0xF0;
 
- 		break;
 
- 	case 0x0B:
 
- 		param = (param & 0x0F) * 0x11;
 
- 		break;
 
- 	case 0x0C: // volume
 
- 		param /= 4u;
 
- 		break;
 
- 	case 0x0D: // pattern break
 
- 		param = 10 * (param >> 4) + (param & 0x0F);
 
- 		break;
 
- 	case 0x0E: // special
 
- 		switch(param >> 4)
 
- 		{
 
- 		case 0x01:
 
- 			effect = CMD_PORTAMENTOUP;
 
- 			param = 0xF0 | (param & 0x0F);
 
- 			break;
 
- 		case 0x02:
 
- 			effect = CMD_PORTAMENTODOWN;
 
- 			param = 0xF0 | (param & 0x0F);
 
- 			break;
 
- 		case 0x08:
 
- 			if(version >= '4')
 
- 			{
 
- 				effect = CMD_S3MCMDEX;
 
- 				param = 0x60 | (param & 0x0F);
 
- 			}
 
- 			break;
 
- 		case 0x09:
 
- 			effect = CMD_RETRIG;
 
- 			param &= 0x0F;
 
- 			break;
 
- 		case 0x0A:
 
- 			effect = CMD_VOLUMESLIDE;
 
- 			param = ((param & 0x0F) << 4) | 0x0F;
 
- 			break;
 
- 		case 0x0B:
 
- 			effect = CMD_VOLUMESLIDE;
 
- 			param = 0xF0 | (param & 0x0F);
 
- 			break;
 
- 		case 0x0C: case 0x0D:
 
- 			effect = CMD_S3MCMDEX;
 
- 			break;
 
- 		}
 
- 		break;
 
- 	case 0x0F:
 
- 		if(param > 0x2F)
 
- 			effect = CMD_TEMPO;
 
- 		break;
 
- 	}
 
- }
 
- static int ReadULTEvent(ModCommand &m, FileReader &file, uint8 version)
 
- {
 
- 	uint8 repeat = 1;
 
- 	uint8 b = file.ReadUint8();
 
- 	if(b == 0xFC)	// repeat event
 
- 	{
 
- 		repeat = file.ReadUint8();
 
- 		b = file.ReadUint8();
 
- 	}
 
- 	m.note = (b > 0 && b < 61) ? (b + 35 + NOTE_MIN) : NOTE_NONE;
 
- 	const auto [instr, cmd, para1, para2] = file.ReadArray<uint8, 4>();
 
- 	
 
- 	m.instr = instr;
 
- 	uint8 cmd1 = cmd & 0x0F;
 
- 	uint8 cmd2 = cmd >> 4;
 
- 	uint8 param1 = para1;
 
- 	uint8 param2 = para2;
 
- 	TranslateULTCommands(cmd1, param1, version);
 
- 	TranslateULTCommands(cmd2, param2, version);
 
- 	// sample offset -- this is even more special than digitrakker's
 
- 	if(cmd1 == CMD_OFFSET && cmd2 == CMD_OFFSET)
 
- 	{
 
- 		uint32 offset = ((param2 << 8) | param1) >> 6;
 
- 		m.command = CMD_OFFSET;
 
- 		m.param = static_cast<ModCommand::PARAM>(offset);
 
- 		if(offset > 0xFF)
 
- 		{
 
- 			m.volcmd = VOLCMD_OFFSET;
 
- 			m.vol = static_cast<ModCommand::VOL>(offset >> 8);
 
- 		}
 
- 		return repeat;
 
- 	} else if(cmd1 == CMD_OFFSET)
 
- 	{
 
- 		uint32 offset = param1 * 4;
 
- 		param1 = mpt::saturate_cast<uint8>(offset);
 
- 		if(offset > 0xFF && ModCommand::GetEffectWeight(cmd2) < ModCommand::GetEffectType(CMD_OFFSET))
 
- 		{
 
- 			m.command = CMD_OFFSET;
 
- 			m.param = static_cast<ModCommand::PARAM>(offset);
 
- 			m.volcmd = VOLCMD_OFFSET;
 
- 			m.vol = static_cast<ModCommand::VOL>(offset >> 8);
 
- 			return repeat;
 
- 		}
 
- 	} else if(cmd2 == CMD_OFFSET)
 
- 	{
 
- 		uint32 offset = param2 * 4;
 
- 		param2 = mpt::saturate_cast<uint8>(offset);
 
- 		if(offset > 0xFF && ModCommand::GetEffectWeight(cmd1) < ModCommand::GetEffectType(CMD_OFFSET))
 
- 		{
 
- 			m.command = CMD_OFFSET;
 
- 			m.param = static_cast<ModCommand::PARAM>(offset);
 
- 			m.volcmd = VOLCMD_OFFSET;
 
- 			m.vol = static_cast<ModCommand::VOL>(offset >> 8);
 
- 			return repeat;
 
- 		}
 
- 	} else if(cmd1 == cmd2)
 
- 	{
 
- 		// don't try to figure out how ultratracker does this, it's quite random
 
- 		cmd2 = CMD_NONE;
 
- 	}
 
- 	if(cmd2 == CMD_VOLUME || (cmd2 == CMD_NONE && cmd1 != CMD_VOLUME))
 
- 	{
 
- 		// swap commands
 
- 		std::swap(cmd1, cmd2);
 
- 		std::swap(param1, param2);
 
- 	}
 
- 	// Combine slide commands, if possible
 
- 	ModCommand::CombineEffects(cmd2, param2, cmd1, param1);
 
- 	ModCommand::TwoRegularCommandsToMPT(cmd1, param1, cmd2, param2);
 
- 	m.volcmd = cmd1;
 
- 	m.vol = param1;
 
- 	m.command = cmd2;
 
- 	m.param = param2;
 
- 	return repeat;
 
- }
 
- // Functor for postfixing ULT patterns (this is easier than just remembering everything WHILE we're reading the pattern events)
 
- struct PostFixUltCommands
 
- {
 
- 	PostFixUltCommands(CHANNELINDEX numChannels)
 
- 	{
 
- 		this->numChannels = numChannels;
 
- 		curChannel = 0;
 
- 		writeT125 = false;
 
- 		isPortaActive.resize(numChannels, false);
 
- 	}
 
- 	void operator()(ModCommand &m)
 
- 	{
 
- 		// Attempt to fix portamentos.
 
- 		// UltraTracker will slide until the destination note is reached or 300 is encountered.
 
- 		// Stop porta?
 
- 		if(m.command == CMD_TONEPORTAMENTO && m.param == 0)
 
- 		{
 
- 			isPortaActive[curChannel] = false;
 
- 			m.command = CMD_NONE;
 
- 		}
 
- 		if(m.volcmd == VOLCMD_TONEPORTAMENTO && m.vol == 0)
 
- 		{
 
- 			isPortaActive[curChannel] = false;
 
- 			m.volcmd = VOLCMD_NONE;
 
- 		}
 
- 		// Apply porta?
 
- 		if(m.note == NOTE_NONE && isPortaActive[curChannel])
 
- 		{
 
- 			if(m.command == CMD_NONE && m.volcmd != VOLCMD_TONEPORTAMENTO)
 
- 			{
 
- 				m.command = CMD_TONEPORTAMENTO;
 
- 				m.param = 0;
 
- 			} else if(m.volcmd == VOLCMD_NONE && m.command != CMD_TONEPORTAMENTO)
 
- 			{
 
- 				m.volcmd = VOLCMD_TONEPORTAMENTO;
 
- 				m.vol = 0;
 
- 			}
 
- 		} else	// new note -> stop porta (or initialize again)
 
- 		{
 
- 			isPortaActive[curChannel] = (m.command == CMD_TONEPORTAMENTO || m.volcmd == VOLCMD_TONEPORTAMENTO);
 
- 		}
 
- 		// attempt to fix F00 (reset to tempo 125, speed 6)
 
- 		if(writeT125 && m.command == CMD_NONE)
 
- 		{
 
- 			m.command = CMD_TEMPO;
 
- 			m.param = 125;
 
- 		}
 
- 		if(m.command == CMD_SPEED && m.param == 0)
 
- 		{
 
- 			m.param = 6;
 
- 			writeT125 = true;
 
- 		}
 
- 		if(m.command == CMD_TEMPO)	// don't try to fix this anymore if the tempo has already changed.
 
- 		{
 
- 			writeT125 = false;
 
- 		}
 
- 		curChannel = (curChannel + 1) % numChannels;
 
- 	}
 
- 	std::vector<bool> isPortaActive;
 
- 	CHANNELINDEX numChannels, curChannel;
 
- 	bool writeT125;
 
- };
 
- static bool ValidateHeader(const UltFileHeader &fileHeader)
 
- {
 
- 	if(fileHeader.version < '1'
 
- 		|| fileHeader.version > '4'
 
- 		|| std::memcmp(fileHeader.signature, "MAS_UTrack_V00", sizeof(fileHeader.signature))
 
- 		)
 
- 	{
 
- 		return false;
 
- 	}
 
- 	return true;
 
- }
 
- static uint64 GetHeaderMinimumAdditionalSize(const UltFileHeader &fileHeader)
 
- {
 
- 	return fileHeader.messageLength * 32u + 3u + 256u;
 
- }
 
- CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderULT(MemoryFileReader file, const uint64 *pfilesize)
 
- {
 
- 	UltFileHeader fileHeader;
 
- 	if(!file.ReadStruct(fileHeader))
 
- 	{
 
- 		return ProbeWantMoreData;
 
- 	}
 
- 	if(!ValidateHeader(fileHeader))
 
- 	{
 
- 		return ProbeFailure;
 
- 	}
 
- 	return ProbeAdditionalSize(file, pfilesize, GetHeaderMinimumAdditionalSize(fileHeader));
 
- }
 
- bool CSoundFile::ReadULT(FileReader &file, ModLoadingFlags loadFlags)
 
- {
 
- 	file.Rewind();
 
- 	UltFileHeader fileHeader;
 
- 	if(!file.ReadStruct(fileHeader))
 
- 	{
 
- 		return false;
 
- 	}
 
- 	if(!ValidateHeader(fileHeader))
 
- 	{
 
- 		return false;
 
- 	}
 
- 	if(loadFlags == onlyVerifyHeader)
 
- 	{
 
- 		return true;
 
- 	}
 
- 	if(!file.CanRead(mpt::saturate_cast<FileReader::off_t>(GetHeaderMinimumAdditionalSize(fileHeader))))
 
- 	{
 
- 		return false;
 
- 	}
 
- 	InitializeGlobals(MOD_TYPE_ULT);
 
- 	m_songName = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, fileHeader.songName);
 
- 	const mpt::uchar *versions[] = {UL_("<1.4"), UL_("1.4"), UL_("1.5"), UL_("1.6")};
 
- 	m_modFormat.formatName = U_("UltraTracker");
 
- 	m_modFormat.type = U_("ult");
 
- 	m_modFormat.madeWithTracker = U_("UltraTracker ") + versions[fileHeader.version - '1'];
 
- 	m_modFormat.charset = mpt::Charset::CP437;
 
- 	m_SongFlags = SONG_ITCOMPATGXX | SONG_ITOLDEFFECTS;  // this will be converted to IT format by MPT.
 
- 	// Read "messageLength" lines, each containing 32 characters.
 
- 	m_songMessage.ReadFixedLineLength(file, fileHeader.messageLength * 32, 32, 0);
 
- 	if(SAMPLEINDEX numSamples = file.ReadUint8(); numSamples < MAX_SAMPLES)
 
- 		m_nSamples = numSamples;
 
- 	else
 
- 		return false;
 
- 	for(SAMPLEINDEX smp = 1; smp <= GetNumSamples(); smp++)
 
- 	{
 
- 		UltSample sampleHeader;
 
- 		// Annoying: v4 added a field before the end of the struct
 
- 		if(fileHeader.version >= '4')
 
- 		{
 
- 			file.ReadStruct(sampleHeader);
 
- 		} else
 
- 		{
 
- 			file.ReadStructPartial(sampleHeader, 64);
 
- 			sampleHeader.finetune = sampleHeader.speed;
 
- 			sampleHeader.speed = 8363;
 
- 		}
 
- 		sampleHeader.ConvertToMPT(Samples[smp]);
 
- 		m_szNames[smp] = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, sampleHeader.name);
 
- 	}
 
- 	ReadOrderFromFile<uint8>(Order(), file, 256, 0xFF, 0xFE);
 
- 	if(CHANNELINDEX numChannels = file.ReadUint8() + 1u; numChannels <= MAX_BASECHANNELS)
 
- 		m_nChannels = numChannels;
 
- 	else
 
- 		return false;
 
- 	PATTERNINDEX numPats = file.ReadUint8() + 1;
 
- 	for(CHANNELINDEX chn = 0; chn < GetNumChannels(); chn++)
 
- 	{
 
- 		ChnSettings[chn].Reset();
 
- 		if(fileHeader.version >= '3')
 
- 			ChnSettings[chn].nPan = ((file.ReadUint8() & 0x0F) << 4) + 8;
 
- 		else
 
- 			ChnSettings[chn].nPan = (chn & 1) ? 192 : 64;
 
- 	}
 
- 	Patterns.ResizeArray(numPats);
 
- 	for(PATTERNINDEX pat = 0; pat < numPats; pat++)
 
- 	{
 
- 		if(!Patterns.Insert(pat, 64))
 
- 			return false;
 
- 	}
 
- 	for(CHANNELINDEX chn = 0; chn < m_nChannels; chn++)
 
- 	{
 
- 		ModCommand evnote;
 
- 		for(PATTERNINDEX pat = 0; pat < numPats && file.CanRead(5); pat++)
 
- 		{
 
- 			ModCommand *note = Patterns[pat].GetpModCommand(0, chn);
 
- 			ROWINDEX row = 0;
 
- 			while(row < 64)
 
- 			{
 
- 				int repeat = ReadULTEvent(evnote, file, fileHeader.version);
 
- 				if(repeat + row > 64)
 
- 					repeat = 64 - row;
 
- 				if(repeat == 0) break;
 
- 				while(repeat--)
 
- 				{
 
- 					*note = evnote;
 
- 					note += GetNumChannels();
 
- 					row++;
 
- 				}
 
- 			}
 
- 		}
 
- 	}
 
- 	// Post-fix some effects.
 
- 	Patterns.ForEachModCommand(PostFixUltCommands(GetNumChannels()));
 
- 	if(loadFlags & loadSampleData)
 
- 	{
 
- 		for(SAMPLEINDEX smp = 1; smp <= GetNumSamples(); smp++)
 
- 		{
 
- 			SampleIO(
 
- 				Samples[smp].uFlags[CHN_16BIT] ? SampleIO::_16bit : SampleIO::_8bit,
 
- 				SampleIO::mono,
 
- 				SampleIO::littleEndian,
 
- 				SampleIO::signedPCM)
 
- 				.ReadSample(Samples[smp], file);
 
- 		}
 
- 	}
 
- 	return true;
 
- }
 
- OPENMPT_NAMESPACE_END
 
 
  |