1 /*
2  * ModInstrument.cpp
3  * -----------------
4  * Purpose: Helper functions for Module Instrument handling
5  * Notes  : (currently none)
6  * Authors: OpenMPT Devs
7  * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
8  */
9 
10 
11 #include "stdafx.h"
12 #include "Sndfile.h"
13 #include "ModInstrument.h"
14 
15 
16 OPENMPT_NAMESPACE_BEGIN
17 
18 
19 // Convert envelope data between various formats.
Convert(MODTYPE fromType,MODTYPE toType)20 void InstrumentEnvelope::Convert(MODTYPE fromType, MODTYPE toType)
21 {
22 	if(!(fromType & MOD_TYPE_XM) && (toType & MOD_TYPE_XM))
23 	{
24 		// IT / MPTM -> XM: Expand loop by one tick, convert sustain loops to sustain points, remove carry flag.
25 		nSustainStart = nSustainEnd;
26 		dwFlags.reset(ENV_CARRY);
27 
28 		if(nLoopEnd > nLoopStart && dwFlags[ENV_LOOP])
29 		{
30 			for(uint32 node = nLoopEnd; node < size(); node++)
31 			{
32 				at(node).tick++;
33 			}
34 		}
35 	} else if((fromType & MOD_TYPE_XM) && !(toType & MOD_TYPE_XM))
36 	{
37 		if(nSustainStart > nLoopEnd && dwFlags[ENV_LOOP])
38 		{
39 			// In the IT format, the sustain loop is always considered before the envelope loop.
40 			// In the XM format, whichever of the two is encountered first is considered.
41 			// So we have to disable the sustain loop if it was behind the normal loop.
42 			dwFlags.reset(ENV_SUSTAIN);
43 		}
44 
45 		// XM -> IT / MPTM: Shorten loop by one tick by inserting bogus point
46 		if(nLoopEnd > nLoopStart && dwFlags[ENV_LOOP] && nLoopEnd < size())
47 		{
48 			if(at(nLoopEnd).tick - 1 > at(nLoopEnd - 1).tick)
49 			{
50 				// Insert an interpolated point just before the loop point.
51 				EnvelopeNode::tick_t tick = at(nLoopEnd).tick - 1u;
52 				auto interpolatedValue = static_cast<EnvelopeNode::value_t>(GetValueFromPosition(tick, 64));
53 				insert(begin() + nLoopEnd, EnvelopeNode(tick, interpolatedValue));
54 			} else
55 			{
56 				// There is already a point before the loop point: Use it as new loop end.
57 				nLoopEnd--;
58 			}
59 		}
60 	}
61 
62 	if(toType != MOD_TYPE_MPT)
63 	{
64 		nReleaseNode = ENV_RELEASE_NODE_UNSET;
65 	}
66 }
67 
68 
69 // Get envelope value at a given tick. Assumes that the envelope data is in rage [0, rangeIn],
70 // returns value in range [0, rangeOut].
GetValueFromPosition(int position,int32 rangeOut,int32 rangeIn) const71 int32 InstrumentEnvelope::GetValueFromPosition(int position, int32 rangeOut, int32 rangeIn) const
72 {
73 	uint32 pt = size() - 1u;
74 	const int32 ENV_PRECISION = 1 << 16;
75 
76 	// Checking where current 'tick' is relative to the envelope points.
77 	for(uint32 i = 0; i < size() - 1u; i++)
78 	{
79 		if (position <= at(i).tick)
80 		{
81 			pt = i;
82 			break;
83 		}
84 	}
85 
86 	int x2 = at(pt).tick;
87 	int32 value = 0;
88 
89 	if(position >= x2)
90 	{
91 		// Case: current 'tick' is on a envelope point.
92 		value = at(pt).value * ENV_PRECISION / rangeIn;
93 	} else
94 	{
95 		// Case: current 'tick' is between two envelope points.
96 		int x1 = 0;
97 
98 		if(pt)
99 		{
100 			// Get previous node's value and tick.
101 			value = at(pt - 1).value * ENV_PRECISION / rangeIn;
102 			x1 = at(pt - 1).tick;
103 		}
104 
105 		if(x2 > x1 && position > x1)
106 		{
107 			// Linear approximation between the points;
108 			// f(x + d) ~ f(x) + f'(x) * d, where f'(x) = (y2 - y1) / (x2 - x1)
109 			value += Util::muldiv(position - x1, (at(pt).value * ENV_PRECISION / rangeIn - value), x2 - x1);
110 		}
111 	}
112 
113 	Limit(value, int32(0), ENV_PRECISION);
114 	return (value * rangeOut + ENV_PRECISION / 2) / ENV_PRECISION;
115 }
116 
117 
Sanitize(uint8 maxValue)118 void InstrumentEnvelope::Sanitize(uint8 maxValue)
119 {
120 	if(!empty())
121 	{
122 		front().tick = 0;
123 		LimitMax(front().value, maxValue);
124 		for(iterator it = begin() + 1; it != end(); it++)
125 		{
126 			it->tick = std::max(it->tick, (it - 1)->tick);
127 			LimitMax(it->value, maxValue);
128 		}
129 	}
130 	LimitMax(nLoopEnd, static_cast<decltype(nLoopEnd)>(size() - 1));
131 	LimitMax(nLoopStart, nLoopEnd);
132 	LimitMax(nSustainEnd, static_cast<decltype(nSustainEnd)>(size() - 1));
133 	LimitMax(nSustainStart, nSustainEnd);
134 	if(nReleaseNode != ENV_RELEASE_NODE_UNSET)
135 		LimitMax(nReleaseNode, static_cast<decltype(nReleaseNode)>(size() - 1));
136 }
137 
138 
ModInstrument(SAMPLEINDEX sample)139 ModInstrument::ModInstrument(SAMPLEINDEX sample)
140 {
141 	SetCutoff(0, false);
142 	SetResonance(0, false);
143 
144 	pitchToTempoLock.Set(0);
145 
146 	pTuning = CSoundFile::GetDefaultTuning();
147 
148 	AssignSample(sample);
149 	ResetNoteMap();
150 }
151 
152 
153 // Translate instrument properties between two given formats.
Convert(MODTYPE fromType,MODTYPE toType)154 void ModInstrument::Convert(MODTYPE fromType, MODTYPE toType)
155 {
156 	MPT_UNREFERENCED_PARAMETER(fromType);
157 
158 	if(toType & MOD_TYPE_XM)
159 	{
160 		ResetNoteMap();
161 
162 		PitchEnv.dwFlags.reset(ENV_ENABLED | ENV_FILTER);
163 
164 		dwFlags.reset(INS_SETPANNING);
165 		SetCutoff(GetCutoff(), false);
166 		SetResonance(GetResonance(), false);
167 		filterMode = FilterMode::Unchanged;
168 
169 		nCutSwing = nPanSwing = nResSwing = nVolSwing = 0;
170 
171 		nPPC = NOTE_MIDDLEC - 1;
172 		nPPS = 0;
173 
174 		nNNA = NewNoteAction::NoteCut;
175 		nDCT = DuplicateCheckType::None;
176 		nDNA = DuplicateNoteAction::NoteCut;
177 
178 		if(nMidiChannel == MidiMappedChannel)
179 		{
180 			nMidiChannel = MidiFirstChannel;
181 		}
182 
183 		// FT2 only has unsigned Pitch Wheel Depth, and it's limited to 0...36 (in the GUI, at least. As you would expect it from FT2, this value is actually not sanitized on load).
184 		midiPWD = static_cast<int8>(std::abs(midiPWD));
185 		Limit(midiPWD, int8(0), int8(36));
186 
187 		nGlobalVol = 64;
188 		nPan = 128;
189 
190 		LimitMax(nFadeOut, 32767u);
191 	}
192 
193 	VolEnv.Convert(fromType, toType);
194 	PanEnv.Convert(fromType, toType);
195 	PitchEnv.Convert(fromType, toType);
196 
197 	if(fromType == MOD_TYPE_XM && (toType & (MOD_TYPE_IT | MOD_TYPE_MPT)))
198 	{
199 		if(!VolEnv.dwFlags[ENV_ENABLED])
200 		{
201 			// Note-Off with no envelope cuts the note immediately in XM
202 			VolEnv.resize(2);
203 			VolEnv[0].tick = 0;
204 			VolEnv[0].value =  ENVELOPE_MAX;
205 			VolEnv[1].tick = 1;
206 			VolEnv[1].value =  ENVELOPE_MIN;
207 			VolEnv.dwFlags.set(ENV_ENABLED | ENV_SUSTAIN);
208 			VolEnv.dwFlags.reset(ENV_LOOP);
209 			VolEnv.nSustainStart = VolEnv.nSustainEnd = 0;
210 		}
211 	}
212 
213 	// Limit fadeout length for IT
214 	if(toType & MOD_TYPE_IT)
215 	{
216 		LimitMax(nFadeOut, 8192u);
217 	}
218 
219 	// MPT-specific features - remove instrument tunings, Pitch/Tempo Lock, cutoff / resonance swing and filter mode for other formats
220 	if(!(toType & MOD_TYPE_MPT))
221 	{
222 		SetTuning(nullptr);
223 		pitchToTempoLock.Set(0);
224 		nCutSwing = nResSwing = 0;
225 		filterMode = FilterMode::Unchanged;
226 		nVolRampUp = 0;
227 	}
228 }
229 
230 
231 // Get a set of all samples referenced by this instrument
GetSamples() const232 std::set<SAMPLEINDEX> ModInstrument::GetSamples() const
233 {
234 	std::set<SAMPLEINDEX> referencedSamples;
235 
236 	for(const auto sample : Keyboard)
237 	{
238 		if(sample)
239 		{
240 			referencedSamples.insert(sample);
241 		}
242 	}
243 
244 	return referencedSamples;
245 }
246 
247 
248 // Write sample references into a bool vector. If a sample is referenced by this instrument, true is written.
249 // The caller has to initialize the vector.
GetSamples(std::vector<bool> & referencedSamples) const250 void ModInstrument::GetSamples(std::vector<bool> &referencedSamples) const
251 {
252 	for(const auto sample : Keyboard)
253 	{
254 		if(sample != 0 && sample < referencedSamples.size())
255 		{
256 			referencedSamples[sample] = true;
257 		}
258 	}
259 }
260 
261 
Sanitize(MODTYPE modType)262 void ModInstrument::Sanitize(MODTYPE modType)
263 {
264 	LimitMax(nFadeOut, 65536u);
265 	LimitMax(nGlobalVol, 64u);
266 	LimitMax(nPan, 256u);
267 
268 	LimitMax(wMidiBank, uint16(16384));
269 	LimitMax(nMidiProgram, uint8(128));
270 	LimitMax(nMidiChannel, uint8(17));
271 
272 	if(nNNA > NewNoteAction::NoteFade) nNNA = NewNoteAction::NoteCut;
273 	if(nDCT > DuplicateCheckType::Plugin) nDCT = DuplicateCheckType::None;
274 	if(nDNA > DuplicateNoteAction::NoteFade) nDNA = DuplicateNoteAction::NoteCut;
275 
276 	LimitMax(nPanSwing, uint8(64));
277 	LimitMax(nVolSwing, uint8(100));
278 
279 	Limit(nPPS, int8(-32), int8(32));
280 
281 	LimitMax(nCutSwing, uint8(64));
282 	LimitMax(nResSwing, uint8(64));
283 
284 #ifdef MODPLUG_TRACKER
285 	MPT_UNREFERENCED_PARAMETER(modType);
286 	const uint8 range = ENVELOPE_MAX;
287 #else
288 	const uint8 range = modType == MOD_TYPE_AMS ? uint8_max : ENVELOPE_MAX;
289 #endif
290 	VolEnv.Sanitize();
291 	PanEnv.Sanitize();
292 	PitchEnv.Sanitize(range);
293 
294 	for(size_t i = 0; i < std::size(NoteMap); i++)
295 	{
296 		if(NoteMap[i] < NOTE_MIN || NoteMap[i] > NOTE_MAX)
297 			NoteMap[i] = static_cast<uint8>(i + NOTE_MIN);
298 	}
299 
300 	if(!Resampling::IsKnownMode(resampling))
301 		resampling = SRCMODE_DEFAULT;
302 
303 	if(nMixPlug > MAX_MIXPLUGINS)
304 		nMixPlug = 0;
305 }
306 
307 
Transpose(int8 amount)308 void ModInstrument::Transpose(int8 amount)
309 {
310 	for(auto &note : NoteMap)
311 	{
312 		note = static_cast<uint8>(Clamp(note + amount, NOTE_MIN, NOTE_MAX));
313 	}
314 }
315 
316 
GetMIDIChannel(const CSoundFile & sndFile,CHANNELINDEX chn) const317 uint8 ModInstrument::GetMIDIChannel(const CSoundFile &sndFile, CHANNELINDEX chn) const
318 {
319 	if(chn >= std::size(sndFile.m_PlayState.Chn))
320 		return 0;
321 
322 	// For mapped channels, return their pattern channel, modulo 16 (because there are only 16 MIDI channels)
323 	const ModChannel &channel = sndFile.m_PlayState.Chn[chn];
324 	if(nMidiChannel == MidiMappedChannel)
325 		return static_cast<uint8>((channel.nMasterChn ? (channel.nMasterChn - 1u) : chn) % 16u);
326 	else if(HasValidMIDIChannel())
327 		return (nMidiChannel - MidiFirstChannel) % 16u;
328 	else
329 		return 0;
330 
331 }
332 
333 
334 OPENMPT_NAMESPACE_END
335