File: Load_etx.cpp

package info (click to toggle)
libopenmpt 0.8.4-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 9,844 kB
  • sloc: cpp: 129,441; sh: 4,695; ansic: 1,107; makefile: 712
file content (177 lines) | stat: -rw-r--r-- 4,787 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
/*
 * Load_etx.cpp
 * ------------
 * Purpose: EasyTrax (ETX) module loader
 * Notes  : (currently none)
 * Authors: OpenMPT Devs
 * 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 ETXFileHeader
{
	char     magic[14];  // 'EASYTRAX 1.0\x01\x00'
	uint8    tempo;
	uint8    lastPattern;
	uint32le orderlistOffset;
	uint32le patternsOffset;
	uint32le sampleHeadersOffset;
	uint32le sampleDataOffset;

	bool IsValid() const
	{
		return !memcmp(magic, "EASYTRAX 1.0\x01\x00", 14)
			&& tempo > 0
			&& lastPattern <= 127
			&& orderlistOffset >= 32 && orderlistOffset < 0x80'0000
			&& patternsOffset >= 32 && patternsOffset < 0x80'0000
			&& sampleHeadersOffset >= 32 && sampleHeadersOffset < 0x80'0000
			&& sampleDataOffset >= 32 && sampleDataOffset < 0x80'0000;
	}

	uint32 GetHeaderMinimumAdditionalSize() const
	{
		return 1024;  // Order list
	}
};

MPT_BINARY_STRUCT(ETXFileHeader, 32)


struct ETXSampleHeader
{
	char     name[13];
	uint32le offset;  // Relative to fileHeader.sampleDataOffset
	uint32le length;
	uint32le loopStart;  // 0xFFFFFFFF if no loop
	uint32le sampleRate;
	int8     transpose;
	int8     finetune;
	uint8    zero;

	void ConvertToMPT(ModSample &mptSmp) const
	{
		mptSmp.Initialize();
		mptSmp.nC5Speed = sampleRate;
		mptSmp.nLength = length;
		if(loopStart != uint32_max)
		{
			mptSmp.nLoopStart = loopStart;
			mptSmp.nLoopEnd = mptSmp.nLength;
			mptSmp.uFlags.set(CHN_LOOP);
		}
		mptSmp.Transpose((transpose * 100 + finetune) / 1200.0);
	}
};

MPT_BINARY_STRUCT(ETXSampleHeader, 32)


CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderETX(MemoryFileReader file, const uint64 *pfilesize)
{
	ETXFileHeader fileHeader;
	if(!file.ReadStruct(fileHeader))
		return ProbeWantMoreData;
	if(!fileHeader.IsValid())
		return ProbeFailure;
	return ProbeAdditionalSize(file, pfilesize, fileHeader.GetHeaderMinimumAdditionalSize());
}


bool CSoundFile::ReadETX(FileReader &file, ModLoadingFlags loadFlags)
{
	ETXFileHeader fileHeader;
	file.Rewind();
	if(!file.ReadStruct(fileHeader) || !fileHeader.IsValid())
		return false;
	if(!file.CanRead(fileHeader.GetHeaderMinimumAdditionalSize()))
		return false;
	if(loadFlags == onlyVerifyHeader)
		return true;

	InitializeGlobals(MOD_TYPE_S3M, 4);
	m_SongFlags.set(SONG_IMPORTED);
	m_playBehaviour.reset(kST3EffectMemory);
	m_nMinPeriod = 218;  // Highest possible sample playback rate appears to be 65535 Hz
	m_nSamples = 128;
	Order().SetDefaultTempoInt(fileHeader.tempo);
	Order().SetDefaultSpeed(6);

	if(!file.Seek(fileHeader.orderlistOffset))
		return false;
	ReadOrderFromFile<uint8>(Order(), file, 1024, 0xFF);
	for(ORDERINDEX ord = 0; ord < Order().size(); ord++)
	{
		if(Order()[ord] == PATTERNINDEX_INVALID)
			Order().resize(ord);
		else if(Order()[ord] > 127)
			return false;
	}

	if(!file.Seek(fileHeader.patternsOffset))
		return false;
	Patterns.ResizeArray(fileHeader.lastPattern + 1);
	for(PATTERNINDEX pat = 0; pat < Patterns.Size(); pat++)
	{
		if(!(loadFlags & loadPatternData) || !file.CanRead(1024) || !Patterns.Insert(pat, 64))
			break;

		auto m = Patterns[pat].begin();
		for(ROWINDEX row = 0; row < Patterns[pat].GetNumRows(); row++)
		{
			for(CHANNELINDEX chn = 0; chn < 4; chn++, m++)
			{
				const auto [note, vol, instr, unused] = file.ReadArray<uint8, 4>();
				MPT_UNUSED_VARIABLE(unused);
				if(note == 0xFF && !chn)
				{
					if(!Patterns[pat].WriteEffect(EffectWriter(CMD_PATTERNBREAK, 0).Row(std::max(row, ROWINDEX(1)) - 1)))
					{
						Patterns[pat].Resize(row, false);
						break;
					}
				} else if(note == 0xFE)
				{
					m->SetEffectCommand(CMD_VOLUMEDOWN_ETX, vol);
				} else if(note > 0 && note <= 96)
				{
					m->note = NOTE_MIDDLEC - 24 + note;
					m->instr = instr + 1;
					m->SetVolumeCommand(VOLCMD_VOLUME, static_cast<ModCommand::VOL>((std::min(vol, uint8(127)) + 1u) / 2u));
				}
			}
		}
	}

	if(!file.Seek(fileHeader.sampleHeadersOffset))
		return false;
	FileReader sampleHeaderChunk = file.ReadChunk(128 * sizeof(ETXSampleHeader));
	for(SAMPLEINDEX smp = 1; smp <= m_nSamples; smp++)
	{
		ETXSampleHeader sampleHeader;
		sampleHeaderChunk.ReadStruct(sampleHeader);
		sampleHeader.ConvertToMPT(Samples[smp]);
		m_szNames[smp] = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, sampleHeader.name);
		if(loadFlags & loadSampleData)
		{
			if(!file.Seek(fileHeader.sampleDataOffset + sampleHeader.offset))
				return false;
			SampleIO(SampleIO::_8bit, SampleIO::mono, SampleIO::littleEndian, SampleIO::unsignedPCM)
				.ReadSample(Samples[smp], file);
		}
	}

	m_modFormat.formatName = UL_("EasyTrax");
	m_modFormat.type = UL_("etx");
	m_modFormat.charset = mpt::Charset::CP437;

	return true;
}


OPENMPT_NAMESPACE_END