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 ¬e : 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