| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148 | /* * load_dmf.cpp * ------------ * Purpose: DMF module loader (X-Tracker by D-LUSiON). * Notes  : If it wasn't already outdated when the tracker left beta state, this would be a rather interesting *          and in some parts even sophisticated format - effect columns are separated by effect type, an easy to *          understand BPM tempo mode, effect durations are always divided into a 256th row, vibrato effects are *          specified by period length and the same 8-Bit granularity is used for both volume and panning. *          Unluckily, this format does not offer any envelopes or multi-sample instruments, and bidi sample loops *          are missing as well, so it was already well behind FT2 back then. * Authors: Johannes Schultz (mostly based on DMF.TXT, DMF_EFFC.TXT, trial and error and some invaluable hints by Zatzen) * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. */#include "stdafx.h"#include "Loaders.h"#include "BitReader.h"OPENMPT_NAMESPACE_BEGIN// DMF headerstruct DMFFileHeader{	char   signature[4];  // "DDMF"	uint8  version;       // 1 - 7 are beta versions, 8 is the official thing, 10 is xtracker32	char   tracker[8];    // "XTRACKER"	char   songname[30];	char   composer[20];	uint8  creationDay;	uint8  creationMonth;	uint8  creationYear;};MPT_BINARY_STRUCT(DMFFileHeader, 66)struct DMFChunk{	// 32-Bit chunk identifiers	enum ChunkIdentifiers	{		idCMSG = MagicLE("CMSG"),  // Song message		idSEQU = MagicLE("SEQU"),  // Order list		idPATT = MagicLE("PATT"),  // Patterns		idSMPI = MagicLE("SMPI"),  // Sample headers		idSMPD = MagicLE("SMPD"),  // Sample data		idSMPJ = MagicLE("SMPJ"),  // Sample jump table (XTracker 32 only)		idENDE = MagicLE("ENDE"),  // Last four bytes of DMF file		idSETT = MagicLE("SETT"),  // Probably contains GUI settings	};	uint32le id;	uint32le length;	size_t GetLength() const	{		return length;	}	ChunkIdentifiers GetID() const	{		return static_cast<ChunkIdentifiers>(id.get());	}};MPT_BINARY_STRUCT(DMFChunk, 8)// Pattern header (global)struct DMFPatterns{	uint16le numPatterns;  // 1..1024 patterns	uint8le  numTracks;    // 1..32 channels};MPT_BINARY_STRUCT(DMFPatterns, 3)// Pattern header (for each pattern)struct DMFPatternHeader{	uint8le  numTracks;  // 1..32 channels	uint8le  beat;       // [hi|lo] -> hi = rows per beat, lo = reserved	uint16le numRows;	uint32le patternLength;	// patttern data follows here ...};MPT_BINARY_STRUCT(DMFPatternHeader, 8)// Sample headerstruct DMFSampleHeader{	enum SampleFlags	{		// Sample flags		smpLoop     = 0x01,		smp16Bit    = 0x02,		smpCompMask = 0x0C,		smpComp1    = 0x04,  // Compression type 1		smpComp2    = 0x08,  // Compression type 2 (unused)		smpComp3    = 0x0C,  // Compression type 3 (ditto)		smpLibrary  = 0x80,  // Sample is stored in a library	};	uint32le length;	uint32le loopStart;	uint32le loopEnd;	uint16le c3freq;  // 1000..45000hz	uint8le  volume;  // 0 = ignore	uint8le  flags;	// Convert an DMFSampleHeader to OpenMPT's internal sample representation.	void ConvertToMPT(ModSample &mptSmp) const	{		mptSmp.Initialize();		mptSmp.nLength = length;		mptSmp.nSustainStart = loopStart;		mptSmp.nSustainEnd = loopEnd;		mptSmp.nC5Speed = c3freq;		mptSmp.nGlobalVol = 64;		if(volume)			mptSmp.nVolume = volume + 1;		else			mptSmp.nVolume = 256;		mptSmp.uFlags.set(SMP_NODEFAULTVOLUME, volume == 0);		if((flags & smpLoop) != 0 && mptSmp.nSustainEnd > mptSmp.nSustainStart)		{			mptSmp.uFlags.set(CHN_SUSTAINLOOP);		}		if((flags & smp16Bit) != 0)		{			mptSmp.uFlags.set(CHN_16BIT);			mptSmp.nLength /= 2;			mptSmp.nSustainStart /= 2;			mptSmp.nSustainEnd /= 2;		}	}};MPT_BINARY_STRUCT(DMFSampleHeader, 16)// Pattern translation memorystruct DMFPatternSettings{	struct ChannelState	{		ModCommand::NOTE noteBuffer = NOTE_NONE; // Note buffer		ModCommand::NOTE lastNote = NOTE_NONE;   // Last played note on channel		uint8 vibratoType = 8;                   // Last used vibrato type on channel		uint8 tremoloType = 4;                   // Last used tremolo type on channel		uint8 highOffset = 6;                    // Last used high offset on channel		bool playDir = false;                    // Sample play direction... false = forward (default)	};	std::vector<ChannelState> channels; // Memory for each channel's state	bool realBPMmode = false;           // true = BPM mode	uint8 beat = 0;                     // Rows per beat	uint8 tempoTicks = 32;              // Tick mode param	uint8 tempoBPM = 120;               // BPM mode param	uint8 internalTicks = 6;            // Ticks per row in final pattern	DMFPatternSettings(CHANNELINDEX numChannels)	    : channels(numChannels)	{ }};// Convert portamento value (not very accurate due to X-Tracker's higher granularity, to say the least)static uint8 DMFporta2MPT(uint8 val, const uint8 internalTicks, const bool hasFine){	if(val == 0)		return 0;	else if((val <= 0x0F && hasFine) || internalTicks < 2)		return (val | 0xF0);	else		return std::max(uint8(1), static_cast<uint8>((val / (internalTicks - 1))));	// no porta on first tick!}// Convert portamento / volume slide value (not very accurate due to X-Tracker's higher granularity, to say the least)static uint8 DMFslide2MPT(uint8 val, const uint8 internalTicks, const bool up){	val = std::max(uint8(1), static_cast<uint8>(val / 4));	const bool isFine = (val < 0x0F) || (internalTicks < 2);	if(!isFine)		val = std::max(uint8(1), static_cast<uint8>((val + internalTicks - 2) / (internalTicks - 1)));	// no slides on first tick! "+ internalTicks - 2" for rounding precision	if(up)		return (isFine ? 0x0F : 0x00) | (val << 4);	else		return (isFine ? 0xF0 : 0x00) | (val & 0x0F);}// Calculate tremor on/off paramstatic uint8 DMFtremor2MPT(uint8 val, const uint8 internalTicks){	uint8 ontime = (val >> 4);	uint8 offtime = (val & 0x0F);	ontime = static_cast<uint8>(Clamp(ontime * internalTicks / 15, 1, 15));	offtime = static_cast<uint8>(Clamp(offtime * internalTicks / 15, 1, 15));	return (ontime << 4) | offtime;}// Calculate delay parameter for note cuts / delaysstatic uint8 DMFdelay2MPT(uint8 val, const uint8 internalTicks){	int newval = (int)val * (int)internalTicks / 255;	Limit(newval, 0, 15);	return (uint8)newval;}// Convert vibrato-style command parametersstatic uint8 DMFvibrato2MPT(uint8 val, const uint8 internalTicks){	// MPT: 1 vibrato period == 64 ticks... we have internalTicks ticks per row.	// X-Tracker: Period length specified in rows!	const int periodInTicks = std::max(1, (val >> 4)) * internalTicks;	const uint8 matchingPeriod = static_cast<uint8>(Clamp((128 / periodInTicks), 1, 15));	return (matchingPeriod << 4) | std::max(uint8(1), static_cast<uint8>(val & 0x0F));}// Try using effect memory (zero paramer) to give the effect swapper some optimization hints.static void ApplyEffectMemory(const ModCommand *m, ROWINDEX row, CHANNELINDEX numChannels, uint8 effect, uint8 ¶m){	if(effect == CMD_NONE || param == 0)		return;	const bool isTonePortaEffect = (effect == CMD_PORTAMENTOUP || effect == CMD_PORTAMENTODOWN || effect == CMD_TONEPORTAMENTO);	const bool isVolSlideEffect = (effect == CMD_VOLUMESLIDE || effect == CMD_TONEPORTAVOL || effect == CMD_VIBRATOVOL);	while(row > 0)	{		m -= numChannels;		row--;		// First, keep some extra rules in mind for portamento, where effect memory is shared between various commands.		bool isSame = (effect == m->command);		if(isTonePortaEffect && (m->command == CMD_PORTAMENTOUP || m->command == CMD_PORTAMENTODOWN || m->command == CMD_TONEPORTAMENTO))		{			if(m->param < 0xE0)			{				// Avoid effect param for fine slides, or else we could accidentally put this command in the volume column, where fine slides won't work!				isSame = true;			} else			{				return;			}		} else if(isVolSlideEffect && (m->command == CMD_VOLUMESLIDE || m->command == CMD_TONEPORTAVOL || m->command == CMD_VIBRATOVOL))		{			isSame = true;		}		if(isTonePortaEffect			&& (m->volcmd == VOLCMD_PORTAUP || m->volcmd == VOLCMD_PORTADOWN || m->volcmd == VOLCMD_TONEPORTAMENTO)			&& m->vol != 0)		{			// Uuh... Don't even try			return;		} else if(isVolSlideEffect			&& (m->volcmd == VOLCMD_FINEVOLUP || m->volcmd == VOLCMD_FINEVOLDOWN || m->volcmd == VOLCMD_VOLSLIDEUP || m->volcmd == VOLCMD_VOLSLIDEDOWN)			&& m->vol != 0)		{			// Same!			return;		}		if(isSame)		{			if(param != m->param && m->param != 0)			{				// No way to optimize this				return;			} else if(param == m->param)			{				// Yay!				param = 0;				return;			}		}	}}static PATTERNINDEX ConvertDMFPattern(FileReader &file, const uint8 fileVersion, DMFPatternSettings &settings, CSoundFile &sndFile){	// Pattern flags	enum PatternFlags	{		// Global Track		patGlobPack = 0x80,  // Pack information for global track follows		patGlobMask = 0x3F,  // Mask for global effects		// Note tracks		patCounter = 0x80,  // Pack information for current channel follows		patInstr   = 0x40,  // Instrument number present		patNote    = 0x20,  // Note present		patVolume  = 0x10,  // Volume present		patInsEff  = 0x08,  // Instrument effect present		patNoteEff = 0x04,  // Note effect present		patVolEff  = 0x02,  // Volume effect stored	};	file.Rewind();		DMFPatternHeader patHead;	if(fileVersion < 3)	{		patHead.numTracks = file.ReadUint8();		file.Skip(2);  // not sure what this is, later X-Tracker versions just skip over it		patHead.numRows = file.ReadUint16LE();		patHead.patternLength = file.ReadUint32LE();	} else	{		file.ReadStruct(patHead);	}	if(fileVersion < 6)		patHead.beat = 0;	const ROWINDEX numRows = Clamp(ROWINDEX(patHead.numRows), ROWINDEX(1), MAX_PATTERN_ROWS);	const PATTERNINDEX pat = sndFile.Patterns.InsertAny(numRows);	if(pat == PATTERNINDEX_INVALID)	{		return pat;	}	PatternRow m = sndFile.Patterns[pat].GetRow(0);	const CHANNELINDEX numChannels = std::min(static_cast<CHANNELINDEX>(sndFile.GetNumChannels() - 1), static_cast<CHANNELINDEX>(patHead.numTracks));	// When breaking to a pattern with less channels that the previous pattern,	// all voices in the now unused channels are killed:	for(CHANNELINDEX chn = numChannels + 1; chn < sndFile.GetNumChannels(); chn++)	{		m[chn].note = NOTE_NOTECUT;	}	// Initialize tempo stuff	settings.beat = (patHead.beat >> 4);	bool tempoChange = settings.realBPMmode;	uint8 writeDelay = 0;	// Counters for channel packing (including global track)	std::vector<uint8> channelCounter(numChannels + 1, 0);	for(ROWINDEX row = 0; row < numRows; row++)	{		// Global track info counter reached 0 => read global track data		if(channelCounter[0] == 0)		{			uint8 globalInfo = file.ReadUint8();			// 0x80: Packing counter (if not present, counter stays at 0)			if((globalInfo & patGlobPack) != 0)			{				channelCounter[0] = file.ReadUint8();			}			globalInfo &= patGlobMask;			uint8 globalData = 0;			if(globalInfo != 0)			{				globalData = file.ReadUint8();			}			switch(globalInfo)			{			case 1:  // Set Tick Frame Speed				settings.realBPMmode = false;				settings.tempoTicks = std::max(uint8(1), globalData);  // Tempo in 1/4 rows per second				settings.tempoBPM = 0;                                 // Automatically updated by X-Tracker				tempoChange = true;				break;			case 2:             // Set BPM Speed (real BPM mode)				if(globalData)  // DATA = 0 doesn't do anything				{					settings.realBPMmode = true;					settings.tempoBPM = globalData;  // Tempo in real BPM (depends on rows per beat)					if(settings.beat != 0)					{						settings.tempoTicks = (globalData * settings.beat * 15);	// Automatically updated by X-Tracker					}					tempoChange = true;				}				break;			case 3:  // Set Beat				settings.beat = (globalData >> 4);				if(settings.beat != 0)				{					// Tempo changes only if we're in real BPM mode					tempoChange = settings.realBPMmode;				} else				{					// If beat is 0, change to tick speed mode, but keep current tempo					settings.realBPMmode = false;				}				break;			case 4:  // Tick Delay				writeDelay = globalData;				break;			case 5:  // Set External Flag				break;			case 6:  // Slide Speed Up				if(globalData > 0)				{					uint8 &tempoData = (settings.realBPMmode) ? settings.tempoBPM : settings.tempoTicks;					if(tempoData < 256 - globalData)					{						tempoData += globalData;					} else					{						tempoData = 255;					}					tempoChange = true;				}				break;			case 7:  // Slide Speed Down				if(globalData > 0)				{					uint8 &tempoData = (settings.realBPMmode) ? settings.tempoBPM : settings.tempoTicks;					if(tempoData > 1 + globalData)					{						tempoData -= globalData;					} else					{						tempoData = 1;					}					tempoChange = true;				}				break;			}		} else		{			channelCounter[0]--;		}		// These will eventually be written to the pattern		int speed = 0, tempo = 0;		if(tempoChange)		{			// Can't do anything if we're in BPM mode and there's no rows per beat set...			if(!settings.realBPMmode || settings.beat)			{				// My approach to convert X-Tracker's "tick speed" (1/4 rows per second):				// Tempo * 6 / Speed = Beats per Minute				// => Tempo * 6 / (Speed * 60) = Beats per Second				// => Tempo * 24 / (Speed * 60) = Rows per Second (4 rows per beat at tempo 6)				// => Tempo = 60 * Rows per Second * Speed / 24				// For some reason, using settings.tempoTicks + 1 gives more accurate results than just settings.tempoTicks... (same problem in the old libmodplug DMF loader)				// Original unoptimized formula:				//const int tickspeed = (tempoRealBPMmode) ? std::max(1, (tempoData * beat * 4) / 60) : tempoData;				const int tickspeed = (settings.realBPMmode) ? std::max(1, settings.tempoBPM * settings.beat * 2) : ((settings.tempoTicks + 1) * 30);				// Try to find matching speed - try higher speeds first, so that effects like arpeggio and tremor work better.				for(speed = 255; speed >= 1; speed--)				{					// Original unoptimized formula:					// tempo = 30 * tickspeed * speed / 48;					tempo = tickspeed * speed / 48;					if(tempo >= 32 && tempo <= 255)						break;				}				Limit(tempo, 32, 255);				settings.internalTicks = static_cast<uint8>(std::max(1, speed));			} else			{				tempoChange = false;			}		}		m = sndFile.Patterns[pat].GetpModCommand(row, 1);  // Reserve first channel for global effects		for(CHANNELINDEX chn = 1; chn <= numChannels; chn++, m++)		{			// Track info counter reached 0 => read track data			if(channelCounter[chn] == 0)			{				const uint8 channelInfo = file.ReadUint8();				////////////////////////////////////////////////////////////////				// 0x80: Packing counter (if not present, counter stays at 0)				if((channelInfo & patCounter) != 0)				{					channelCounter[chn] = file.ReadUint8();				}				////////////////////////////////////////////////////////////////				// 0x40: Instrument				bool slideNote = true;  // If there is no instrument number next to a note, the note is not retriggered!				if((channelInfo & patInstr) != 0)				{					m->instr = file.ReadUint8();					if(m->instr != 0)					{						slideNote = false;					}				}				////////////////////////////////////////////////////////////////				// 0x20: Note				if((channelInfo & patNote) != 0)				{					m->note = file.ReadUint8();					if(m->note >= 1 && m->note <= 108)					{						m->note = static_cast<uint8>(Clamp(m->note + 24, NOTE_MIN, NOTE_MAX));						settings.channels[chn].lastNote = m->note;					} else if(m->note >= 129 && m->note <= 236)					{						// "Buffer notes" for portamento (and other effects?) that are actually not played, but just "queued"...						m->note = static_cast<uint8>(Clamp((m->note & 0x7F) + 24, NOTE_MIN, NOTE_MAX));						settings.channels[chn].noteBuffer = m->note;						m->note = NOTE_NONE;					} else if(m->note == 255)					{						m->note = NOTE_NOTECUT;					}				}				// If there's just an instrument number, but no note, retrigger sample.				if(m->note == NOTE_NONE && m->instr > 0)				{					m->note = settings.channels[chn].lastNote;					m->instr = 0;				}				if(m->IsNote())				{					settings.channels[chn].playDir = false;				}				uint8 effect1 = CMD_NONE, effect2 = CMD_NONE, effect3 = CMD_NONE;				uint8 effectParam1 = 0, effectParam2 = 0, effectParam3 = 0;				bool useMem2 = false, useMem3 = false;	// Effect can use memory if necessary				////////////////////////////////////////////////////////////////				// 0x10: Volume				if((channelInfo & patVolume) != 0)				{					m->volcmd = VOLCMD_VOLUME;					m->vol = (file.ReadUint8() + 2) / 4;  // Should be + 3 instead of + 2, but volume 1 is silent in X-Tracker.				}				////////////////////////////////////////////////////////////////				// 0x08: Instrument effect				if((channelInfo & patInsEff) != 0)				{					effect1 = file.ReadUint8();					effectParam1 = file.ReadUint8();					switch(effect1)					{					case 1:  // Stop Sample						m->note = NOTE_NOTECUT;						effect1 = CMD_NONE;						break;					case 2:  // Stop Sample Loop						m->note = NOTE_KEYOFF;						effect1 = CMD_NONE;						break;					case 3:  // Instrument Volume Override (aka "Restart")						m->note = settings.channels[chn].lastNote;						settings.channels[chn].playDir = false;						effect1 = CMD_NONE;						break;					case 4:  // Sample Delay						effectParam1 = DMFdelay2MPT(effectParam1, settings.internalTicks);						if(effectParam1)						{							effect1 = CMD_S3MCMDEX;							effectParam1 = 0xD0 | (effectParam1);						} else						{							effect1 = CMD_NONE;						}						if(m->note == NOTE_NONE)						{							m->note = settings.channels[chn].lastNote;							settings.channels[chn].playDir = false;						}						break;					case 5:  // Tremolo Retrig Sample (who invented those stupid effect names?)						effectParam1 = std::max(uint8(1), DMFdelay2MPT(effectParam1, settings.internalTicks));						effect1 = CMD_RETRIG;						settings.channels[chn].playDir = false;						break;					case 6:  // Offset					case 7:  // Offset + 64k					case 8:  // Offset + 128k					case 9:  // Offset + 192k						// Put high offset on previous row						if(row > 0 && effect1 != settings.channels[chn].highOffset)						{							if(sndFile.Patterns[pat].WriteEffect(EffectWriter(CMD_S3MCMDEX, (0xA0 | (effect1 - 6))).Row(row - 1).Channel(chn).RetryPreviousRow()))							{								settings.channels[chn].highOffset = effect1;							}						}						effect1 = CMD_OFFSET;						if(m->note == NOTE_NONE)						{							// Offset without note does also work in DMF.							m->note = settings.channels[chn].lastNote;						}						settings.channels[chn].playDir = false;						break;					case 10:	// Invert Sample play direction ("Tekkno Invert")						effect1 = CMD_S3MCMDEX;						if(settings.channels[chn].playDir == false)							effectParam1 = 0x9F;						else							effectParam1 = 0x9E;						settings.channels[chn].playDir = !settings.channels[chn].playDir;						break;					default:						effect1 = CMD_NONE;						break;					}				}				////////////////////////////////////////////////////////////////				// 0x04: Note effect				if((channelInfo & patNoteEff) != 0)				{					effect2 = file.ReadUint8();					effectParam2 = file.ReadUint8();					switch(effect2)					{					case 1:  // Note Finetune (1/16th of a semitone signed 8-bit value, not 1/128th as the interface claims)						{							const auto fine = std::div(static_cast<int8>(effectParam2) * 8, 128);							if(m->IsNote())								m->note = static_cast<ModCommand::NOTE>(Clamp(m->note + fine.quot, NOTE_MIN, NOTE_MAX));							effect2 = CMD_FINETUNE;							effectParam2 = static_cast<uint8>(fine.rem) ^ 0x80;						}						break;					case 2:  // Note Delay (wtf is the difference to Sample Delay?)						effectParam2 = DMFdelay2MPT(effectParam2, settings.internalTicks);						if(effectParam2)						{							effect2 = CMD_S3MCMDEX;							effectParam2 = 0xD0 | (effectParam2);						} else						{							effect2 = CMD_NONE;						}						useMem2 = true;						break;					case 3:  // Arpeggio						effect2 = CMD_ARPEGGIO;						useMem2 = true;						break;					case 4:  // Portamento Up					case 5:  // Portamento Down						effectParam2 = DMFporta2MPT(effectParam2, settings.internalTicks, true);						effect2 = (effect2 == 4) ? CMD_PORTAMENTOUP : CMD_PORTAMENTODOWN;						useMem2 = true;						break;					case 6:  // Portamento to Note						if(m->note == NOTE_NONE)						{							m->note = settings.channels[chn].noteBuffer;						}						effectParam2 = DMFporta2MPT(effectParam2, settings.internalTicks, false);						effect2 = CMD_TONEPORTAMENTO;						useMem2 = true;						break;					case 7:  // Scratch to Note (neat! but we don't have such an effect...)						m->note = static_cast<ModCommand::NOTE>(Clamp(effectParam2 + 25, NOTE_MIN, NOTE_MAX));						effect2 = CMD_TONEPORTAMENTO;						effectParam2 = 0xFF;						useMem2 = true;						break;					case 8:   // Vibrato Sine					case 9:   // Vibrato Triangle (ramp down should be close enough)					case 10:  // Vibrato Square						// Put vibrato type on previous row						if(row > 0 && effect2 != settings.channels[chn].vibratoType)						{							if(sndFile.Patterns[pat].WriteEffect(EffectWriter(CMD_S3MCMDEX, (0x30 | (effect2 - 8))).Row(row - 1).Channel(chn).RetryPreviousRow()))							{								settings.channels[chn].vibratoType = effect2;							}						}						effect2 = CMD_VIBRATO;						effectParam2 = DMFvibrato2MPT(effectParam2, settings.internalTicks);						useMem2 = true;						break;					case 11:  // Note Tremolo						effectParam2 = DMFtremor2MPT(effectParam2, settings.internalTicks);						effect2 = CMD_TREMOR;						useMem2 = true;						break;					case 12:  // Note Cut						effectParam2 = DMFdelay2MPT(effectParam2, settings.internalTicks);						if(effectParam2)						{							effect2 = CMD_S3MCMDEX;							effectParam2 = 0xC0 | (effectParam2);						} else						{							effect2 = CMD_NONE;							m->note = NOTE_NOTECUT;						}						useMem2 = true;						break;					default:						effect2 = CMD_NONE;						break;					}				}				////////////////////////////////////////////////////////////////				// 0x02: Volume effect				if((channelInfo & patVolEff) != 0)				{					effect3 = file.ReadUint8();					effectParam3 = file.ReadUint8();					switch(effect3)					{					case 1:  // Volume Slide Up					case 2:  // Volume Slide Down						effectParam3 = DMFslide2MPT(effectParam3, settings.internalTicks, (effect3 == 1));						effect3 = CMD_VOLUMESLIDE;						useMem3 = true;						break;					case 3:  // Volume Tremolo (actually this is Tremor)						effectParam3 = DMFtremor2MPT(effectParam3, settings.internalTicks);						effect3 = CMD_TREMOR;						useMem3 = true;						break;					case 4:  // Tremolo Sine					case 5:  // Tremolo Triangle (ramp down should be close enough)					case 6:  // Tremolo Square						// Put tremolo type on previous row						if(row > 0 && effect3 != settings.channels[chn].tremoloType)						{							if(sndFile.Patterns[pat].WriteEffect(EffectWriter(CMD_S3MCMDEX, (0x40 | (effect3 - 4))).Row(row - 1).Channel(chn).RetryPreviousRow()))							{								settings.channels[chn].tremoloType = effect3;							}						}						effect3 = CMD_TREMOLO;						effectParam3 = DMFvibrato2MPT(effectParam3, settings.internalTicks);						useMem3 = true;						break;					case 7:  // Set Balance						effect3 = CMD_PANNING8;						break;					case 8:  // Slide Balance Left					case 9:  // Slide Balance Right						effectParam3 = DMFslide2MPT(effectParam3, settings.internalTicks, (effect3 == 8));						effect3 = CMD_PANNINGSLIDE;						useMem3 = true;						break;					case 10:  // Balance Vibrato Left/Right (always sine modulated)						effect3 = CMD_PANBRELLO;						effectParam3 = DMFvibrato2MPT(effectParam3, settings.internalTicks);						useMem3 = true;						break;					default:						effect3 = CMD_NONE;						break;					}				}				// Let's see if we can help the effect swapper by reducing some effect parameters to "continue" parameters.				if(useMem2)					ApplyEffectMemory(m, row, sndFile.GetNumChannels(), effect2, effectParam2);				if(useMem3)					ApplyEffectMemory(m, row, sndFile.GetNumChannels(), effect3, effectParam3);				// I guess this is close enough to "not retriggering the note"				if(slideNote && m->IsNote())				{					if(effect2 == CMD_NONE)					{						effect2 = CMD_TONEPORTAMENTO;						effectParam2 = 0xFF;					} else if(effect3 == CMD_NONE && effect2 != CMD_TONEPORTAMENTO)	// Tone portamentos normally go in effect #2					{						effect3 = CMD_TONEPORTAMENTO;						effectParam3 = 0xFF;					}				}				// If one of the effects is unused, temporarily put volume commands in there				if(m->volcmd == VOLCMD_VOLUME)				{					if(effect2 == CMD_NONE)					{						effect2 = CMD_VOLUME;						effectParam2 = m->vol;						m->volcmd = VOLCMD_NONE;					} else if(effect3 == CMD_NONE)					{						effect3 = CMD_VOLUME;						effectParam3 = m->vol;						m->volcmd = VOLCMD_NONE;					}				}				ModCommand::TwoRegularCommandsToMPT(effect2, effectParam2, effect3, effectParam3);				if(m->volcmd == VOLCMD_NONE && effect2 != VOLCMD_NONE)				{					m->volcmd = effect2;					m->vol = effectParam2;				}				// Prefer instrument effects over any other effects				if(effect1 != CMD_NONE)				{					ModCommand::TwoRegularCommandsToMPT(effect3, effectParam3, effect1, effectParam1);					if(m->volcmd == VOLCMD_NONE && effect3 != VOLCMD_NONE)					{						m->volcmd = effect3;						m->vol = effectParam3;					}					m->command = effect1;					m->param = effectParam1;				} else if(effect3 != CMD_NONE)				{					m->command = effect3;					m->param = effectParam3;				}			} else			{				channelCounter[chn]--;			}		}  // End for all channels		// Now we can try to write tempo information.		if(tempoChange)		{			tempoChange = false;						sndFile.Patterns[pat].WriteEffect(EffectWriter(CMD_TEMPO, static_cast<ModCommand::PARAM>(tempo)).Row(row).Channel(0).RetryNextRow());			sndFile.Patterns[pat].WriteEffect(EffectWriter(CMD_SPEED, static_cast<ModCommand::PARAM>(speed)).Row(row).RetryNextRow());		}		// Try to put delay effects somewhere as well		if(writeDelay & 0xF0)		{			sndFile.Patterns[pat].WriteEffect(EffectWriter(CMD_S3MCMDEX, 0xE0 | (writeDelay >> 4)).Row(row).AllowMultiple());		}		if(writeDelay & 0x0F)		{			const uint8 param = (writeDelay & 0x0F) * settings.internalTicks / 15;			sndFile.Patterns[pat].WriteEffect(EffectWriter(CMD_S3MCMDEX, 0x60u | Clamp(param, uint8(1), uint8(15))).Row(row).AllowMultiple());		}		writeDelay = 0;	}  // End for all rows	return pat;}static bool ValidateHeader(const DMFFileHeader &fileHeader){	if(std::memcmp(fileHeader.signature, "DDMF", 4)		|| !fileHeader.version || fileHeader.version > 10)	{		return false;	}	return true;}CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderDMF(MemoryFileReader file, const uint64 *pfilesize){	DMFFileHeader fileHeader;	if(!file.ReadStruct(fileHeader))	{		return ProbeWantMoreData;	}	if(!ValidateHeader(fileHeader))	{		return ProbeFailure;	}	MPT_UNREFERENCED_PARAMETER(pfilesize);	return ProbeSuccess;}bool CSoundFile::ReadDMF(FileReader &file, ModLoadingFlags loadFlags){	file.Rewind();	DMFFileHeader fileHeader;	if(!file.ReadStruct(fileHeader))	{		return false;	}	if(!ValidateHeader(fileHeader))	{		return false;	}	if(loadFlags == onlyVerifyHeader)	{		return true;	}	InitializeGlobals(MOD_TYPE_DMF);	m_modFormat.formatName = MPT_UFORMAT("X-Tracker v{}")(fileHeader.version);	m_modFormat.type = U_("dmf");	m_modFormat.charset = mpt::Charset::CP437;	m_songName = mpt::String::ReadBuf(mpt::String::spacePadded, fileHeader.songname);	m_songArtist = mpt::ToUnicode(mpt::Charset::CP437, mpt::String::ReadBuf(mpt::String::spacePadded, fileHeader.composer));	FileHistory mptHistory;	mptHistory.loadDate.tm_mday = Clamp(fileHeader.creationDay, uint8(1), uint8(31));	mptHistory.loadDate.tm_mon = Clamp(fileHeader.creationMonth, uint8(1), uint8(12)) - 1;	mptHistory.loadDate.tm_year = fileHeader.creationYear;	m_FileHistory.clear();	m_FileHistory.push_back(mptHistory);	// Go through all chunks now... cannot use our standard IFF chunk reader here because early X-Tracker versions write some malformed chunk headers... fun code ahead!	ChunkReader::ChunkList<DMFChunk> chunks;	while(file.CanRead(sizeof(DMFChunk)))	{		DMFChunk chunkHeader;		file.Read(chunkHeader);		uint32 chunkLength = chunkHeader.length, chunkSkip = 0;		// When loop start was added to version 3, the chunk size was not updated...		if(fileHeader.version == 3 && chunkHeader.GetID() == DMFChunk::idSEQU)			chunkSkip = 2;		// ...and when the loop end was added to version 4, it was also note updated! Luckily they fixed it in version 5.		else if(fileHeader.version == 4 && chunkHeader.GetID() == DMFChunk::idSEQU)			chunkSkip = 4;		// Earlier X-Tracker versions also write a garbage length for the SMPD chunk if samples are compressed.		// I don't know when exactly this stopped, but I have no version 5-7 files to check (and no X-Tracker version that writes those versions).		// Since this is practically always the last chunk in the file, the following code is safe for those versions, though.		else if(fileHeader.version < 8 && chunkHeader.GetID() == DMFChunk::idSMPD)			chunkLength = uint32_max;		chunks.chunks.push_back(ChunkReader::Item<DMFChunk>{chunkHeader, file.ReadChunk(chunkLength)});		file.Skip(chunkSkip);	}	FileReader chunk;	// Read order list	chunk = chunks.GetChunk(DMFChunk::idSEQU);	ORDERINDEX seqLoopStart = 0, seqLoopEnd = ORDERINDEX_MAX;	if(fileHeader.version >= 3)		seqLoopStart = chunk.ReadUint16LE();	if(fileHeader.version >= 4)		seqLoopEnd = chunk.ReadUint16LE();	// HIPOMATK.DMF has a loop end of 0, other v4 files have proper loop ends. Later X-Tracker versions import it as-is but it cannot be intentional.	// We just assume that this feature might have been buggy in early v4 versions and ignore the loop end in that case.	if(fileHeader.version == 4 && seqLoopEnd == 0)		seqLoopEnd = ORDERINDEX_MAX;	ReadOrderFromFile<uint16le>(Order(), chunk, chunk.BytesLeft() / 2);	LimitMax(seqLoopStart, Order().GetLastIndex());	LimitMax(seqLoopEnd, Order().GetLastIndex());	// Read patterns	chunk = chunks.GetChunk(DMFChunk::idPATT);	if(chunk.IsValid() && (loadFlags & loadPatternData))	{		DMFPatterns patHeader;		chunk.ReadStruct(patHeader);		m_nChannels = Clamp<uint8, uint8>(patHeader.numTracks, 1, 32) + 1;	// + 1 for global track (used for tempo stuff)		// First, find out where all of our patterns are...		std::vector<FileReader> patternChunks(patHeader.numPatterns);		for(auto &patternChunk : patternChunks)		{			const uint8 headerSize = fileHeader.version < 3 ? 9 : 8;			chunk.Skip(headerSize - sizeof(uint32le));			const uint32 patLength = chunk.ReadUint32LE();			chunk.SkipBack(headerSize);			patternChunk = chunk.ReadChunk(headerSize + patLength);		}		// Now go through the order list and load them.		DMFPatternSettings settings(GetNumChannels());		Patterns.ResizeArray(Order().GetLength());		for(PATTERNINDEX &pat : Order())		{			// Create one pattern for each order item, as the same pattern can be played with different settings			if(pat < patternChunks.size())			{				pat = ConvertDMFPattern(patternChunks[pat], fileHeader.version, settings, *this);			}		}		// Write loop end if necessary		if(Order().IsValidPat(seqLoopEnd) && (seqLoopStart > 0 || seqLoopEnd < Order().GetLastIndex()))		{			PATTERNINDEX pat = Order()[seqLoopEnd];			Patterns[pat].WriteEffect(EffectWriter(CMD_POSITIONJUMP, static_cast<ModCommand::PARAM>(seqLoopStart)).Row(Patterns[pat].GetNumRows() - 1).RetryPreviousRow());		}	}	// Read song message	chunk = chunks.GetChunk(DMFChunk::idCMSG);	if(chunk.IsValid())	{		// The song message seems to start at a 1 byte offset.		// The skipped byte seems to always be 0.		// This also matches how XT 1.03 itself displays the song message.		chunk.Skip(1);		m_songMessage.ReadFixedLineLength(chunk, chunk.BytesLeft(), 40, 0);	}		// Read sample headers + data	FileReader sampleDataChunk = chunks.GetChunk(DMFChunk::idSMPD);	chunk = chunks.GetChunk(DMFChunk::idSMPI);	m_nSamples = chunk.ReadUint8();	for(SAMPLEINDEX smp = 1; smp <= GetNumSamples(); smp++)	{		const uint8 nameLength = (fileHeader.version < 2) ? 30 : chunk.ReadUint8();		chunk.ReadString<mpt::String::spacePadded>(m_szNames[smp], nameLength);		DMFSampleHeader sampleHeader;		ModSample &sample = Samples[smp];		chunk.ReadStruct(sampleHeader);		sampleHeader.ConvertToMPT(sample);		// Read library name in version 8 files		if(fileHeader.version >= 8)			chunk.ReadString<mpt::String::spacePadded>(sample.filename, 8);		// Filler + CRC		chunk.Skip(fileHeader.version > 1 ? 6 : 2);		// Now read the sample data from the data chunk		FileReader sampleData = sampleDataChunk.ReadChunk(sampleDataChunk.ReadUint32LE());		if(sampleData.IsValid() && (loadFlags & loadSampleData))		{			SampleIO(				sample.uFlags[CHN_16BIT] ? SampleIO::_16bit : SampleIO::_8bit,				SampleIO::mono,				SampleIO::littleEndian,				(sampleHeader.flags & DMFSampleHeader::smpCompMask) == DMFSampleHeader::smpComp1 ? SampleIO::DMF : SampleIO::signedPCM)				.ReadSample(sample, sampleData);		}	}	InitializeChannels();	m_SongFlags = SONG_LINEARSLIDES | SONG_ITCOMPATGXX;  // this will be converted to IT format by MPT. SONG_ITOLDEFFECTS is not set because of tremor and vibrato.	m_nDefaultSpeed = 6;	m_nDefaultTempo.Set(120);	m_nDefaultGlobalVolume = 256;	m_nSamplePreAmp = m_nVSTiVolume = 48;	m_playBehaviour.set(kApplyOffsetWithoutNote);	return true;}///////////////////////////////////////////////////////////////////////// DMF Compressionstruct DMFHNode{	int16 left, right;	uint8 value;};struct DMFHTree{	BitReader file;	int lastnode = 0, nodecount = 0;	DMFHNode nodes[256]{};	DMFHTree(FileReader &file)	    : file(file)	{	}		//	// tree: [8-bit value][12-bit index][12-bit index] = 32-bit	//	void DMFNewNode()	{		int actnode = nodecount;		if(actnode > 255) return;		nodes[actnode].value = static_cast<uint8>(file.ReadBits(7));		bool isLeft = file.ReadBits(1) != 0;		bool isRight = file.ReadBits(1) != 0;		actnode = lastnode;		if(actnode > 255) return;		nodecount++;		lastnode = nodecount;		if(isLeft)		{			nodes[actnode].left = (int16)lastnode;			DMFNewNode();		} else		{			nodes[actnode].left = -1;		}		lastnode = nodecount;		if(isRight)		{			nodes[actnode].right = (int16)lastnode;			DMFNewNode();		} else		{			nodes[actnode].right = -1;		}	}};uintptr_t DMFUnpack(FileReader &file, uint8 *psample, uint32 maxlen){	DMFHTree tree(file);	uint8 value = 0, delta = 0;	try	{		tree.DMFNewNode();		if(tree.nodes[0].left < 0 || tree.nodes[0].right < 0)			return tree.file.GetPosition();		for(uint32 i = 0; i < maxlen; i++)		{			int actnode = 0;			bool sign = tree.file.ReadBits(1) != 0;			do			{				if(tree.file.ReadBits(1))					actnode = tree.nodes[actnode].right;				else					actnode = tree.nodes[actnode].left;				if(actnode > 255) break;				delta = tree.nodes[actnode].value;			} while((tree.nodes[actnode].left >= 0) && (tree.nodes[actnode].right >= 0));			if(sign) delta ^= 0xFF;			value += delta;			psample[i] = value;		}	} catch(const BitReader::eof &)	{		//AddToLog(LogWarning, "Truncated DMF sample block");	}	return tree.file.GetPosition();}OPENMPT_NAMESPACE_END
 |