1 /*
2 * Snd_fx.cpp
3 * -----------
4 * Purpose: Processing of pattern commands, song length calculation...
5 * Notes : This needs some heavy refactoring.
6 * I thought of actually adding an effect interface class. Every pattern effect
7 * could then be moved into its own class that inherits from the effect interface.
8 * If effect handling differs severly between module formats, every format would have
9 * its own class for that effect. Then, a call chain of effect classes could be set up
10 * for each format, since effects cannot be processed in the same order in all formats.
11 * Authors: Olivier Lapicque
12 * OpenMPT Devs
13 * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
14 */
15
16
17 #include "stdafx.h"
18 #include "Sndfile.h"
19 #include "mod_specifications.h"
20 #ifdef MODPLUG_TRACKER
21 #include "../mptrack/Moddoc.h"
22 #endif // MODPLUG_TRACKER
23 #include "tuning.h"
24 #include "Tables.h"
25 #include "modsmp_ctrl.h" // For updating the loop wraparound data with the invert loop effect
26 #include "plugins/PlugInterface.h"
27 #include "OPL.h"
28 #include "MIDIEvents.h"
29
30 OPENMPT_NAMESPACE_BEGIN
31
32 // Formats which have 7-bit (0...128) instead of 6-bit (0...64) global volume commands, or which are imported to this range (mostly formats which are converted to IT internally)
33 #ifdef MODPLUG_TRACKER
34 static constexpr auto GLOBALVOL_7BIT_FORMATS_EXT = MOD_TYPE_MT2;
35 #else
36 static constexpr auto GLOBALVOL_7BIT_FORMATS_EXT = MOD_TYPE_NONE;
37 #endif // MODPLUG_TRACKER
38 static constexpr auto GLOBALVOL_7BIT_FORMATS = MOD_TYPE_IT | MOD_TYPE_MPT | MOD_TYPE_IMF | MOD_TYPE_J2B | MOD_TYPE_MID | MOD_TYPE_AMS | MOD_TYPE_DBM | MOD_TYPE_PTM | MOD_TYPE_MDL | MOD_TYPE_DTM | GLOBALVOL_7BIT_FORMATS_EXT;
39
40
41 // Compensate frequency slide LUTs depending on whether we are handling periods or frequency - "up" and "down" in function name are seen from frequency perspective.
GetLinearSlideDownTable(const CSoundFile * sndFile,uint32 i)42 static uint32 GetLinearSlideDownTable (const CSoundFile *sndFile, uint32 i) { MPT_ASSERT(i < std::size(LinearSlideDownTable)); return sndFile->m_playBehaviour[kPeriodsAreHertz] ? LinearSlideDownTable[i] : LinearSlideUpTable[i]; }
GetLinearSlideUpTable(const CSoundFile * sndFile,uint32 i)43 static uint32 GetLinearSlideUpTable (const CSoundFile *sndFile, uint32 i) { MPT_ASSERT(i < std::size(LinearSlideDownTable)); return sndFile->m_playBehaviour[kPeriodsAreHertz] ? LinearSlideUpTable[i] : LinearSlideDownTable[i]; }
GetFineLinearSlideDownTable(const CSoundFile * sndFile,uint32 i)44 static uint32 GetFineLinearSlideDownTable(const CSoundFile *sndFile, uint32 i) { MPT_ASSERT(i < std::size(FineLinearSlideDownTable)); return sndFile->m_playBehaviour[kPeriodsAreHertz] ? FineLinearSlideDownTable[i] : FineLinearSlideUpTable[i]; }
GetFineLinearSlideUpTable(const CSoundFile * sndFile,uint32 i)45 static uint32 GetFineLinearSlideUpTable (const CSoundFile *sndFile, uint32 i) { MPT_ASSERT(i < std::size(FineLinearSlideDownTable)); return sndFile->m_playBehaviour[kPeriodsAreHertz] ? FineLinearSlideUpTable[i] : FineLinearSlideDownTable[i]; }
46
47
48 ////////////////////////////////////////////////////////////
49 // Length
50
51
52 // Memory class for GetLength() code
53 class GetLengthMemory
54 {
55 protected:
56 const CSoundFile &sndFile;
57
58 public:
59 std::unique_ptr<CSoundFile::PlayState> state;
60 struct ChnSettings
61 {
62 uint32 ticksToRender = 0; // When using sample sync, we still need to render this many ticks
63 bool incChanged = false; // When using sample sync, note frequency has changed
64 uint8 vol = 0xFF;
65 };
66
67 #ifndef NO_PLUGINS
68 typedef std::map<std::pair<ModCommand::INSTR, uint16>, uint16> PlugParamMap;
69 PlugParamMap plugParams;
70 #endif
71 std::vector<ChnSettings> chnSettings;
72 double elapsedTime;
73 static constexpr uint32 IGNORE_CHANNEL = uint32_max;
74
GetLengthMemory(const CSoundFile & sf)75 GetLengthMemory(const CSoundFile &sf)
76 : sndFile(sf)
77 , state(std::make_unique<CSoundFile::PlayState>(sf.m_PlayState))
78 {
79 Reset();
80 }
81
Reset()82 void Reset()
83 {
84 #ifndef NO_PLUGINS
85 plugParams.clear();
86 #endif
87 elapsedTime = 0.0;
88 state->m_lTotalSampleCount = 0;
89 state->m_nMusicSpeed = sndFile.m_nDefaultSpeed;
90 state->m_nMusicTempo = sndFile.m_nDefaultTempo;
91 state->m_nGlobalVolume = sndFile.m_nDefaultGlobalVolume;
92 chnSettings.assign(sndFile.GetNumChannels(), ChnSettings());
93 const auto muteFlag = CSoundFile::GetChannelMuteFlag();
94 for(CHANNELINDEX chn = 0; chn < sndFile.GetNumChannels(); chn++)
95 {
96 state->Chn[chn].Reset(ModChannel::resetTotal, sndFile, chn, muteFlag);
97 state->Chn[chn].nOldGlobalVolSlide = 0;
98 state->Chn[chn].nOldChnVolSlide = 0;
99 state->Chn[chn].nNote = state->Chn[chn].nNewNote = state->Chn[chn].nLastNote = NOTE_NONE;
100 }
101 }
102
103 // Increment playback position of sample and envelopes on a channel
RenderChannel(CHANNELINDEX channel,uint32 tickDuration,uint32 portaStart=uint32_max)104 void RenderChannel(CHANNELINDEX channel, uint32 tickDuration, uint32 portaStart = uint32_max)
105 {
106 ModChannel &chn = state->Chn[channel];
107 uint32 numTicks = chnSettings[channel].ticksToRender;
108 if(numTicks == IGNORE_CHANNEL || numTicks == 0 || (!chn.IsSamplePlaying() && !chnSettings[channel].incChanged) || chn.pModSample == nullptr)
109 {
110 return;
111 }
112
113 const SamplePosition loopStart(chn.dwFlags[CHN_LOOP] ? chn.nLoopStart : 0u, 0);
114 const SamplePosition sampleEnd(chn.dwFlags[CHN_LOOP] ? chn.nLoopEnd : chn.nLength, 0);
115 const SmpLength loopLength = chn.nLoopEnd - chn.nLoopStart;
116 const bool itEnvMode = sndFile.m_playBehaviour[kITEnvelopePositionHandling];
117 const bool updatePitchEnv = (chn.PitchEnv.flags & (ENV_ENABLED | ENV_FILTER)) == ENV_ENABLED;
118 bool stopNote = false;
119
120 SamplePosition inc = chn.increment * tickDuration;
121 if(chn.dwFlags[CHN_PINGPONGFLAG]) inc.Negate();
122
123 for(uint32 i = 0; i < numTicks; i++)
124 {
125 bool updateInc = (chn.PitchEnv.flags & (ENV_ENABLED | ENV_FILTER)) == ENV_ENABLED;
126 if(i >= portaStart)
127 {
128 chn.isFirstTick = false;
129 const ModCommand &m = *sndFile.Patterns[state->m_nPattern].GetpModCommand(state->m_nRow, channel);
130 auto command = m.command;
131 if(m.volcmd == VOLCMD_TONEPORTAMENTO)
132 {
133 const auto [porta, clearEffectCommand] = sndFile.GetVolCmdTonePorta(m, 0);
134 sndFile.TonePortamento(chn, porta);
135 if(clearEffectCommand)
136 command = CMD_NONE;
137 }
138 if(command == CMD_TONEPORTAMENTO)
139 sndFile.TonePortamento(chn, m.param);
140 else if(command == CMD_TONEPORTAVOL)
141 sndFile.TonePortamento(chn, 0);
142 updateInc = true;
143 }
144
145 int32 period = chn.nPeriod;
146 if(itEnvMode) sndFile.IncrementEnvelopePositions(chn);
147 if(updatePitchEnv)
148 {
149 sndFile.ProcessPitchFilterEnvelope(chn, period);
150 updateInc = true;
151 }
152 if(!itEnvMode) sndFile.IncrementEnvelopePositions(chn);
153 int vol = 0;
154 sndFile.ProcessInstrumentFade(chn, vol);
155
156 if(chn.dwFlags[CHN_ADLIB])
157 continue;
158
159 if(updateInc || chnSettings[channel].incChanged)
160 {
161 if(chn.m_CalculateFreq || chn.m_ReCalculateFreqOnFirstTick)
162 {
163 chn.RecalcTuningFreq(1, 0, sndFile);
164 if(!chn.m_CalculateFreq)
165 chn.m_ReCalculateFreqOnFirstTick = false;
166 else
167 chn.m_CalculateFreq = false;
168 }
169 chn.increment = sndFile.GetChannelIncrement(chn, period, 0).first;
170 chnSettings[channel].incChanged = false;
171 inc = chn.increment * tickDuration;
172 if(chn.dwFlags[CHN_PINGPONGFLAG]) inc.Negate();
173 }
174
175 chn.position += inc;
176
177 if(chn.position >= sampleEnd || (chn.position < loopStart && inc.IsNegative()))
178 {
179 if(!chn.dwFlags[CHN_LOOP])
180 {
181 // Past sample end.
182 stopNote = true;
183 break;
184 }
185 // We exceeded the sample loop, go back to loop start.
186 if(chn.dwFlags[CHN_PINGPONGLOOP])
187 {
188 if(chn.position < loopStart)
189 {
190 chn.position = SamplePosition(chn.nLoopStart + chn.nLoopStart, 0) - chn.position;
191 chn.dwFlags.flip(CHN_PINGPONGFLAG);
192 inc.Negate();
193 }
194 SmpLength posInt = chn.position.GetUInt() - chn.nLoopStart;
195 SmpLength pingpongLength = loopLength * 2;
196 if(sndFile.m_playBehaviour[kITPingPongMode]) pingpongLength--;
197 posInt %= pingpongLength;
198 bool forward = (posInt < loopLength);
199 if(forward)
200 chn.position.SetInt(chn.nLoopStart + posInt);
201 else
202 chn.position.SetInt(chn.nLoopEnd - (posInt - loopLength));
203 if(forward == chn.dwFlags[CHN_PINGPONGFLAG])
204 {
205 chn.dwFlags.flip(CHN_PINGPONGFLAG);
206 inc.Negate();
207 }
208 } else
209 {
210 SmpLength posInt = chn.position.GetUInt();
211 if(posInt >= chn.nLoopEnd + loopLength)
212 {
213 const SmpLength overshoot = posInt - chn.nLoopEnd;
214 posInt -= (overshoot / loopLength) * loopLength;
215 }
216 while(posInt >= chn.nLoopEnd)
217 {
218 posInt -= loopLength;
219 }
220 chn.position.SetInt(posInt);
221 }
222 }
223 }
224
225 if(stopNote)
226 {
227 chn.Stop();
228 chn.nPortamentoDest = 0;
229 }
230 chnSettings[channel].ticksToRender = 0;
231 }
232 };
233
234
235 // Get mod length in various cases. Parameters:
236 // [in] adjustMode: See enmGetLengthResetMode for possible adjust modes.
237 // [in] target: Time or position target which should be reached, or no target to get length of the first sub song. Use GetLengthTarget::StartPos to also specify a position from where the seeking should begin.
238 // [out] See definition of type GetLengthType for the returned values.
GetLength(enmGetLengthResetMode adjustMode,GetLengthTarget target)239 std::vector<GetLengthType> CSoundFile::GetLength(enmGetLengthResetMode adjustMode, GetLengthTarget target)
240 {
241 std::vector<GetLengthType> results;
242 GetLengthType retval;
243
244 // Are we trying to reach a certain pattern position?
245 const bool hasSearchTarget = target.mode != GetLengthTarget::NoTarget && target.mode != GetLengthTarget::GetAllSubsongs;
246 const bool adjustSamplePos = (adjustMode & eAdjustSamplePositions) == eAdjustSamplePositions;
247
248 SEQUENCEINDEX sequence = target.sequence;
249 if(sequence >= Order.GetNumSequences()) sequence = Order.GetCurrentSequenceIndex();
250 const ModSequence &orderList = Order(sequence);
251
252 GetLengthMemory memory(*this);
253 CSoundFile::PlayState &playState = *memory.state;
254 // Temporary visited rows vector (so that GetLength() won't interfere with the player code if the module is playing at the same time)
255 RowVisitor visitedRows(*this, sequence);
256 ROWINDEX allowedPatternLoopComplexity = 32768;
257
258 // If sequence starts with some non-existent patterns, find a better start
259 while(target.startOrder < orderList.size() && !orderList.IsValidPat(target.startOrder))
260 {
261 target.startOrder++;
262 target.startRow = 0;
263 }
264 retval.startRow = playState.m_nNextRow = playState.m_nRow = target.startRow;
265 retval.startOrder = playState.m_nNextOrder = playState.m_nCurrentOrder = target.startOrder;
266
267 // Fast LUTs for commands that are too weird / complicated / whatever to emulate in sample position adjust mode.
268 std::bitset<MAX_EFFECTS> forbiddenCommands;
269 std::bitset<MAX_VOLCMDS> forbiddenVolCommands;
270
271 if(adjustSamplePos)
272 {
273 forbiddenCommands.set(CMD_ARPEGGIO); forbiddenCommands.set(CMD_PORTAMENTOUP);
274 forbiddenCommands.set(CMD_PORTAMENTODOWN); forbiddenCommands.set(CMD_XFINEPORTAUPDOWN);
275 forbiddenCommands.set(CMD_NOTESLIDEUP); forbiddenCommands.set(CMD_NOTESLIDEUPRETRIG);
276 forbiddenCommands.set(CMD_NOTESLIDEDOWN); forbiddenCommands.set(CMD_NOTESLIDEDOWNRETRIG);
277 forbiddenVolCommands.set(VOLCMD_PORTAUP); forbiddenVolCommands.set(VOLCMD_PORTADOWN);
278
279 if(target.mode == GetLengthTarget::SeekPosition && target.pos.order < orderList.size())
280 {
281 // If we know where to seek, we can directly rule out any channels on which a new note would be triggered right at the start.
282 const PATTERNINDEX seekPat = orderList[target.pos.order];
283 if(Patterns.IsValidPat(seekPat) && Patterns[seekPat].IsValidRow(target.pos.row))
284 {
285 const ModCommand *m = Patterns[seekPat].GetpModCommand(target.pos.row, 0);
286 for(CHANNELINDEX i = 0; i < GetNumChannels(); i++, m++)
287 {
288 if(m->note == NOTE_NOTECUT || m->note == NOTE_KEYOFF || (m->note == NOTE_FADE && GetNumInstruments())
289 || (m->IsNote() && !m->IsPortamento()))
290 {
291 memory.chnSettings[i].ticksToRender = GetLengthMemory::IGNORE_CHANNEL;
292 }
293 }
294 }
295 }
296 }
297
298 // If samples are being synced, force them to resync if tick duration changes
299 uint32 oldTickDuration = 0;
300 bool breakToRow = false;
301
302 for (;;)
303 {
304 const bool ignoreRow = NextRow(playState, breakToRow).first;
305
306 // Time target reached.
307 if(target.mode == GetLengthTarget::SeekSeconds && memory.elapsedTime >= target.time)
308 {
309 retval.targetReached = true;
310 break;
311 }
312
313 // Check if pattern is valid
314 playState.m_nPattern = playState.m_nCurrentOrder < orderList.size() ? orderList[playState.m_nCurrentOrder] : orderList.GetInvalidPatIndex();
315
316 if(!Patterns.IsValidPat(playState.m_nPattern) && playState.m_nPattern != orderList.GetInvalidPatIndex() && target.mode == GetLengthTarget::SeekPosition && playState.m_nCurrentOrder == target.pos.order)
317 {
318 // Early test: Target is inside +++ or non-existing pattern
319 retval.targetReached = true;
320 break;
321 }
322
323 while(playState.m_nPattern >= Patterns.Size())
324 {
325 // End of song?
326 if((playState.m_nPattern == orderList.GetInvalidPatIndex()) || (playState.m_nCurrentOrder >= orderList.size()))
327 {
328 if(playState.m_nCurrentOrder == orderList.GetRestartPos())
329 break;
330 else
331 playState.m_nCurrentOrder = orderList.GetRestartPos();
332 } else
333 {
334 playState.m_nCurrentOrder++;
335 }
336 playState.m_nPattern = (playState.m_nCurrentOrder < orderList.size()) ? orderList[playState.m_nCurrentOrder] : orderList.GetInvalidPatIndex();
337 playState.m_nNextOrder = playState.m_nCurrentOrder;
338 if((!Patterns.IsValidPat(playState.m_nPattern)) && visitedRows.Visit(playState.m_nCurrentOrder, 0, playState.Chn, ignoreRow))
339 {
340 if(!hasSearchTarget)
341 {
342 retval.lastOrder = playState.m_nCurrentOrder;
343 retval.lastRow = 0;
344 }
345 if(target.mode == GetLengthTarget::NoTarget || !visitedRows.GetFirstUnvisitedRow(playState.m_nNextOrder, playState.m_nRow, true))
346 {
347 // We aren't searching for a specific row, or we couldn't find any more unvisited rows.
348 break;
349 } else
350 {
351 // We haven't found the target row yet, but we found some other unplayed row... continue searching from here.
352 retval.duration = memory.elapsedTime;
353 results.push_back(retval);
354 retval.startRow = playState.m_nRow;
355 retval.startOrder = playState.m_nNextOrder;
356 memory.Reset();
357
358 playState.m_nCurrentOrder = playState.m_nNextOrder;
359 playState.m_nPattern = orderList[playState.m_nCurrentOrder];
360 playState.m_nNextRow = playState.m_nRow;
361 break;
362 }
363 }
364 }
365 if(playState.m_nNextOrder == ORDERINDEX_INVALID)
366 {
367 // GetFirstUnvisitedRow failed, so there is nothing more to play
368 break;
369 }
370
371 // Skip non-existing patterns
372 if(!Patterns.IsValidPat(playState.m_nPattern))
373 {
374 // If there isn't even a tune, we should probably stop here.
375 if(playState.m_nCurrentOrder == orderList.GetRestartPos())
376 {
377 if(target.mode == GetLengthTarget::NoTarget || !visitedRows.GetFirstUnvisitedRow(playState.m_nNextOrder, playState.m_nRow, true))
378 {
379 // We aren't searching for a specific row, or we couldn't find any more unvisited rows.
380 break;
381 } else
382 {
383 // We haven't found the target row yet, but we found some other unplayed row... continue searching from here.
384 retval.duration = memory.elapsedTime;
385 results.push_back(retval);
386 retval.startRow = playState.m_nRow;
387 retval.startOrder = playState.m_nNextOrder;
388 memory.Reset();
389 playState.m_nNextRow = playState.m_nRow;
390 continue;
391 }
392 }
393 playState.m_nNextOrder = playState.m_nCurrentOrder + 1;
394 continue;
395 }
396 // Should never happen
397 if(playState.m_nRow >= Patterns[playState.m_nPattern].GetNumRows())
398 playState.m_nRow = 0;
399
400 // Check whether target was reached.
401 if(target.mode == GetLengthTarget::SeekPosition && playState.m_nCurrentOrder == target.pos.order && playState.m_nRow == target.pos.row)
402 {
403 retval.targetReached = true;
404 break;
405 }
406
407 // If pattern loops are nested too deeply, they can cause an effectively infinite amount of loop evalations to be generated.
408 // As we don't want the user to wait forever, we bail out if the pattern loops are too complex.
409 const bool moduleTooComplex = target.mode != GetLengthTarget::SeekSeconds && visitedRows.ModuleTooComplex(allowedPatternLoopComplexity);
410 if(moduleTooComplex)
411 {
412 memory.elapsedTime = std::numeric_limits<decltype(memory.elapsedTime)>::infinity();
413 // Decrease allowed complexity with each subsong, as this seems to be a malicious module
414 if(allowedPatternLoopComplexity > 256)
415 allowedPatternLoopComplexity /= 2;
416 visitedRows.ResetComplexity();
417 }
418
419 if(visitedRows.Visit(playState.m_nCurrentOrder, playState.m_nRow, playState.Chn, ignoreRow) || moduleTooComplex)
420 {
421 if(!hasSearchTarget)
422 {
423 retval.lastOrder = playState.m_nCurrentOrder;
424 retval.lastRow = playState.m_nRow;
425 }
426 if(target.mode == GetLengthTarget::NoTarget || !visitedRows.GetFirstUnvisitedRow(playState.m_nNextOrder, playState.m_nRow, true))
427 {
428 // We aren't searching for a specific row, or we couldn't find any more unvisited rows.
429 break;
430 } else
431 {
432 // We haven't found the target row yet, but we found some other unplayed row... continue searching from here.
433 retval.duration = memory.elapsedTime;
434 results.push_back(retval);
435 retval.startRow = playState.m_nRow;
436 retval.startOrder = playState.m_nNextOrder;
437 memory.Reset();
438 playState.m_nNextRow = playState.m_nRow;
439 continue;
440 }
441 }
442
443 retval.endOrder = playState.m_nCurrentOrder;
444 retval.endRow = playState.m_nRow;
445
446 // Update next position
447 SetupNextRow(playState, false);
448
449 // Jumped to invalid pattern row?
450 if(playState.m_nRow >= Patterns[playState.m_nPattern].GetNumRows())
451 {
452 playState.m_nRow = 0;
453 }
454
455 if(ignoreRow)
456 continue;
457
458 // For various effects, we need to know first how many ticks there are in this row.
459 const ModCommand *p = Patterns[playState.m_nPattern].GetpModCommand(playState.m_nRow, 0);
460 const bool ignoreMutedChn = m_playBehaviour[kST3NoMutedChannels];
461 for(CHANNELINDEX nChn = 0; nChn < GetNumChannels(); nChn++, p++)
462 {
463 ModChannel &chn = playState.Chn[nChn];
464 if(p->IsEmpty() || (ignoreMutedChn && ChnSettings[nChn].dwFlags[CHN_MUTE])) // not even effects are processed on muted S3M channels
465 {
466 chn.rowCommand.Clear();
467 continue;
468 }
469 if(p->IsPcNote())
470 {
471 #ifndef NO_PLUGINS
472 if((adjustMode & eAdjust) && p->instr > 0 && p->instr <= MAX_MIXPLUGINS)
473 {
474 memory.plugParams[std::make_pair(p->instr, p->GetValueVolCol())] = p->GetValueEffectCol();
475 }
476 #endif // NO_PLUGINS
477 chn.rowCommand.Clear();
478 continue;
479 }
480 chn.rowCommand = *p;
481 switch(p->command)
482 {
483 case CMD_SPEED:
484 SetSpeed(playState, p->param);
485 break;
486
487 case CMD_TEMPO:
488 if(m_playBehaviour[kMODVBlankTiming])
489 {
490 // ProTracker MODs with VBlank timing: All Fxx parameters set the tick count.
491 if(p->param != 0) SetSpeed(playState, p->param);
492 }
493 break;
494
495 case CMD_S3MCMDEX:
496 if((p->param & 0xF0) == 0x60)
497 {
498 // Fine Pattern Delay
499 playState.m_nFrameDelay += (p->param & 0x0F);
500 } else if((p->param & 0xF0) == 0xE0 && !playState.m_nPatternDelay)
501 {
502 // Pattern Delay
503 if(!(GetType() & MOD_TYPE_S3M) || (p->param & 0x0F) != 0)
504 {
505 // While Impulse Tracker *does* count S60 as a valid row delay (and thus ignores any other row delay commands on the right),
506 // Scream Tracker 3 simply ignores such commands.
507 playState.m_nPatternDelay = 1 + (p->param & 0x0F);
508 }
509 }
510 break;
511
512 case CMD_MODCMDEX:
513 if((p->param & 0xF0) == 0xE0)
514 {
515 // Pattern Delay
516 playState.m_nPatternDelay = 1 + (p->param & 0x0F);
517 }
518 break;
519 }
520 }
521 const uint32 numTicks = playState.TicksOnRow();
522 const uint32 nonRowTicks = numTicks - std::max(playState.m_nPatternDelay, uint32(1));
523
524 playState.m_patLoopRow = ROWINDEX_INVALID;
525 playState.m_breakRow = ROWINDEX_INVALID;
526 playState.m_posJump = ORDERINDEX_INVALID;
527
528 for(CHANNELINDEX nChn = 0; nChn < GetNumChannels(); nChn++)
529 {
530 ModChannel &chn = playState.Chn[nChn];
531 if(chn.rowCommand.IsEmpty())
532 continue;
533 ModCommand::COMMAND command = chn.rowCommand.command;
534 ModCommand::PARAM param = chn.rowCommand.param;
535 ModCommand::NOTE note = chn.rowCommand.note;
536
537 if(adjustMode & eAdjust)
538 {
539 if(chn.rowCommand.instr)
540 {
541 chn.nNewIns = chn.rowCommand.instr;
542 chn.nLastNote = NOTE_NONE;
543 memory.chnSettings[nChn].vol = 0xFF;
544 }
545 if(chn.rowCommand.IsNote())
546 chn.nLastNote = note;
547
548 // Update channel panning
549 if(chn.rowCommand.IsNote() || chn.rowCommand.instr)
550 {
551 ModInstrument *pIns;
552 if(chn.nNewIns > 0 && chn.nNewIns <= GetNumInstruments() && (pIns = Instruments[chn.nNewIns]) != nullptr)
553 {
554 if(pIns->dwFlags[INS_SETPANNING])
555 chn.SetInstrumentPan(pIns->nPan, *this);
556 }
557 const SAMPLEINDEX smp = GetSampleIndex(note, chn.nNewIns);
558 if(smp > 0)
559 {
560 if(Samples[smp].uFlags[CHN_PANNING])
561 chn.SetInstrumentPan(Samples[smp].nPan, *this);
562 }
563 }
564
565 switch(chn.rowCommand.volcmd)
566 {
567 case VOLCMD_VOLUME:
568 memory.chnSettings[nChn].vol = chn.rowCommand.vol;
569 break;
570 case VOLCMD_VOLSLIDEUP:
571 case VOLCMD_VOLSLIDEDOWN:
572 if(chn.rowCommand.vol != 0)
573 chn.nOldVolParam = chn.rowCommand.vol;
574 break;
575 case VOLCMD_TONEPORTAMENTO:
576 if(chn.rowCommand.vol)
577 {
578 const auto [porta, clearEffectCommand] = GetVolCmdTonePorta(chn.rowCommand, 0);
579 chn.portamentoSlide = porta;
580 if(clearEffectCommand)
581 command = CMD_NONE;
582 }
583 break;
584 }
585 }
586
587 switch(command)
588 {
589 // Position Jump
590 case CMD_POSITIONJUMP:
591 PositionJump(playState, nChn);
592 break;
593
594 // Pattern Break
595 case CMD_PATTERNBREAK:
596 if(ROWINDEX row = PatternBreak(playState, nChn, param); row != ROWINDEX_INVALID)
597 playState.m_breakRow = row;
598 break;
599
600 // Set Tempo
601 case CMD_TEMPO:
602 if(!m_playBehaviour[kMODVBlankTiming])
603 {
604 TEMPO tempo(CalculateXParam(playState.m_nPattern, playState.m_nRow, nChn), 0);
605 if ((adjustMode & eAdjust) && (GetType() & (MOD_TYPE_S3M | MOD_TYPE_IT | MOD_TYPE_MPT)))
606 {
607 if (tempo.GetInt()) chn.nOldTempo = static_cast<uint8>(tempo.GetInt()); else tempo.Set(chn.nOldTempo);
608 }
609
610 if (tempo.GetInt() >= 0x20) playState.m_nMusicTempo = tempo;
611 else
612 {
613 // Tempo Slide
614 TEMPO tempoDiff((tempo.GetInt() & 0x0F) * nonRowTicks, 0);
615 if ((tempo.GetInt() & 0xF0) == 0x10)
616 {
617 playState.m_nMusicTempo += tempoDiff;
618 } else
619 {
620 if(tempoDiff < playState.m_nMusicTempo)
621 playState.m_nMusicTempo -= tempoDiff;
622 else
623 playState.m_nMusicTempo.Set(0);
624 }
625 }
626
627 TEMPO tempoMin = GetModSpecifications().GetTempoMin(), tempoMax = GetModSpecifications().GetTempoMax();
628 if(m_playBehaviour[kTempoClamp]) // clamp tempo correctly in compatible mode
629 {
630 tempoMax.Set(255);
631 }
632 Limit(playState.m_nMusicTempo, tempoMin, tempoMax);
633 }
634 break;
635
636 case CMD_S3MCMDEX:
637 switch(param & 0xF0)
638 {
639 case 0x90:
640 if(param <= 0x91)
641 chn.dwFlags.set(CHN_SURROUND, param == 0x91);
642 break;
643
644 case 0xA0: // High sample offset
645 chn.nOldHiOffset = param & 0x0F;
646 break;
647
648 case 0xB0: // Pattern Loop
649 PatternLoop(playState, chn, param & 0x0F);
650 break;
651
652 case 0xF0: // Active macro
653 chn.nActiveMacro = param & 0x0F;
654 break;
655 }
656 break;
657
658 case CMD_MODCMDEX:
659 switch(param & 0xF0)
660 {
661 case 0x60: // Pattern Loop
662 PatternLoop(playState, chn, param & 0x0F);
663 break;
664
665 case 0xF0: // Active macro
666 chn.nActiveMacro = param & 0x0F;
667 break;
668 }
669 break;
670
671 case CMD_XFINEPORTAUPDOWN:
672 // ignore high offset in compatible mode
673 if(((param & 0xF0) == 0xA0) && !m_playBehaviour[kFT2RestrictXCommand])
674 chn.nOldHiOffset = param & 0x0F;
675 break;
676 }
677
678 // The following calculations are not interesting if we just want to get the song length.
679 if(!(adjustMode & eAdjust))
680 continue;
681 switch(command)
682 {
683 // Portamento Up/Down
684 case CMD_PORTAMENTOUP:
685 if(param)
686 {
687 // FT2 compatibility: Separate effect memory for all portamento commands
688 // Test case: Porta-LinkMem.xm
689 if(!m_playBehaviour[kFT2PortaUpDownMemory])
690 chn.nOldPortaDown = param;
691 chn.nOldPortaUp = param;
692 }
693 break;
694 case CMD_PORTAMENTODOWN:
695 if(param)
696 {
697 // FT2 compatibility: Separate effect memory for all portamento commands
698 // Test case: Porta-LinkMem.xm
699 if(!m_playBehaviour[kFT2PortaUpDownMemory])
700 chn.nOldPortaUp = param;
701 chn.nOldPortaDown = param;
702 }
703 break;
704 // Tone-Portamento
705 case CMD_TONEPORTAMENTO:
706 if (param) chn.portamentoSlide = param;
707 break;
708 // Offset
709 case CMD_OFFSET:
710 if(param)
711 chn.oldOffset = param << 8;
712 break;
713 // Volume Slide
714 case CMD_VOLUMESLIDE:
715 case CMD_TONEPORTAVOL:
716 if (param) chn.nOldVolumeSlide = param;
717 break;
718 // Set Volume
719 case CMD_VOLUME:
720 memory.chnSettings[nChn].vol = param;
721 break;
722 // Global Volume
723 case CMD_GLOBALVOLUME:
724 if(!(GetType() & GLOBALVOL_7BIT_FORMATS) && param < 128) param *= 2;
725 // IT compatibility 16. ST3 and IT ignore out-of-range values
726 if(param <= 128)
727 {
728 playState.m_nGlobalVolume = param * 2;
729 } else if(!(GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT | MOD_TYPE_S3M)))
730 {
731 playState.m_nGlobalVolume = 256;
732 }
733 break;
734 // Global Volume Slide
735 case CMD_GLOBALVOLSLIDE:
736 if(m_playBehaviour[kPerChannelGlobalVolSlide])
737 {
738 // IT compatibility 16. Global volume slide params are stored per channel (FT2/IT)
739 if (param) chn.nOldGlobalVolSlide = param; else param = chn.nOldGlobalVolSlide;
740 } else
741 {
742 if (param) playState.Chn[0].nOldGlobalVolSlide = param; else param = playState.Chn[0].nOldGlobalVolSlide;
743 }
744 if (((param & 0x0F) == 0x0F) && (param & 0xF0))
745 {
746 param >>= 4;
747 if (!(GetType() & GLOBALVOL_7BIT_FORMATS)) param <<= 1;
748 playState.m_nGlobalVolume += param << 1;
749 } else if (((param & 0xF0) == 0xF0) && (param & 0x0F))
750 {
751 param = (param & 0x0F) << 1;
752 if (!(GetType() & GLOBALVOL_7BIT_FORMATS)) param <<= 1;
753 playState.m_nGlobalVolume -= param;
754 } else if (param & 0xF0)
755 {
756 param >>= 4;
757 param <<= 1;
758 if (!(GetType() & GLOBALVOL_7BIT_FORMATS)) param <<= 1;
759 playState.m_nGlobalVolume += param * nonRowTicks;
760 } else
761 {
762 param = (param & 0x0F) << 1;
763 if (!(GetType() & GLOBALVOL_7BIT_FORMATS)) param <<= 1;
764 playState.m_nGlobalVolume -= param * nonRowTicks;
765 }
766 Limit(playState.m_nGlobalVolume, 0, 256);
767 break;
768 case CMD_CHANNELVOLUME:
769 if (param <= 64) chn.nGlobalVol = param;
770 break;
771 case CMD_CHANNELVOLSLIDE:
772 {
773 if (param) chn.nOldChnVolSlide = param; else param = chn.nOldChnVolSlide;
774 int32 volume = chn.nGlobalVol;
775 if((param & 0x0F) == 0x0F && (param & 0xF0))
776 volume += (param >> 4); // Fine Up
777 else if((param & 0xF0) == 0xF0 && (param & 0x0F))
778 volume -= (param & 0x0F); // Fine Down
779 else if(param & 0x0F) // Down
780 volume -= (param & 0x0F) * nonRowTicks;
781 else // Up
782 volume += ((param & 0xF0) >> 4) * nonRowTicks;
783 Limit(volume, 0, 64);
784 chn.nGlobalVol = volume;
785 }
786 break;
787 case CMD_PANNING8:
788 Panning(chn, param, Pan8bit);
789 break;
790 case CMD_MODCMDEX:
791 if(param < 0x10)
792 {
793 // LED filter
794 for(CHANNELINDEX channel = 0; channel < GetNumChannels(); channel++)
795 {
796 playState.Chn[channel].dwFlags.set(CHN_AMIGAFILTER, !(param & 1));
797 }
798 }
799 [[fallthrough]];
800 case CMD_S3MCMDEX:
801 if((param & 0xF0) == 0x80)
802 {
803 Panning(chn, (param & 0x0F), Pan4bit);
804 }
805 break;
806
807 case CMD_VIBRATOVOL:
808 if (param) chn.nOldVolumeSlide = param;
809 param = 0;
810 [[fallthrough]];
811 case CMD_VIBRATO:
812 Vibrato(chn, param);
813 break;
814 case CMD_FINEVIBRATO:
815 FineVibrato(chn, param);
816 break;
817 case CMD_TREMOLO:
818 Tremolo(chn, param);
819 break;
820 case CMD_PANBRELLO:
821 Panbrello(chn, param);
822 break;
823 }
824
825 switch(chn.rowCommand.volcmd)
826 {
827 case VOLCMD_PANNING:
828 Panning(chn, chn.rowCommand.vol, Pan6bit);
829 break;
830
831 case VOLCMD_VIBRATOSPEED:
832 // FT2 does not automatically enable vibrato with the "set vibrato speed" command
833 if(m_playBehaviour[kFT2VolColVibrato])
834 chn.nVibratoSpeed = chn.rowCommand.vol & 0x0F;
835 else
836 Vibrato(chn, chn.rowCommand.vol << 4);
837 break;
838 case VOLCMD_VIBRATODEPTH:
839 Vibrato(chn, chn.rowCommand.vol);
840 break;
841 }
842
843 // Process vibrato / tremolo / panbrello
844 switch(chn.rowCommand.command)
845 {
846 case CMD_VIBRATO:
847 case CMD_FINEVIBRATO:
848 case CMD_VIBRATOVOL:
849 if(adjustMode & eAdjust)
850 {
851 uint32 vibTicks = ((GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT)) && !m_SongFlags[SONG_ITOLDEFFECTS]) ? numTicks : nonRowTicks;
852 uint32 inc = chn.nVibratoSpeed * vibTicks;
853 if(m_playBehaviour[kITVibratoTremoloPanbrello])
854 inc *= 4;
855 chn.nVibratoPos += static_cast<uint8>(inc);
856 }
857 break;
858
859 case CMD_TREMOLO:
860 if(adjustMode & eAdjust)
861 {
862 uint32 tremTicks = ((GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT)) && !m_SongFlags[SONG_ITOLDEFFECTS]) ? numTicks : nonRowTicks;
863 uint32 inc = chn.nTremoloSpeed * tremTicks;
864 if(m_playBehaviour[kITVibratoTremoloPanbrello])
865 inc *= 4;
866 chn.nTremoloPos += static_cast<uint8>(inc);
867 }
868 break;
869
870 case CMD_PANBRELLO:
871 if(adjustMode & eAdjust)
872 {
873 // Panbrello effect is permanent in compatible mode, so actually apply panbrello for the last tick of this row
874 chn.nPanbrelloPos += static_cast<uint8>(chn.nPanbrelloSpeed * (numTicks - 1));
875 ProcessPanbrello(chn);
876 }
877 break;
878 }
879 }
880
881 // Interpret F00 effect in XM files as "stop song"
882 if(GetType() == MOD_TYPE_XM && playState.m_nMusicSpeed == uint16_max)
883 {
884 break;
885 }
886
887 playState.m_nCurrentRowsPerBeat = m_nDefaultRowsPerBeat;
888 if(Patterns[playState.m_nPattern].GetOverrideSignature())
889 {
890 playState.m_nCurrentRowsPerBeat = Patterns[playState.m_nPattern].GetRowsPerBeat();
891 }
892
893 const uint32 tickDuration = GetTickDuration(playState);
894 const uint32 rowDuration = tickDuration * numTicks;
895 memory.elapsedTime += static_cast<double>(rowDuration) / static_cast<double>(m_MixerSettings.gdwMixingFreq);
896 playState.m_lTotalSampleCount += rowDuration;
897
898 if(adjustSamplePos)
899 {
900 // Super experimental and dirty sample seeking
901 for(CHANNELINDEX nChn = 0; nChn < GetNumChannels(); nChn++)
902 {
903 if(memory.chnSettings[nChn].ticksToRender == GetLengthMemory::IGNORE_CHANNEL)
904 continue;
905
906 ModChannel &chn = playState.Chn[nChn];
907 const ModCommand &m = chn.rowCommand;
908 if(!chn.nPeriod && m.IsEmpty())
909 continue;
910
911 uint32 paramHi = m.param >> 4, paramLo = m.param & 0x0F;
912 uint32 startTick = 0;
913 bool porta = m.command == CMD_TONEPORTAMENTO || m.command == CMD_TONEPORTAVOL || m.volcmd == VOLCMD_TONEPORTAMENTO;
914 bool stopNote = false;
915
916 if(m.instr) chn.prevNoteOffset = 0;
917 if(m.IsNote())
918 {
919 if(porta && memory.chnSettings[nChn].incChanged)
920 {
921 // If there's a portamento, the current channel increment mustn't be 0 in NoteChange()
922 chn.increment = GetChannelIncrement(chn, chn.nPeriod, 0).first;
923 }
924 int32 setPan = chn.nPan;
925 chn.nNewNote = chn.nLastNote;
926 if(chn.nNewIns != 0) InstrumentChange(chn, chn.nNewIns, porta);
927 NoteChange(chn, m.note, porta);
928 memory.chnSettings[nChn].incChanged = true;
929
930 if((m.command == CMD_MODCMDEX || m.command == CMD_S3MCMDEX) && (m.param & 0xF0) == 0xD0 && paramLo < numTicks)
931 {
932 startTick = paramLo;
933 } else if(m.command == CMD_DELAYCUT && paramHi < numTicks)
934 {
935 startTick = paramHi;
936 }
937 if(playState.m_nPatternDelay > 1 && startTick != 0 && (GetType() & (MOD_TYPE_S3M | MOD_TYPE_IT | MOD_TYPE_MPT)))
938 {
939 startTick += (playState.m_nMusicSpeed + playState.m_nFrameDelay) * (playState.m_nPatternDelay - 1);
940 }
941 if(!porta) memory.chnSettings[nChn].ticksToRender = 0;
942
943 // Panning commands have to be re-applied after a note change with potential pan change.
944 if(m.command == CMD_PANNING8
945 || ((m.command == CMD_MODCMDEX || m.command == CMD_S3MCMDEX) && paramHi == 0x8)
946 || m.volcmd == VOLCMD_PANNING)
947 {
948 chn.nPan = setPan;
949 }
950 }
951
952 if(m.IsNote() || m_playBehaviour[kApplyOffsetWithoutNote])
953 {
954 if(m.command == CMD_OFFSET)
955 {
956 ProcessSampleOffset(chn, nChn, playState);
957 } else if(m.command == CMD_OFFSETPERCENTAGE)
958 {
959 SampleOffset(chn, Util::muldiv_unsigned(chn.nLength, m.param, 256));
960 } else if(m.command == CMD_REVERSEOFFSET && chn.pModSample != nullptr)
961 {
962 memory.RenderChannel(nChn, oldTickDuration); // Re-sync what we've got so far
963 ReverseSampleOffset(chn, m.param);
964 startTick = playState.m_nMusicSpeed - 1;
965 } else if(m.volcmd == VOLCMD_OFFSET)
966 {
967 if(chn.pModSample != nullptr && m.vol <= std::size(chn.pModSample->cues))
968 {
969 SmpLength offset;
970 if(m.vol == 0)
971 offset = chn.oldOffset;
972 else
973 offset = chn.oldOffset = chn.pModSample->cues[m.vol - 1];
974 SampleOffset(chn, offset);
975 }
976 }
977 }
978
979 if(m.note == NOTE_KEYOFF || m.note == NOTE_NOTECUT || (m.note == NOTE_FADE && GetNumInstruments())
980 || ((m.command == CMD_MODCMDEX || m.command == CMD_S3MCMDEX) && (m.param & 0xF0) == 0xC0 && paramLo < numTicks)
981 || (m.command == CMD_DELAYCUT && paramLo != 0 && startTick + paramLo < numTicks)
982 || m.command == CMD_KEYOFF)
983 {
984 stopNote = true;
985 }
986
987 if(m.command == CMD_VOLUME)
988 {
989 chn.nVolume = m.param * 4;
990 } else if(m.volcmd == VOLCMD_VOLUME)
991 {
992 chn.nVolume = m.vol * 4;
993 }
994
995 if(chn.pModSample && !stopNote)
996 {
997 // Check if we don't want to emulate some effect and thus stop processing.
998 if(m.command < MAX_EFFECTS)
999 {
1000 if(forbiddenCommands[m.command])
1001 {
1002 stopNote = true;
1003 } else if(m.command == CMD_MODCMDEX)
1004 {
1005 // Special case: Slides using extended commands
1006 switch(m.param & 0xF0)
1007 {
1008 case 0x10:
1009 case 0x20:
1010 stopNote = true;
1011 }
1012 }
1013 }
1014
1015 if(m.volcmd < forbiddenVolCommands.size() && forbiddenVolCommands[m.volcmd])
1016 {
1017 stopNote = true;
1018 }
1019 }
1020
1021 if(stopNote)
1022 {
1023 chn.Stop();
1024 memory.chnSettings[nChn].ticksToRender = 0;
1025 } else
1026 {
1027 if(oldTickDuration != tickDuration && oldTickDuration != 0)
1028 {
1029 memory.RenderChannel(nChn, oldTickDuration); // Re-sync what we've got so far
1030 }
1031
1032 switch(m.command)
1033 {
1034 case CMD_TONEPORTAVOL:
1035 case CMD_VOLUMESLIDE:
1036 case CMD_VIBRATOVOL:
1037 if(m.param || (GetType() != MOD_TYPE_MOD))
1038 {
1039 for(uint32 i = 0; i < numTicks; i++)
1040 {
1041 chn.isFirstTick = (i == 0);
1042 VolumeSlide(chn, m.param);
1043 }
1044 }
1045 break;
1046
1047 case CMD_MODCMDEX:
1048 if((m.param & 0x0F) || (GetType() & (MOD_TYPE_XM | MOD_TYPE_MT2)))
1049 {
1050 chn.isFirstTick = true;
1051 switch(m.param & 0xF0)
1052 {
1053 case 0xA0: FineVolumeUp(chn, m.param & 0x0F, false); break;
1054 case 0xB0: FineVolumeDown(chn, m.param & 0x0F, false); break;
1055 }
1056 }
1057 break;
1058
1059 case CMD_S3MCMDEX:
1060 if(m.param == 0x9E)
1061 {
1062 // Play forward
1063 memory.RenderChannel(nChn, oldTickDuration); // Re-sync what we've got so far
1064 chn.dwFlags.reset(CHN_PINGPONGFLAG);
1065 } else if(m.param == 0x9F)
1066 {
1067 // Reverse
1068 memory.RenderChannel(nChn, oldTickDuration); // Re-sync what we've got so far
1069 chn.dwFlags.set(CHN_PINGPONGFLAG);
1070 if(!chn.position.GetInt() && chn.nLength && (m.IsNote() || !chn.dwFlags[CHN_LOOP]))
1071 {
1072 chn.position.Set(chn.nLength - 1, SamplePosition::fractMax);
1073 }
1074 } else if((m.param & 0xF0) == 0x70)
1075 {
1076 if(m.param >= 0x73)
1077 chn.InstrumentControl(m.param, *this);
1078 }
1079 break;
1080
1081 case CMD_FINETUNE:
1082 case CMD_FINETUNE_SMOOTH:
1083 memory.RenderChannel(nChn, oldTickDuration); // Re-sync what we've got so far
1084 SetFinetune(nChn, playState, false); // TODO should render each tick individually for CMD_FINETUNE_SMOOTH for higher sync accuracy
1085 break;
1086 }
1087 chn.isFirstTick = true;
1088 switch(m.volcmd)
1089 {
1090 case VOLCMD_FINEVOLUP: FineVolumeUp(chn, m.vol, m_playBehaviour[kITVolColMemory]); break;
1091 case VOLCMD_FINEVOLDOWN: FineVolumeDown(chn, m.vol, m_playBehaviour[kITVolColMemory]); break;
1092 case VOLCMD_VOLSLIDEUP:
1093 case VOLCMD_VOLSLIDEDOWN:
1094 {
1095 // IT Compatibility: Volume column volume slides have their own memory
1096 // Test case: VolColMemory.it
1097 ModCommand::VOL vol = m.vol;
1098 if(vol == 0 && m_playBehaviour[kITVolColMemory])
1099 {
1100 vol = chn.nOldVolParam;
1101 if(vol == 0)
1102 break;
1103 }
1104 if(m.volcmd == VOLCMD_VOLSLIDEUP)
1105 vol <<= 4;
1106 for(uint32 i = 0; i < numTicks; i++)
1107 {
1108 chn.isFirstTick = (i == 0);
1109 VolumeSlide(chn, vol);
1110 }
1111 }
1112 break;
1113 case VOLCMD_PLAYCONTROL:
1114 if(m.vol <= 1)
1115 chn.isPaused = (m.vol == 0);
1116 break;
1117 }
1118
1119 if(chn.isPaused)
1120 continue;
1121 if(porta)
1122 {
1123 // Portamento needs immediate syncing, as the pitch changes on each tick
1124 uint32 portaTick = memory.chnSettings[nChn].ticksToRender + startTick + 1;
1125 memory.chnSettings[nChn].ticksToRender += numTicks;
1126 memory.RenderChannel(nChn, tickDuration, portaTick);
1127 } else
1128 {
1129 memory.chnSettings[nChn].ticksToRender += (numTicks - startTick);
1130 }
1131 }
1132 }
1133 }
1134 oldTickDuration = tickDuration;
1135
1136 breakToRow = HandleNextRow(playState, orderList, false);
1137 }
1138
1139 // Now advance the sample positions for sample seeking on channels that are still playing
1140 if(adjustSamplePos)
1141 {
1142 for(CHANNELINDEX nChn = 0; nChn < GetNumChannels(); nChn++)
1143 {
1144 if(memory.chnSettings[nChn].ticksToRender != GetLengthMemory::IGNORE_CHANNEL)
1145 {
1146 memory.RenderChannel(nChn, oldTickDuration);
1147 }
1148 }
1149 }
1150
1151 if(retval.targetReached)
1152 {
1153 retval.lastOrder = playState.m_nCurrentOrder;
1154 retval.lastRow = playState.m_nRow;
1155 }
1156 retval.duration = memory.elapsedTime;
1157 results.push_back(retval);
1158
1159 // Store final variables
1160 if(adjustMode & eAdjust)
1161 {
1162 if(retval.targetReached || target.mode == GetLengthTarget::NoTarget)
1163 {
1164 // Target found, or there is no target (i.e. play whole song)...
1165 m_PlayState = std::move(playState);
1166 m_PlayState.ResetGlobalVolumeRamping();
1167 m_PlayState.m_nNextRow = m_PlayState.m_nRow;
1168 m_PlayState.m_nFrameDelay = m_PlayState.m_nPatternDelay = 0;
1169 m_PlayState.m_nTickCount = TICKS_ROW_FINISHED;
1170 m_PlayState.m_bPositionChanged = true;
1171 if(m_opl != nullptr)
1172 m_opl->Reset();
1173 for(CHANNELINDEX n = 0; n < GetNumChannels(); n++)
1174 {
1175 auto &chn = m_PlayState.Chn[n];
1176 if(chn.nLastNote != NOTE_NONE)
1177 {
1178 chn.nNewNote = chn.nLastNote;
1179 }
1180 if(memory.chnSettings[n].vol != 0xFF && !adjustSamplePos)
1181 {
1182 chn.nVolume = std::min(memory.chnSettings[n].vol, uint8(64)) * 4;
1183 }
1184 if(chn.pModSample != nullptr && chn.pModSample->uFlags[CHN_ADLIB] && m_opl)
1185 {
1186 m_opl->Patch(n, chn.pModSample->adlib);
1187 m_opl->NoteCut(n);
1188 }
1189 chn.pCurrentSample = nullptr;
1190 }
1191
1192 #ifndef NO_PLUGINS
1193 // If there were any PC events, update plugin parameters to their latest value.
1194 std::bitset<MAX_MIXPLUGINS> plugSetProgram;
1195 for(const auto ¶m : memory.plugParams)
1196 {
1197 PLUGINDEX plug = param.first.first - 1;
1198 IMixPlugin *plugin = m_MixPlugins[plug].pMixPlugin;
1199 if(plugin != nullptr)
1200 {
1201 if(!plugSetProgram[plug])
1202 {
1203 // Used for bridged plugins to avoid sending out individual messages for each parameter.
1204 plugSetProgram.set(plug);
1205 plugin->BeginSetProgram();
1206 }
1207 plugin->SetParameter(param.first.second, param.second / PlugParamValue(ModCommand::maxColumnValue));
1208 }
1209 }
1210 if(plugSetProgram.any())
1211 {
1212 for(PLUGINDEX i = 0; i < MAX_MIXPLUGINS; i++)
1213 {
1214 if(plugSetProgram[i])
1215 {
1216 m_MixPlugins[i].pMixPlugin->EndSetProgram();
1217 }
1218 }
1219 }
1220 #endif // NO_PLUGINS
1221 } else if(adjustMode != eAdjustOnSuccess)
1222 {
1223 // Target not found (e.g. when jumping to a hidden sub song), reset global variables...
1224 m_PlayState.m_nMusicSpeed = m_nDefaultSpeed;
1225 m_PlayState.m_nMusicTempo = m_nDefaultTempo;
1226 m_PlayState.m_nGlobalVolume = m_nDefaultGlobalVolume;
1227 }
1228 // When adjusting the playback status, we will also want to update the visited rows vector according to the current position.
1229 if(sequence != Order.GetCurrentSequenceIndex())
1230 {
1231 Order.SetSequence(sequence);
1232 }
1233 }
1234 if(adjustMode & (eAdjust | eAdjustOnlyVisitedRows))
1235 m_visitedRows.MoveVisitedRowsFrom(visitedRows);
1236
1237 return results;
1238 }
1239
1240
1241 //////////////////////////////////////////////////////////////////////////////////////////////////
1242 // Effects
1243
1244 // Change sample or instrument number.
InstrumentChange(ModChannel & chn,uint32 instr,bool bPorta,bool bUpdVol,bool bResetEnv) const1245 void CSoundFile::InstrumentChange(ModChannel &chn, uint32 instr, bool bPorta, bool bUpdVol, bool bResetEnv) const
1246 {
1247 const ModInstrument *pIns = instr <= GetNumInstruments() ? Instruments[instr] : nullptr;
1248 const ModSample *pSmp = &Samples[instr];
1249 const auto oldInsVol = chn.nInsVol;
1250 ModCommand::NOTE note = chn.nNewNote;
1251
1252 if(note == NOTE_NONE && m_playBehaviour[kITInstrWithoutNote]) return;
1253
1254 if(pIns != nullptr && ModCommand::IsNote(note))
1255 {
1256 // Impulse Tracker ignores empty slots.
1257 // We won't ignore them if a plugin is assigned to this slot, so that VSTis still work as intended.
1258 // Test case: emptyslot.it, PortaInsNum.it, gxsmp.it, gxsmp2.it
1259 if(pIns->Keyboard[note - NOTE_MIN] == 0 && m_playBehaviour[kITEmptyNoteMapSlot] && !pIns->HasValidMIDIChannel())
1260 {
1261 chn.pModInstrument = pIns;
1262 return;
1263 }
1264
1265 if(pIns->NoteMap[note - NOTE_MIN] > NOTE_MAX) return;
1266 uint32 n = pIns->Keyboard[note - NOTE_MIN];
1267 pSmp = ((n) && (n < MAX_SAMPLES)) ? &Samples[n] : nullptr;
1268 } else if(GetNumInstruments())
1269 {
1270 // No valid instrument, or not a valid note.
1271 if (note >= NOTE_MIN_SPECIAL) return;
1272 if(m_playBehaviour[kITEmptyNoteMapSlot] && (pIns == nullptr || !pIns->HasValidMIDIChannel()))
1273 {
1274 // Impulse Tracker ignores empty slots.
1275 // We won't ignore them if a plugin is assigned to this slot, so that VSTis still work as intended.
1276 // Test case: emptyslot.it, PortaInsNum.it, gxsmp.it, gxsmp2.it
1277 chn.pModInstrument = nullptr;
1278 chn.nNewIns = 0;
1279 return;
1280 }
1281 pSmp = nullptr;
1282 }
1283
1284 bool returnAfterVolumeAdjust = false;
1285
1286 // instrumentChanged is used for IT carry-on env option
1287 bool instrumentChanged = (pIns != chn.pModInstrument);
1288 const bool sampleChanged = (chn.pModSample != nullptr) && (pSmp != chn.pModSample);
1289 const bool newTuning = (GetType() == MOD_TYPE_MPT && pIns && pIns->pTuning);
1290
1291 if(!bPorta || instrumentChanged || sampleChanged)
1292 chn.microTuning = 0;
1293
1294 // Playback behavior change for MPT: With portamento don't change sample if it is in
1295 // the same instrument as previous sample.
1296 if(bPorta && newTuning && pIns == chn.pModInstrument && sampleChanged)
1297 return;
1298
1299 if(sampleChanged && bPorta)
1300 {
1301 // IT compatibility: No sample change (also within multi-sample instruments) during portamento when using Compatible Gxx.
1302 // Test case: PortaInsNumCompat.it, PortaSampleCompat.it, PortaCutCompat.it
1303 if(m_playBehaviour[kITPortamentoInstrument] && m_SongFlags[SONG_ITCOMPATGXX] && !chn.increment.IsZero())
1304 {
1305 pSmp = chn.pModSample;
1306 }
1307
1308 // Special XM hack (also applies to MOD / S3M, except when playing IT-style S3Ms, such as k_vision.s3m)
1309 // Test case: PortaSmpChange.mod, PortaSmpChange.s3m, PortaSwap.s3m
1310 if((!instrumentChanged && (GetType() & (MOD_TYPE_XM | MOD_TYPE_MT2)) && pIns)
1311 || (GetType() == MOD_TYPE_PLM)
1312 || (GetType() == MOD_TYPE_MOD && chn.IsSamplePlaying())
1313 || (m_playBehaviour[kST3PortaSampleChange] && chn.IsSamplePlaying()))
1314 {
1315 // FT2 doesn't change the sample in this case,
1316 // but still uses the sample info from the old one (bug?)
1317 returnAfterVolumeAdjust = true;
1318 }
1319 }
1320 // IT compatibility: A lone instrument number should only reset sample properties to those of the corresponding sample in instrument mode.
1321 // C#5 01 ... <-- sample 1
1322 // C-5 .. g02 <-- sample 2
1323 // ... 01 ... <-- still sample 1, but with properties of sample 2
1324 // In the above example, no sample change happens on the second row. In the third row, sample 1 keeps playing but with the
1325 // volume and panning properties of sample 2.
1326 // Test case: InstrAfterMultisamplePorta.it
1327 if(m_nInstruments && !instrumentChanged && sampleChanged && chn.pCurrentSample != nullptr && m_playBehaviour[kITMultiSampleInstrumentNumber] && !chn.rowCommand.IsNote())
1328 {
1329 returnAfterVolumeAdjust = true;
1330 }
1331
1332 // IT Compatibility: Envelope pickup after SCx cut (but don't do this when working with plugins, or else envelope carry stops working)
1333 // Test case: cut-carry.it
1334 if(!chn.IsSamplePlaying() && (GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT)) && (!pIns || !pIns->HasValidMIDIChannel()))
1335 {
1336 instrumentChanged = true;
1337 }
1338
1339 // FT2 compatibility: new instrument + portamento = ignore new instrument number, but reload old instrument settings (the world of XM is upside down...)
1340 // And this does *not* happen if volume column portamento is used together with note delay... (handled in ProcessEffects(), where all the other note delay stuff is.)
1341 // Test case: porta-delay.xm, SamplePortaInInstrument.xm
1342 if((instrumentChanged || sampleChanged) && bPorta && m_playBehaviour[kFT2PortaIgnoreInstr] && (chn.pModInstrument != nullptr || chn.pModSample != nullptr))
1343 {
1344 pIns = chn.pModInstrument;
1345 pSmp = chn.pModSample;
1346 instrumentChanged = false;
1347 } else
1348 {
1349 chn.pModInstrument = pIns;
1350 }
1351
1352 // Update Volume
1353 if (bUpdVol && (!(GetType() & (MOD_TYPE_MOD | MOD_TYPE_S3M)) || ((pSmp != nullptr && pSmp->HasSampleData()) || chn.HasMIDIOutput())))
1354 {
1355 if(pSmp)
1356 {
1357 if(!pSmp->uFlags[SMP_NODEFAULTVOLUME])
1358 chn.nVolume = pSmp->nVolume;
1359 } else if(pIns && pIns->nMixPlug)
1360 {
1361 chn.nVolume = chn.GetVSTVolume();
1362 } else
1363 {
1364 chn.nVolume = 0;
1365 }
1366 }
1367
1368 if(returnAfterVolumeAdjust && sampleChanged && pSmp != nullptr)
1369 {
1370 // ProTracker applies new instrument's finetune but keeps the old sample playing.
1371 // Test case: PortaSwapPT.mod
1372 if(m_playBehaviour[kMODSampleSwap])
1373 chn.nFineTune = pSmp->nFineTune;
1374 // ST3 does it similarly for middle-C speed.
1375 // Test case: PortaSwap.s3m, SampleSwap.s3m
1376 if(GetType() == MOD_TYPE_S3M && pSmp->HasSampleData())
1377 chn.nC5Speed = pSmp->nC5Speed;
1378 }
1379
1380 if(returnAfterVolumeAdjust) return;
1381
1382 // Instrument adjust
1383 chn.nNewIns = 0;
1384
1385 // IT Compatiblity: NNA is reset on every note change, not every instrument change (fixes s7xinsnum.it).
1386 if (pIns && ((!m_playBehaviour[kITNNAReset] && pSmp) || pIns->nMixPlug || instrumentChanged))
1387 chn.nNNA = pIns->nNNA;
1388
1389 // Update volume
1390 chn.UpdateInstrumentVolume(pSmp, pIns);
1391
1392 // Update panning
1393 // FT2 compatibility: Only reset panning on instrument numbers, not notes (bUpdVol condition)
1394 // Test case: PanMemory.xm
1395 // IT compatibility: Sample and instrument panning is only applied on note change, not instrument change
1396 // Test case: PanReset.it
1397 if((bUpdVol || !(GetType() & (MOD_TYPE_XM | MOD_TYPE_MT2))) && !m_playBehaviour[kITPanningReset])
1398 {
1399 ApplyInstrumentPanning(chn, pIns, pSmp);
1400 }
1401
1402 // Reset envelopes
1403 if(bResetEnv)
1404 {
1405 // Blurb by Storlek (from the SchismTracker code):
1406 // Conditions experimentally determined to cause envelope reset in Impulse Tracker:
1407 // - no note currently playing (of course)
1408 // - note given, no portamento
1409 // - instrument number given, portamento, compat gxx enabled
1410 // - instrument number given, no portamento, after keyoff, old effects enabled
1411 // If someone can enlighten me to what the logic really is here, I'd appreciate it.
1412 // Seems like it's just a total mess though, probably to get XMs to play right.
1413
1414 bool reset, resetAlways;
1415
1416 // IT Compatibility: Envelope reset
1417 // Test case: EnvReset.it
1418 if(m_playBehaviour[kITEnvelopeReset])
1419 {
1420 const bool insNumber = (instr != 0);
1421 reset = (!chn.nLength
1422 || (insNumber && bPorta && m_SongFlags[SONG_ITCOMPATGXX])
1423 || (insNumber && !bPorta && chn.dwFlags[CHN_NOTEFADE | CHN_KEYOFF] && m_SongFlags[SONG_ITOLDEFFECTS]));
1424 // NOTE: IT2.14 with SB/GUS/etc. output is different. We are going after IT's WAV writer here.
1425 // For SB/GUS/etc. emulation, envelope carry should only apply when the NNA isn't set to "Note Cut".
1426 // Test case: CarryNNA.it
1427 resetAlways = (!chn.nFadeOutVol || instrumentChanged || chn.dwFlags[CHN_KEYOFF]);
1428 } else
1429 {
1430 reset = (!bPorta || !(GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT | MOD_TYPE_DBM)) || m_SongFlags[SONG_ITCOMPATGXX]
1431 || !chn.nLength || (chn.dwFlags[CHN_NOTEFADE] && !chn.nFadeOutVol));
1432 resetAlways = !(GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT | MOD_TYPE_DBM)) || instrumentChanged || pIns == nullptr || chn.dwFlags[CHN_KEYOFF | CHN_NOTEFADE];
1433 }
1434
1435 if(reset)
1436 {
1437 chn.dwFlags.set(CHN_FASTVOLRAMP);
1438 if(pIns != nullptr)
1439 {
1440 if(resetAlways)
1441 {
1442 chn.ResetEnvelopes();
1443 } else
1444 {
1445 if(!pIns->VolEnv.dwFlags[ENV_CARRY]) chn.VolEnv.Reset();
1446 if(!pIns->PanEnv.dwFlags[ENV_CARRY]) chn.PanEnv.Reset();
1447 if(!pIns->PitchEnv.dwFlags[ENV_CARRY]) chn.PitchEnv.Reset();
1448 }
1449 }
1450
1451 // IT Compatibility: Autovibrato reset
1452 if(!m_playBehaviour[kITVibratoTremoloPanbrello])
1453 {
1454 chn.nAutoVibDepth = 0;
1455 chn.nAutoVibPos = 0;
1456 }
1457 } else if(pIns != nullptr && !pIns->VolEnv.dwFlags[ENV_ENABLED])
1458 {
1459 if(m_playBehaviour[kITPortamentoInstrument])
1460 {
1461 chn.VolEnv.Reset();
1462 } else
1463 {
1464 chn.ResetEnvelopes();
1465 }
1466 }
1467 }
1468 // Invalid sample ?
1469 if(pSmp == nullptr && (pIns == nullptr || !pIns->HasValidMIDIChannel()))
1470 {
1471 chn.pModSample = nullptr;
1472 chn.nInsVol = 0;
1473 return;
1474 }
1475
1476 // Tone-Portamento doesn't reset the pingpong direction flag
1477 if(bPorta && pSmp == chn.pModSample && pSmp != nullptr)
1478 {
1479 // If channel length is 0, we cut a previous sample using SCx. In that case, we have to update sample length, loop points, etc...
1480 if(GetType() & (MOD_TYPE_S3M|MOD_TYPE_IT|MOD_TYPE_MPT) && chn.nLength != 0)
1481 return;
1482 // FT2 compatibility: Do not reset key-off status on portamento without instrument number
1483 // Test case: Off-Porta.xm
1484 if(GetType() != MOD_TYPE_XM || !m_playBehaviour[kITFT2DontResetNoteOffOnPorta] || chn.rowCommand.instr != 0)
1485 chn.dwFlags.reset(CHN_KEYOFF | CHN_NOTEFADE);
1486 chn.dwFlags = (chn.dwFlags & (CHN_CHANNELFLAGS | CHN_PINGPONGFLAG));
1487 } else //if(!instrumentChanged || chn.rowCommand.instr != 0 || !IsCompatibleMode(TRK_FASTTRACKER2)) // SampleChange.xm?
1488 {
1489 chn.dwFlags.reset(CHN_KEYOFF | CHN_NOTEFADE);
1490
1491 // IT compatibility: Don't change bidi loop direction when no sample nor instrument is changed.
1492 if((m_playBehaviour[kITPingPongNoReset] || !(GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT))) && pSmp == chn.pModSample && !instrumentChanged)
1493 chn.dwFlags = (chn.dwFlags & (CHN_CHANNELFLAGS | CHN_PINGPONGFLAG));
1494 else
1495 chn.dwFlags = (chn.dwFlags & CHN_CHANNELFLAGS);
1496
1497 if(pIns)
1498 {
1499 // Copy envelope flags (we actually only need the "enabled" and "pitch" flag)
1500 chn.VolEnv.flags = pIns->VolEnv.dwFlags;
1501 chn.PanEnv.flags = pIns->PanEnv.dwFlags;
1502 chn.PitchEnv.flags = pIns->PitchEnv.dwFlags;
1503
1504 // A cutoff frequency of 0 should not be reset just because the filter envelope is enabled.
1505 // Test case: FilterEnvReset.it
1506 if((pIns->PitchEnv.dwFlags & (ENV_ENABLED | ENV_FILTER)) == (ENV_ENABLED | ENV_FILTER) && !m_playBehaviour[kITFilterBehaviour])
1507 {
1508 if(!chn.nCutOff) chn.nCutOff = 0x7F;
1509 }
1510
1511 if(pIns->IsCutoffEnabled()) chn.nCutOff = pIns->GetCutoff();
1512 if(pIns->IsResonanceEnabled()) chn.nResonance = pIns->GetResonance();
1513 }
1514 }
1515
1516 if(pSmp == nullptr)
1517 {
1518 chn.pModSample = nullptr;
1519 chn.nLength = 0;
1520 return;
1521 }
1522
1523 if(bPorta && chn.nLength == 0 && (m_playBehaviour[kFT2PortaNoNote] || m_playBehaviour[kITPortaNoNote]))
1524 {
1525 // IT/FT2 compatibility: If the note just stopped on the previous tick, prevent it from restarting.
1526 // Test cases: PortaJustStoppedNote.xm, PortaJustStoppedNote.it
1527 chn.increment.Set(0);
1528 }
1529
1530 // IT compatibility: Note-off with instrument number + Old Effects retriggers envelopes.
1531 // If the instrument changes, keep playing the previous sample, but load the new instrument's envelopes.
1532 // Test case: ResetEnvNoteOffOldFx.it
1533 if(chn.rowCommand.note == NOTE_KEYOFF && m_playBehaviour[kITInstrWithNoteOffOldEffects] && m_SongFlags[SONG_ITOLDEFFECTS] && sampleChanged)
1534 {
1535 if(chn.pModSample)
1536 {
1537 chn.dwFlags |= (chn.pModSample->uFlags & CHN_SAMPLEFLAGS);
1538 }
1539 chn.nInsVol = oldInsVol;
1540 chn.nVolume = pSmp->nVolume;
1541 if(pSmp->uFlags[CHN_PANNING]) chn.SetInstrumentPan(pSmp->nPan, *this);
1542 return;
1543 }
1544
1545 chn.pModSample = pSmp;
1546 chn.nLength = pSmp->nLength;
1547 chn.nLoopStart = pSmp->nLoopStart;
1548 chn.nLoopEnd = pSmp->nLoopEnd;
1549 // ProTracker "oneshot" loops (if loop start is 0, play the whole sample once and then repeat until loop end)
1550 if(m_playBehaviour[kMODOneShotLoops] && chn.nLoopStart == 0) chn.nLoopEnd = pSmp->nLength;
1551 chn.dwFlags |= (pSmp->uFlags & CHN_SAMPLEFLAGS);
1552
1553 // IT Compatibility: Autovibrato reset
1554 if(m_playBehaviour[kITVibratoTremoloPanbrello])
1555 {
1556 chn.nAutoVibDepth = 0;
1557 chn.nAutoVibPos = 0;
1558 }
1559
1560 if(newTuning)
1561 {
1562 chn.nC5Speed = pSmp->nC5Speed;
1563 chn.m_CalculateFreq = true;
1564 chn.nFineTune = 0;
1565 } else if(!bPorta || sampleChanged || !(GetType() & (MOD_TYPE_MOD | MOD_TYPE_XM)))
1566 {
1567 // Don't reset finetune changed by "set finetune" command.
1568 // Test case: finetune.xm, finetune.mod
1569 // But *do* change the finetune if we switch to a different sample, to fix
1570 // Miranda`s axe by Jamson (jam007.xm).
1571 chn.nC5Speed = pSmp->nC5Speed;
1572 chn.nFineTune = pSmp->nFineTune;
1573 }
1574
1575 chn.nTranspose = UseFinetuneAndTranspose() ? pSmp->RelativeTone : 0;
1576
1577 // FT2 compatibility: Don't reset portamento target with new instrument numbers.
1578 // Test case: Porta-Pickup.xm
1579 // ProTracker does the same.
1580 // Test case: PortaTarget.mod
1581 if(!m_playBehaviour[kFT2PortaTargetNoReset] && GetType() != MOD_TYPE_MOD)
1582 {
1583 chn.nPortamentoDest = 0;
1584 }
1585 chn.m_PortamentoFineSteps = 0;
1586
1587 if(chn.dwFlags[CHN_SUSTAINLOOP])
1588 {
1589 chn.nLoopStart = pSmp->nSustainStart;
1590 chn.nLoopEnd = pSmp->nSustainEnd;
1591 if(chn.dwFlags[CHN_PINGPONGSUSTAIN]) chn.dwFlags.set(CHN_PINGPONGLOOP);
1592 chn.dwFlags.set(CHN_LOOP);
1593 }
1594 if(chn.dwFlags[CHN_LOOP] && chn.nLoopEnd < chn.nLength) chn.nLength = chn.nLoopEnd;
1595
1596 // Fix sample position on instrument change. This is needed for IT "on the fly" sample change.
1597 // XXX is this actually called? In ProcessEffects(), a note-on effect is emulated if there's an on the fly sample change!
1598 if(chn.position.GetUInt() >= chn.nLength)
1599 {
1600 if((GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT)))
1601 {
1602 chn.position.Set(0);
1603 }
1604 }
1605 }
1606
1607
NoteChange(ModChannel & chn,int note,bool bPorta,bool bResetEnv,bool bManual,CHANNELINDEX channelHint) const1608 void CSoundFile::NoteChange(ModChannel &chn, int note, bool bPorta, bool bResetEnv, bool bManual, CHANNELINDEX channelHint) const
1609 {
1610 if(note < NOTE_MIN)
1611 return;
1612 const int origNote = note;
1613 const ModSample *pSmp = chn.pModSample;
1614 const ModInstrument *pIns = chn.pModInstrument;
1615
1616 const bool newTuning = (GetType() == MOD_TYPE_MPT && pIns != nullptr && pIns->pTuning);
1617 // save the note that's actually used, as it's necessary to properly calculate PPS and stuff
1618 const int realnote = note;
1619
1620 if((pIns) && (note - NOTE_MIN < (int)std::size(pIns->Keyboard)))
1621 {
1622 uint32 n = pIns->Keyboard[note - NOTE_MIN];
1623 if((n) && (n < MAX_SAMPLES))
1624 {
1625 pSmp = &Samples[n];
1626 } else if(m_playBehaviour[kITEmptyNoteMapSlot] && !chn.HasMIDIOutput())
1627 {
1628 // Impulse Tracker ignores empty slots.
1629 // We won't ignore them if a plugin is assigned to this slot, so that VSTis still work as intended.
1630 // Test case: emptyslot.it, PortaInsNum.it, gxsmp.it, gxsmp2.it
1631 return;
1632 }
1633 note = pIns->NoteMap[note - NOTE_MIN];
1634 }
1635 // Key Off
1636 if(note > NOTE_MAX)
1637 {
1638 // Key Off (+ Invalid Note for XM - TODO is this correct?)
1639 if(note == NOTE_KEYOFF || !(GetType() & (MOD_TYPE_IT|MOD_TYPE_MPT)))
1640 {
1641 KeyOff(chn);
1642 // IT compatibility: Note-off + instrument releases sample sustain but does not release envelopes or fade the instrument
1643 // Test case: noteoff3.it, ResetEnvNoteOffOldFx2.it
1644 if(!bPorta && m_playBehaviour[kITInstrWithNoteOffOldEffects] && m_SongFlags[SONG_ITOLDEFFECTS] && chn.rowCommand.instr)
1645 chn.dwFlags.reset(CHN_NOTEFADE | CHN_KEYOFF);
1646 } else // Invalid Note -> Note Fade
1647 {
1648 if(/*note == NOTE_FADE && */ GetNumInstruments())
1649 chn.dwFlags.set(CHN_NOTEFADE);
1650 }
1651
1652 // Note Cut
1653 if (note == NOTE_NOTECUT)
1654 {
1655 if(chn.dwFlags[CHN_ADLIB] && GetType() == MOD_TYPE_S3M)
1656 {
1657 // OPL voices are not cut but enter the release portion of their envelope
1658 // In S3M we can still modify the volume after note-off, in legacy MPTM mode we can't
1659 chn.dwFlags.set(CHN_KEYOFF);
1660 } else
1661 {
1662 chn.dwFlags.set(CHN_NOTEFADE | CHN_FASTVOLRAMP);
1663 // IT compatibility: Stopping sample playback by setting sample increment to 0 rather than volume
1664 // Test case: NoteOffInstr.it
1665 if ((!(GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT))) || (m_nInstruments != 0 && !m_playBehaviour[kITInstrWithNoteOff])) chn.nVolume = 0;
1666 if (m_playBehaviour[kITInstrWithNoteOff]) chn.increment.Set(0);
1667 chn.nFadeOutVol = 0;
1668 }
1669 }
1670
1671 // IT compatibility tentative fix: Clear channel note memory (TRANCE_N.IT by A3F).
1672 if(m_playBehaviour[kITClearOldNoteAfterCut])
1673 {
1674 chn.nNote = chn.nNewNote = NOTE_NONE;
1675 }
1676 return;
1677 }
1678
1679 if(newTuning)
1680 {
1681 if(!bPorta || chn.nNote == NOTE_NONE)
1682 chn.nPortamentoDest = 0;
1683 else
1684 {
1685 chn.nPortamentoDest = pIns->pTuning->GetStepDistance(chn.nNote, chn.m_PortamentoFineSteps, static_cast<Tuning::NOTEINDEXTYPE>(note), 0);
1686 //Here chn.nPortamentoDest means 'steps to slide'.
1687 chn.m_PortamentoFineSteps = -chn.nPortamentoDest;
1688 }
1689 }
1690
1691 if(!bPorta && (GetType() & (MOD_TYPE_XM | MOD_TYPE_MED | MOD_TYPE_MT2)))
1692 {
1693 if(pSmp)
1694 {
1695 chn.nTranspose = pSmp->RelativeTone;
1696 chn.nFineTune = pSmp->nFineTune;
1697 }
1698 }
1699 // IT Compatibility: Update multisample instruments frequency even if instrument is not specified (fixes the guitars in spx-shuttledeparture.it)
1700 // Test case: freqreset-noins.it
1701 if(!bPorta && pSmp && m_playBehaviour[kITMultiSampleBehaviour])
1702 chn.nC5Speed = pSmp->nC5Speed;
1703
1704 if(bPorta && !chn.IsSamplePlaying())
1705 {
1706 if(m_playBehaviour[kFT2PortaNoNote])
1707 {
1708 // FT2 Compatibility: Ignore notes with portamento if there was no note playing.
1709 // Test case: 3xx-no-old-samp.xm
1710 chn.nPeriod = 0;
1711 return;
1712 } else if(m_playBehaviour[kITPortaNoNote])
1713 {
1714 // IT Compatibility: Ignore portamento command if no note was playing (e.g. if a previous note has faded out).
1715 // Test case: Fade-Porta.it
1716 bPorta = false;
1717 }
1718 }
1719
1720 if(UseFinetuneAndTranspose())
1721 {
1722 note += chn.nTranspose;
1723 // RealNote = PatternNote + RelativeTone; (0..118, 0 = C-0, 118 = A#9)
1724 Limit(note, NOTE_MIN + 11, NOTE_MIN + 130); // 119 possible notes
1725 } else
1726 {
1727 Limit(note, NOTE_MIN, NOTE_MAX);
1728 }
1729 if(m_playBehaviour[kITRealNoteMapping])
1730 {
1731 // need to memorize the original note for various effects (e.g. PPS)
1732 chn.nNote = static_cast<ModCommand::NOTE>(Clamp(realnote, NOTE_MIN, NOTE_MAX));
1733 } else
1734 {
1735 chn.nNote = static_cast<ModCommand::NOTE>(note);
1736 }
1737 chn.m_CalculateFreq = true;
1738 chn.isPaused = false;
1739
1740 if ((!bPorta) || (GetType() & (MOD_TYPE_S3M|MOD_TYPE_IT|MOD_TYPE_MPT)))
1741 chn.nNewIns = 0;
1742
1743 uint32 period = GetPeriodFromNote(note, chn.nFineTune, chn.nC5Speed);
1744 chn.nPanbrelloOffset = 0;
1745
1746 // IT compatibility: Sample and instrument panning is only applied on note change, not instrument change
1747 // Test case: PanReset.it
1748 if(m_playBehaviour[kITPanningReset])
1749 ApplyInstrumentPanning(chn, pIns, pSmp);
1750
1751 // IT compatibility: Pitch/Pan Separation can be overriden by panning commands, and shouldn't be affected by note-off commands
1752 // Test case: PitchPanReset.it
1753 if(m_playBehaviour[kITPitchPanSeparation] && pIns && pIns->nPPS)
1754 {
1755 if(!chn.nRestorePanOnNewNote)
1756 chn.nRestorePanOnNewNote = static_cast<uint16>(chn.nPan + 1);
1757 ProcessPitchPanSeparation(chn.nPan, origNote, *pIns);
1758 }
1759
1760 if(bResetEnv && !bPorta)
1761 {
1762 chn.nVolSwing = chn.nPanSwing = 0;
1763 chn.nResSwing = chn.nCutSwing = 0;
1764 if(pIns)
1765 {
1766 // IT Compatiblity: NNA is reset on every note change, not every instrument change (fixes spx-farspacedance.it).
1767 if(m_playBehaviour[kITNNAReset]) chn.nNNA = pIns->nNNA;
1768
1769 if(!pIns->VolEnv.dwFlags[ENV_CARRY]) chn.VolEnv.Reset();
1770 if(!pIns->PanEnv.dwFlags[ENV_CARRY]) chn.PanEnv.Reset();
1771 if(!pIns->PitchEnv.dwFlags[ENV_CARRY]) chn.PitchEnv.Reset();
1772
1773 // Volume Swing
1774 if(pIns->nVolSwing)
1775 {
1776 chn.nVolSwing = static_cast<int16>(((mpt::random<int8>(AccessPRNG()) * pIns->nVolSwing) / 64 + 1) * (m_playBehaviour[kITSwingBehaviour] ? chn.nInsVol : ((chn.nVolume + 1) / 2)) / 199);
1777 }
1778 // Pan Swing
1779 if(pIns->nPanSwing)
1780 {
1781 chn.nPanSwing = static_cast<int16>(((mpt::random<int8>(AccessPRNG()) * pIns->nPanSwing * 4) / 128));
1782 if(!m_playBehaviour[kITSwingBehaviour] && chn.nRestorePanOnNewNote == 0)
1783 {
1784 chn.nRestorePanOnNewNote = static_cast<uint16>(chn.nPan + 1);
1785 }
1786 }
1787 // Cutoff Swing
1788 if(pIns->nCutSwing)
1789 {
1790 int32 d = ((int32)pIns->nCutSwing * (int32)(static_cast<int32>(mpt::random<int8>(AccessPRNG())) + 1)) / 128;
1791 chn.nCutSwing = static_cast<int16>((d * chn.nCutOff + 1) / 128);
1792 chn.nRestoreCutoffOnNewNote = chn.nCutOff + 1;
1793 }
1794 // Resonance Swing
1795 if(pIns->nResSwing)
1796 {
1797 int32 d = ((int32)pIns->nResSwing * (int32)(static_cast<int32>(mpt::random<int8>(AccessPRNG())) + 1)) / 128;
1798 chn.nResSwing = static_cast<int16>((d * chn.nResonance + 1) / 128);
1799 chn.nRestoreResonanceOnNewNote = chn.nResonance + 1;
1800 }
1801 }
1802 }
1803
1804 if(!pSmp) return;
1805 if(period)
1806 {
1807 if((!bPorta) || (!chn.nPeriod)) chn.nPeriod = period;
1808 if(!newTuning)
1809 {
1810 // FT2 compatibility: Don't reset portamento target with new notes.
1811 // Test case: Porta-Pickup.xm
1812 // ProTracker does the same.
1813 // Test case: PortaTarget.mod
1814 // IT compatibility: Portamento target is completely cleared with new notes.
1815 // Test case: PortaReset.it
1816 if(bPorta || !(m_playBehaviour[kFT2PortaTargetNoReset] || m_playBehaviour[kITClearPortaTarget] || GetType() == MOD_TYPE_MOD))
1817 {
1818 chn.nPortamentoDest = period;
1819 chn.portaTargetReached = false;
1820 }
1821 }
1822
1823 if(!bPorta || (!chn.nLength && !(GetType() & MOD_TYPE_S3M)))
1824 {
1825 chn.pModSample = pSmp;
1826 chn.nLength = pSmp->nLength;
1827 chn.nLoopEnd = pSmp->nLength;
1828 chn.nLoopStart = 0;
1829 chn.position.Set(0);
1830 if((m_SongFlags[SONG_PT_MODE] || m_playBehaviour[kST3OffsetWithoutInstrument]) && !chn.rowCommand.instr)
1831 {
1832 chn.position.SetInt(std::min(chn.prevNoteOffset, chn.nLength - SmpLength(1)));
1833 } else
1834 {
1835 chn.prevNoteOffset = 0;
1836 }
1837 chn.dwFlags = (chn.dwFlags & CHN_CHANNELFLAGS) | (pSmp->uFlags & CHN_SAMPLEFLAGS);
1838 chn.dwFlags.reset(CHN_PORTAMENTO);
1839 if(chn.dwFlags[CHN_SUSTAINLOOP])
1840 {
1841 chn.nLoopStart = pSmp->nSustainStart;
1842 chn.nLoopEnd = pSmp->nSustainEnd;
1843 chn.dwFlags.set(CHN_PINGPONGLOOP, chn.dwFlags[CHN_PINGPONGSUSTAIN]);
1844 chn.dwFlags.set(CHN_LOOP);
1845 if (chn.nLength > chn.nLoopEnd) chn.nLength = chn.nLoopEnd;
1846 } else if(chn.dwFlags[CHN_LOOP])
1847 {
1848 chn.nLoopStart = pSmp->nLoopStart;
1849 chn.nLoopEnd = pSmp->nLoopEnd;
1850 if (chn.nLength > chn.nLoopEnd) chn.nLength = chn.nLoopEnd;
1851 }
1852 // ProTracker "oneshot" loops (if loop start is 0, play the whole sample once and then repeat until loop end)
1853 if(m_playBehaviour[kMODOneShotLoops] && chn.nLoopStart == 0) chn.nLoopEnd = chn.nLength = pSmp->nLength;
1854
1855 if(chn.dwFlags[CHN_REVERSE] && chn.nLength > 0)
1856 {
1857 chn.dwFlags.set(CHN_PINGPONGFLAG);
1858 chn.position.SetInt(chn.nLength - 1);
1859 }
1860
1861 // Handle "retrigger" waveform type
1862 if(chn.nVibratoType < 4)
1863 {
1864 // IT Compatibilty: Slightly different waveform offsets (why does MPT have two different offsets here with IT old effects enabled and disabled?)
1865 if(!m_playBehaviour[kITVibratoTremoloPanbrello] && (GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT)) && !m_SongFlags[SONG_ITOLDEFFECTS])
1866 chn.nVibratoPos = 0x10;
1867 else if(GetType() == MOD_TYPE_MTM)
1868 chn.nVibratoPos = 0x20;
1869 else if(!(GetType() & (MOD_TYPE_DIGI | MOD_TYPE_DBM)))
1870 chn.nVibratoPos = 0;
1871 }
1872 // IT Compatibility: No "retrigger" waveform here
1873 if(!m_playBehaviour[kITVibratoTremoloPanbrello] && chn.nTremoloType < 4)
1874 {
1875 chn.nTremoloPos = 0;
1876 }
1877 }
1878 if(chn.position.GetUInt() >= chn.nLength) chn.position.SetInt(chn.nLoopStart);
1879 } else
1880 {
1881 bPorta = false;
1882 }
1883
1884 if (!bPorta
1885 || (!(GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT | MOD_TYPE_DBM)))
1886 || (chn.dwFlags[CHN_NOTEFADE] && !chn.nFadeOutVol)
1887 || (m_SongFlags[SONG_ITCOMPATGXX] && chn.rowCommand.instr != 0))
1888 {
1889 if((GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT | MOD_TYPE_DBM)) && chn.dwFlags[CHN_NOTEFADE] && !chn.nFadeOutVol)
1890 {
1891 chn.ResetEnvelopes();
1892 // IT Compatibility: Autovibrato reset
1893 if(!m_playBehaviour[kITVibratoTremoloPanbrello])
1894 {
1895 chn.nAutoVibDepth = 0;
1896 chn.nAutoVibPos = 0;
1897 }
1898 chn.dwFlags.reset(CHN_NOTEFADE);
1899 chn.nFadeOutVol = 65536;
1900 }
1901 if ((!bPorta) || (!m_SongFlags[SONG_ITCOMPATGXX]) || (chn.rowCommand.instr))
1902 {
1903 if ((!(GetType() & (MOD_TYPE_XM|MOD_TYPE_MT2))) || (chn.rowCommand.instr))
1904 {
1905 chn.dwFlags.reset(CHN_NOTEFADE);
1906 chn.nFadeOutVol = 65536;
1907 }
1908 }
1909 }
1910
1911 // IT compatibility: Don't reset key-off flag on porta notes unless Compat Gxx is enabled.
1912 // Test case: Off-Porta.it, Off-Porta-CompatGxx.it, Off-Porta.xm
1913 if(m_playBehaviour[kITFT2DontResetNoteOffOnPorta] && bPorta && (!m_SongFlags[SONG_ITCOMPATGXX] || chn.rowCommand.instr == 0))
1914 chn.dwFlags.reset(CHN_EXTRALOUD);
1915 else
1916 chn.dwFlags.reset(CHN_EXTRALOUD | CHN_KEYOFF);
1917
1918 // Enable Ramping
1919 if(!bPorta)
1920 {
1921 chn.nLeftVU = chn.nRightVU = 0xFF;
1922 chn.dwFlags.reset(CHN_FILTER);
1923 chn.dwFlags.set(CHN_FASTVOLRAMP);
1924
1925 // IT compatibility 15. Retrigger is reset in RetrigNote (Tremor doesn't store anything here, so we just don't reset this as well)
1926 if(!m_playBehaviour[kITRetrigger] && !m_playBehaviour[kITTremor])
1927 {
1928 // FT2 compatibility: Retrigger is reset in RetrigNote, tremor in ProcessEffects
1929 if(!m_playBehaviour[kFT2Retrigger] && !m_playBehaviour[kFT2Tremor])
1930 {
1931 chn.nRetrigCount = 0;
1932 chn.nTremorCount = 0;
1933 }
1934 }
1935
1936 if(bResetEnv)
1937 {
1938 chn.nAutoVibDepth = 0;
1939 chn.nAutoVibPos = 0;
1940 }
1941 chn.rightVol = chn.leftVol = 0;
1942 bool useFilter = !m_SongFlags[SONG_MPTFILTERMODE];
1943 // Setup Initial Filter for this note
1944 if(pIns)
1945 {
1946 if(pIns->IsResonanceEnabled())
1947 {
1948 chn.nResonance = pIns->GetResonance();
1949 useFilter = true;
1950 }
1951 if(pIns->IsCutoffEnabled())
1952 {
1953 chn.nCutOff = pIns->GetCutoff();
1954 useFilter = true;
1955 }
1956 if(useFilter && (pIns->filterMode != FilterMode::Unchanged))
1957 {
1958 chn.nFilterMode = pIns->filterMode;
1959 }
1960 } else
1961 {
1962 chn.nVolSwing = chn.nPanSwing = 0;
1963 chn.nCutSwing = chn.nResSwing = 0;
1964 }
1965 if((chn.nCutOff < 0x7F || m_playBehaviour[kITFilterBehaviour]) && useFilter)
1966 {
1967 int cutoff = SetupChannelFilter(chn, true);
1968 if(cutoff >= 0 && chn.dwFlags[CHN_ADLIB] && m_opl && channelHint != CHANNELINDEX_INVALID)
1969 m_opl->Volume(channelHint, chn.nCutOff / 2u, true);
1970 }
1971
1972 if(chn.dwFlags[CHN_ADLIB] && m_opl && channelHint != CHANNELINDEX_INVALID)
1973 {
1974 // Test case: AdlibZeroVolumeNote.s3m
1975 if(m_playBehaviour[kOPLNoteOffOnNoteChange])
1976 m_opl->NoteOff(channelHint);
1977 else if(m_playBehaviour[kOPLNoteStopWith0Hz])
1978 m_opl->Frequency(channelHint, 0, true, false);
1979 }
1980 }
1981
1982 // Special case for MPT
1983 if (bManual) chn.dwFlags.reset(CHN_MUTE);
1984 if((chn.dwFlags[CHN_MUTE] && (m_MixerSettings.MixerFlags & SNDMIX_MUTECHNMODE))
1985 || (chn.pModSample != nullptr && chn.pModSample->uFlags[CHN_MUTE] && !bManual)
1986 || (chn.pModInstrument != nullptr && chn.pModInstrument->dwFlags[INS_MUTE] && !bManual))
1987 {
1988 if (!bManual) chn.nPeriod = 0;
1989 }
1990
1991 // Reset the Amiga resampler for this channel
1992 if(!bPorta)
1993 {
1994 chn.paulaState.Reset();
1995 }
1996 }
1997
1998
1999 // Apply sample or instrument panning
ApplyInstrumentPanning(ModChannel & chn,const ModInstrument * instr,const ModSample * smp) const2000 void CSoundFile::ApplyInstrumentPanning(ModChannel &chn, const ModInstrument *instr, const ModSample *smp) const
2001 {
2002 int32 newPan = int32_min;
2003 // Default instrument panning
2004 if(instr != nullptr && instr->dwFlags[INS_SETPANNING])
2005 newPan = instr->nPan;
2006 // Default sample panning
2007 if(smp != nullptr && smp->uFlags[CHN_PANNING])
2008 newPan = smp->nPan;
2009
2010 if(newPan != int32_min)
2011 {
2012 chn.SetInstrumentPan(newPan, *this);
2013 // IT compatibility: Sample and instrument panning overrides channel surround status.
2014 // Test case: SmpInsPanSurround.it
2015 if(m_playBehaviour[kPanOverride] && !m_SongFlags[SONG_SURROUNDPAN])
2016 {
2017 chn.dwFlags.reset(CHN_SURROUND);
2018 }
2019 }
2020 }
2021
2022
GetNNAChannel(CHANNELINDEX nChn) const2023 CHANNELINDEX CSoundFile::GetNNAChannel(CHANNELINDEX nChn) const
2024 {
2025 // Check for empty channel
2026 for(CHANNELINDEX i = m_nChannels; i < MAX_CHANNELS; i++)
2027 {
2028 const ModChannel &c = m_PlayState.Chn[i];
2029 // No sample and no plugin playing
2030 if(!c.nLength && !c.HasMIDIOutput())
2031 return i;
2032 // Plugin channel with already released note
2033 if(!c.nLength && c.dwFlags[CHN_KEYOFF | CHN_NOTEFADE])
2034 return i;
2035 // Stopped OPL channel
2036 if(c.dwFlags[CHN_ADLIB] && (!m_opl || !m_opl->IsActive(i)))
2037 return i;
2038 }
2039
2040 uint32 vol = 0x800000;
2041 if(nChn < MAX_CHANNELS)
2042 {
2043 const ModChannel &srcChn = m_PlayState.Chn[nChn];
2044 if(!srcChn.nFadeOutVol && srcChn.nLength)
2045 return CHANNELINDEX_INVALID;
2046 vol = (srcChn.nRealVolume << 9) | srcChn.nVolume;
2047 }
2048
2049 // All channels are used: check for lowest volume
2050 CHANNELINDEX result = CHANNELINDEX_INVALID;
2051 uint32 envpos = 0;
2052 for(CHANNELINDEX i = m_nChannels; i < MAX_CHANNELS; i++)
2053 {
2054 const ModChannel &c = m_PlayState.Chn[i];
2055 if(c.nLength && !c.nFadeOutVol)
2056 return i;
2057 // Use a combination of real volume [14 bit] (which includes volume envelopes, but also potentially global volume) and note volume [9 bit].
2058 // Rationale: We need volume envelopes in case e.g. all NNA channels are playing at full volume but are looping on a 0-volume envelope node.
2059 // But if global volume is not applied to master and the global volume temporarily drops to 0, we would kill arbitrary channels. Hence, add the note volume as well.
2060 uint32 v = (c.nRealVolume << 9) | c.nVolume;
2061 if(c.dwFlags[CHN_LOOP])
2062 v /= 2;
2063 if((v < vol) || ((v == vol) && (c.VolEnv.nEnvPosition > envpos)))
2064 {
2065 envpos = c.VolEnv.nEnvPosition;
2066 vol = v;
2067 result = i;
2068 }
2069 }
2070 return result;
2071 }
2072
2073
CheckNNA(CHANNELINDEX nChn,uint32 instr,int note,bool forceCut)2074 CHANNELINDEX CSoundFile::CheckNNA(CHANNELINDEX nChn, uint32 instr, int note, bool forceCut)
2075 {
2076 ModChannel &srcChn = m_PlayState.Chn[nChn];
2077 const ModInstrument *pIns = nullptr;
2078 if(!ModCommand::IsNote(static_cast<ModCommand::NOTE>(note)))
2079 return CHANNELINDEX_INVALID;
2080
2081 // Always NNA cut - using
2082 if((!(GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT | MOD_TYPE_MT2)) || !m_nInstruments || forceCut) && !srcChn.HasMIDIOutput())
2083 {
2084 if(!srcChn.nLength || srcChn.dwFlags[CHN_MUTE] || !(srcChn.rightVol | srcChn.leftVol))
2085 return CHANNELINDEX_INVALID;
2086
2087 if(srcChn.dwFlags[CHN_ADLIB] && m_opl)
2088 {
2089 m_opl->NoteCut(nChn, false);
2090 return CHANNELINDEX_INVALID;
2091 }
2092
2093 const CHANNELINDEX nnaChn = GetNNAChannel(nChn);
2094 if(nnaChn == CHANNELINDEX_INVALID)
2095 return CHANNELINDEX_INVALID;
2096 ModChannel &chn = m_PlayState.Chn[nnaChn];
2097 // Copy Channel
2098 chn = srcChn;
2099 chn.dwFlags.reset(CHN_VIBRATO | CHN_TREMOLO | CHN_MUTE | CHN_PORTAMENTO);
2100 chn.nPanbrelloOffset = 0;
2101 chn.nMasterChn = nChn + 1;
2102 chn.nCommand = CMD_NONE;
2103 chn.rowCommand.Clear();
2104 // Cut the note
2105 chn.nFadeOutVol = 0;
2106 chn.dwFlags.set(CHN_NOTEFADE | CHN_FASTVOLRAMP);
2107 // Stop this channel
2108 srcChn.nLength = 0;
2109 srcChn.position.Set(0);
2110 srcChn.nROfs = srcChn.nLOfs = 0;
2111 srcChn.rightVol = srcChn.leftVol = 0;
2112 return nnaChn;
2113 }
2114 if(instr > GetNumInstruments())
2115 instr = 0;
2116 const ModSample *pSample = srcChn.pModSample;
2117 // If no instrument is given, assume previous instrument to still be valid.
2118 // Test case: DNA-NoInstr.it
2119 pIns = instr > 0 ? Instruments[instr] : srcChn.pModInstrument;
2120 auto dnaNote = note;
2121 if(pIns != nullptr)
2122 {
2123 auto smp = pIns->Keyboard[note - NOTE_MIN];
2124 // IT compatibility: DCT = note uses pattern notes for comparison
2125 // Note: This is not applied in case kITRealNoteMapping is not set to keep playback of legacy modules simple (chn.nNote is translated note in that case)
2126 // Test case: dct_smp_note_test.it
2127 if(!m_playBehaviour[kITDCTBehaviour] || !m_playBehaviour[kITRealNoteMapping])
2128 dnaNote = pIns->NoteMap[note - NOTE_MIN];
2129 if(smp > 0 && smp < MAX_SAMPLES)
2130 {
2131 pSample = &Samples[smp];
2132 } else if(m_playBehaviour[kITEmptyNoteMapSlot] && !pIns->HasValidMIDIChannel())
2133 {
2134 // Impulse Tracker ignores empty slots.
2135 // We won't ignore them if a plugin is assigned to this slot, so that VSTis still work as intended.
2136 // Test case: emptyslot.it, PortaInsNum.it, gxsmp.it, gxsmp2.it
2137 return CHANNELINDEX_INVALID;
2138 }
2139 }
2140 if(srcChn.dwFlags[CHN_MUTE])
2141 return CHANNELINDEX_INVALID;
2142
2143 for(CHANNELINDEX i = nChn; i < MAX_CHANNELS; i++)
2144 {
2145 // Only apply to background channels, or the same pattern channel
2146 if(i < m_nChannels && i != nChn)
2147 continue;
2148
2149 ModChannel &chn = m_PlayState.Chn[i];
2150 bool applyDNAtoPlug = false;
2151 if((chn.nMasterChn == nChn + 1 || i == nChn) && chn.pModInstrument != nullptr)
2152 {
2153 bool applyDNA = false;
2154 // Duplicate Check Type
2155 switch(chn.pModInstrument->nDCT)
2156 {
2157 case DuplicateCheckType::None:
2158 break;
2159 // Note
2160 case DuplicateCheckType::Note:
2161 if(dnaNote != NOTE_NONE && chn.nNote == dnaNote && pIns == chn.pModInstrument)
2162 applyDNA = true;
2163 if(pIns && pIns->nMixPlug)
2164 applyDNAtoPlug = true;
2165 break;
2166 // Sample
2167 case DuplicateCheckType::Sample:
2168 // IT compatibility: DCT = sample only applies to same instrument
2169 // Test case: dct_smp_note_test.it
2170 if(pSample != nullptr && pSample == chn.pModSample && (pIns == chn.pModInstrument || !m_playBehaviour[kITDCTBehaviour]))
2171 applyDNA = true;
2172 break;
2173 // Instrument
2174 case DuplicateCheckType::Instrument:
2175 if(pIns == chn.pModInstrument)
2176 applyDNA = true;
2177 if(pIns && pIns->nMixPlug)
2178 applyDNAtoPlug = true;
2179 break;
2180 // Plugin
2181 case DuplicateCheckType::Plugin:
2182 if(pIns && (pIns->nMixPlug) && (pIns->nMixPlug == chn.pModInstrument->nMixPlug))
2183 {
2184 applyDNAtoPlug = true;
2185 applyDNA = true;
2186 }
2187 break;
2188 }
2189
2190 // Duplicate Note Action
2191 if(applyDNA)
2192 {
2193 #ifndef NO_PLUGINS
2194 if(applyDNAtoPlug && chn.nNote != NOTE_NONE)
2195 {
2196 switch(chn.pModInstrument->nDNA)
2197 {
2198 case DuplicateNoteAction::NoteCut:
2199 case DuplicateNoteAction::NoteOff:
2200 case DuplicateNoteAction::NoteFade:
2201 // Switch off duplicated note played on this plugin
2202 SendMIDINote(i, chn.GetPluginNote(m_playBehaviour[kITRealNoteMapping]) + NOTE_MAX_SPECIAL, 0);
2203 chn.nArpeggioLastNote = NOTE_NONE;
2204 break;
2205 }
2206 }
2207 #endif // NO_PLUGINS
2208
2209 switch(chn.pModInstrument->nDNA)
2210 {
2211 // Cut
2212 case DuplicateNoteAction::NoteCut:
2213 KeyOff(chn);
2214 chn.nVolume = 0;
2215 if(chn.dwFlags[CHN_ADLIB] && m_opl)
2216 m_opl->NoteCut(i);
2217 break;
2218 // Note Off
2219 case DuplicateNoteAction::NoteOff:
2220 KeyOff(chn);
2221 if(chn.dwFlags[CHN_ADLIB] && m_opl)
2222 m_opl->NoteOff(i);
2223 break;
2224 // Note Fade
2225 case DuplicateNoteAction::NoteFade:
2226 chn.dwFlags.set(CHN_NOTEFADE);
2227 if(chn.dwFlags[CHN_ADLIB] && m_opl && !m_playBehaviour[kOPLwithNNA])
2228 m_opl->NoteOff(i);
2229 break;
2230 }
2231 if(!chn.nVolume)
2232 {
2233 chn.nFadeOutVol = 0;
2234 chn.dwFlags.set(CHN_NOTEFADE | CHN_FASTVOLRAMP);
2235 }
2236 }
2237 }
2238 }
2239
2240 // Do we need to apply New/Duplicate Note Action to a VSTi?
2241 bool applyNNAtoPlug = false;
2242 #ifndef NO_PLUGINS
2243 IMixPlugin *pPlugin = nullptr;
2244 if(srcChn.HasMIDIOutput() && ModCommand::IsNote(srcChn.nNote)) // instro sends to a midi chan
2245 {
2246 PLUGINDEX plugin = GetBestPlugin(nChn, PrioritiseInstrument, RespectMutes);
2247
2248 if(plugin > 0 && plugin <= MAX_MIXPLUGINS)
2249 {
2250 pPlugin = m_MixPlugins[plugin - 1].pMixPlugin;
2251 if(pPlugin)
2252 {
2253 // apply NNA to this plugin iff it is currently playing a note on this tracker channel
2254 // (and if it is playing a note, we know that would be the last note played on this chan).
2255 applyNNAtoPlug = pPlugin->IsNotePlaying(srcChn.GetPluginNote(m_playBehaviour[kITRealNoteMapping]), nChn);
2256 }
2257 }
2258 }
2259 #endif // NO_PLUGINS
2260
2261 // New Note Action
2262 if(!srcChn.IsSamplePlaying() && !applyNNAtoPlug)
2263 return CHANNELINDEX_INVALID;
2264
2265 CHANNELINDEX nnaChn = GetNNAChannel(nChn);
2266 if(nnaChn == CHANNELINDEX_INVALID)
2267 return CHANNELINDEX_INVALID;
2268
2269 ModChannel &chn = m_PlayState.Chn[nnaChn];
2270 if(chn.dwFlags[CHN_ADLIB] && m_opl)
2271 m_opl->NoteCut(nnaChn);
2272 // Copy Channel
2273 chn = srcChn;
2274 chn.dwFlags.reset(CHN_VIBRATO | CHN_TREMOLO | CHN_PORTAMENTO);
2275 chn.nPanbrelloOffset = 0;
2276
2277 chn.nMasterChn = nChn < GetNumChannels() ? nChn + 1 : 0;
2278 chn.nCommand = CMD_NONE;
2279 #ifndef NO_PLUGINS
2280 if(applyNNAtoPlug && pPlugin)
2281 {
2282 switch(srcChn.nNNA)
2283 {
2284 case NewNoteAction::NoteOff:
2285 case NewNoteAction::NoteCut:
2286 case NewNoteAction::NoteFade:
2287 // Switch off note played on this plugin, on this tracker channel and midi channel
2288 SendMIDINote(nChn, NOTE_KEYOFF, 0);
2289 srcChn.nArpeggioLastNote = NOTE_NONE;
2290 break;
2291 case NewNoteAction::Continue:
2292 break;
2293 }
2294 }
2295 #endif // NO_PLUGINS
2296
2297 // Key Off the note
2298 switch(srcChn.nNNA)
2299 {
2300 case NewNoteAction::NoteOff:
2301 KeyOff(chn);
2302 if(chn.dwFlags[CHN_ADLIB] && m_opl)
2303 {
2304 m_opl->NoteOff(nChn);
2305 if(m_playBehaviour[kOPLwithNNA])
2306 m_opl->MoveChannel(nChn, nnaChn);
2307 }
2308 break;
2309 case NewNoteAction::NoteCut:
2310 chn.nFadeOutVol = 0;
2311 chn.dwFlags.set(CHN_NOTEFADE);
2312 if(chn.dwFlags[CHN_ADLIB] && m_opl)
2313 m_opl->NoteCut(nChn);
2314 break;
2315 case NewNoteAction::NoteFade:
2316 chn.dwFlags.set(CHN_NOTEFADE);
2317 if(chn.dwFlags[CHN_ADLIB] && m_opl)
2318 {
2319 if(m_playBehaviour[kOPLwithNNA])
2320 m_opl->MoveChannel(nChn, nnaChn);
2321 else
2322 m_opl->NoteOff(nChn);
2323 }
2324 break;
2325 case NewNoteAction::Continue:
2326 if(chn.dwFlags[CHN_ADLIB] && m_opl)
2327 m_opl->MoveChannel(nChn, nnaChn);
2328 break;
2329 }
2330 if(!chn.nVolume)
2331 {
2332 chn.nFadeOutVol = 0;
2333 chn.dwFlags.set(CHN_NOTEFADE | CHN_FASTVOLRAMP);
2334 }
2335 // Stop this channel
2336 srcChn.nLength = 0;
2337 srcChn.position.Set(0);
2338 srcChn.nROfs = srcChn.nLOfs = 0;
2339
2340 return nnaChn;
2341 }
2342
2343
ProcessEffects()2344 bool CSoundFile::ProcessEffects()
2345 {
2346 m_PlayState.m_breakRow = ROWINDEX_INVALID; // Is changed if a break to row command is encountered
2347 m_PlayState.m_patLoopRow = ROWINDEX_INVALID; // Is changed if a pattern loop jump-back is executed
2348 m_PlayState.m_posJump = ORDERINDEX_INVALID;
2349
2350 for(CHANNELINDEX nChn = 0; nChn < GetNumChannels(); nChn++)
2351 {
2352 ModChannel &chn = m_PlayState.Chn[nChn];
2353 const uint32 tickCount = m_PlayState.m_nTickCount % (m_PlayState.m_nMusicSpeed + m_PlayState.m_nFrameDelay);
2354 uint32 instr = chn.rowCommand.instr;
2355 ModCommand::VOLCMD volcmd = chn.rowCommand.volcmd;
2356 uint32 vol = chn.rowCommand.vol;
2357 ModCommand::COMMAND cmd = chn.rowCommand.command;
2358 uint32 param = chn.rowCommand.param;
2359 bool bPorta = chn.rowCommand.IsPortamento();
2360
2361 uint32 nStartTick = 0;
2362 chn.isFirstTick = m_SongFlags[SONG_FIRSTTICK];
2363
2364 // Process parameter control note.
2365 if(chn.rowCommand.note == NOTE_PC)
2366 {
2367 #ifndef NO_PLUGINS
2368 const PLUGINDEX plug = chn.rowCommand.instr;
2369 const PlugParamIndex plugparam = chn.rowCommand.GetValueVolCol();
2370 const PlugParamValue value = chn.rowCommand.GetValueEffectCol() / PlugParamValue(ModCommand::maxColumnValue);
2371
2372 if(plug > 0 && plug <= MAX_MIXPLUGINS && m_MixPlugins[plug - 1].pMixPlugin)
2373 m_MixPlugins[plug-1].pMixPlugin->SetParameter(plugparam, value);
2374 #endif // NO_PLUGINS
2375 }
2376
2377 // Process continuous parameter control note.
2378 // Row data is cleared after first tick so on following
2379 // ticks using channels m_nPlugParamValueStep to identify
2380 // the need for parameter control. The condition cmd == 0
2381 // is to make sure that m_nPlugParamValueStep != 0 because
2382 // of NOTE_PCS, not because of macro.
2383 if(chn.rowCommand.note == NOTE_PCS || (cmd == CMD_NONE && chn.m_plugParamValueStep != 0))
2384 {
2385 #ifndef NO_PLUGINS
2386 const bool isFirstTick = m_SongFlags[SONG_FIRSTTICK];
2387 if(isFirstTick)
2388 chn.m_RowPlug = chn.rowCommand.instr;
2389 const PLUGINDEX plugin = chn.m_RowPlug;
2390 const bool hasValidPlug = (plugin > 0 && plugin <= MAX_MIXPLUGINS && m_MixPlugins[plugin - 1].pMixPlugin);
2391 if(hasValidPlug)
2392 {
2393 if(isFirstTick)
2394 chn.m_RowPlugParam = ModCommand::GetValueVolCol(chn.rowCommand.volcmd, chn.rowCommand.vol);
2395 const PlugParamIndex plugparam = chn.m_RowPlugParam;
2396 if(isFirstTick)
2397 {
2398 PlugParamValue targetvalue = ModCommand::GetValueEffectCol(chn.rowCommand.command, chn.rowCommand.param) / PlugParamValue(ModCommand::maxColumnValue);
2399 chn.m_plugParamTargetValue = targetvalue;
2400 chn.m_plugParamValueStep = (targetvalue - m_MixPlugins[plugin - 1].pMixPlugin->GetParameter(plugparam)) / PlugParamValue(m_PlayState.TicksOnRow());
2401 }
2402 if(m_PlayState.m_nTickCount + 1 == m_PlayState.TicksOnRow())
2403 { // On last tick, set parameter exactly to target value.
2404 m_MixPlugins[plugin - 1].pMixPlugin->SetParameter(plugparam, chn.m_plugParamTargetValue);
2405 }
2406 else
2407 m_MixPlugins[plugin - 1].pMixPlugin->ModifyParameter(plugparam, chn.m_plugParamValueStep);
2408 }
2409 #endif // NO_PLUGINS
2410 }
2411
2412 // Apart from changing parameters, parameter control notes are intended to be 'invisible'.
2413 // To achieve this, clearing the note data so that rest of the process sees the row as empty row.
2414 if(ModCommand::IsPcNote(chn.rowCommand.note))
2415 {
2416 chn.ClearRowCmd();
2417 instr = 0;
2418 volcmd = VOLCMD_NONE;
2419 vol = 0;
2420 cmd = CMD_NONE;
2421 param = 0;
2422 bPorta = false;
2423 }
2424
2425 // Process Invert Loop (MOD Effect, called every row if it's active)
2426 if(!m_SongFlags[SONG_FIRSTTICK])
2427 {
2428 InvertLoop(m_PlayState.Chn[nChn]);
2429 } else
2430 {
2431 if(instr) m_PlayState.Chn[nChn].nEFxOffset = 0;
2432 }
2433
2434 // Process special effects (note delay, pattern delay, pattern loop)
2435 if (cmd == CMD_DELAYCUT)
2436 {
2437 //:xy --> note delay until tick x, note cut at tick x+y
2438 nStartTick = (param & 0xF0) >> 4;
2439 const uint32 cutAtTick = nStartTick + (param & 0x0F);
2440 NoteCut(nChn, cutAtTick, m_playBehaviour[kITSCxStopsSample]);
2441 } else if ((cmd == CMD_MODCMDEX) || (cmd == CMD_S3MCMDEX))
2442 {
2443 if ((!param) && (GetType() & (MOD_TYPE_S3M|MOD_TYPE_IT|MOD_TYPE_MPT)))
2444 param = chn.nOldCmdEx;
2445 else
2446 chn.nOldCmdEx = static_cast<ModCommand::PARAM>(param);
2447
2448 // Note Delay ?
2449 if ((param & 0xF0) == 0xD0)
2450 {
2451 nStartTick = param & 0x0F;
2452 if(nStartTick == 0)
2453 {
2454 //IT compatibility 22. SD0 == SD1
2455 if(GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT))
2456 nStartTick = 1;
2457 //ST3 ignores notes with SD0 completely
2458 else if(GetType() == MOD_TYPE_S3M)
2459 continue;
2460 } else if(nStartTick >= (m_PlayState.m_nMusicSpeed + m_PlayState.m_nFrameDelay) && m_playBehaviour[kITOutOfRangeDelay])
2461 {
2462 // IT compatibility 08. Handling of out-of-range delay command.
2463 // Additional test case: tickdelay.it
2464 if(instr)
2465 {
2466 chn.nNewIns = static_cast<ModCommand::INSTR>(instr);
2467 }
2468 continue;
2469 }
2470 } else if(m_SongFlags[SONG_FIRSTTICK])
2471 {
2472 // Pattern Loop ?
2473 if((param & 0xF0) == 0xE0)
2474 {
2475 // Pattern Delay
2476 // In Scream Tracker 3 / Impulse Tracker, only the first delay command on this row is considered.
2477 // Test cases: PatternDelays.it, PatternDelays.s3m, PatternDelays.xm
2478 // XXX In Scream Tracker 3, the "left" channels are evaluated before the "right" channels, which is not emulated here!
2479 if(!(GetType() & (MOD_TYPE_S3M | MOD_TYPE_IT | MOD_TYPE_MPT)) || !m_PlayState.m_nPatternDelay)
2480 {
2481 if(!(GetType() & (MOD_TYPE_S3M)) || (param & 0x0F) != 0)
2482 {
2483 // While Impulse Tracker *does* count S60 as a valid row delay (and thus ignores any other row delay commands on the right),
2484 // Scream Tracker 3 simply ignores such commands.
2485 m_PlayState.m_nPatternDelay = 1 + (param & 0x0F);
2486 }
2487 }
2488 }
2489 }
2490 }
2491
2492 if(GetType() == MOD_TYPE_MTM && cmd == CMD_MODCMDEX && (param & 0xF0) == 0xD0)
2493 {
2494 // Apparently, retrigger and note delay have the same behaviour in MultiTracker:
2495 // They both restart the note at tick x, and if there is a note on the same row,
2496 // this note is started on the first tick.
2497 nStartTick = 0;
2498 param = 0x90 | (param & 0x0F);
2499 }
2500
2501 if(nStartTick != 0 && chn.rowCommand.note == NOTE_KEYOFF && chn.rowCommand.volcmd == VOLCMD_PANNING && m_playBehaviour[kFT2PanWithDelayedNoteOff])
2502 {
2503 // FT2 compatibility: If there's a delayed note off, panning commands are ignored. WTF!
2504 // Test case: PanOff.xm
2505 chn.rowCommand.volcmd = VOLCMD_NONE;
2506 }
2507
2508 bool triggerNote = (m_PlayState.m_nTickCount == nStartTick); // Can be delayed by a note delay effect
2509 if(m_playBehaviour[kFT2OutOfRangeDelay] && nStartTick >= m_PlayState.m_nMusicSpeed)
2510 {
2511 // FT2 compatibility: Note delays greater than the song speed should be ignored.
2512 // However, EEx pattern delay is *not* considered at all.
2513 // Test case: DelayCombination.xm, PortaDelay.xm
2514 triggerNote = false;
2515 } else if(m_playBehaviour[kRowDelayWithNoteDelay] && nStartTick > 0 && tickCount == nStartTick)
2516 {
2517 // IT compatibility: Delayed notes (using SDx) that are on the same row as a Row Delay effect are retriggered.
2518 // ProTracker / Scream Tracker 3 / FastTracker 2 do the same.
2519 // Test case: PatternDelay-NoteDelay.it, PatternDelay-NoteDelay.xm, PatternDelaysRetrig.mod
2520 triggerNote = true;
2521 }
2522
2523 // IT compatibility: Tick-0 vs non-tick-0 effect distinction is always based on tick delay.
2524 // Test case: SlideDelay.it
2525 if(m_playBehaviour[kITFirstTickHandling])
2526 {
2527 chn.isFirstTick = tickCount == nStartTick;
2528 }
2529 chn.triggerNote = triggerNote;
2530
2531 // FT2 compatibility: Note + portamento + note delay = no portamento
2532 // Test case: PortaDelay.xm
2533 if(m_playBehaviour[kFT2PortaDelay] && nStartTick != 0)
2534 {
2535 bPorta = false;
2536 }
2537
2538 if(m_SongFlags[SONG_PT_MODE] && instr && !m_PlayState.m_nTickCount)
2539 {
2540 // Instrument number resets the stacked ProTracker offset.
2541 // Test case: ptoffset.mod
2542 chn.prevNoteOffset = 0;
2543 // ProTracker compatibility: Sample properties are always loaded on the first tick, even when there is a note delay.
2544 // Test case: InstrDelay.mod
2545 if(!triggerNote && chn.IsSamplePlaying())
2546 {
2547 chn.nNewIns = static_cast<ModCommand::INSTR>(instr);
2548 if(instr <= GetNumSamples())
2549 {
2550 chn.nVolume = Samples[instr].nVolume;
2551 chn.nFineTune = Samples[instr].nFineTune;
2552 }
2553 }
2554 }
2555
2556 // Handles note/instrument/volume changes
2557 if(triggerNote)
2558 {
2559 ModCommand::NOTE note = chn.rowCommand.note;
2560 if(instr) chn.nNewIns = static_cast<ModCommand::INSTR>(instr);
2561
2562 if(ModCommand::IsNote(note) && m_playBehaviour[kFT2Transpose])
2563 {
2564 // Notes that exceed FT2's limit are completely ignored.
2565 // Test case: NoteLimit.xm
2566 int transpose = chn.nTranspose;
2567 if(instr && !bPorta)
2568 {
2569 // Refresh transpose
2570 // Test case: NoteLimit2.xm
2571 const SAMPLEINDEX sample = GetSampleIndex(note, instr);
2572 if(sample > 0)
2573 transpose = GetSample(sample).RelativeTone;
2574 }
2575
2576 const int computedNote = note + transpose;
2577 if((computedNote < NOTE_MIN + 11 || computedNote > NOTE_MIN + 130))
2578 {
2579 note = NOTE_NONE;
2580 }
2581 } else if((GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT | MOD_TYPE_J2B)) && GetNumInstruments() != 0 && ModCommand::IsNoteOrEmpty(static_cast<ModCommand::NOTE>(note)))
2582 {
2583 // IT compatibility: Invalid instrument numbers do nothing, but they are remembered for upcoming notes and do not trigger a note in that case.
2584 // Test case: InstrumentNumberChange.it
2585 INSTRUMENTINDEX instrToCheck = static_cast<INSTRUMENTINDEX>((instr != 0) ? instr : chn.nOldIns);
2586 if(instrToCheck != 0 && (instrToCheck > GetNumInstruments() || Instruments[instrToCheck] == nullptr))
2587 {
2588 note = NOTE_NONE;
2589 instr = 0;
2590 }
2591 }
2592
2593 // XM: FT2 ignores a note next to a K00 effect, and a fade-out seems to be done when no volume envelope is present (not exactly the Kxx behaviour)
2594 if(cmd == CMD_KEYOFF && param == 0 && m_playBehaviour[kFT2KeyOff])
2595 {
2596 note = NOTE_NONE;
2597 instr = 0;
2598 }
2599
2600 bool retrigEnv = note == NOTE_NONE && instr != 0;
2601
2602 // Apparently, any note number in a pattern causes instruments to recall their original volume settings - no matter if there's a Note Off next to it or whatever.
2603 // Test cases: keyoff+instr.xm, delay.xm
2604 bool reloadSampleSettings = (m_playBehaviour[kFT2ReloadSampleSettings] && instr != 0);
2605 // ProTracker Compatibility: If a sample was stopped before, lone instrument numbers can retrigger it
2606 // Test case: PTSwapEmpty.mod, PTInstrVolume.mod, SampleSwap.s3m
2607 bool keepInstr = (GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT))
2608 || m_playBehaviour[kST3SampleSwap]
2609 || (m_playBehaviour[kMODSampleSwap] && !chn.IsSamplePlaying() && (chn.pModSample == nullptr || !chn.pModSample->HasSampleData()));
2610
2611 // Now it's time for some FT2 crap...
2612 if (GetType() & (MOD_TYPE_XM | MOD_TYPE_MT2))
2613 {
2614 // XM: Key-Off + Sample == Note Cut (BUT: Only if no instr number or volume effect is present!)
2615 // Test case: NoteOffVolume.xm
2616 if(note == NOTE_KEYOFF
2617 && ((!instr && volcmd != VOLCMD_VOLUME && cmd != CMD_VOLUME) || !m_playBehaviour[kFT2KeyOff])
2618 && (chn.pModInstrument == nullptr || !chn.pModInstrument->VolEnv.dwFlags[ENV_ENABLED]))
2619 {
2620 chn.dwFlags.set(CHN_FASTVOLRAMP);
2621 chn.nVolume = 0;
2622 note = NOTE_NONE;
2623 instr = 0;
2624 retrigEnv = false;
2625 // FT2 Compatbility: Start fading the note for notes with no delay. Only relevant when a volume command is encountered after the note-off.
2626 // Test case: NoteOffFadeNoEnv.xm
2627 if(m_SongFlags[SONG_FIRSTTICK] && m_playBehaviour[kFT2NoteOffFlags])
2628 chn.dwFlags.set(CHN_NOTEFADE);
2629 } else if(m_playBehaviour[kFT2RetrigWithNoteDelay] && !m_SongFlags[SONG_FIRSTTICK])
2630 {
2631 // FT2 Compatibility: Some special hacks for rogue note delays... (EDx with x > 0)
2632 // Apparently anything that is next to a note delay behaves totally unpredictable in FT2. Swedish tracker logic. :)
2633
2634 retrigEnv = true;
2635
2636 // Portamento + Note Delay = No Portamento
2637 // Test case: porta-delay.xm
2638 bPorta = false;
2639
2640 if(note == NOTE_NONE)
2641 {
2642 // If there's a note delay but no real note, retrig the last note.
2643 // Test case: delay2.xm, delay3.xm
2644 note = static_cast<ModCommand::NOTE>(chn.nNote - chn.nTranspose);
2645 } else if(note >= NOTE_MIN_SPECIAL)
2646 {
2647 // Gah! Even Note Off + Note Delay will cause envelopes to *retrigger*! How stupid is that?
2648 // ... Well, and that is actually all it does if there's an envelope. No fade out, no nothing. *sigh*
2649 // Test case: OffDelay.xm
2650 note = NOTE_NONE;
2651 keepInstr = false;
2652 reloadSampleSettings = true;
2653 } else if(instr || !m_playBehaviour[kFT2NoteDelayWithoutInstr])
2654 {
2655 // Normal note (only if there is an instrument, test case: DelayVolume.xm)
2656 keepInstr = true;
2657 reloadSampleSettings = true;
2658 }
2659 }
2660 }
2661
2662 if((retrigEnv && !m_playBehaviour[kFT2ReloadSampleSettings]) || reloadSampleSettings)
2663 {
2664 const ModSample *oldSample = nullptr;
2665 // Reset default volume when retriggering envelopes
2666
2667 if(GetNumInstruments())
2668 {
2669 oldSample = chn.pModSample;
2670 } else if (instr <= GetNumSamples())
2671 {
2672 // Case: Only samples are used; no instruments.
2673 oldSample = &Samples[instr];
2674 }
2675
2676 if(oldSample != nullptr)
2677 {
2678 if(!oldSample->uFlags[SMP_NODEFAULTVOLUME] && (GetType() != MOD_TYPE_S3M || oldSample->HasSampleData()))
2679 chn.nVolume = oldSample->nVolume;
2680 if(reloadSampleSettings)
2681 {
2682 // Also reload panning
2683 chn.SetInstrumentPan(oldSample->nPan, *this);
2684 }
2685 }
2686 }
2687
2688 // FT2 compatibility: Instrument number disables tremor effect
2689 // Test case: TremorInstr.xm, TremoRecover.xm
2690 if(m_playBehaviour[kFT2Tremor] && instr != 0)
2691 {
2692 chn.nTremorCount = 0x20;
2693 }
2694
2695 // IT compatibility: Envelope retriggering with instrument number based on Old Effects and Compatible Gxx flags:
2696 // OldFX CompatGxx Env Behaviour
2697 // ----- --------- -------------
2698 // off off never reset
2699 // on off reset on instrument without portamento
2700 // off on reset on instrument with portamento
2701 // on on always reset
2702 // Test case: ins-xx.it, ins-ox.it, ins-oc.it, ins-xc.it, ResetEnvNoteOffOldFx.it, ResetEnvNoteOffOldFx2.it, noteoff3.it
2703 if(GetNumInstruments() && m_playBehaviour[kITInstrWithNoteOffOldEffects]
2704 && instr && !ModCommand::IsNote(note))
2705 {
2706 if((bPorta && m_SongFlags[SONG_ITCOMPATGXX])
2707 || (!bPorta && m_SongFlags[SONG_ITOLDEFFECTS]))
2708 {
2709 chn.ResetEnvelopes();
2710 chn.dwFlags.set(CHN_FASTVOLRAMP);
2711 chn.nFadeOutVol = 65536;
2712 }
2713 }
2714
2715 if(retrigEnv) //Case: instrument with no note data.
2716 {
2717 //IT compatibility: Instrument with no note.
2718 if(m_playBehaviour[kITInstrWithoutNote] || GetType() == MOD_TYPE_PLM)
2719 {
2720 // IT compatibility: Completely retrigger note after sample end to also reset portamento.
2721 // Test case: PortaResetAfterRetrigger.it
2722 bool triggerAfterSmpEnd = m_playBehaviour[kITMultiSampleInstrumentNumber] && !chn.IsSamplePlaying();
2723 if(GetNumInstruments())
2724 {
2725 // Instrument mode
2726 if(instr <= GetNumInstruments() && (chn.pModInstrument != Instruments[instr] || triggerAfterSmpEnd))
2727 note = chn.nNote;
2728 } else
2729 {
2730 // Sample mode
2731 if(instr < MAX_SAMPLES && (chn.pModSample != &Samples[instr] || triggerAfterSmpEnd))
2732 note = chn.nNote;
2733 }
2734 }
2735
2736 if(GetNumInstruments() && (GetType() & (MOD_TYPE_XM | MOD_TYPE_MT2 | MOD_TYPE_MED)))
2737 {
2738 chn.ResetEnvelopes();
2739 chn.dwFlags.set(CHN_FASTVOLRAMP);
2740 chn.dwFlags.reset(CHN_NOTEFADE);
2741 chn.nAutoVibDepth = 0;
2742 chn.nAutoVibPos = 0;
2743 chn.nFadeOutVol = 65536;
2744 // FT2 Compatbility: Reset key-off status with instrument number
2745 // Test case: NoteOffInstrChange.xm
2746 if(m_playBehaviour[kFT2NoteOffFlags])
2747 chn.dwFlags.reset(CHN_KEYOFF);
2748 }
2749 if (!keepInstr) instr = 0;
2750 }
2751
2752 // Note Cut/Off/Fade => ignore instrument
2753 if (note >= NOTE_MIN_SPECIAL)
2754 {
2755 // IT compatibility: Default volume of sample is recalled if instrument number is next to a note-off.
2756 // Test case: NoteOffInstr.it, noteoff2.it
2757 if(m_playBehaviour[kITInstrWithNoteOff] && instr)
2758 {
2759 const SAMPLEINDEX smp = GetSampleIndex(chn.nLastNote, instr);
2760 if(smp > 0 && !Samples[smp].uFlags[SMP_NODEFAULTVOLUME])
2761 chn.nVolume = Samples[smp].nVolume;
2762 }
2763 // IT compatibility: Note-off with instrument number + Old Effects retriggers envelopes.
2764 // Test case: ResetEnvNoteOffOldFx.it
2765 if(!m_playBehaviour[kITInstrWithNoteOffOldEffects] || !m_SongFlags[SONG_ITOLDEFFECTS])
2766 instr = 0;
2767 }
2768
2769 if(ModCommand::IsNote(note))
2770 {
2771 chn.nNewNote = chn.nLastNote = note;
2772
2773 // New Note Action ?
2774 if(!bPorta)
2775 {
2776 CheckNNA(nChn, instr, note, false);
2777 }
2778
2779 if(chn.nRestorePanOnNewNote > 0)
2780 {
2781 chn.nPan = (chn.nRestorePanOnNewNote & 0x7FFF) - 1;
2782 if(chn.nRestorePanOnNewNote & 0x8000)
2783 chn.dwFlags.set(CHN_SURROUND);
2784 chn.nRestorePanOnNewNote = 0;
2785 }
2786 if(chn.nRestoreResonanceOnNewNote > 0)
2787 {
2788 chn.nResonance = chn.nRestoreResonanceOnNewNote - 1;
2789 chn.nRestoreResonanceOnNewNote = 0;
2790 }
2791 if(chn.nRestoreCutoffOnNewNote > 0)
2792 {
2793 chn.nCutOff = chn.nRestoreCutoffOnNewNote - 1;
2794 chn.nRestoreCutoffOnNewNote = 0;
2795 }
2796 }
2797
2798 // Instrument Change ?
2799 if(instr)
2800 {
2801 const ModSample *oldSample = chn.pModSample;
2802 //const ModInstrument *oldInstrument = chn.pModInstrument;
2803
2804 InstrumentChange(chn, instr, bPorta, true);
2805
2806 if(chn.pModSample != nullptr && chn.pModSample->uFlags[CHN_ADLIB] && m_opl)
2807 {
2808 m_opl->Patch(nChn, chn.pModSample->adlib);
2809 }
2810
2811 // IT compatibility: Keep new instrument number for next instrument-less note even if sample playback is stopped
2812 // Test case: StoppedInstrSwap.it
2813 if(GetType() == MOD_TYPE_MOD)
2814 {
2815 // Test case: PortaSwapPT.mod
2816 if(!bPorta || !m_playBehaviour[kMODSampleSwap]) chn.nNewIns = 0;
2817 } else
2818 {
2819 if(!m_playBehaviour[kITInstrWithNoteOff] || ModCommand::IsNote(note)) chn.nNewIns = 0;
2820 }
2821
2822 if(m_playBehaviour[kITPortamentoSwapResetsPos])
2823 {
2824 // Test cases: PortaInsNum.it, PortaSample.it
2825 if(ModCommand::IsNote(note) && oldSample != chn.pModSample)
2826 {
2827 //const bool newInstrument = oldInstrument != chn.pModInstrument && chn.pModInstrument->Keyboard[chn.nNewNote - NOTE_MIN] != 0;
2828 chn.position.Set(0);
2829 }
2830 } else if((GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT)) && oldSample != chn.pModSample && ModCommand::IsNote(note))
2831 {
2832 // Special IT case: portamento+note causes sample change -> ignore portamento
2833 bPorta = false;
2834 } else if(m_playBehaviour[kST3SampleSwap] && oldSample != chn.pModSample && (bPorta || !ModCommand::IsNote(note)) && chn.position.GetUInt() > chn.nLength)
2835 {
2836 // ST3 with SoundBlaster does sample swapping and continues playing the new sample where the old sample was stopped.
2837 // If the new sample is shorter than that, it is stopped, even if it could be looped.
2838 // This also applies to portamento between different samples.
2839 // Test case: SampleSwap.s3m
2840 chn.nLength = 0;
2841 } else if(m_playBehaviour[kMODSampleSwap] && !chn.IsSamplePlaying())
2842 {
2843 // If channel was paused and is resurrected by a lone instrument number, reset the sample position.
2844 // Test case: PTSwapEmpty.mod
2845 chn.position.Set(0);
2846 }
2847 }
2848 // New Note ?
2849 if (note != NOTE_NONE)
2850 {
2851 const bool instrChange = (!instr) && (chn.nNewIns) && ModCommand::IsNote(note);
2852 if(instrChange)
2853 {
2854 InstrumentChange(chn, chn.nNewIns, bPorta, chn.pModSample == nullptr && chn.pModInstrument == nullptr, !(GetType() & (MOD_TYPE_XM|MOD_TYPE_MT2)));
2855 chn.nNewIns = 0;
2856 }
2857 if(chn.pModSample != nullptr && chn.pModSample->uFlags[CHN_ADLIB] && m_opl && (instrChange || !m_opl->IsActive(nChn)))
2858 {
2859 m_opl->Patch(nChn, chn.pModSample->adlib);
2860 }
2861
2862 NoteChange(chn, note, bPorta, !(GetType() & (MOD_TYPE_XM | MOD_TYPE_MT2)), false, nChn);
2863 if ((bPorta) && (GetType() & (MOD_TYPE_XM|MOD_TYPE_MT2)) && (instr))
2864 {
2865 chn.dwFlags.set(CHN_FASTVOLRAMP);
2866 chn.ResetEnvelopes();
2867 chn.nAutoVibDepth = 0;
2868 chn.nAutoVibPos = 0;
2869 }
2870 if(chn.dwFlags[CHN_ADLIB] && m_opl
2871 && ((note == NOTE_NOTECUT || note == NOTE_KEYOFF) || (note == NOTE_FADE && !m_playBehaviour[kOPLFlexibleNoteOff])))
2872 {
2873 if(m_playBehaviour[kOPLNoteStopWith0Hz])
2874 m_opl->Frequency(nChn, 0, true, false);
2875 m_opl->NoteOff(nChn);
2876 }
2877 }
2878 // Tick-0 only volume commands
2879 if (volcmd == VOLCMD_VOLUME)
2880 {
2881 if (vol > 64) vol = 64;
2882 chn.nVolume = vol << 2;
2883 chn.dwFlags.set(CHN_FASTVOLRAMP);
2884 } else
2885 if (volcmd == VOLCMD_PANNING)
2886 {
2887 Panning(chn, vol, Pan6bit);
2888 }
2889
2890 #ifndef NO_PLUGINS
2891 if (m_nInstruments) ProcessMidiOut(nChn);
2892 #endif // NO_PLUGINS
2893 }
2894
2895 if(m_playBehaviour[kST3NoMutedChannels] && ChnSettings[nChn].dwFlags[CHN_MUTE]) // not even effects are processed on muted S3M channels
2896 continue;
2897
2898 // Volume Column Effect (except volume & panning)
2899 /* A few notes, paraphrased from ITTECH.TXT by Storlek (creator of schismtracker):
2900 Ex/Fx/Gx are shared with Exx/Fxx/Gxx; Ex/Fx are 4x the 'normal' slide value
2901 Gx is linked with Ex/Fx if Compat Gxx is off, just like Gxx is with Exx/Fxx
2902 Gx values: 1, 4, 8, 16, 32, 64, 96, 128, 255
2903 Ax/Bx/Cx/Dx values are used directly (i.e. D9 == D09), and are NOT shared with Dxx
2904 (value is stored into nOldVolParam and used by A0/B0/C0/D0)
2905 Hx uses the same value as Hxx and Uxx, and affects the *depth*
2906 so... hxx = (hx | (oldhxx & 0xf0)) ???
2907 TODO is this done correctly?
2908 */
2909 bool doVolumeColumn = m_PlayState.m_nTickCount >= nStartTick;
2910 // FT2 compatibility: If there's a note delay, volume column effects are NOT executed
2911 // on the first tick and, if there's an instrument number, on the delayed tick.
2912 // Test case: VolColDelay.xm, PortaDelay.xm
2913 if(m_playBehaviour[kFT2VolColDelay] && nStartTick != 0)
2914 {
2915 doVolumeColumn = m_PlayState.m_nTickCount != 0 && (m_PlayState.m_nTickCount != nStartTick || (chn.rowCommand.instr == 0 && volcmd != VOLCMD_TONEPORTAMENTO));
2916 }
2917 if(volcmd > VOLCMD_PANNING && doVolumeColumn)
2918 {
2919 if(volcmd == VOLCMD_TONEPORTAMENTO)
2920 {
2921 const auto [porta, clearEffectCommand] = GetVolCmdTonePorta(chn.rowCommand, nStartTick);
2922 if(clearEffectCommand)
2923 cmd = CMD_NONE;
2924
2925 TonePortamento(chn, porta);
2926 } else
2927 {
2928 // FT2 Compatibility: FT2 ignores some volume commands with parameter = 0.
2929 if(m_playBehaviour[kFT2VolColMemory] && vol == 0)
2930 {
2931 switch(volcmd)
2932 {
2933 case VOLCMD_VOLUME:
2934 case VOLCMD_PANNING:
2935 case VOLCMD_VIBRATODEPTH:
2936 break;
2937 case VOLCMD_PANSLIDELEFT:
2938 // FT2 Compatibility: Pan slide left with zero parameter causes panning to be set to full left on every non-row tick.
2939 // Test case: PanSlideZero.xm
2940 if(!m_SongFlags[SONG_FIRSTTICK])
2941 {
2942 chn.nPan = 0;
2943 }
2944 [[fallthrough]];
2945 default:
2946 // no memory here.
2947 volcmd = VOLCMD_NONE;
2948 }
2949
2950 } else if(!m_playBehaviour[kITVolColMemory])
2951 {
2952 // IT Compatibility: Effects in the volume column don't have an unified memory.
2953 // Test case: VolColMemory.it
2954 if(vol) chn.nOldVolParam = static_cast<ModCommand::PARAM>(vol); else vol = chn.nOldVolParam;
2955 }
2956
2957 switch(volcmd)
2958 {
2959 case VOLCMD_VOLSLIDEUP:
2960 case VOLCMD_VOLSLIDEDOWN:
2961 // IT Compatibility: Volume column volume slides have their own memory
2962 // Test case: VolColMemory.it
2963 if(vol == 0 && m_playBehaviour[kITVolColMemory])
2964 {
2965 vol = chn.nOldVolParam;
2966 if(vol == 0)
2967 break;
2968 } else
2969 {
2970 chn.nOldVolParam = static_cast<ModCommand::PARAM>(vol);
2971 }
2972 VolumeSlide(chn, static_cast<ModCommand::PARAM>(volcmd == VOLCMD_VOLSLIDEUP ? (vol << 4) : vol));
2973 break;
2974
2975 case VOLCMD_FINEVOLUP:
2976 // IT Compatibility: Fine volume slides in the volume column are only executed on the first tick, not on multiples of the first tick in case of pattern delay
2977 // Test case: FineVolColSlide.it
2978 if(m_PlayState.m_nTickCount == nStartTick || !m_playBehaviour[kITVolColMemory])
2979 {
2980 // IT Compatibility: Volume column volume slides have their own memory
2981 // Test case: VolColMemory.it
2982 FineVolumeUp(chn, static_cast<ModCommand::PARAM>(vol), m_playBehaviour[kITVolColMemory]);
2983 }
2984 break;
2985
2986 case VOLCMD_FINEVOLDOWN:
2987 // IT Compatibility: Fine volume slides in the volume column are only executed on the first tick, not on multiples of the first tick in case of pattern delay
2988 // Test case: FineVolColSlide.it
2989 if(m_PlayState.m_nTickCount == nStartTick || !m_playBehaviour[kITVolColMemory])
2990 {
2991 // IT Compatibility: Volume column volume slides have their own memory
2992 // Test case: VolColMemory.it
2993 FineVolumeDown(chn, static_cast<ModCommand::PARAM>(vol), m_playBehaviour[kITVolColMemory]);
2994 }
2995 break;
2996
2997 case VOLCMD_VIBRATOSPEED:
2998 // FT2 does not automatically enable vibrato with the "set vibrato speed" command
2999 if(m_playBehaviour[kFT2VolColVibrato])
3000 chn.nVibratoSpeed = vol & 0x0F;
3001 else
3002 Vibrato(chn, vol << 4);
3003 break;
3004
3005 case VOLCMD_VIBRATODEPTH:
3006 Vibrato(chn, vol);
3007 break;
3008
3009 case VOLCMD_PANSLIDELEFT:
3010 PanningSlide(chn, static_cast<ModCommand::PARAM>(vol), !m_playBehaviour[kFT2VolColMemory]);
3011 break;
3012
3013 case VOLCMD_PANSLIDERIGHT:
3014 PanningSlide(chn, static_cast<ModCommand::PARAM>(vol << 4), !m_playBehaviour[kFT2VolColMemory]);
3015 break;
3016
3017 case VOLCMD_PORTAUP:
3018 // IT compatibility (one of the first testcases - link effect memory)
3019 PortamentoUp(nChn, static_cast<ModCommand::PARAM>(vol << 2), m_playBehaviour[kITVolColFinePortamento]);
3020 break;
3021
3022 case VOLCMD_PORTADOWN:
3023 // IT compatibility (one of the first testcases - link effect memory)
3024 PortamentoDown(nChn, static_cast<ModCommand::PARAM>(vol << 2), m_playBehaviour[kITVolColFinePortamento]);
3025 break;
3026
3027 case VOLCMD_OFFSET:
3028 if(triggerNote && chn.pModSample && vol <= std::size(chn.pModSample->cues))
3029 {
3030 SmpLength offset;
3031 if(vol == 0)
3032 offset = chn.oldOffset;
3033 else
3034 offset = chn.oldOffset = chn.pModSample->cues[vol - 1];
3035 SampleOffset(chn, offset);
3036 }
3037 break;
3038
3039 case VOLCMD_PLAYCONTROL:
3040 if(vol <= 1)
3041 chn.isPaused = (vol == 0);
3042 break;
3043 }
3044 }
3045 }
3046
3047 // Effects
3048 if(cmd != CMD_NONE) switch (cmd)
3049 {
3050 // Set Volume
3051 case CMD_VOLUME:
3052 if(m_SongFlags[SONG_FIRSTTICK])
3053 {
3054 chn.nVolume = (param < 64) ? param * 4 : 256;
3055 chn.dwFlags.set(CHN_FASTVOLRAMP);
3056 }
3057 break;
3058
3059 // Portamento Up
3060 case CMD_PORTAMENTOUP:
3061 if ((!param) && (GetType() & MOD_TYPE_MOD)) break;
3062 PortamentoUp(nChn, static_cast<ModCommand::PARAM>(param));
3063 break;
3064
3065 // Portamento Down
3066 case CMD_PORTAMENTODOWN:
3067 if ((!param) && (GetType() & MOD_TYPE_MOD)) break;
3068 PortamentoDown(nChn, static_cast<ModCommand::PARAM>(param));
3069 break;
3070
3071 // Volume Slide
3072 case CMD_VOLUMESLIDE:
3073 if (param || (GetType() != MOD_TYPE_MOD)) VolumeSlide(chn, static_cast<ModCommand::PARAM>(param));
3074 break;
3075
3076 // Tone-Portamento
3077 case CMD_TONEPORTAMENTO:
3078 TonePortamento(chn, static_cast<uint16>(param));
3079 break;
3080
3081 // Tone-Portamento + Volume Slide
3082 case CMD_TONEPORTAVOL:
3083 if ((param) || (GetType() != MOD_TYPE_MOD)) VolumeSlide(chn, static_cast<ModCommand::PARAM>(param));
3084 TonePortamento(chn, 0);
3085 break;
3086
3087 // Vibrato
3088 case CMD_VIBRATO:
3089 Vibrato(chn, param);
3090 break;
3091
3092 // Vibrato + Volume Slide
3093 case CMD_VIBRATOVOL:
3094 if ((param) || (GetType() != MOD_TYPE_MOD)) VolumeSlide(chn, static_cast<ModCommand::PARAM>(param));
3095 Vibrato(chn, 0);
3096 break;
3097
3098 // Set Speed
3099 case CMD_SPEED:
3100 if(m_SongFlags[SONG_FIRSTTICK])
3101 SetSpeed(m_PlayState, param);
3102 break;
3103
3104 // Set Tempo
3105 case CMD_TEMPO:
3106 if(m_playBehaviour[kMODVBlankTiming])
3107 {
3108 // ProTracker MODs with VBlank timing: All Fxx parameters set the tick count.
3109 if(m_SongFlags[SONG_FIRSTTICK] && param != 0) SetSpeed(m_PlayState, param);
3110 break;
3111 }
3112 {
3113 param = CalculateXParam(m_PlayState.m_nPattern, m_PlayState.m_nRow, nChn);
3114 if (GetType() & (MOD_TYPE_S3M|MOD_TYPE_IT|MOD_TYPE_MPT))
3115 {
3116 if (param) chn.nOldTempo = static_cast<ModCommand::PARAM>(param); else param = chn.nOldTempo;
3117 }
3118 TEMPO t(param, 0);
3119 LimitMax(t, GetModSpecifications().GetTempoMax());
3120 SetTempo(t);
3121 }
3122 break;
3123
3124 // Set Offset
3125 case CMD_OFFSET:
3126 if(triggerNote)
3127 {
3128 // FT2 compatibility: Portamento + Offset = Ignore offset
3129 // Test case: porta-offset.xm
3130 if(bPorta && GetType() == MOD_TYPE_XM)
3131 break;
3132
3133 ProcessSampleOffset(chn, nChn, m_PlayState);
3134 }
3135 break;
3136
3137 // Disorder Tracker 2 percentage offset
3138 case CMD_OFFSETPERCENTAGE:
3139 if(triggerNote)
3140 {
3141 SampleOffset(chn, Util::muldiv_unsigned(chn.nLength, param, 256));
3142 }
3143 break;
3144
3145 // Arpeggio
3146 case CMD_ARPEGGIO:
3147 // IT compatibility 01. Don't ignore Arpeggio if no note is playing (also valid for ST3)
3148 if(m_PlayState.m_nTickCount) break;
3149 if((!chn.nPeriod || !chn.nNote)
3150 && (chn.pModInstrument == nullptr || !chn.pModInstrument->HasValidMIDIChannel()) // Plugin arpeggio
3151 && !m_playBehaviour[kITArpeggio] && (GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT))) break;
3152 if (!param && (GetType() & (MOD_TYPE_XM | MOD_TYPE_MOD))) break; // Only important when editing MOD/XM files (000 effects are removed when loading files where this means "no effect")
3153 chn.nCommand = CMD_ARPEGGIO;
3154 if (param) chn.nArpeggio = static_cast<ModCommand::PARAM>(param);
3155 break;
3156
3157 // Retrig
3158 case CMD_RETRIG:
3159 if (GetType() & (MOD_TYPE_XM|MOD_TYPE_MT2))
3160 {
3161 if (!(param & 0xF0)) param |= chn.nRetrigParam & 0xF0;
3162 if (!(param & 0x0F)) param |= chn.nRetrigParam & 0x0F;
3163 param |= 0x100; // increment retrig count on first row
3164 }
3165 // IT compatibility 15. Retrigger
3166 if(m_playBehaviour[kITRetrigger])
3167 {
3168 if (param) chn.nRetrigParam = static_cast<uint8>(param & 0xFF);
3169 RetrigNote(nChn, chn.nRetrigParam, (volcmd == VOLCMD_OFFSET) ? vol + 1 : 0);
3170 } else
3171 {
3172 // XM Retrig
3173 if (param) chn.nRetrigParam = static_cast<uint8>(param & 0xFF); else param = chn.nRetrigParam;
3174 RetrigNote(nChn, param, (volcmd == VOLCMD_OFFSET) ? vol + 1 : 0);
3175 }
3176 break;
3177
3178 // Tremor
3179 case CMD_TREMOR:
3180 if(!m_SongFlags[SONG_FIRSTTICK])
3181 {
3182 break;
3183 }
3184
3185 // IT compatibility 12. / 13. Tremor (using modified DUMB's Tremor logic here because of old effects - http://dumb.sf.net/)
3186 if(m_playBehaviour[kITTremor])
3187 {
3188 if(param && !m_SongFlags[SONG_ITOLDEFFECTS])
3189 {
3190 // Old effects have different length interpretation (+1 for both on and off)
3191 if(param & 0xF0)
3192 param -= 0x10;
3193 if(param & 0x0F)
3194 param -= 0x01;
3195 chn.nTremorParam = static_cast<ModCommand::PARAM>(param);
3196 }
3197 chn.nTremorCount |= 0x80; // set on/off flag
3198 } else if(m_playBehaviour[kFT2Tremor])
3199 {
3200 // XM Tremor. Logic is being processed in sndmix.cpp
3201 chn.nTremorCount |= 0x80; // set on/off flag
3202 }
3203
3204 chn.nCommand = CMD_TREMOR;
3205 if(param)
3206 chn.nTremorParam = static_cast<ModCommand::PARAM>(param);
3207
3208 break;
3209
3210 // Set Global Volume
3211 case CMD_GLOBALVOLUME:
3212 // IT compatibility: Only apply global volume on first tick (and multiples)
3213 // Test case: GlobalVolFirstTick.it
3214 if(!m_SongFlags[SONG_FIRSTTICK])
3215 break;
3216 // ST3 applies global volume on tick 1 and does other weird things, but we won't emulate this for now.
3217 // if(((GetType() & MOD_TYPE_S3M) && m_nTickCount != 1)
3218 // || (!(GetType() & MOD_TYPE_S3M) && !m_SongFlags[SONG_FIRSTTICK]))
3219 // {
3220 // break;
3221 // }
3222
3223 // FT2 compatibility: On channels that are "left" of the global volume command, the new global volume is not applied
3224 // until the second tick of the row. Since we apply global volume on the mix buffer rather than note volumes, this
3225 // cannot be fixed for now.
3226 // Test case: GlobalVolume.xm
3227 // if(IsCompatibleMode(TRK_FASTTRACKER2) && m_SongFlags[SONG_FIRSTTICK] && m_nMusicSpeed > 1)
3228 // {
3229 // break;
3230 // }
3231
3232 if (!(GetType() & GLOBALVOL_7BIT_FORMATS)) param *= 2;
3233
3234 // IT compatibility 16. ST3 and IT ignore out-of-range values.
3235 // Test case: globalvol-invalid.it
3236 if(param <= 128)
3237 {
3238 m_PlayState.m_nGlobalVolume = param * 2;
3239 } else if(!(GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT | MOD_TYPE_S3M)))
3240 {
3241 m_PlayState.m_nGlobalVolume = 256;
3242 }
3243 break;
3244
3245 // Global Volume Slide
3246 case CMD_GLOBALVOLSLIDE:
3247 //IT compatibility 16. Saving last global volume slide param per channel (FT2/IT)
3248 if(m_playBehaviour[kPerChannelGlobalVolSlide])
3249 GlobalVolSlide(static_cast<ModCommand::PARAM>(param), chn.nOldGlobalVolSlide);
3250 else
3251 GlobalVolSlide(static_cast<ModCommand::PARAM>(param), m_PlayState.Chn[0].nOldGlobalVolSlide);
3252 break;
3253
3254 // Set 8-bit Panning
3255 case CMD_PANNING8:
3256 if(m_SongFlags[SONG_FIRSTTICK])
3257 {
3258 Panning(chn, param, Pan8bit);
3259 }
3260 break;
3261
3262 // Panning Slide
3263 case CMD_PANNINGSLIDE:
3264 PanningSlide(chn, static_cast<ModCommand::PARAM>(param));
3265 break;
3266
3267 // Tremolo
3268 case CMD_TREMOLO:
3269 Tremolo(chn, param);
3270 break;
3271
3272 // Fine Vibrato
3273 case CMD_FINEVIBRATO:
3274 FineVibrato(chn, param);
3275 break;
3276
3277 // MOD/XM Exx Extended Commands
3278 case CMD_MODCMDEX:
3279 ExtendedMODCommands(nChn, static_cast<ModCommand::PARAM>(param));
3280 break;
3281
3282 // S3M/IT Sxx Extended Commands
3283 case CMD_S3MCMDEX:
3284 if(m_playBehaviour[kST3EffectMemory] && param == 0)
3285 {
3286 param = chn.nArpeggio; // S00 uses the last non-zero effect parameter as memory, like other effects including Arpeggio, so we "borrow" our memory there.
3287 }
3288 ExtendedS3MCommands(nChn, static_cast<ModCommand::PARAM>(param));
3289 break;
3290
3291 // Key Off
3292 case CMD_KEYOFF:
3293 // This is how Key Off is supposed to sound... (in FT2 at least)
3294 if(m_playBehaviour[kFT2KeyOff])
3295 {
3296 if (m_PlayState.m_nTickCount == param)
3297 {
3298 // XM: Key-Off + Sample == Note Cut
3299 if(chn.pModInstrument == nullptr || !chn.pModInstrument->VolEnv.dwFlags[ENV_ENABLED])
3300 {
3301 if(param == 0 && (chn.rowCommand.instr || chn.rowCommand.volcmd != VOLCMD_NONE)) // FT2 is weird....
3302 {
3303 chn.dwFlags.set(CHN_NOTEFADE);
3304 }
3305 else
3306 {
3307 chn.dwFlags.set(CHN_FASTVOLRAMP);
3308 chn.nVolume = 0;
3309 }
3310 }
3311 KeyOff(chn);
3312 }
3313 }
3314 // This is how it's NOT supposed to sound...
3315 else
3316 {
3317 if(m_SongFlags[SONG_FIRSTTICK])
3318 KeyOff(chn);
3319 }
3320 break;
3321
3322 // Extra-fine porta up/down
3323 case CMD_XFINEPORTAUPDOWN:
3324 switch(param & 0xF0)
3325 {
3326 case 0x10: ExtraFinePortamentoUp(chn, param & 0x0F); break;
3327 case 0x20: ExtraFinePortamentoDown(chn, param & 0x0F); break;
3328 // ModPlug XM Extensions (ignore in compatible mode)
3329 case 0x50:
3330 case 0x60:
3331 case 0x70:
3332 case 0x90:
3333 case 0xA0:
3334 if(!m_playBehaviour[kFT2RestrictXCommand]) ExtendedS3MCommands(nChn, static_cast<ModCommand::PARAM>(param));
3335 break;
3336 }
3337 break;
3338
3339 case CMD_FINETUNE:
3340 case CMD_FINETUNE_SMOOTH:
3341 if(m_SongFlags[SONG_FIRSTTICK] || cmd == CMD_FINETUNE_SMOOTH)
3342 {
3343 SetFinetune(nChn, m_PlayState, cmd == CMD_FINETUNE_SMOOTH);
3344 #ifndef NO_PLUGINS
3345 if(IMixPlugin *plugin = GetChannelInstrumentPlugin(nChn); plugin != nullptr)
3346 plugin->MidiPitchBendRaw(chn.GetMIDIPitchBend(), nChn);
3347 #endif // NO_PLUGINS
3348 }
3349 break;
3350
3351 // Set Channel Global Volume
3352 case CMD_CHANNELVOLUME:
3353 if(!m_SongFlags[SONG_FIRSTTICK]) break;
3354 if (param <= 64)
3355 {
3356 chn.nGlobalVol = param;
3357 chn.dwFlags.set(CHN_FASTVOLRAMP);
3358 }
3359 break;
3360
3361 // Channel volume slide
3362 case CMD_CHANNELVOLSLIDE:
3363 ChannelVolSlide(chn, static_cast<ModCommand::PARAM>(param));
3364 break;
3365
3366 // Panbrello (IT)
3367 case CMD_PANBRELLO:
3368 Panbrello(chn, param);
3369 break;
3370
3371 // Set Envelope Position
3372 case CMD_SETENVPOSITION:
3373 if(m_SongFlags[SONG_FIRSTTICK])
3374 {
3375 chn.VolEnv.nEnvPosition = param;
3376
3377 // FT2 compatibility: FT2 only sets the position of the panning envelope if the volume envelope's sustain flag is set
3378 // Test case: SetEnvPos.xm
3379 if(!m_playBehaviour[kFT2SetPanEnvPos] || chn.VolEnv.flags[ENV_SUSTAIN])
3380 {
3381 chn.PanEnv.nEnvPosition = param;
3382 chn.PitchEnv.nEnvPosition = param;
3383 }
3384
3385 }
3386 break;
3387
3388 // Position Jump
3389 case CMD_POSITIONJUMP:
3390 PositionJump(m_PlayState, nChn);
3391 break;
3392
3393 // Pattern Break
3394 case CMD_PATTERNBREAK:
3395 if(ROWINDEX row = PatternBreak(m_PlayState, nChn, static_cast<ModCommand::PARAM>(param)); row != ROWINDEX_INVALID)
3396 {
3397 m_PlayState.m_breakRow = row;
3398 if(m_SongFlags[SONG_PATTERNLOOP])
3399 {
3400 //If song is set to loop and a pattern break occurs we should stay on the same pattern.
3401 //Use nPosJump to force playback to "jump to this pattern" rather than move to next, as by default.
3402 m_PlayState.m_posJump = m_PlayState.m_nCurrentOrder;
3403 }
3404 }
3405 break;
3406
3407 // IMF / PTM Note Slides
3408 case CMD_NOTESLIDEUP:
3409 case CMD_NOTESLIDEDOWN:
3410 case CMD_NOTESLIDEUPRETRIG:
3411 case CMD_NOTESLIDEDOWNRETRIG:
3412 // Note that this command seems to be a bit buggy in Polytracker... Luckily, no tune seems to seriously use this
3413 // (Vic uses it e.g. in Spaceman or Perfect Reason to slide effect samples, noone will notice the difference :)
3414 NoteSlide(chn, param, cmd == CMD_NOTESLIDEUP || cmd == CMD_NOTESLIDEUPRETRIG, cmd == CMD_NOTESLIDEUPRETRIG || cmd == CMD_NOTESLIDEDOWNRETRIG);
3415 break;
3416
3417 // PTM Reverse sample + offset (executed on every tick)
3418 case CMD_REVERSEOFFSET:
3419 ReverseSampleOffset(chn, static_cast<ModCommand::PARAM>(param));
3420 break;
3421
3422 #ifndef NO_PLUGINS
3423 // DBM: Toggle DSP Echo
3424 case CMD_DBMECHO:
3425 if(m_PlayState.m_nTickCount == 0)
3426 {
3427 uint32 echoType = (param >> 4), enable = (param & 0x0F);
3428 if(echoType > 2 || enable > 1)
3429 {
3430 break;
3431 }
3432 CHANNELINDEX firstChn = nChn, lastChn = nChn;
3433 if(echoType == 1)
3434 {
3435 firstChn = 0;
3436 lastChn = m_nChannels - 1;
3437 }
3438 for(CHANNELINDEX c = firstChn; c <= lastChn; c++)
3439 {
3440 ChnSettings[c].dwFlags.set(CHN_NOFX, enable == 1);
3441 m_PlayState.Chn[c].dwFlags.set(CHN_NOFX, enable == 1);
3442 }
3443 }
3444 break;
3445 #endif // NO_PLUGINS
3446 }
3447
3448 if(m_playBehaviour[kST3EffectMemory] && param != 0)
3449 {
3450 UpdateS3MEffectMemory(chn, static_cast<ModCommand::PARAM>(param));
3451 }
3452
3453 if(chn.rowCommand.instr)
3454 {
3455 // Not necessarily consistent with actually playing instrument for IT compatibility
3456 chn.nOldIns = chn.rowCommand.instr;
3457 }
3458
3459 } // for(...) end
3460
3461 // Navigation Effects
3462 if(m_SongFlags[SONG_FIRSTTICK])
3463 {
3464 if(HandleNextRow(m_PlayState, Order(), true))
3465 m_SongFlags.set(SONG_BREAKTOROW);
3466 }
3467 return true;
3468 }
3469
3470
HandleNextRow(PlayState & state,const ModSequence & order,bool honorPatternLoop) const3471 bool CSoundFile::HandleNextRow(PlayState &state, const ModSequence &order, bool honorPatternLoop) const
3472 {
3473 const bool doPatternLoop = (state.m_patLoopRow != ROWINDEX_INVALID);
3474 const bool doBreakRow = (state.m_breakRow != ROWINDEX_INVALID);
3475 const bool doPosJump = (state.m_posJump != ORDERINDEX_INVALID);
3476 bool breakToRow = false;
3477
3478 // Pattern Break / Position Jump only if no loop running
3479 // Exception: FastTracker 2 in all cases, Impulse Tracker in case of position jump
3480 // Test case for FT2 exception: PatLoop-Jumps.xm, PatLoop-Various.xm
3481 // Test case for IT: exception: LoopBreak.it, sbx-priority.it
3482 if((doBreakRow || doPosJump)
3483 && (!doPatternLoop
3484 || m_playBehaviour[kFT2PatternLoopWithJumps]
3485 || (m_playBehaviour[kITPatternLoopWithJumps] && doPosJump)
3486 || (m_playBehaviour[kITPatternLoopWithJumpsOld] && doPosJump)))
3487 {
3488 if(!doPosJump)
3489 state.m_posJump = state.m_nCurrentOrder + 1;
3490 if(!doBreakRow)
3491 state.m_breakRow = 0;
3492 breakToRow = true;
3493
3494 if(state.m_posJump >= order.size())
3495 state.m_posJump = order.GetRestartPos();
3496
3497 // IT / FT2 compatibility: don't reset loop count on pattern break.
3498 // Test case: gm-trippy01.it, PatLoop-Break.xm, PatLoop-Weird.xm, PatLoop-Break.mod
3499 if(state.m_posJump != state.m_nCurrentOrder
3500 && !m_playBehaviour[kITPatternLoopBreak] && !m_playBehaviour[kFT2PatternLoopWithJumps] && GetType() != MOD_TYPE_MOD)
3501 {
3502 for(CHANNELINDEX i = 0; i < GetNumChannels(); i++)
3503 {
3504 state.Chn[i].nPatternLoopCount = 0;
3505 }
3506 }
3507
3508 state.m_nNextRow = state.m_breakRow;
3509 if(!honorPatternLoop || !m_SongFlags[SONG_PATTERNLOOP])
3510 state.m_nNextOrder = state.m_posJump;
3511 } else if(doPatternLoop)
3512 {
3513 // Pattern Loop
3514 state.m_nNextOrder = state.m_nCurrentOrder;
3515 state.m_nNextRow = state.m_patLoopRow;
3516 // FT2 skips the first row of the pattern loop if there's a pattern delay, ProTracker sometimes does it too (didn't quite figure it out yet).
3517 // But IT and ST3 don't do this.
3518 // Test cases: PatLoopWithDelay.it, PatLoopWithDelay.s3m
3519 if(state.m_nPatternDelay
3520 && (GetType() != MOD_TYPE_IT || !m_playBehaviour[kITPatternLoopWithJumps])
3521 && GetType() != MOD_TYPE_S3M)
3522 {
3523 state.m_nNextRow++;
3524 }
3525
3526 // IT Compatibility: If the restart row is past the end of the current pattern
3527 // (e.g. when continued from a previous pattern without explicit SB0 effect), continue the next pattern.
3528 // Test case: LoopStartAfterPatternEnd.it
3529 if(state.m_patLoopRow >= Patterns[state.m_nPattern].GetNumRows())
3530 {
3531 state.m_nNextOrder++;
3532 state.m_nNextRow = 0;
3533 }
3534 }
3535
3536 return breakToRow;
3537 }
3538
3539
3540 ////////////////////////////////////////////////////////////
3541 // Channels effects
3542
3543
3544 // Update the effect memory of all S3M effects that use the last non-zero effect parameter as memory (Dxy, Exx, Fxx, Ixy, Jxy, Kxy, Lxy, Qxy, Rxy, Sxy)
3545 // Test case: ParamMemory.s3m
UpdateS3MEffectMemory(ModChannel & chn,ModCommand::PARAM param) const3546 void CSoundFile::UpdateS3MEffectMemory(ModChannel &chn, ModCommand::PARAM param) const
3547 {
3548 chn.nOldVolumeSlide = param; // Dxy / Kxy / Lxy
3549 chn.nOldPortaUp = param; // Exx / Fxx
3550 chn.nOldPortaDown = param; // Exx / Fxx
3551 chn.nTremorParam = param; // Ixy
3552 chn.nArpeggio = param; // Jxy
3553 chn.nRetrigParam = param; // Qxy
3554 chn.nTremoloDepth = (param & 0x0F) << 2; // Rxy
3555 chn.nTremoloSpeed = (param >> 4) & 0x0F; // Rxy
3556 // Sxy is not handled here.
3557 }
3558
3559
3560 // Calculate full parameter for effects that support parameter extension at the given pattern location.
3561 // maxCommands sets the maximum number of XParam commands to look at for this effect
3562 // extendedRows returns how many extended rows are used (i.e. a value of 0 means the command is not extended).
CalculateXParam(PATTERNINDEX pat,ROWINDEX row,CHANNELINDEX chn,uint32 * extendedRows) const3563 uint32 CSoundFile::CalculateXParam(PATTERNINDEX pat, ROWINDEX row, CHANNELINDEX chn, uint32 *extendedRows) const
3564 {
3565 if(extendedRows != nullptr)
3566 *extendedRows = 0;
3567 if(!Patterns.IsValidPat(pat))
3568 {
3569 #ifdef MPT_BUILD_FUZZER
3570 // Ending up in this situation implies a logic error
3571 std::abort();
3572 #else
3573 return 0;
3574 #endif
3575 }
3576 ROWINDEX maxCommands = 4;
3577 const ModCommand *m = Patterns[pat].GetpModCommand(row, chn);
3578 const auto startCmd = m->command;
3579 uint32 val = m->param;
3580
3581 switch(m->command)
3582 {
3583 case CMD_OFFSET:
3584 // 24 bit command
3585 maxCommands = 2;
3586 break;
3587 case CMD_TEMPO:
3588 case CMD_PATTERNBREAK:
3589 case CMD_POSITIONJUMP:
3590 case CMD_FINETUNE:
3591 case CMD_FINETUNE_SMOOTH:
3592 // 16 bit command
3593 maxCommands = 1;
3594 break;
3595 default:
3596 return val;
3597 }
3598
3599 const bool xmTempoFix = m->command == CMD_TEMPO && GetType() == MOD_TYPE_XM;
3600 ROWINDEX numRows = std::min(Patterns[pat].GetNumRows() - row - 1, maxCommands);
3601 uint32 extRows = 0;
3602 while(numRows > 0)
3603 {
3604 m += Patterns[pat].GetNumChannels();
3605 if(m->command != CMD_XPARAM)
3606 break;
3607
3608 if(xmTempoFix && val < 256)
3609 {
3610 // With XM, 0x20 is the lowest tempo. Anything below changes ticks per row.
3611 val -= 0x20;
3612 }
3613 val = (val << 8) | m->param;
3614 numRows--;
3615 extRows++;
3616 }
3617
3618 // Always return a full-precision value for finetune
3619 if((startCmd == CMD_FINETUNE || startCmd == CMD_FINETUNE_SMOOTH) && !extRows)
3620 val <<= 8;
3621
3622 if(extendedRows != nullptr)
3623 *extendedRows = extRows;
3624
3625 return val;
3626 }
3627
3628
PositionJump(PlayState & state,CHANNELINDEX chn) const3629 void CSoundFile::PositionJump(PlayState &state, CHANNELINDEX chn) const
3630 {
3631 state.m_nextPatStartRow = 0; // FT2 E60 bug
3632 state.m_posJump = static_cast<ORDERINDEX>(CalculateXParam(state.m_nPattern, state.m_nRow, chn));
3633
3634 // see https://forum.openmpt.org/index.php?topic=2769.0 - FastTracker resets Dxx if Bxx is called _after_ Dxx
3635 // Test case: PatternJump.mod
3636 if((GetType() & (MOD_TYPE_MOD | MOD_TYPE_XM)) && state.m_breakRow != ROWINDEX_INVALID)
3637 {
3638 state.m_breakRow = 0;
3639 }
3640 }
3641
3642
PatternBreak(PlayState & state,CHANNELINDEX chn,uint8 param) const3643 ROWINDEX CSoundFile::PatternBreak(PlayState &state, CHANNELINDEX chn, uint8 param) const
3644 {
3645 if(param >= 64 && (GetType() & MOD_TYPE_S3M))
3646 {
3647 // ST3 ignores invalid pattern breaks.
3648 return ROWINDEX_INVALID;
3649 }
3650
3651 state.m_nextPatStartRow = 0; // FT2 E60 bug
3652
3653 return static_cast<ROWINDEX>(CalculateXParam(state.m_nPattern, state.m_nRow, chn));
3654 }
3655
3656
PortamentoUp(CHANNELINDEX nChn,ModCommand::PARAM param,const bool doFinePortamentoAsRegular)3657 void CSoundFile::PortamentoUp(CHANNELINDEX nChn, ModCommand::PARAM param, const bool doFinePortamentoAsRegular)
3658 {
3659 ModChannel &chn = m_PlayState.Chn[nChn];
3660
3661 if(param)
3662 {
3663 // FT2 compatibility: Separate effect memory for all portamento commands
3664 // Test case: Porta-LinkMem.xm
3665 if(!m_playBehaviour[kFT2PortaUpDownMemory])
3666 chn.nOldPortaDown = param;
3667 chn.nOldPortaUp = param;
3668 } else
3669 {
3670 param = chn.nOldPortaUp;
3671 }
3672
3673 const bool doFineSlides = !doFinePortamentoAsRegular && !(GetType() & (MOD_TYPE_MOD | MOD_TYPE_XM | MOD_TYPE_MT2 | MOD_TYPE_MED | MOD_TYPE_AMF0 | MOD_TYPE_DIGI | MOD_TYPE_STP | MOD_TYPE_DTM));
3674
3675 // Process MIDI pitch bend for instrument plugins
3676 MidiPortamento(nChn, param, doFineSlides);
3677
3678 if(GetType() == MOD_TYPE_MPT && chn.pModInstrument && chn.pModInstrument->pTuning)
3679 {
3680 // Portamento for instruments with custom tuning
3681 if(param >= 0xF0 && !doFinePortamentoAsRegular)
3682 PortamentoFineMPT(chn, param - 0xF0);
3683 else if(param >= 0xE0 && !doFinePortamentoAsRegular)
3684 PortamentoExtraFineMPT(chn, param - 0xE0);
3685 else
3686 PortamentoMPT(chn, param);
3687 return;
3688 } else if(GetType() == MOD_TYPE_PLM)
3689 {
3690 // A normal portamento up or down makes a follow-up tone portamento go the same direction.
3691 chn.nPortamentoDest = 1;
3692 }
3693
3694 if (doFineSlides && param >= 0xE0)
3695 {
3696 if (param & 0x0F)
3697 {
3698 if ((param & 0xF0) == 0xF0)
3699 {
3700 FinePortamentoUp(chn, param & 0x0F);
3701 return;
3702 } else if ((param & 0xF0) == 0xE0 && GetType() != MOD_TYPE_DBM)
3703 {
3704 ExtraFinePortamentoUp(chn, param & 0x0F);
3705 return;
3706 }
3707 }
3708 if(GetType() != MOD_TYPE_DBM)
3709 {
3710 // DBM only has fine slides, no extra-fine slides.
3711 return;
3712 }
3713 }
3714 // Regular Slide
3715 if(!chn.isFirstTick
3716 || (m_PlayState.m_nMusicSpeed == 1 && m_playBehaviour[kSlidesAtSpeed1])
3717 || (GetType() & (MOD_TYPE_669 | MOD_TYPE_OKT))
3718 || (GetType() == MOD_TYPE_MED && m_SongFlags[SONG_FASTVOLSLIDES]))
3719 {
3720 DoFreqSlide(chn, chn.nPeriod, param * 4);
3721 }
3722 }
3723
3724
PortamentoDown(CHANNELINDEX nChn,ModCommand::PARAM param,const bool doFinePortamentoAsRegular)3725 void CSoundFile::PortamentoDown(CHANNELINDEX nChn, ModCommand::PARAM param, const bool doFinePortamentoAsRegular)
3726 {
3727 ModChannel &chn = m_PlayState.Chn[nChn];
3728
3729 if(param)
3730 {
3731 // FT2 compatibility: Separate effect memory for all portamento commands
3732 // Test case: Porta-LinkMem.xm
3733 if(!m_playBehaviour[kFT2PortaUpDownMemory])
3734 chn.nOldPortaUp = param;
3735 chn.nOldPortaDown = param;
3736 } else
3737 {
3738 param = chn.nOldPortaDown;
3739 }
3740
3741 const bool doFineSlides = !doFinePortamentoAsRegular && !(GetType() & (MOD_TYPE_MOD | MOD_TYPE_XM | MOD_TYPE_MT2 | MOD_TYPE_MED | MOD_TYPE_AMF0 | MOD_TYPE_DIGI | MOD_TYPE_STP | MOD_TYPE_DTM));
3742
3743 // Process MIDI pitch bend for instrument plugins
3744 MidiPortamento(nChn, -static_cast<int>(param), doFineSlides);
3745
3746 if(GetType() == MOD_TYPE_MPT && chn.pModInstrument && chn.pModInstrument->pTuning)
3747 {
3748 // Portamento for instruments with custom tuning
3749 if(param >= 0xF0 && !doFinePortamentoAsRegular)
3750 PortamentoFineMPT(chn, -static_cast<int>(param - 0xF0));
3751 else if(param >= 0xE0 && !doFinePortamentoAsRegular)
3752 PortamentoExtraFineMPT(chn, -static_cast<int>(param - 0xE0));
3753 else
3754 PortamentoMPT(chn, -static_cast<int>(param));
3755 return;
3756 } else if(GetType() == MOD_TYPE_PLM)
3757 {
3758 // A normal portamento up or down makes a follow-up tone portamento go the same direction.
3759 chn.nPortamentoDest = 65535;
3760 }
3761
3762 if(doFineSlides && param >= 0xE0)
3763 {
3764 if (param & 0x0F)
3765 {
3766 if ((param & 0xF0) == 0xF0)
3767 {
3768 FinePortamentoDown(chn, param & 0x0F);
3769 return;
3770 } else if ((param & 0xF0) == 0xE0 && GetType() != MOD_TYPE_DBM)
3771 {
3772 ExtraFinePortamentoDown(chn, param & 0x0F);
3773 return;
3774 }
3775 }
3776 if(GetType() != MOD_TYPE_DBM)
3777 {
3778 // DBM only has fine slides, no extra-fine slides.
3779 return;
3780 }
3781 }
3782
3783 if(!chn.isFirstTick
3784 || (m_PlayState.m_nMusicSpeed == 1 && m_playBehaviour[kSlidesAtSpeed1])
3785 || (GetType() & (MOD_TYPE_669 | MOD_TYPE_OKT))
3786 || (GetType() == MOD_TYPE_MED && m_SongFlags[SONG_FASTVOLSLIDES]))
3787 {
3788 DoFreqSlide(chn, chn.nPeriod, param * -4);
3789 }
3790 }
3791
3792
3793 // Send portamento commands to plugins
MidiPortamento(CHANNELINDEX nChn,int param,bool doFineSlides)3794 void CSoundFile::MidiPortamento(CHANNELINDEX nChn, int param, bool doFineSlides)
3795 {
3796 int actualParam = std::abs(param);
3797 int pitchBend = 0;
3798
3799 // Old MIDI Pitch Bends:
3800 // - Applied on every tick
3801 // - No fine pitch slides (they are interpreted as normal slides)
3802 // New MIDI Pitch Bends:
3803 // - Behaviour identical to sample pitch bends if the instrument's PWD parameter corresponds to the actual VSTi setting.
3804
3805 if(doFineSlides && actualParam >= 0xE0 && !m_playBehaviour[kOldMIDIPitchBends])
3806 {
3807 if(m_PlayState.Chn[nChn].isFirstTick)
3808 {
3809 // Extra fine slide...
3810 pitchBend = (actualParam & 0x0F) * mpt::signum(param);
3811 if(actualParam >= 0xF0)
3812 {
3813 // ... or just a fine slide!
3814 pitchBend *= 4;
3815 }
3816 }
3817 } else if(!m_PlayState.Chn[nChn].isFirstTick || m_playBehaviour[kOldMIDIPitchBends])
3818 {
3819 // Regular slide
3820 pitchBend = param * 4;
3821 }
3822
3823 if(pitchBend)
3824 {
3825 #ifndef NO_PLUGINS
3826 IMixPlugin *plugin = GetChannelInstrumentPlugin(nChn);
3827 if(plugin != nullptr)
3828 {
3829 int8 pwd = 13; // Early OpenMPT legacy... Actually it's not *exactly* 13, but close enough...
3830 if(m_PlayState.Chn[nChn].pModInstrument != nullptr)
3831 {
3832 pwd = m_PlayState.Chn[nChn].pModInstrument->midiPWD;
3833 }
3834 plugin->MidiPitchBend(pitchBend, pwd, nChn);
3835 }
3836 #endif // NO_PLUGINS
3837 }
3838 }
3839
3840
FinePortamentoUp(ModChannel & chn,ModCommand::PARAM param) const3841 void CSoundFile::FinePortamentoUp(ModChannel &chn, ModCommand::PARAM param) const
3842 {
3843 MPT_ASSERT(!chn.HasCustomTuning());
3844 if(GetType() == MOD_TYPE_XM)
3845 {
3846 // FT2 compatibility: E1x / E2x / X1x / X2x memory is not linked
3847 // Test case: Porta-LinkMem.xm
3848 if(param) chn.nOldFinePortaUpDown = (chn.nOldFinePortaUpDown & 0x0F) | (param << 4); else param = (chn.nOldFinePortaUpDown >> 4);
3849 } else if(GetType() == MOD_TYPE_MT2)
3850 {
3851 if(param) chn.nOldFinePortaUpDown = param; else param = chn.nOldFinePortaUpDown;
3852 }
3853
3854 if(chn.isFirstTick && chn.nPeriod && param)
3855 DoFreqSlide(chn, chn.nPeriod, param * 4);
3856 }
3857
3858
FinePortamentoDown(ModChannel & chn,ModCommand::PARAM param) const3859 void CSoundFile::FinePortamentoDown(ModChannel &chn, ModCommand::PARAM param) const
3860 {
3861 MPT_ASSERT(!chn.HasCustomTuning());
3862 if(GetType() == MOD_TYPE_XM)
3863 {
3864 // FT2 compatibility: E1x / E2x / X1x / X2x memory is not linked
3865 // Test case: Porta-LinkMem.xm
3866 if(param) chn.nOldFinePortaUpDown = (chn.nOldFinePortaUpDown & 0xF0) | (param & 0x0F); else param = (chn.nOldFinePortaUpDown & 0x0F);
3867 } else if(GetType() == MOD_TYPE_MT2)
3868 {
3869 if(param) chn.nOldFinePortaUpDown = param; else param = chn.nOldFinePortaUpDown;
3870 }
3871
3872 if(chn.isFirstTick && chn.nPeriod && param)
3873 {
3874 DoFreqSlide(chn, chn.nPeriod, param * -4);
3875 if(chn.nPeriod > 0xFFFF && !m_playBehaviour[kPeriodsAreHertz] && (!m_SongFlags[SONG_LINEARSLIDES] || GetType() == MOD_TYPE_XM))
3876 chn.nPeriod = 0xFFFF;
3877 }
3878 }
3879
3880
ExtraFinePortamentoUp(ModChannel & chn,ModCommand::PARAM param) const3881 void CSoundFile::ExtraFinePortamentoUp(ModChannel &chn, ModCommand::PARAM param) const
3882 {
3883 MPT_ASSERT(!chn.HasCustomTuning());
3884 if(GetType() == MOD_TYPE_XM)
3885 {
3886 // FT2 compatibility: E1x / E2x / X1x / X2x memory is not linked
3887 // Test case: Porta-LinkMem.xm
3888 if(param) chn.nOldExtraFinePortaUpDown = (chn.nOldExtraFinePortaUpDown & 0x0F) | (param << 4); else param = (chn.nOldExtraFinePortaUpDown >> 4);
3889 } else if(GetType() == MOD_TYPE_MT2)
3890 {
3891 if(param) chn.nOldFinePortaUpDown = param; else param = chn.nOldFinePortaUpDown;
3892 }
3893
3894 if(chn.isFirstTick && chn.nPeriod && param)
3895 DoFreqSlide(chn, chn.nPeriod, param);
3896 }
3897
3898
ExtraFinePortamentoDown(ModChannel & chn,ModCommand::PARAM param) const3899 void CSoundFile::ExtraFinePortamentoDown(ModChannel &chn, ModCommand::PARAM param) const
3900 {
3901 MPT_ASSERT(!chn.HasCustomTuning());
3902 if(GetType() == MOD_TYPE_XM)
3903 {
3904 // FT2 compatibility: E1x / E2x / X1x / X2x memory is not linked
3905 // Test case: Porta-LinkMem.xm
3906 if(param) chn.nOldExtraFinePortaUpDown = (chn.nOldExtraFinePortaUpDown & 0xF0) | (param & 0x0F); else param = (chn.nOldExtraFinePortaUpDown & 0x0F);
3907 } else if(GetType() == MOD_TYPE_MT2)
3908 {
3909 if(param) chn.nOldFinePortaUpDown = param; else param = chn.nOldFinePortaUpDown;
3910 }
3911
3912 if(chn.isFirstTick && chn.nPeriod && param)
3913 {
3914 DoFreqSlide(chn, chn.nPeriod, -static_cast<int32>(param));
3915 if(chn.nPeriod > 0xFFFF && !m_playBehaviour[kPeriodsAreHertz] && (!m_SongFlags[SONG_LINEARSLIDES] || GetType() == MOD_TYPE_XM))
3916 chn.nPeriod = 0xFFFF;
3917 }
3918 }
3919
3920
SetFinetune(CHANNELINDEX channel,PlayState & playState,bool isSmooth) const3921 void CSoundFile::SetFinetune(CHANNELINDEX channel, PlayState &playState, bool isSmooth) const
3922 {
3923 ModChannel &chn = playState.Chn[channel];
3924 int16 newTuning = mpt::saturate_cast<int16>(static_cast<int32>(CalculateXParam(playState.m_nPattern, playState.m_nRow, channel, nullptr)) - 0x8000);
3925
3926 if(isSmooth)
3927 {
3928 const int32 ticksLeft = playState.TicksOnRow() - playState.m_nTickCount;
3929 if(ticksLeft > 1)
3930 {
3931 const int32 step = (newTuning - chn.microTuning) / ticksLeft;
3932 newTuning = mpt::saturate_cast<int16>(chn.microTuning + step);
3933 }
3934 }
3935 chn.microTuning = newTuning;
3936 }
3937
3938
3939 // Implemented for IMF / PTM / OKT compatibility, can't actually save this in any formats
3940 // Slide up / down every x ticks by y semitones
3941 // Oktalyzer: Slide down on first tick only, or on every tick
NoteSlide(ModChannel & chn,uint32 param,bool slideUp,bool retrig) const3942 void CSoundFile::NoteSlide(ModChannel &chn, uint32 param, bool slideUp, bool retrig) const
3943 {
3944 if(m_SongFlags[SONG_FIRSTTICK])
3945 {
3946 if(param & 0xF0)
3947 chn.noteSlideParam = static_cast<uint8>(param & 0xF0) | (chn.noteSlideParam & 0x0F);
3948 if(param & 0x0F)
3949 chn.noteSlideParam = (chn.noteSlideParam & 0xF0) | static_cast<uint8>(param & 0x0F);
3950 chn.noteSlideCounter = (chn.noteSlideParam >> 4);
3951 }
3952
3953 bool doTrigger = false;
3954 if(GetType() == MOD_TYPE_OKT)
3955 doTrigger = ((chn.noteSlideParam & 0xF0) == 0x10) || m_SongFlags[SONG_FIRSTTICK];
3956 else
3957 doTrigger = !m_SongFlags[SONG_FIRSTTICK] && (--chn.noteSlideCounter == 0);
3958
3959 if(doTrigger)
3960 {
3961 const uint8 speed = (chn.noteSlideParam >> 4), steps = (chn.noteSlideParam & 0x0F);
3962 chn.noteSlideCounter = speed;
3963 // update it
3964 const int32 delta = (slideUp ? steps : -steps);
3965 if(chn.HasCustomTuning())
3966 chn.m_PortamentoFineSteps += delta * chn.pModInstrument->pTuning->GetFineStepCount();
3967 else
3968 chn.nPeriod = GetPeriodFromNote(delta + GetNoteFromPeriod(chn.nPeriod, chn.nFineTune, chn.nC5Speed), chn.nFineTune, chn.nC5Speed);
3969
3970 if(retrig)
3971 chn.position.Set(0);
3972 }
3973 }
3974
3975
GetVolCmdTonePorta(const ModCommand & m,uint32 startTick) const3976 std::pair<uint16, bool> CSoundFile::GetVolCmdTonePorta(const ModCommand &m, uint32 startTick) const
3977 {
3978 if(GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT | MOD_TYPE_AMS | MOD_TYPE_DMF | MOD_TYPE_DBM | MOD_TYPE_IMF | MOD_TYPE_PSM | MOD_TYPE_J2B | MOD_TYPE_ULT | MOD_TYPE_OKT | MOD_TYPE_MT2 | MOD_TYPE_MDL))
3979 {
3980 return {ImpulseTrackerPortaVolCmd[m.vol & 0x0F], false};
3981 } else
3982 {
3983 bool clearEffectColumn = false;
3984 uint16 vol = m.vol;
3985 if(m.command == CMD_TONEPORTAMENTO && GetType() == MOD_TYPE_XM)
3986 {
3987 // Yes, FT2 is *that* weird. If there is a Mx command in the volume column
3988 // and a normal 3xx command, the 3xx command is ignored but the Mx command's
3989 // effectiveness is doubled.
3990 // Test case: TonePortamentoMemory.xm
3991 clearEffectColumn = true;
3992 vol *= 2;
3993 }
3994
3995 // FT2 compatibility: If there's a portamento and a note delay, execute the portamento, but don't update the parameter
3996 // Test case: PortaDelay.xm
3997 if(m_playBehaviour[kFT2PortaDelay] && startTick != 0)
3998 return {uint16(0), clearEffectColumn};
3999 else
4000 return {static_cast<uint16>(vol * 16), clearEffectColumn};
4001 }
4002 }
4003
4004
4005 // Portamento Slide
TonePortamento(ModChannel & chn,uint16 param) const4006 void CSoundFile::TonePortamento(ModChannel &chn, uint16 param) const
4007 {
4008 chn.dwFlags.set(CHN_PORTAMENTO);
4009
4010 //IT compatibility 03: Share effect memory with portamento up/down
4011 if((!m_SongFlags[SONG_ITCOMPATGXX] && m_playBehaviour[kITPortaMemoryShare]) || GetType() == MOD_TYPE_PLM)
4012 {
4013 if(param == 0) param = chn.nOldPortaUp;
4014 chn.nOldPortaUp = chn.nOldPortaDown = static_cast<uint8>(param);
4015 }
4016
4017 if(param)
4018 chn.portamentoSlide = param;
4019
4020 if(chn.HasCustomTuning())
4021 {
4022 //Behavior: Param tells number of finesteps(or 'fullsteps'(notes) with glissando)
4023 //to slide per row(not per tick).
4024 if(chn.portamentoSlide == 0)
4025 return;
4026
4027 const int32 oldPortamentoTickSlide = (m_PlayState.m_nTickCount != 0) ? chn.m_PortamentoTickSlide : 0;
4028
4029 int32 delta = chn.portamentoSlide;
4030 if(chn.nPortamentoDest < 0)
4031 delta = -delta;
4032
4033 chn.m_PortamentoTickSlide = static_cast<int32>((m_PlayState.m_nTickCount + 1.0) * delta / m_PlayState.m_nMusicSpeed);
4034
4035 if(chn.dwFlags[CHN_GLISSANDO])
4036 {
4037 chn.m_PortamentoTickSlide *= chn.pModInstrument->pTuning->GetFineStepCount() + 1;
4038 //With glissando interpreting param as notes instead of finesteps.
4039 }
4040
4041 const int32 slide = chn.m_PortamentoTickSlide - oldPortamentoTickSlide;
4042
4043 if(std::abs(chn.nPortamentoDest) <= std::abs(slide))
4044 {
4045 if(chn.nPortamentoDest != 0)
4046 {
4047 chn.m_PortamentoFineSteps += chn.nPortamentoDest;
4048 chn.nPortamentoDest = 0;
4049 chn.m_CalculateFreq = true;
4050 }
4051 } else
4052 {
4053 chn.m_PortamentoFineSteps += slide;
4054 chn.nPortamentoDest -= slide;
4055 chn.m_CalculateFreq = true;
4056 }
4057
4058 return;
4059 }
4060
4061 bool doPorta = !chn.isFirstTick
4062 || (GetType() & (MOD_TYPE_DBM | MOD_TYPE_669))
4063 || (m_PlayState.m_nMusicSpeed == 1 && m_playBehaviour[kSlidesAtSpeed1])
4064 || (GetType() == MOD_TYPE_MED && m_SongFlags[SONG_FASTVOLSLIDES]);
4065
4066 int32 delta = chn.portamentoSlide;
4067 if(GetType() == MOD_TYPE_PLM && delta >= 0xF0)
4068 {
4069 delta -= 0xF0;
4070 doPorta = chn.isFirstTick;
4071 }
4072
4073 if(chn.nPeriod && chn.nPortamentoDest && doPorta)
4074 {
4075 delta *= (GetType() == MOD_TYPE_669) ? 2 : 4;
4076 if(!PeriodsAreFrequencies())
4077 delta = -delta;
4078 if(chn.nPeriod < chn.nPortamentoDest || chn.portaTargetReached)
4079 {
4080 DoFreqSlide(chn, chn.nPeriod, delta, true);
4081 if(chn.nPeriod > chn.nPortamentoDest)
4082 chn.nPeriod = chn.nPortamentoDest;
4083 } else if(chn.nPeriod > chn.nPortamentoDest)
4084 {
4085 DoFreqSlide(chn, chn.nPeriod, -delta, true);
4086 if(chn.nPeriod < chn.nPortamentoDest)
4087 chn.nPeriod = chn.nPortamentoDest;
4088 // FT2 compatibility: Reaching portamento target from below forces subsequent portamentos on the same note to use the logic for reaching the note from above instead.
4089 // Test case: PortaResetDirection.xm
4090 if(chn.nPeriod == chn.nPortamentoDest && m_playBehaviour[kFT2PortaResetDirection])
4091 chn.portaTargetReached = true;
4092 }
4093 }
4094
4095 // IT compatibility 23. Portamento with no note
4096 // ProTracker also disables portamento once the target is reached.
4097 // Test case: PortaTarget.mod
4098 if(chn.nPeriod == chn.nPortamentoDest && (m_playBehaviour[kITPortaTargetReached] || GetType() == MOD_TYPE_MOD))
4099 chn.nPortamentoDest = 0;
4100
4101 }
4102
4103
Vibrato(ModChannel & chn,uint32 param) const4104 void CSoundFile::Vibrato(ModChannel &chn, uint32 param) const
4105 {
4106 if (param & 0x0F) chn.nVibratoDepth = (param & 0x0F) * 4;
4107 if (param & 0xF0) chn.nVibratoSpeed = (param >> 4) & 0x0F;
4108 chn.dwFlags.set(CHN_VIBRATO);
4109 }
4110
4111
FineVibrato(ModChannel & chn,uint32 param) const4112 void CSoundFile::FineVibrato(ModChannel &chn, uint32 param) const
4113 {
4114 if (param & 0x0F) chn.nVibratoDepth = param & 0x0F;
4115 if (param & 0xF0) chn.nVibratoSpeed = (param >> 4) & 0x0F;
4116 chn.dwFlags.set(CHN_VIBRATO);
4117 // ST3 compatibility: Do not distinguish between vibrato types in effect memory
4118 // Test case: VibratoTypeChange.s3m
4119 if(m_playBehaviour[kST3VibratoMemory] && (param & 0x0F))
4120 {
4121 chn.nVibratoDepth *= 4u;
4122 }
4123 }
4124
4125
Panbrello(ModChannel & chn,uint32 param) const4126 void CSoundFile::Panbrello(ModChannel &chn, uint32 param) const
4127 {
4128 if (param & 0x0F) chn.nPanbrelloDepth = param & 0x0F;
4129 if (param & 0xF0) chn.nPanbrelloSpeed = (param >> 4) & 0x0F;
4130 }
4131
4132
Panning(ModChannel & chn,uint32 param,PanningType panBits) const4133 void CSoundFile::Panning(ModChannel &chn, uint32 param, PanningType panBits) const
4134 {
4135 // No panning in ProTracker mode
4136 if(m_playBehaviour[kMODIgnorePanning])
4137 {
4138 return;
4139 }
4140 // IT Compatibility (and other trackers as well): panning disables surround (unless panning in rear channels is enabled, which is not supported by the original trackers anyway)
4141 if (!m_SongFlags[SONG_SURROUNDPAN] && (panBits == Pan8bit || m_playBehaviour[kPanOverride]))
4142 {
4143 chn.dwFlags.reset(CHN_SURROUND);
4144 }
4145 if(panBits == Pan4bit)
4146 {
4147 // 0...15 panning
4148 chn.nPan = (param * 256 + 8) / 15;
4149 } else if(panBits == Pan6bit)
4150 {
4151 // 0...64 panning
4152 if(param > 64) param = 64;
4153 chn.nPan = param * 4;
4154 } else
4155 {
4156 if(!(GetType() & (MOD_TYPE_S3M | MOD_TYPE_DSM | MOD_TYPE_AMF0 | MOD_TYPE_AMF | MOD_TYPE_MTM)))
4157 {
4158 // Real 8-bit panning
4159 chn.nPan = param;
4160 } else
4161 {
4162 // 7-bit panning + surround
4163 if(param <= 0x80)
4164 {
4165 chn.nPan = param << 1;
4166 } else if(param == 0xA4)
4167 {
4168 chn.dwFlags.set(CHN_SURROUND);
4169 chn.nPan = 0x80;
4170 }
4171 }
4172 }
4173
4174 chn.dwFlags.set(CHN_FASTVOLRAMP);
4175 chn.nRestorePanOnNewNote = 0;
4176 //IT compatibility 20. Set pan overrides random pan
4177 if(m_playBehaviour[kPanOverride])
4178 {
4179 chn.nPanSwing = 0;
4180 chn.nPanbrelloOffset = 0;
4181 }
4182 }
4183
4184
VolumeSlide(ModChannel & chn,ModCommand::PARAM param) const4185 void CSoundFile::VolumeSlide(ModChannel &chn, ModCommand::PARAM param) const
4186 {
4187 if (param)
4188 chn.nOldVolumeSlide = param;
4189 else
4190 param = chn.nOldVolumeSlide;
4191
4192 if((GetType() & (MOD_TYPE_MOD | MOD_TYPE_XM | MOD_TYPE_MT2 | MOD_TYPE_MED | MOD_TYPE_DIGI | MOD_TYPE_STP | MOD_TYPE_DTM)))
4193 {
4194 // MOD / XM nibble priority
4195 if((param & 0xF0) != 0)
4196 {
4197 param &= 0xF0;
4198 } else
4199 {
4200 param &= 0x0F;
4201 }
4202 }
4203
4204 int newVolume = chn.nVolume;
4205 if(!(GetType() & (MOD_TYPE_MOD | MOD_TYPE_XM | MOD_TYPE_AMF0 | MOD_TYPE_MED | MOD_TYPE_DIGI)))
4206 {
4207 if ((param & 0x0F) == 0x0F) //Fine upslide or slide -15
4208 {
4209 if (param & 0xF0) //Fine upslide
4210 {
4211 FineVolumeUp(chn, (param >> 4), false);
4212 return;
4213 } else //Slide -15
4214 {
4215 if(chn.isFirstTick && !m_SongFlags[SONG_FASTVOLSLIDES])
4216 {
4217 newVolume -= 0x0F * 4;
4218 }
4219 }
4220 } else
4221 if ((param & 0xF0) == 0xF0) //Fine downslide or slide +15
4222 {
4223 if (param & 0x0F) //Fine downslide
4224 {
4225 FineVolumeDown(chn, (param & 0x0F), false);
4226 return;
4227 } else //Slide +15
4228 {
4229 if(chn.isFirstTick && !m_SongFlags[SONG_FASTVOLSLIDES])
4230 {
4231 newVolume += 0x0F * 4;
4232 }
4233 }
4234 }
4235 }
4236 if(!chn.isFirstTick || m_SongFlags[SONG_FASTVOLSLIDES] || (m_PlayState.m_nMusicSpeed == 1 && GetType() == MOD_TYPE_DBM))
4237 {
4238 // IT compatibility: Ignore slide commands with both nibbles set.
4239 if (param & 0x0F)
4240 {
4241 if(!(GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT)) || (param & 0xF0) == 0)
4242 newVolume -= (int)((param & 0x0F) * 4);
4243 }
4244 else
4245 {
4246 newVolume += (int)((param & 0xF0) >> 2);
4247 }
4248 if (GetType() == MOD_TYPE_MOD) chn.dwFlags.set(CHN_FASTVOLRAMP);
4249 }
4250 newVolume = Clamp(newVolume, 0, 256);
4251
4252 chn.nVolume = newVolume;
4253 }
4254
4255
PanningSlide(ModChannel & chn,ModCommand::PARAM param,bool memory) const4256 void CSoundFile::PanningSlide(ModChannel &chn, ModCommand::PARAM param, bool memory) const
4257 {
4258 if(memory)
4259 {
4260 // FT2 compatibility: Use effect memory (lxx and rxx in XM shouldn't use effect memory).
4261 // Test case: PanSlideMem.xm
4262 if(param)
4263 chn.nOldPanSlide = param;
4264 else
4265 param = chn.nOldPanSlide;
4266 }
4267
4268 if((GetType() & (MOD_TYPE_XM | MOD_TYPE_MT2)))
4269 {
4270 // XM nibble priority
4271 if((param & 0xF0) != 0)
4272 {
4273 param &= 0xF0;
4274 } else
4275 {
4276 param &= 0x0F;
4277 }
4278 }
4279
4280 int32 nPanSlide = 0;
4281
4282 if(!(GetType() & (MOD_TYPE_XM | MOD_TYPE_MT2)))
4283 {
4284 if (((param & 0x0F) == 0x0F) && (param & 0xF0))
4285 {
4286 if(m_SongFlags[SONG_FIRSTTICK])
4287 {
4288 param = (param & 0xF0) / 4u;
4289 nPanSlide = - (int)param;
4290 }
4291 } else if (((param & 0xF0) == 0xF0) && (param & 0x0F))
4292 {
4293 if(m_SongFlags[SONG_FIRSTTICK])
4294 {
4295 nPanSlide = (param & 0x0F) * 4u;
4296 }
4297 } else if(!m_SongFlags[SONG_FIRSTTICK])
4298 {
4299 if (param & 0x0F)
4300 {
4301 // IT compatibility: Ignore slide commands with both nibbles set.
4302 if(!(GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT)) || (param & 0xF0) == 0)
4303 nPanSlide = (int)((param & 0x0F) * 4u);
4304 } else
4305 {
4306 nPanSlide = -(int)((param & 0xF0) / 4u);
4307 }
4308 }
4309 } else
4310 {
4311 if(!m_SongFlags[SONG_FIRSTTICK])
4312 {
4313 if (param & 0xF0)
4314 {
4315 nPanSlide = (int)((param & 0xF0) / 4u);
4316 } else
4317 {
4318 nPanSlide = -(int)((param & 0x0F) * 4u);
4319 }
4320 // FT2 compatibility: FT2's panning slide is like IT's fine panning slide (not as deep)
4321 if(m_playBehaviour[kFT2PanSlide])
4322 nPanSlide /= 4;
4323 }
4324 }
4325 if (nPanSlide)
4326 {
4327 nPanSlide += chn.nPan;
4328 nPanSlide = Clamp(nPanSlide, 0, 256);
4329 chn.nPan = nPanSlide;
4330 chn.nRestorePanOnNewNote = 0;
4331 }
4332 }
4333
4334
FineVolumeUp(ModChannel & chn,ModCommand::PARAM param,bool volCol) const4335 void CSoundFile::FineVolumeUp(ModChannel &chn, ModCommand::PARAM param, bool volCol) const
4336 {
4337 if(GetType() == MOD_TYPE_XM)
4338 {
4339 // FT2 compatibility: EAx / EBx memory is not linked
4340 // Test case: FineVol-LinkMem.xm
4341 if(param) chn.nOldFineVolUpDown = (param << 4) | (chn.nOldFineVolUpDown & 0x0F); else param = (chn.nOldFineVolUpDown >> 4);
4342 } else if(volCol)
4343 {
4344 if(param) chn.nOldVolParam = param; else param = chn.nOldVolParam;
4345 } else
4346 {
4347 if(param) chn.nOldFineVolUpDown = param; else param = chn.nOldFineVolUpDown;
4348 }
4349
4350 if(chn.isFirstTick)
4351 {
4352 chn.nVolume += param * 4;
4353 if(chn.nVolume > 256) chn.nVolume = 256;
4354 if(GetType() & MOD_TYPE_MOD) chn.dwFlags.set(CHN_FASTVOLRAMP);
4355 }
4356 }
4357
4358
FineVolumeDown(ModChannel & chn,ModCommand::PARAM param,bool volCol) const4359 void CSoundFile::FineVolumeDown(ModChannel &chn, ModCommand::PARAM param, bool volCol) const
4360 {
4361 if(GetType() == MOD_TYPE_XM)
4362 {
4363 // FT2 compatibility: EAx / EBx memory is not linked
4364 // Test case: FineVol-LinkMem.xm
4365 if(param) chn.nOldFineVolUpDown = param | (chn.nOldFineVolUpDown & 0xF0); else param = (chn.nOldFineVolUpDown & 0x0F);
4366 } else if(volCol)
4367 {
4368 if(param) chn.nOldVolParam = param; else param = chn.nOldVolParam;
4369 } else
4370 {
4371 if(param) chn.nOldFineVolUpDown = param; else param = chn.nOldFineVolUpDown;
4372 }
4373
4374 if(chn.isFirstTick)
4375 {
4376 chn.nVolume -= param * 4;
4377 if(chn.nVolume < 0) chn.nVolume = 0;
4378 if(GetType() & MOD_TYPE_MOD) chn.dwFlags.set(CHN_FASTVOLRAMP);
4379 }
4380 }
4381
4382
Tremolo(ModChannel & chn,uint32 param) const4383 void CSoundFile::Tremolo(ModChannel &chn, uint32 param) const
4384 {
4385 if (param & 0x0F) chn.nTremoloDepth = (param & 0x0F) << 2;
4386 if (param & 0xF0) chn.nTremoloSpeed = (param >> 4) & 0x0F;
4387 chn.dwFlags.set(CHN_TREMOLO);
4388 }
4389
4390
ChannelVolSlide(ModChannel & chn,ModCommand::PARAM param) const4391 void CSoundFile::ChannelVolSlide(ModChannel &chn, ModCommand::PARAM param) const
4392 {
4393 int32 nChnSlide = 0;
4394 if (param) chn.nOldChnVolSlide = param; else param = chn.nOldChnVolSlide;
4395
4396 if (((param & 0x0F) == 0x0F) && (param & 0xF0))
4397 {
4398 if(m_SongFlags[SONG_FIRSTTICK]) nChnSlide = param >> 4;
4399 } else if (((param & 0xF0) == 0xF0) && (param & 0x0F))
4400 {
4401 if(m_SongFlags[SONG_FIRSTTICK]) nChnSlide = - (int)(param & 0x0F);
4402 } else
4403 {
4404 if(!m_SongFlags[SONG_FIRSTTICK])
4405 {
4406 if (param & 0x0F)
4407 {
4408 if(!(GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT | MOD_TYPE_J2B | MOD_TYPE_DBM)) || (param & 0xF0) == 0)
4409 nChnSlide = -(int)(param & 0x0F);
4410 } else
4411 {
4412 nChnSlide = (int)((param & 0xF0) >> 4);
4413 }
4414 }
4415 }
4416 if (nChnSlide)
4417 {
4418 nChnSlide += chn.nGlobalVol;
4419 nChnSlide = Clamp(nChnSlide, 0, 64);
4420 chn.nGlobalVol = nChnSlide;
4421 }
4422 }
4423
4424
ExtendedMODCommands(CHANNELINDEX nChn,ModCommand::PARAM param)4425 void CSoundFile::ExtendedMODCommands(CHANNELINDEX nChn, ModCommand::PARAM param)
4426 {
4427 ModChannel &chn = m_PlayState.Chn[nChn];
4428 uint8 command = param & 0xF0;
4429 param &= 0x0F;
4430 switch(command)
4431 {
4432 // E0x: Set Filter
4433 case 0x00:
4434 for(CHANNELINDEX channel = 0; channel < GetNumChannels(); channel++)
4435 {
4436 m_PlayState.Chn[channel].dwFlags.set(CHN_AMIGAFILTER, !(param & 1));
4437 }
4438 break;
4439 // E1x: Fine Portamento Up
4440 case 0x10: if ((param) || (GetType() & (MOD_TYPE_XM|MOD_TYPE_MT2))) FinePortamentoUp(chn, param); break;
4441 // E2x: Fine Portamento Down
4442 case 0x20: if ((param) || (GetType() & (MOD_TYPE_XM|MOD_TYPE_MT2))) FinePortamentoDown(chn, param); break;
4443 // E3x: Set Glissando Control
4444 case 0x30: chn.dwFlags.set(CHN_GLISSANDO, param != 0); break;
4445 // E4x: Set Vibrato WaveForm
4446 case 0x40: chn.nVibratoType = param & 0x07; break;
4447 // E5x: Set FineTune
4448 case 0x50: if(!m_SongFlags[SONG_FIRSTTICK])
4449 break;
4450 if(GetType() & (MOD_TYPE_MOD | MOD_TYPE_DIGI | MOD_TYPE_AMF0 | MOD_TYPE_MED))
4451 {
4452 chn.nFineTune = MOD2XMFineTune(param);
4453 if(chn.nPeriod && chn.rowCommand.IsNote()) chn.nPeriod = GetPeriodFromNote(chn.nNote, chn.nFineTune, chn.nC5Speed);
4454 } else if(GetType() == MOD_TYPE_MTM)
4455 {
4456 if(chn.rowCommand.IsNote() && chn.pModSample != nullptr)
4457 {
4458 // Effect is permanent in MultiTracker
4459 const_cast<ModSample *>(chn.pModSample)->nFineTune = param;
4460 chn.nFineTune = param;
4461 if(chn.nPeriod) chn.nPeriod = GetPeriodFromNote(chn.nNote, chn.nFineTune, chn.nC5Speed);
4462 }
4463 } else if(chn.rowCommand.IsNote())
4464 {
4465 chn.nFineTune = MOD2XMFineTune(param - 8);
4466 if(chn.nPeriod) chn.nPeriod = GetPeriodFromNote(chn.nNote, chn.nFineTune, chn.nC5Speed);
4467 }
4468 break;
4469 // E6x: Pattern Loop
4470 case 0x60:
4471 if(m_SongFlags[SONG_FIRSTTICK])
4472 PatternLoop(m_PlayState, chn, param & 0x0F);
4473 break;
4474 // E7x: Set Tremolo WaveForm
4475 case 0x70: chn.nTremoloType = param & 0x07; break;
4476 // E8x: Set 4-bit Panning
4477 case 0x80:
4478 if(m_SongFlags[SONG_FIRSTTICK])
4479 {
4480 Panning(chn, param, Pan4bit);
4481 }
4482 break;
4483 // E9x: Retrig
4484 case 0x90: RetrigNote(nChn, param); break;
4485 // EAx: Fine Volume Up
4486 case 0xA0: if ((param) || (GetType() & (MOD_TYPE_XM|MOD_TYPE_MT2))) FineVolumeUp(chn, param, false); break;
4487 // EBx: Fine Volume Down
4488 case 0xB0: if ((param) || (GetType() & (MOD_TYPE_XM|MOD_TYPE_MT2))) FineVolumeDown(chn, param, false); break;
4489 // ECx: Note Cut
4490 case 0xC0: NoteCut(nChn, param, false); break;
4491 // EDx: Note Delay
4492 // EEx: Pattern Delay
4493 case 0xF0:
4494 if(GetType() == MOD_TYPE_MOD) // MOD: Invert Loop
4495 {
4496 chn.nEFxSpeed = param;
4497 if(m_SongFlags[SONG_FIRSTTICK]) InvertLoop(chn);
4498 } else // XM: Set Active Midi Macro
4499 {
4500 chn.nActiveMacro = param;
4501 }
4502 break;
4503 }
4504 }
4505
4506
ExtendedS3MCommands(CHANNELINDEX nChn,ModCommand::PARAM param)4507 void CSoundFile::ExtendedS3MCommands(CHANNELINDEX nChn, ModCommand::PARAM param)
4508 {
4509 ModChannel &chn = m_PlayState.Chn[nChn];
4510 uint8 command = param & 0xF0;
4511 param &= 0x0F;
4512 switch(command)
4513 {
4514 // S0x: Set Filter
4515 // S1x: Set Glissando Control
4516 case 0x10: chn.dwFlags.set(CHN_GLISSANDO, param != 0); break;
4517 // S2x: Set FineTune
4518 case 0x20: if(!m_SongFlags[SONG_FIRSTTICK])
4519 break;
4520 if(chn.HasCustomTuning())
4521 {
4522 chn.nFineTune = param - 8;
4523 chn.m_CalculateFreq = true;
4524 } else if(GetType() != MOD_TYPE_669)
4525 {
4526 chn.nC5Speed = S3MFineTuneTable[param];
4527 chn.nFineTune = MOD2XMFineTune(param);
4528 if(chn.nPeriod)
4529 chn.nPeriod = GetPeriodFromNote(chn.nNote, chn.nFineTune, chn.nC5Speed);
4530 } else if(chn.pModSample != nullptr)
4531 {
4532 chn.nC5Speed = chn.pModSample->nC5Speed + param * 80;
4533 }
4534 break;
4535 // S3x: Set Vibrato Waveform
4536 case 0x30: if(GetType() == MOD_TYPE_S3M)
4537 {
4538 chn.nVibratoType = param & 0x03;
4539 } else
4540 {
4541 // IT compatibility: Ignore waveform types > 3
4542 if(m_playBehaviour[kITVibratoTremoloPanbrello])
4543 chn.nVibratoType = (param < 0x04) ? param : 0;
4544 else
4545 chn.nVibratoType = param & 0x07;
4546 }
4547 break;
4548 // S4x: Set Tremolo Waveform
4549 case 0x40: if(GetType() == MOD_TYPE_S3M)
4550 {
4551 chn.nTremoloType = param & 0x03;
4552 } else
4553 {
4554 // IT compatibility: Ignore waveform types > 3
4555 if(m_playBehaviour[kITVibratoTremoloPanbrello])
4556 chn.nTremoloType = (param < 0x04) ? param : 0;
4557 else
4558 chn.nTremoloType = param & 0x07;
4559 }
4560 break;
4561 // S5x: Set Panbrello Waveform
4562 case 0x50:
4563 // IT compatibility: Ignore waveform types > 3
4564 if(m_playBehaviour[kITVibratoTremoloPanbrello])
4565 {
4566 chn.nPanbrelloType = (param < 0x04) ? param : 0;
4567 chn.nPanbrelloPos = 0;
4568 } else
4569 {
4570 chn.nPanbrelloType = param & 0x07;
4571 }
4572 break;
4573 // S6x: Pattern Delay for x frames
4574 case 0x60:
4575 if(m_SongFlags[SONG_FIRSTTICK] && m_PlayState.m_nTickCount == 0)
4576 {
4577 // Tick delays are added up.
4578 // Scream Tracker 3 does actually not support this command.
4579 // We'll use the same behaviour as for Impulse Tracker, as we can assume that
4580 // most S3Ms that make use of this command were made with Impulse Tracker.
4581 // MPT added this command to the XM format through the X6x effect, so we will use
4582 // the same behaviour here as well.
4583 // Test cases: PatternDelays.it, PatternDelays.s3m, PatternDelays.xm
4584 m_PlayState.m_nFrameDelay += param;
4585 }
4586 break;
4587 // S7x: Envelope Control / Instrument Control
4588 case 0x70: if(!m_SongFlags[SONG_FIRSTTICK]) break;
4589 switch(param)
4590 {
4591 case 0:
4592 case 1:
4593 case 2:
4594 {
4595 for (CHANNELINDEX i = m_nChannels; i < MAX_CHANNELS; i++)
4596 {
4597 ModChannel &bkChn = m_PlayState.Chn[i];
4598 if (bkChn.nMasterChn == nChn + 1)
4599 {
4600 if (param == 1)
4601 {
4602 KeyOff(bkChn);
4603 if(bkChn.dwFlags[CHN_ADLIB] && m_opl)
4604 m_opl->NoteOff(i);
4605 } else if (param == 2)
4606 {
4607 bkChn.dwFlags.set(CHN_NOTEFADE);
4608 if(bkChn.dwFlags[CHN_ADLIB] && m_opl)
4609 m_opl->NoteOff(i);
4610 } else
4611 {
4612 bkChn.dwFlags.set(CHN_NOTEFADE);
4613 bkChn.nFadeOutVol = 0;
4614 if(bkChn.dwFlags[CHN_ADLIB] && m_opl)
4615 m_opl->NoteCut(i);
4616 }
4617 #ifndef NO_PLUGINS
4618 const ModInstrument *pIns = bkChn.pModInstrument;
4619 IMixPlugin *pPlugin;
4620 if(pIns != nullptr && pIns->nMixPlug && (pPlugin = m_MixPlugins[pIns->nMixPlug - 1].pMixPlugin) != nullptr)
4621 {
4622 pPlugin->MidiCommand(*pIns, bkChn.nNote + NOTE_MAX_SPECIAL, 0, nChn);
4623 }
4624 #endif // NO_PLUGINS
4625 }
4626 }
4627 }
4628 break;
4629 default: // S73-S7E
4630 chn.InstrumentControl(param, *this);
4631 break;
4632 }
4633 break;
4634 // S8x: Set 4-bit Panning
4635 case 0x80:
4636 if(m_SongFlags[SONG_FIRSTTICK])
4637 {
4638 Panning(chn, param, Pan4bit);
4639 }
4640 break;
4641 // S9x: Sound Control
4642 case 0x90: ExtendedChannelEffect(chn, param); break;
4643 // SAx: Set 64k Offset
4644 case 0xA0: if(m_SongFlags[SONG_FIRSTTICK])
4645 {
4646 chn.nOldHiOffset = static_cast<uint8>(param);
4647 if (!m_playBehaviour[kITHighOffsetNoRetrig] && chn.rowCommand.IsNote())
4648 {
4649 SmpLength pos = param << 16;
4650 if (pos < chn.nLength) chn.position.SetInt(pos);
4651 }
4652 }
4653 break;
4654 // SBx: Pattern Loop
4655 case 0xB0:
4656 if(m_SongFlags[SONG_FIRSTTICK])
4657 PatternLoop(m_PlayState, chn, param & 0x0F);
4658 break;
4659 // SCx: Note Cut
4660 case 0xC0:
4661 if(param == 0)
4662 {
4663 //IT compatibility 22. SC0 == SC1
4664 if(GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT))
4665 param = 1;
4666 // ST3 doesn't cut notes with SC0
4667 else if(GetType() == MOD_TYPE_S3M)
4668 return;
4669 }
4670 // S3M/IT compatibility: Note Cut really cuts notes and does not just mute them (so that following volume commands could restore the sample)
4671 // Test case: scx.it
4672 NoteCut(nChn, param, m_playBehaviour[kITSCxStopsSample] || GetType() == MOD_TYPE_S3M);
4673 break;
4674 // SDx: Note Delay
4675 // SEx: Pattern Delay for x rows
4676 // SFx: S3M: Not used, IT: Set Active Midi Macro
4677 case 0xF0:
4678 if(GetType() != MOD_TYPE_S3M)
4679 {
4680 chn.nActiveMacro = static_cast<uint8>(param);
4681 }
4682 break;
4683 }
4684 }
4685
4686
ExtendedChannelEffect(ModChannel & chn,uint32 param)4687 void CSoundFile::ExtendedChannelEffect(ModChannel &chn, uint32 param)
4688 {
4689 // S9x and X9x commands (S3M/XM/IT only)
4690 if(!m_SongFlags[SONG_FIRSTTICK]) return;
4691 switch(param & 0x0F)
4692 {
4693 // S90: Surround Off
4694 case 0x00: chn.dwFlags.reset(CHN_SURROUND); break;
4695 // S91: Surround On
4696 case 0x01: chn.dwFlags.set(CHN_SURROUND); chn.nPan = 128; break;
4697
4698 ////////////////////////////////////////////////////////////
4699 // ModPlug Extensions
4700 // S98: Reverb Off
4701 case 0x08:
4702 chn.dwFlags.reset(CHN_REVERB);
4703 chn.dwFlags.set(CHN_NOREVERB);
4704 break;
4705 // S99: Reverb On
4706 case 0x09:
4707 chn.dwFlags.reset(CHN_NOREVERB);
4708 chn.dwFlags.set(CHN_REVERB);
4709 break;
4710 // S9A: 2-Channels surround mode
4711 case 0x0A:
4712 m_SongFlags.reset(SONG_SURROUNDPAN);
4713 break;
4714 // S9B: 4-Channels surround mode
4715 case 0x0B:
4716 m_SongFlags.set(SONG_SURROUNDPAN);
4717 break;
4718 // S9C: IT Filter Mode
4719 case 0x0C:
4720 m_SongFlags.reset(SONG_MPTFILTERMODE);
4721 break;
4722 // S9D: MPT Filter Mode
4723 case 0x0D:
4724 m_SongFlags.set(SONG_MPTFILTERMODE);
4725 break;
4726 // S9E: Go forward
4727 case 0x0E:
4728 chn.dwFlags.reset(CHN_PINGPONGFLAG);
4729 break;
4730 // S9F: Go backward (and set playback position to the end if sample just started)
4731 case 0x0F:
4732 if(chn.position.IsZero() && chn.nLength && (chn.rowCommand.IsNote() || !chn.dwFlags[CHN_LOOP]))
4733 {
4734 chn.position.Set(chn.nLength - 1, SamplePosition::fractMax);
4735 }
4736 chn.dwFlags.set(CHN_PINGPONGFLAG);
4737 break;
4738 }
4739 }
4740
4741
InvertLoop(ModChannel & chn)4742 void CSoundFile::InvertLoop(ModChannel &chn)
4743 {
4744 // EFx implementation for MOD files (PT 1.1A and up: Invert Loop)
4745 // This effect trashes samples. Thanks to 8bitbubsy for making this work. :)
4746 if(GetType() != MOD_TYPE_MOD || chn.nEFxSpeed == 0)
4747 return;
4748
4749 ModSample *pModSample = const_cast<ModSample *>(chn.pModSample);
4750 if(pModSample == nullptr || !pModSample->HasSampleData() || !pModSample->uFlags[CHN_LOOP | CHN_SUSTAINLOOP])
4751 return;
4752
4753 chn.nEFxDelay += ModEFxTable[chn.nEFxSpeed & 0x0F];
4754 if(chn.nEFxDelay < 128)
4755 return;
4756 chn.nEFxDelay = 0;
4757
4758 const SmpLength loopStart = pModSample->uFlags[CHN_LOOP] ? pModSample->nLoopStart : pModSample->nSustainStart;
4759 const SmpLength loopEnd = pModSample->uFlags[CHN_LOOP] ? pModSample->nLoopEnd : pModSample->nSustainEnd;
4760
4761 if(++chn.nEFxOffset >= loopEnd - loopStart)
4762 chn.nEFxOffset = 0;
4763
4764 // TRASH IT!!! (Yes, the sample!)
4765 const uint8 bps = pModSample->GetBytesPerSample();
4766 uint8 *begin = mpt::byte_cast<uint8 *>(pModSample->sampleb()) + (loopStart + chn.nEFxOffset) * bps;
4767 for(auto &sample : mpt::as_span(begin, bps))
4768 {
4769 sample = ~sample;
4770 }
4771 pModSample->PrecomputeLoops(*this, false);
4772 }
4773
4774
4775 // Process a MIDI Macro.
4776 // Parameters:
4777 // nChn: Mod channel to apply macro on
4778 // isSmooth: If true, internal macros are interpolated between two rows
4779 // macro: Actual MIDI Macro string
4780 // param: Parameter for parametric macros (Z00 - Z7F)
4781 // plugin: Plugin to send MIDI message to (if not specified but needed, it is autodetected)
ProcessMIDIMacro(CHANNELINDEX nChn,bool isSmooth,const char * macro,uint8 param,PLUGINDEX plugin)4782 void CSoundFile::ProcessMIDIMacro(CHANNELINDEX nChn, bool isSmooth, const char *macro, uint8 param, PLUGINDEX plugin)
4783 {
4784 ModChannel &chn = m_PlayState.Chn[nChn];
4785 const ModInstrument *pIns = GetNumInstruments() ? chn.pModInstrument : nullptr;
4786
4787 uint8 out[MACRO_LENGTH];
4788 uint32 outPos = 0; // output buffer position, which also equals the number of complete bytes
4789 const uint8 lastZxxParam = chn.lastZxxParam; // always interpolate based on original value in case z appears multiple times in macro string
4790 uint8 updateZxxParam = 0xFF; // avoid updating lastZxxParam immediately if macro contains both internal and external MIDI message
4791 bool firstNibble = true;
4792
4793 for(uint32 pos = 0; pos < (MACRO_LENGTH - 1) && macro[pos]; pos++)
4794 {
4795 bool isNibble = false; // did we parse a nibble or a byte value?
4796 uint8 data = 0; // data that has just been parsed
4797
4798 // Parse next macro byte... See Impulse Tracker's MIDI.TXT for detailed information on each possible character.
4799 if(macro[pos] >= '0' && macro[pos] <= '9')
4800 {
4801 isNibble = true;
4802 data = static_cast<uint8>(macro[pos] - '0');
4803 }
4804 else if(macro[pos] >= 'A' && macro[pos] <= 'F')
4805 {
4806 isNibble = true;
4807 data = static_cast<uint8>(macro[pos] - 'A' + 0x0A);
4808 } else if(macro[pos] == 'c')
4809 {
4810 // MIDI channel
4811 isNibble = true;
4812 data = 0xFF;
4813 #ifndef NO_PLUGINS
4814 const PLUGINDEX plug = (plugin != 0) ? plugin : GetBestPlugin(nChn, PrioritiseChannel, EvenIfMuted);
4815 if(plug > 0 && plug <= MAX_MIXPLUGINS)
4816 {
4817 auto midiPlug = dynamic_cast<const IMidiPlugin *>(m_MixPlugins[plug - 1u].pMixPlugin);
4818 if(midiPlug)
4819 data = midiPlug->GetMidiChannel(nChn);
4820 }
4821 #endif // NO_PLUGINS
4822 if(data == 0xFF)
4823 {
4824 // Fallback if no plugin was found
4825 if(pIns)
4826 data = pIns->GetMIDIChannel(*this, nChn);
4827 else
4828 data = 0;
4829 }
4830 } else if(macro[pos] == 'n')
4831 {
4832 // Last triggered note
4833 if(ModCommand::IsNote(chn.nLastNote))
4834 {
4835 data = chn.nLastNote - NOTE_MIN;
4836 }
4837 } else if(macro[pos] == 'v')
4838 {
4839 // Velocity
4840 // This is "almost" how IT does it - apparently, IT seems to lag one row behind on global volume or channel volume changes.
4841 const int swing = (m_playBehaviour[kITSwingBehaviour] || m_playBehaviour[kMPTOldSwingBehaviour]) ? chn.nVolSwing : 0;
4842 const int vol = Util::muldiv((chn.nVolume + swing) * m_PlayState.m_nGlobalVolume, chn.nGlobalVol * chn.nInsVol, 1 << 20);
4843 data = static_cast<uint8>(Clamp(vol / 2, 1, 127));
4844 //data = (unsigned char)std::min((chn.nVolume * chn.nGlobalVol * m_nGlobalVolume) >> (1 + 6 + 8), 127);
4845 } else if(macro[pos] == 'u')
4846 {
4847 // Calculated volume
4848 // Same note as with velocity applies here, but apparently also for instrument / sample volumes?
4849 const int vol = Util::muldiv(chn.nCalcVolume * m_PlayState.m_nGlobalVolume, chn.nGlobalVol * chn.nInsVol, 1 << 26);
4850 data = static_cast<uint8>(Clamp(vol / 2, 1, 127));
4851 //data = (unsigned char)std::min((chn.nCalcVolume * chn.nGlobalVol * m_nGlobalVolume) >> (7 + 6 + 8), 127);
4852 } else if(macro[pos] == 'x')
4853 {
4854 // Pan set
4855 data = static_cast<uint8>(std::min(static_cast<int>(chn.nPan / 2), 127));
4856 } else if(macro[pos] == 'y')
4857 {
4858 // Calculated pan
4859 data = static_cast<uint8>(std::min(static_cast<int>(chn.nRealPan / 2), 127));
4860 } else if(macro[pos] == 'a')
4861 {
4862 // High byte of bank select
4863 if(pIns && pIns->wMidiBank)
4864 {
4865 data = static_cast<uint8>(((pIns->wMidiBank - 1) >> 7) & 0x7F);
4866 }
4867 } else if(macro[pos] == 'b')
4868 {
4869 // Low byte of bank select
4870 if(pIns && pIns->wMidiBank)
4871 {
4872 data = static_cast<uint8>((pIns->wMidiBank - 1) & 0x7F);
4873 }
4874 } else if(macro[pos] == 'o')
4875 {
4876 // Offset (ignoring high offset)
4877 data = static_cast<uint8>((chn.oldOffset >> 8) & 0xFF);
4878 } else if(macro[pos] == 'h')
4879 {
4880 // Host channel number
4881 data = static_cast<uint8>((nChn >= GetNumChannels() ? (chn.nMasterChn - 1) : nChn) & 0x7F);
4882 } else if(macro[pos] == 'm')
4883 {
4884 // Loop direction (judging from the character, it was supposed to be loop type, though)
4885 data = chn.dwFlags[CHN_PINGPONGFLAG] ? 1 : 0;
4886 } else if(macro[pos] == 'p')
4887 {
4888 // Program select
4889 if(pIns && pIns->nMidiProgram)
4890 {
4891 data = static_cast<uint8>((pIns->nMidiProgram - 1) & 0x7F);
4892 }
4893 } else if(macro[pos] == 'z')
4894 {
4895 // Zxx parameter
4896 data = param & 0x7F;
4897 if(isSmooth && chn.lastZxxParam < 0x80
4898 && (outPos < 3 || out[outPos - 3] != 0xF0 || out[outPos - 2] < 0xF0))
4899 {
4900 // Interpolation for external MIDI messages - interpolation for internal messages
4901 // is handled separately to allow for more than 7-bit granularity where it's possible
4902 data = static_cast<uint8>(CalculateSmoothParamChange(lastZxxParam, data));
4903 chn.lastZxxParam = data;
4904 updateZxxParam = 0x80;
4905 } else if(updateZxxParam == 0xFF)
4906 {
4907 updateZxxParam = data;
4908 }
4909 } else if(macro[pos] == 's')
4910 {
4911 // SysEx Checksum (not an original Impulse Tracker macro variable, but added for convenience)
4912 uint32 startPos = outPos;
4913 while(startPos > 0 && out[--startPos] != 0xF0);
4914 if(outPos - startPos < 5 || out[startPos] != 0xF0)
4915 {
4916 continue;
4917 }
4918 for(uint32 p = startPos + 5; p != outPos; p++)
4919 {
4920 data += out[p];
4921 }
4922 data = (~data + 1) & 0x7F;
4923 } else
4924 {
4925 // Unrecognized byte (e.g. space char)
4926 continue;
4927 }
4928
4929 // Append parsed data
4930 if(isNibble) // parsed a nibble (constant or 'c' variable)
4931 {
4932 if(firstNibble)
4933 {
4934 out[outPos] = data;
4935 } else
4936 {
4937 out[outPos] = (out[outPos] << 4) | data;
4938 outPos++;
4939 }
4940 firstNibble = !firstNibble;
4941 } else // parsed a byte (variable)
4942 {
4943 if(!firstNibble) // From MIDI.TXT: '9n' is exactly the same as '09 n' or '9 n' -- so finish current byte first
4944 {
4945 outPos++;
4946 }
4947 out[outPos++] = data;
4948 firstNibble = true;
4949 }
4950 }
4951 if(!firstNibble)
4952 {
4953 // Finish current byte
4954 outPos++;
4955 }
4956 if(updateZxxParam < 0x80)
4957 chn.lastZxxParam = updateZxxParam;
4958
4959 // Macro string has been parsed and translated, now send the message(s)...
4960 uint32 sendPos = 0;
4961 uint8 runningStatus = 0;
4962 while(sendPos < outPos)
4963 {
4964 uint32 sendLen = 0;
4965 if(out[sendPos] == 0xF0)
4966 {
4967 // SysEx start
4968 if((outPos - sendPos >= 4) && (out[sendPos + 1] == 0xF0 || out[sendPos + 1] == 0xF1))
4969 {
4970 // Internal macro (normal (F0F0) or extended (F0F1)), 4 bytes long
4971 sendLen = 4;
4972 } else
4973 {
4974 // SysEx message, find end of message
4975 for(uint32 i = sendPos + 1; i < outPos; i++)
4976 {
4977 if(out[i] == 0xF7)
4978 {
4979 // Found end of SysEx message
4980 sendLen = i - sendPos + 1;
4981 break;
4982 }
4983 }
4984 if(sendLen == 0)
4985 {
4986 // Didn't find end, so "invent" end of SysEx message
4987 out[outPos++] = 0xF7;
4988 sendLen = outPos - sendPos;
4989 }
4990 }
4991 } else if(!(out[sendPos] & 0x80))
4992 {
4993 // Missing status byte? Try inserting running status
4994 if(runningStatus != 0)
4995 {
4996 sendPos--;
4997 out[sendPos] = runningStatus;
4998 } else
4999 {
5000 // No running status to re-use; skip this byte
5001 sendPos++;
5002 }
5003 continue;
5004 } else
5005 {
5006 // Other MIDI messages
5007 sendLen = std::min(static_cast<uint32>(MIDIEvents::GetEventLength(out[sendPos])), outPos - sendPos);
5008 }
5009
5010 if(sendLen == 0)
5011 break;
5012
5013 if(out[sendPos] < 0xF0)
5014 {
5015 runningStatus = out[sendPos];
5016 }
5017 uint32 bytesSent = SendMIDIData(nChn, isSmooth, out + sendPos, sendLen, plugin);
5018 // If there's no error in the macro data (e.g. unrecognized internal MIDI macro), we have sendLen == bytesSent.
5019 if(bytesSent > 0)
5020 sendPos += bytesSent;
5021 else
5022 sendPos += sendLen;
5023 }
5024 }
5025
5026
5027 // Calculate smooth MIDI macro slide parameter for current tick.
CalculateSmoothParamChange(float currentValue,float param) const5028 float CSoundFile::CalculateSmoothParamChange(float currentValue, float param) const
5029 {
5030 MPT_ASSERT(m_PlayState.TicksOnRow() > m_PlayState.m_nTickCount);
5031 const uint32 ticksLeft = m_PlayState.TicksOnRow() - m_PlayState.m_nTickCount;
5032 if(ticksLeft > 1)
5033 {
5034 // Slide param
5035 const float step = (param - currentValue) / (float)ticksLeft;
5036 return (currentValue + step);
5037 } else
5038 {
5039 // On last tick, set exact value.
5040 return param;
5041 }
5042 }
5043
5044
5045 // Process exactly one MIDI message parsed by ProcessMIDIMacro. Returns bytes sent on success, 0 on (parse) failure.
SendMIDIData(CHANNELINDEX nChn,bool isSmooth,const unsigned char * macro,uint32 macroLen,PLUGINDEX plugin)5046 uint32 CSoundFile::SendMIDIData(CHANNELINDEX nChn, bool isSmooth, const unsigned char *macro, uint32 macroLen, PLUGINDEX plugin)
5047 {
5048 if(macroLen < 1)
5049 {
5050 return 0;
5051 }
5052
5053 if(macro[0] == 0xFA || macro[0] == 0xFC || macro[0] == 0xFF)
5054 {
5055 // Start Song, Stop Song, MIDI Reset - both interpreted internally and sent to plugins
5056 for(CHANNELINDEX chn = 0; chn < GetNumChannels(); chn++)
5057 {
5058 m_PlayState.Chn[chn].nCutOff = 0x7F;
5059 m_PlayState.Chn[chn].nResonance = 0x00;
5060 }
5061 }
5062
5063 ModChannel &chn = m_PlayState.Chn[nChn];
5064 if(macro[0] == 0xF0 && (macro[1] == 0xF0 || macro[1] == 0xF1))
5065 {
5066 // Internal device.
5067 if(macroLen < 4)
5068 {
5069 return 0;
5070 }
5071 const bool isExtended = (macro[1] == 0xF1);
5072 const uint8 macroCode = macro[2];
5073 const uint8 param = macro[3];
5074
5075 if(macroCode == 0x00 && !isExtended && param < 0x80)
5076 {
5077 // F0.F0.00.xx: Set CutOff
5078 if(!isSmooth)
5079 {
5080 chn.nCutOff = param;
5081 } else
5082 {
5083 chn.nCutOff = mpt::saturate_round<uint8>(CalculateSmoothParamChange(chn.nCutOff, param));
5084 }
5085 chn.nRestoreCutoffOnNewNote = 0;
5086 int cutoff = SetupChannelFilter(chn, !chn.dwFlags[CHN_FILTER]);
5087
5088 if(cutoff >= 0 && chn.dwFlags[CHN_ADLIB] && m_opl)
5089 {
5090 // Cutoff doubles as modulator intensity for FM instruments
5091 m_opl->Volume(nChn, static_cast<uint8>(cutoff / 4), true);
5092 }
5093
5094 return 4;
5095 } else if(macroCode == 0x01 && !isExtended && param < 0x80)
5096 {
5097 // F0.F0.01.xx: Set Resonance
5098 if(!isSmooth)
5099 {
5100 chn.nResonance = param;
5101 } else
5102 {
5103 chn.nResonance = (uint8)CalculateSmoothParamChange((float)chn.nResonance, (float)param);
5104 }
5105 chn.nRestoreResonanceOnNewNote = 0;
5106 SetupChannelFilter(chn, !chn.dwFlags[CHN_FILTER]);
5107
5108 return 4;
5109 } else if(macroCode == 0x02 && !isExtended)
5110 {
5111 // F0.F0.02.xx: Set filter mode (high nibble determines filter mode)
5112 if(param < 0x20)
5113 {
5114 chn.nFilterMode = static_cast<FilterMode>(param >> 4);
5115 SetupChannelFilter(chn, !chn.dwFlags[CHN_FILTER]);
5116 }
5117
5118 return 4;
5119 #ifndef NO_PLUGINS
5120 } else if(macroCode == 0x03 && !isExtended)
5121 {
5122 // F0.F0.03.xx: Set plug dry/wet
5123 const PLUGINDEX plug = (plugin != 0) ? plugin : GetBestPlugin(nChn, PrioritiseChannel, EvenIfMuted);
5124 if(plug > 0 && plug <= MAX_MIXPLUGINS && param < 0x80)
5125 {
5126 const float newRatio = (0x7F - (param & 0x7F)) / 127.0f;
5127 if(!isSmooth)
5128 {
5129 m_MixPlugins[plug - 1].fDryRatio = newRatio;
5130 } else
5131 {
5132 m_MixPlugins[plug - 1].fDryRatio = CalculateSmoothParamChange(m_MixPlugins[plug - 1].fDryRatio, newRatio);
5133 }
5134 }
5135
5136 return 4;
5137 } else if((macroCode & 0x80) || isExtended)
5138 {
5139 // F0.F0.{80|n}.xx / F0.F1.n.xx: Set VST effect parameter n to xx
5140 const PLUGINDEX plug = (plugin != 0) ? plugin : GetBestPlugin(nChn, PrioritiseChannel, EvenIfMuted);
5141 const uint32 plugParam = isExtended ? (0x80 + macroCode) : (macroCode & 0x7F);
5142 if(plug > 0 && plug <= MAX_MIXPLUGINS)
5143 {
5144 IMixPlugin *pPlugin = m_MixPlugins[plug - 1].pMixPlugin;
5145 if(pPlugin && param < 0x80)
5146 {
5147 const float fParam = param / 127.0f;
5148 if(!isSmooth)
5149 {
5150 pPlugin->SetParameter(plugParam, fParam);
5151 } else
5152 {
5153 pPlugin->SetParameter(plugParam, CalculateSmoothParamChange(pPlugin->GetParameter(plugParam), fParam));
5154 }
5155 }
5156 }
5157
5158 return 4;
5159 #endif // NO_PLUGINS
5160 }
5161
5162 // If we reach this point, the internal macro was invalid.
5163
5164 } else
5165 {
5166 #ifndef NO_PLUGINS
5167 // Not an internal device. Pass on to appropriate plugin.
5168 const CHANNELINDEX plugChannel = (nChn < GetNumChannels()) ? nChn + 1 : chn.nMasterChn;
5169 if(plugChannel > 0 && plugChannel <= GetNumChannels()) // XXX do we need this? I guess it might be relevant for previewing notes in the pattern... Or when using this mechanism for volume/panning!
5170 {
5171 PLUGINDEX plug = 0;
5172 if(!chn.dwFlags[CHN_NOFX])
5173 {
5174 plug = (plugin != 0) ? plugin : GetBestPlugin(nChn, PrioritiseChannel, EvenIfMuted);
5175 }
5176
5177 if(plug > 0 && plug <= MAX_MIXPLUGINS)
5178 {
5179 IMixPlugin *pPlugin = m_MixPlugins[plug - 1].pMixPlugin;
5180 if (pPlugin != nullptr)
5181 {
5182 if(macro[0] == 0xF0)
5183 {
5184 pPlugin->MidiSysexSend(mpt::as_span(mpt::byte_cast<const std::byte*>(macro), macroLen));
5185 } else
5186 {
5187 uint32 len = std::min(static_cast<uint32>(MIDIEvents::GetEventLength(macro[0])), macroLen);
5188 uint32 curData = 0;
5189 memcpy(&curData, macro, len);
5190 pPlugin->MidiSend(curData);
5191 }
5192 }
5193 }
5194 }
5195 #else
5196 MPT_UNREFERENCED_PARAMETER(plugin);
5197 #endif // NO_PLUGINS
5198
5199 return macroLen;
5200 }
5201
5202 return 0;
5203 }
5204
5205
SendMIDINote(CHANNELINDEX chn,uint16 note,uint16 volume)5206 void CSoundFile::SendMIDINote(CHANNELINDEX chn, uint16 note, uint16 volume)
5207 {
5208 #ifndef NO_PLUGINS
5209 auto &channel = m_PlayState.Chn[chn];
5210 const ModInstrument *pIns = channel.pModInstrument;
5211 // instro sends to a midi chan
5212 if (pIns && pIns->HasValidMIDIChannel())
5213 {
5214 PLUGINDEX plug = pIns->nMixPlug;
5215 if(plug > 0 && plug <= MAX_MIXPLUGINS)
5216 {
5217 IMixPlugin *pPlug = m_MixPlugins[plug - 1].pMixPlugin;
5218 if (pPlug != nullptr)
5219 {
5220 pPlug->MidiCommand(*pIns, note, volume, chn);
5221 if(note < NOTE_MIN_SPECIAL)
5222 channel.nLeftVU = channel.nRightVU = 0xFF;
5223 }
5224 }
5225 }
5226 #endif // NO_PLUGINS
5227 }
5228
5229
ProcessSampleOffset(ModChannel & chn,CHANNELINDEX nChn,const PlayState & playState) const5230 void CSoundFile::ProcessSampleOffset(ModChannel& chn, CHANNELINDEX nChn, const PlayState& playState) const
5231 {
5232 const ModCommand &m = chn.rowCommand;
5233 uint32 extendedRows = 0;
5234 SmpLength offset = CalculateXParam(playState.m_nPattern, playState.m_nRow, nChn, &extendedRows), highOffset = 0;
5235 if(!extendedRows)
5236 {
5237 // No X-param (normal behaviour)
5238 const bool isPercentageOffset = (m.volcmd == VOLCMD_OFFSET && m.vol == 0);
5239 offset <<= 8;
5240 if(offset)
5241 chn.oldOffset = offset;
5242 else if(m.volcmd != VOLCMD_OFFSET)
5243 offset = chn.oldOffset;
5244
5245 if(!isPercentageOffset)
5246 highOffset = static_cast<SmpLength>(chn.nOldHiOffset) << 16;
5247 }
5248 if(m.volcmd == VOLCMD_OFFSET)
5249 {
5250 if(m.vol == 0)
5251 offset = Util::muldivr_unsigned(chn.nLength, offset, 256u << (8u * std::max(uint32(1), extendedRows))); // o00 + Oxx = Percentage Offset
5252 else if(m.vol <= std::size(ModSample().cues) && chn.pModSample != nullptr)
5253 offset += chn.pModSample->cues[m.vol - 1]; // Offset relative to cue point
5254 chn.oldOffset = offset;
5255 }
5256 SampleOffset(chn, offset + highOffset);
5257 }
5258
5259
SampleOffset(ModChannel & chn,SmpLength param) const5260 void CSoundFile::SampleOffset(ModChannel &chn, SmpLength param) const
5261 {
5262 // ST3 compatibility: Instrument-less note recalls previous note's offset
5263 // Test case: OxxMemory.s3m
5264 if(m_playBehaviour[kST3OffsetWithoutInstrument])
5265 chn.prevNoteOffset = 0;
5266
5267 chn.prevNoteOffset += param;
5268
5269 if(param >= chn.nLoopEnd && (GetType() & (MOD_TYPE_S3M | MOD_TYPE_MTM)) && chn.dwFlags[CHN_LOOP] && chn.nLoopEnd > 0)
5270 {
5271 // Offset wrap-around
5272 // Note that ST3 only does this in GUS mode. SoundBlaster stops the sample entirely instead.
5273 // Test case: OffsetLoopWraparound.s3m
5274 param = (param - chn.nLoopStart) % (chn.nLoopEnd - chn.nLoopStart) + chn.nLoopStart;
5275 }
5276
5277 if(GetType() == MOD_TYPE_MDL && chn.dwFlags[CHN_16BIT])
5278 {
5279 // Digitrakker really uses byte offsets, not sample offsets. WTF!
5280 param /= 2u;
5281 }
5282
5283 if(chn.rowCommand.IsNote() || m_playBehaviour[kApplyOffsetWithoutNote])
5284 {
5285 // IT compatibility: If this note is not mapped to a sample, ignore it.
5286 // Test case: empty_sample_offset.it
5287 if(chn.pModInstrument != nullptr && chn.rowCommand.IsNote())
5288 {
5289 SAMPLEINDEX smp = chn.pModInstrument->Keyboard[chn.rowCommand.note - NOTE_MIN];
5290 if(smp == 0 || smp > GetNumSamples())
5291 return;
5292 }
5293
5294 if(m_SongFlags[SONG_PT_MODE])
5295 {
5296 // ProTracker compatbility: PT1/2-style funky 9xx offset command
5297 // Test case: ptoffset.mod
5298 chn.position.Set(chn.prevNoteOffset);
5299 chn.prevNoteOffset += param;
5300 } else
5301 {
5302 chn.position.Set(param);
5303 }
5304
5305 if (chn.position.GetUInt() >= chn.nLength || (chn.dwFlags[CHN_LOOP] && chn.position.GetUInt() >= chn.nLoopEnd))
5306 {
5307 // Offset beyond sample size
5308 if(m_playBehaviour[kFT2ST3OffsetOutOfRange] || GetType() == MOD_TYPE_MTM)
5309 {
5310 // FT2 Compatibility: Don't play note if offset is beyond sample length
5311 // ST3 Compatibility: Don't play note if offset is beyond sample length (non-looped samples only)
5312 // Test cases: 3xx-no-old-samp.xm, OffsetPastSampleEnd.s3m
5313 chn.dwFlags.set(CHN_FASTVOLRAMP);
5314 chn.nPeriod = 0;
5315 } else if(!(GetType() & (MOD_TYPE_XM | MOD_TYPE_MT2 | MOD_TYPE_MOD)))
5316 {
5317 // IT Compatibility: Offset
5318 if(m_playBehaviour[kITOffset])
5319 {
5320 if(m_SongFlags[SONG_ITOLDEFFECTS])
5321 chn.position.Set(chn.nLength); // Old FX: Clip to end of sample
5322 else
5323 chn.position.Set(0); // Reset to beginning of sample
5324 } else
5325 {
5326 chn.position.Set(chn.nLoopStart);
5327 if(m_SongFlags[SONG_ITOLDEFFECTS] && chn.nLength > 4)
5328 {
5329 chn.position.Set(chn.nLength - 2);
5330 }
5331 }
5332 } else if(GetType() == MOD_TYPE_MOD && chn.dwFlags[CHN_LOOP])
5333 {
5334 chn.position.Set(chn.nLoopStart);
5335 }
5336 }
5337 } else if ((param < chn.nLength) && (GetType() & (MOD_TYPE_MTM | MOD_TYPE_DMF | MOD_TYPE_MDL | MOD_TYPE_PLM)))
5338 {
5339 // Some trackers can also call offset effects without notes next to them...
5340 chn.position.Set(param);
5341 }
5342 }
5343
5344
ReverseSampleOffset(ModChannel & chn,ModCommand::PARAM param) const5345 void CSoundFile::ReverseSampleOffset(ModChannel &chn, ModCommand::PARAM param) const
5346 {
5347 if(chn.pModSample != nullptr && chn.pModSample->nLength > 0)
5348 {
5349 chn.dwFlags.set(CHN_PINGPONGFLAG);
5350 chn.dwFlags.reset(CHN_LOOP);
5351 chn.nLength = chn.pModSample->nLength; // If there was a loop, extend sample to whole length.
5352 chn.position.Set((chn.nLength - 1) - std::min(SmpLength(param) << 8, chn.nLength - SmpLength(1)), 0);
5353 }
5354 }
5355
5356
RetrigNote(CHANNELINDEX nChn,int param,int offset)5357 void CSoundFile::RetrigNote(CHANNELINDEX nChn, int param, int offset)
5358 {
5359 // Retrig: bit 8 is set if it's the new XM retrig
5360 ModChannel &chn = m_PlayState.Chn[nChn];
5361 int retrigSpeed = param & 0x0F;
5362 uint8 retrigCount = chn.nRetrigCount;
5363 bool doRetrig = false;
5364
5365 // IT compatibility 15. Retrigger
5366 if(m_playBehaviour[kITRetrigger])
5367 {
5368 if(m_PlayState.m_nTickCount == 0 && chn.rowCommand.note)
5369 {
5370 chn.nRetrigCount = param & 0x0F;
5371 } else if(!chn.nRetrigCount || !--chn.nRetrigCount)
5372 {
5373 chn.nRetrigCount = param & 0x0F;
5374 doRetrig = true;
5375 }
5376 } else if(m_playBehaviour[kFT2Retrigger] && (param & 0x100))
5377 {
5378 // Buggy-like-hell FT2 Rxy retrig!
5379 // Test case: retrig.xm
5380 if(m_SongFlags[SONG_FIRSTTICK])
5381 {
5382 // Here are some really stupid things FT2 does on the first tick.
5383 // Test case: RetrigTick0.xm
5384 if(chn.rowCommand.instr > 0 && chn.rowCommand.IsNoteOrEmpty())
5385 retrigCount = 1;
5386 if(chn.rowCommand.volcmd == VOLCMD_VOLUME && chn.rowCommand.vol != 0)
5387 {
5388 // I guess this condition simply checked if the volume byte was != 0 in FT2.
5389 chn.nRetrigCount = retrigCount;
5390 return;
5391 }
5392 }
5393 if(retrigCount >= retrigSpeed)
5394 {
5395 if(!m_SongFlags[SONG_FIRSTTICK] || !chn.rowCommand.IsNote())
5396 {
5397 doRetrig = true;
5398 retrigCount = 0;
5399 }
5400 }
5401 } else
5402 {
5403 // old routines
5404 if (GetType() & (MOD_TYPE_S3M|MOD_TYPE_IT|MOD_TYPE_MPT))
5405 {
5406 if(!retrigSpeed)
5407 retrigSpeed = 1;
5408 if(retrigCount && !(retrigCount % retrigSpeed))
5409 doRetrig = true;
5410 retrigCount++;
5411 } else if(GetType() == MOD_TYPE_MOD)
5412 {
5413 // ProTracker-style retrigger
5414 // Test case: PTRetrigger.mod
5415 const auto tick = m_PlayState.m_nTickCount % m_PlayState.m_nMusicSpeed;
5416 if(!tick && chn.rowCommand.IsNote())
5417 return;
5418 if(retrigSpeed && !(tick % retrigSpeed))
5419 doRetrig = true;
5420 } else if(GetType() == MOD_TYPE_MTM)
5421 {
5422 // In MultiTracker, E9x retriggers the last note at exactly the x-th tick of the row
5423 doRetrig = m_PlayState.m_nTickCount == static_cast<uint32>(param & 0x0F) && retrigSpeed != 0;
5424 } else
5425 {
5426 int realspeed = retrigSpeed;
5427 // FT2 bug: if a retrig (Rxy) occurs together with a volume command, the first retrig interval is increased by one tick
5428 if((param & 0x100) && (chn.rowCommand.volcmd == VOLCMD_VOLUME) && (chn.rowCommand.param & 0xF0))
5429 realspeed++;
5430 if(!m_SongFlags[SONG_FIRSTTICK] || (param & 0x100))
5431 {
5432 if(!realspeed)
5433 realspeed = 1;
5434 if(!(param & 0x100) && m_PlayState.m_nMusicSpeed && !(m_PlayState.m_nTickCount % realspeed))
5435 doRetrig = true;
5436 retrigCount++;
5437 } else if(GetType() & (MOD_TYPE_XM | MOD_TYPE_MT2))
5438 retrigCount = 0;
5439 if (retrigCount >= realspeed)
5440 {
5441 if(m_PlayState.m_nTickCount || ((param & 0x100) && !chn.rowCommand.note))
5442 doRetrig = true;
5443 }
5444 if(m_playBehaviour[kFT2Retrigger] && param == 0)
5445 {
5446 // E90 = Retrig instantly, and only once
5447 doRetrig = (m_PlayState.m_nTickCount == 0);
5448 }
5449 }
5450 }
5451
5452 // IT compatibility: If a sample is shorter than the retrig time (i.e. it stops before the retrig counter hits zero), it is not retriggered.
5453 // Test case: retrig-short.it
5454 if(chn.nLength == 0 && m_playBehaviour[kITShortSampleRetrig] && !chn.HasMIDIOutput())
5455 return;
5456 // ST3 compatibility: No retrig after Note Cut
5457 // Test case: RetrigAfterNoteCut.s3m
5458 if(m_playBehaviour[kST3RetrigAfterNoteCut] && !chn.nFadeOutVol)
5459 return;
5460
5461 if(doRetrig)
5462 {
5463 uint32 dv = (param >> 4) & 0x0F;
5464 int vol = chn.nVolume;
5465 if(dv)
5466 {
5467
5468 // FT2 compatibility: Retrig + volume will not change volume of retrigged notes
5469 if(!m_playBehaviour[kFT2Retrigger] || !(chn.rowCommand.volcmd == VOLCMD_VOLUME))
5470 {
5471 if(retrigTable1[dv])
5472 vol = (vol * retrigTable1[dv]) / 16;
5473 else
5474 vol += ((int)retrigTable2[dv]) * 4;
5475 }
5476 Limit(vol, 0, 256);
5477
5478 chn.dwFlags.set(CHN_FASTVOLRAMP);
5479 }
5480 uint32 note = chn.nNewNote;
5481 int32 oldPeriod = chn.nPeriod;
5482 // ST3 doesn't retrigger OPL notes
5483 if(note >= NOTE_MIN && note <= NOTE_MAX && chn.nLength && (!chn.dwFlags[CHN_ADLIB] || GetType() != MOD_TYPE_S3M || m_playBehaviour[kOPLRealRetrig]))
5484 CheckNNA(nChn, 0, note, true);
5485 bool resetEnv = false;
5486 if(GetType() & (MOD_TYPE_XM | MOD_TYPE_MT2))
5487 {
5488 if(chn.rowCommand.instr && param < 0x100)
5489 {
5490 InstrumentChange(chn, chn.rowCommand.instr, false, false);
5491 resetEnv = true;
5492 }
5493 if(param < 0x100)
5494 resetEnv = true;
5495 }
5496
5497 const bool fading = chn.dwFlags[CHN_NOTEFADE];
5498 const auto oldPrevNoteOffset = chn.prevNoteOffset;
5499 chn.prevNoteOffset = 0; // Retriggered notes should not use previous offset (test case: OxxMemoryWithRetrig.s3m)
5500 // IT compatibility: Really weird combination of envelopes and retrigger (see Storlek's q.it testcase)
5501 // Test case: retrig.it
5502 NoteChange(chn, note, m_playBehaviour[kITRetrigger], resetEnv, false, nChn);
5503 if(!chn.rowCommand.instr)
5504 chn.prevNoteOffset = oldPrevNoteOffset;
5505 // XM compatibility: Prevent NoteChange from resetting the fade flag in case an instrument number + note-off is present.
5506 // Test case: RetrigFade.xm
5507 if(fading && GetType() == MOD_TYPE_XM)
5508 chn.dwFlags.set(CHN_NOTEFADE);
5509 chn.nVolume = vol;
5510 if(m_nInstruments)
5511 {
5512 chn.rowCommand.note = static_cast<ModCommand::NOTE>(note); // No retrig without note...
5513 #ifndef NO_PLUGINS
5514 ProcessMidiOut(nChn); //Send retrig to Midi
5515 #endif // NO_PLUGINS
5516 }
5517 if((GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT)) && chn.rowCommand.note == NOTE_NONE && oldPeriod != 0)
5518 chn.nPeriod = oldPeriod;
5519 if(!(GetType() & (MOD_TYPE_S3M | MOD_TYPE_IT | MOD_TYPE_MPT)))
5520 retrigCount = 0;
5521 // IT compatibility: see previous IT compatibility comment =)
5522 if(m_playBehaviour[kITRetrigger]) chn.position.Set(0);
5523
5524 offset--;
5525 if(chn.pModSample != nullptr && offset >= 0 && offset <= static_cast<int>(std::size(chn.pModSample->cues)))
5526 {
5527 if(offset == 0)
5528 offset = chn.oldOffset;
5529 else
5530 offset = chn.oldOffset = chn.pModSample->cues[offset - 1];
5531 SampleOffset(chn, offset);
5532 }
5533 }
5534
5535 // buggy-like-hell FT2 Rxy retrig!
5536 if(m_playBehaviour[kFT2Retrigger] && (param & 0x100))
5537 retrigCount++;
5538
5539 // Now we can also store the retrig value for IT...
5540 if(!m_playBehaviour[kITRetrigger])
5541 chn.nRetrigCount = retrigCount;
5542 }
5543
5544
5545 // Execute a frequency slide on given channel.
5546 // Positive amounts increase the frequency, negative amounts decrease it.
5547 // The period or frequency that is read and written is in the period variable, chn.nPeriod is not touched.
DoFreqSlide(ModChannel & chn,int32 & period,int32 amount,bool isTonePorta) const5548 void CSoundFile::DoFreqSlide(ModChannel &chn, int32 &period, int32 amount, bool isTonePorta) const
5549 {
5550 if(!period || !amount)
5551 return;
5552 MPT_ASSERT(!chn.HasCustomTuning());
5553
5554 if(GetType() == MOD_TYPE_669)
5555 {
5556 // Like other oldskool trackers, Composer 669 doesn't have linear slides...
5557 // But the slides are done in Hertz rather than periods, meaning that they
5558 // are more effective in the lower notes (rather than the higher notes).
5559 period += amount * 20;
5560 } else if(GetType() == MOD_TYPE_FAR)
5561 {
5562 period += (amount * 36318 / 1024);
5563 } else if(m_SongFlags[SONG_LINEARSLIDES] && GetType() != MOD_TYPE_XM)
5564 {
5565 // IT Linear slides
5566 const auto oldPeriod = period;
5567 uint32 n = std::abs(amount);
5568 LimitMax(n, 255u * 4u);
5569
5570 // Note: IT ignores the lower 2 bits when abs(mount) > 16 (it either uses the fine *or* the regular table, not both)
5571 // This means that vibratos are slightly less accurate in this range than they could be.
5572 // Other code paths will *either* have an amount that's a multiple of 4 *or* it's less than 16.
5573 if(amount > 0)
5574 {
5575 if(n < 16)
5576 period = Util::muldivr(period, GetFineLinearSlideUpTable(this, n), 65536);
5577 else
5578 period = Util::muldivr(period, GetLinearSlideUpTable(this, n / 4u), 65536);
5579 } else
5580 {
5581 if(n < 16)
5582 period = Util::muldivr(period, GetFineLinearSlideDownTable(this, n), 65536);
5583 else
5584 period = Util::muldivr(period, GetLinearSlideDownTable(this, n / 4u), 65536);
5585 }
5586
5587 if(period == oldPeriod)
5588 {
5589 const bool incPeriod = m_playBehaviour[kPeriodsAreHertz] == (amount > 0);
5590 if(incPeriod && period < Util::MaxValueOfType(period))
5591 period++;
5592 else if(!incPeriod && period > 1)
5593 period--;
5594 }
5595 } else if(!m_SongFlags[SONG_LINEARSLIDES] && m_playBehaviour[kPeriodsAreHertz])
5596 {
5597 // IT Amiga slides
5598 if(amount < 0)
5599 {
5600 // Go down
5601 period = mpt::saturate_cast<int32>(Util::mul32to64_unsigned(1712 * 8363, period) / (Util::mul32to64_unsigned(period, -amount) + 1712 * 8363));
5602 } else if(amount > 0)
5603 {
5604 // Go up
5605 const auto periodDiv = 1712 * 8363 - Util::mul32to64(period, amount);
5606 if(periodDiv <= 0)
5607 {
5608 if(isTonePorta)
5609 {
5610 period = int32_max;
5611 return;
5612 } else
5613 {
5614 period = 0;
5615 chn.nFadeOutVol = 0;
5616 chn.dwFlags.set(CHN_NOTEFADE | CHN_FASTVOLRAMP);
5617 }
5618 return;
5619 }
5620 period = mpt::saturate_cast<int32>(Util::mul32to64_unsigned(1712 * 8363, period) / periodDiv);
5621 }
5622 } else
5623 {
5624 period -= amount;
5625 }
5626 if(period < 1)
5627 {
5628 period = 1;
5629 if(GetType() == MOD_TYPE_S3M && !isTonePorta)
5630 {
5631 chn.nFadeOutVol = 0;
5632 chn.dwFlags.set(CHN_NOTEFADE | CHN_FASTVOLRAMP);
5633 }
5634 }
5635 }
5636
5637
NoteCut(CHANNELINDEX nChn,uint32 nTick,bool cutSample)5638 void CSoundFile::NoteCut(CHANNELINDEX nChn, uint32 nTick, bool cutSample)
5639 {
5640 if (m_PlayState.m_nTickCount == nTick)
5641 {
5642 ModChannel &chn = m_PlayState.Chn[nChn];
5643 if(cutSample)
5644 {
5645 chn.increment.Set(0);
5646 chn.nFadeOutVol = 0;
5647 chn.dwFlags.set(CHN_NOTEFADE);
5648 } else
5649 {
5650 chn.nVolume = 0;
5651 }
5652 chn.dwFlags.set(CHN_FASTVOLRAMP);
5653
5654 // instro sends to a midi chan
5655 SendMIDINote(nChn, /*chn.nNote+*/NOTE_MAX_SPECIAL, 0);
5656
5657 if(chn.dwFlags[CHN_ADLIB] && m_opl)
5658 {
5659 m_opl->NoteCut(nChn, false);
5660 }
5661 }
5662 }
5663
5664
KeyOff(ModChannel & chn) const5665 void CSoundFile::KeyOff(ModChannel &chn) const
5666 {
5667 const bool keyIsOn = !chn.dwFlags[CHN_KEYOFF];
5668 chn.dwFlags.set(CHN_KEYOFF);
5669 if(chn.pModInstrument != nullptr && !chn.VolEnv.flags[ENV_ENABLED])
5670 {
5671 chn.dwFlags.set(CHN_NOTEFADE);
5672 }
5673 if (!chn.nLength) return;
5674 if (chn.dwFlags[CHN_SUSTAINLOOP] && chn.pModSample && keyIsOn)
5675 {
5676 const ModSample *pSmp = chn.pModSample;
5677 if(pSmp->uFlags[CHN_LOOP])
5678 {
5679 if (pSmp->uFlags[CHN_PINGPONGLOOP])
5680 chn.dwFlags.set(CHN_PINGPONGLOOP);
5681 else
5682 chn.dwFlags.reset(CHN_PINGPONGLOOP | CHN_PINGPONGFLAG);
5683 chn.dwFlags.set(CHN_LOOP);
5684 chn.nLength = pSmp->nLength;
5685 chn.nLoopStart = pSmp->nLoopStart;
5686 chn.nLoopEnd = pSmp->nLoopEnd;
5687 if (chn.nLength > chn.nLoopEnd) chn.nLength = chn.nLoopEnd;
5688 if(chn.position.GetUInt() > chn.nLength)
5689 {
5690 // Test case: SusAfterLoop.it
5691 chn.position.Set(chn.nLoopStart + ((chn.position.GetInt() - chn.nLoopStart) % (chn.nLoopEnd - chn.nLoopStart)));
5692 }
5693 } else
5694 {
5695 chn.dwFlags.reset(CHN_LOOP | CHN_PINGPONGLOOP | CHN_PINGPONGFLAG);
5696 chn.nLength = pSmp->nLength;
5697 }
5698 }
5699
5700 if (chn.pModInstrument)
5701 {
5702 const ModInstrument *pIns = chn.pModInstrument;
5703 if((pIns->VolEnv.dwFlags[ENV_LOOP] || (GetType() & (MOD_TYPE_XM | MOD_TYPE_MT2 | MOD_TYPE_MDL))) && pIns->nFadeOut != 0)
5704 {
5705 chn.dwFlags.set(CHN_NOTEFADE);
5706 }
5707
5708 if (pIns->VolEnv.nReleaseNode != ENV_RELEASE_NODE_UNSET && chn.VolEnv.nEnvValueAtReleaseJump == NOT_YET_RELEASED)
5709 {
5710 chn.VolEnv.nEnvValueAtReleaseJump = mpt::saturate_cast<int16>(pIns->VolEnv.GetValueFromPosition(chn.VolEnv.nEnvPosition, 256));
5711 chn.VolEnv.nEnvPosition = pIns->VolEnv[pIns->VolEnv.nReleaseNode].tick;
5712 }
5713 }
5714 }
5715
5716
5717 //////////////////////////////////////////////////////////
5718 // CSoundFile: Global Effects
5719
5720
SetSpeed(PlayState & playState,uint32 param) const5721 void CSoundFile::SetSpeed(PlayState &playState, uint32 param) const
5722 {
5723 #ifdef MODPLUG_TRACKER
5724 // FT2 appears to be decrementing the tick count before checking for zero,
5725 // so it effectively counts down 65536 ticks with speed = 0 (song speed is a 16-bit variable in FT2)
5726 if(GetType() == MOD_TYPE_XM && !param)
5727 {
5728 playState.m_nMusicSpeed = uint16_max;
5729 }
5730 #endif // MODPLUG_TRACKER
5731 if(param > 0) playState.m_nMusicSpeed = param;
5732 if(GetType() == MOD_TYPE_STM && param > 0)
5733 {
5734 playState.m_nMusicSpeed = std::max(param >> 4, uint32(1));
5735 playState.m_nMusicTempo = ConvertST2Tempo(static_cast<uint8>(param));
5736 }
5737 }
5738
5739
5740 // Convert a ST2 tempo byte to classic tempo and speed combination
ConvertST2Tempo(uint8 tempo)5741 TEMPO CSoundFile::ConvertST2Tempo(uint8 tempo)
5742 {
5743 static constexpr uint8 ST2TempoFactor[] = { 140, 50, 25, 15, 10, 7, 6, 4, 3, 3, 2, 2, 2, 2, 1, 1 };
5744 static constexpr uint32 st2MixingRate = 23863; // Highest possible setting in ST2
5745
5746 // This underflows at tempo 06...0F, and the resulting tick lengths depend on the mixing rate.
5747 // Note: ST2.3 uses the constant 50 below, earlier versions use 49 but they also play samples at a different speed.
5748 int32 samplesPerTick = st2MixingRate / (50 - ((ST2TempoFactor[tempo >> 4u] * (tempo & 0x0F)) >> 4u));
5749 if(samplesPerTick <= 0)
5750 samplesPerTick += 65536;
5751 return TEMPO().SetRaw(Util::muldivrfloor(st2MixingRate, 5 * TEMPO::fractFact, samplesPerTick * 2));
5752 }
5753
5754
SetTempo(TEMPO param,bool setFromUI)5755 void CSoundFile::SetTempo(TEMPO param, bool setFromUI)
5756 {
5757 const CModSpecifications &specs = GetModSpecifications();
5758
5759 // Anything lower than the minimum tempo is considered to be a tempo slide
5760 const TEMPO minTempo = (GetType() & (MOD_TYPE_MDL | MOD_TYPE_MED)) ? TEMPO(1, 0) : TEMPO(32, 0);
5761
5762 if(setFromUI)
5763 {
5764 // Set tempo from UI - ignore slide commands and such.
5765 m_PlayState.m_nMusicTempo = Clamp(param, specs.GetTempoMin(), specs.GetTempoMax());
5766 } else if(param >= minTempo && m_SongFlags[SONG_FIRSTTICK] == !m_playBehaviour[kMODTempoOnSecondTick])
5767 {
5768 // ProTracker sets the tempo after the first tick.
5769 // Note: The case of one tick per row is handled in ProcessRow() instead.
5770 // Test case: TempoChange.mod
5771 m_PlayState.m_nMusicTempo = std::min(param, specs.GetTempoMax());
5772 } else if(param < minTempo && !m_SongFlags[SONG_FIRSTTICK])
5773 {
5774 // Tempo Slide
5775 TEMPO tempDiff(param.GetInt() & 0x0F, 0);
5776 if((param.GetInt() & 0xF0) == 0x10)
5777 m_PlayState.m_nMusicTempo += tempDiff;
5778 else
5779 m_PlayState.m_nMusicTempo -= tempDiff;
5780
5781 TEMPO tempoMin = specs.GetTempoMin(), tempoMax = specs.GetTempoMax();
5782 if(m_playBehaviour[kTempoClamp]) // clamp tempo correctly in compatible mode
5783 {
5784 tempoMax.Set(255);
5785 }
5786 Limit(m_PlayState.m_nMusicTempo, tempoMin, tempoMax);
5787 }
5788 }
5789
5790
PatternLoop(PlayState & state,ModChannel & chn,ModCommand::PARAM param) const5791 void CSoundFile::PatternLoop(PlayState &state, ModChannel &chn, ModCommand::PARAM param) const
5792 {
5793 if(m_playBehaviour[kST3NoMutedChannels] && chn.dwFlags[CHN_MUTE | CHN_SYNCMUTE])
5794 return; // not even effects are processed on muted S3M channels
5795
5796 if(!param)
5797 {
5798 // Loop Start
5799 chn.nPatternLoop = state.m_nRow;
5800 return;
5801 }
5802
5803 // Loop Repeat
5804 if(chn.nPatternLoopCount)
5805 {
5806 // There's a loop left
5807 chn.nPatternLoopCount--;
5808 if(!chn.nPatternLoopCount)
5809 {
5810 // IT compatibility 10. Pattern loops (+ same fix for S3M files)
5811 // When finishing a pattern loop, the next loop without a dedicated SB0 starts on the first row after the previous loop.
5812 if(m_playBehaviour[kITPatternLoopTargetReset] || (GetType() == MOD_TYPE_S3M))
5813 chn.nPatternLoop = state.m_nRow + 1;
5814
5815 return;
5816 }
5817 } else
5818 {
5819 // First time we get into the loop => Set loop count.
5820
5821 // IT compatibility 10. Pattern loops (+ same fix for XM / MOD / S3M files)
5822 if(!m_playBehaviour[kITFT2PatternLoop] && !(GetType() & (MOD_TYPE_MOD | MOD_TYPE_S3M)))
5823 {
5824 auto p = std::cbegin(state.Chn);
5825 for(CHANNELINDEX i = 0; i < GetNumChannels(); i++, p++)
5826 {
5827 // Loop on other channel
5828 if(p != &chn && p->nPatternLoopCount)
5829 return;
5830 }
5831 }
5832 chn.nPatternLoopCount = param;
5833 }
5834 state.m_nextPatStartRow = chn.nPatternLoop; // Nasty FT2 E60 bug emulation!
5835
5836 const auto loopTarget = chn.nPatternLoop;
5837 if(loopTarget != ROWINDEX_INVALID)
5838 {
5839 // FT2 compatibility: E6x overwrites jump targets of Dxx effects that are located left of the E6x effect.
5840 // Test cases: PatLoop-Jumps.xm, PatLoop-Various.xm
5841 if(state.m_breakRow != ROWINDEX_INVALID && m_playBehaviour[kFT2PatternLoopWithJumps])
5842 state.m_breakRow = loopTarget;
5843
5844 state.m_patLoopRow = loopTarget;
5845 // IT compatibility: SBx is prioritized over Position Jump (Bxx) effects that are located left of the SBx effect.
5846 // Test case: sbx-priority.it, LoopBreak.it
5847 if(m_playBehaviour[kITPatternLoopWithJumps])
5848 state.m_posJump = ORDERINDEX_INVALID;
5849 }
5850
5851 if(GetType() == MOD_TYPE_S3M)
5852 {
5853 // ST3 doesn't have per-channel pattern loop memory, so spam all changes to other channels as well.
5854 for(CHANNELINDEX i = 0; i < GetNumChannels(); i++)
5855 {
5856 state.Chn[i].nPatternLoop = chn.nPatternLoop;
5857 state.Chn[i].nPatternLoopCount = chn.nPatternLoopCount;
5858 }
5859 }
5860
5861 }
5862
5863
GlobalVolSlide(ModCommand::PARAM param,uint8 & nOldGlobalVolSlide)5864 void CSoundFile::GlobalVolSlide(ModCommand::PARAM param, uint8 &nOldGlobalVolSlide)
5865 {
5866 int32 nGlbSlide = 0;
5867 if (param) nOldGlobalVolSlide = param; else param = nOldGlobalVolSlide;
5868
5869 if((GetType() & (MOD_TYPE_XM | MOD_TYPE_MT2)))
5870 {
5871 // XM nibble priority
5872 if((param & 0xF0) != 0)
5873 {
5874 param &= 0xF0;
5875 } else
5876 {
5877 param &= 0x0F;
5878 }
5879 }
5880
5881 if (((param & 0x0F) == 0x0F) && (param & 0xF0))
5882 {
5883 if(m_SongFlags[SONG_FIRSTTICK]) nGlbSlide = (param >> 4) * 2;
5884 } else
5885 if (((param & 0xF0) == 0xF0) && (param & 0x0F))
5886 {
5887 if(m_SongFlags[SONG_FIRSTTICK]) nGlbSlide = - (int)((param & 0x0F) * 2);
5888 } else
5889 {
5890 if(!m_SongFlags[SONG_FIRSTTICK])
5891 {
5892 if (param & 0xF0)
5893 {
5894 // IT compatibility: Ignore slide commands with both nibbles set.
5895 if(!(GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT | MOD_TYPE_IMF | MOD_TYPE_J2B | MOD_TYPE_MID | MOD_TYPE_AMS | MOD_TYPE_DBM)) || (param & 0x0F) == 0)
5896 nGlbSlide = (int)((param & 0xF0) >> 4) * 2;
5897 } else
5898 {
5899 nGlbSlide = -(int)((param & 0x0F) * 2);
5900 }
5901 }
5902 }
5903 if (nGlbSlide)
5904 {
5905 if(!(GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT | MOD_TYPE_IMF | MOD_TYPE_J2B | MOD_TYPE_MID | MOD_TYPE_AMS | MOD_TYPE_DBM))) nGlbSlide *= 2;
5906 nGlbSlide += m_PlayState.m_nGlobalVolume;
5907 Limit(nGlbSlide, 0, 256);
5908 m_PlayState.m_nGlobalVolume = nGlbSlide;
5909 }
5910 }
5911
5912
5913 //////////////////////////////////////////////////////
5914 // Note/Period/Frequency functions
5915
5916 // Find lowest note which has same or lower period as a given period (i.e. the note has the same or higher frequency)
GetNoteFromPeriod(uint32 period,int32 nFineTune,uint32 nC5Speed) const5917 uint32 CSoundFile::GetNoteFromPeriod(uint32 period, int32 nFineTune, uint32 nC5Speed) const
5918 {
5919 if(!period) return 0;
5920 if(m_playBehaviour[kFT2Periods])
5921 {
5922 // FT2's "RelocateTon" function actually rounds up and down, while GetNoteFromPeriod normally just truncates.
5923 nFineTune += 64;
5924 }
5925 // This essentially implements std::lower_bound, with the difference that we don't need an iterable container.
5926 uint32 minNote = NOTE_MIN, maxNote = NOTE_MAX, count = maxNote - minNote + 1;
5927 const bool periodIsFreq = PeriodsAreFrequencies();
5928 while(count > 0)
5929 {
5930 const uint32 step = count / 2, midNote = minNote + step;
5931 uint32 n = GetPeriodFromNote(midNote, nFineTune, nC5Speed);
5932 if((n > period && !periodIsFreq) || (n < period && periodIsFreq) || !n)
5933 {
5934 minNote = midNote + 1;
5935 count -= step + 1;
5936 } else
5937 {
5938 count = step;
5939 }
5940 }
5941 return minNote;
5942 }
5943
5944
GetPeriodFromNote(uint32 note,int32 nFineTune,uint32 nC5Speed) const5945 uint32 CSoundFile::GetPeriodFromNote(uint32 note, int32 nFineTune, uint32 nC5Speed) const
5946 {
5947 if (note == NOTE_NONE || (note >= NOTE_MIN_SPECIAL)) return 0;
5948 note -= NOTE_MIN;
5949 if(!UseFinetuneAndTranspose())
5950 {
5951 if(GetType() & (MOD_TYPE_MDL | MOD_TYPE_DTM))
5952 {
5953 // MDL uses non-linear slides, but their effectiveness does not depend on the middle-C frequency.
5954 return (FreqS3MTable[note % 12u] << 4) >> (note / 12);
5955 }
5956 if(!nC5Speed)
5957 nC5Speed = 8363;
5958 if(PeriodsAreFrequencies())
5959 {
5960 // Compute everything in Hertz rather than periods.
5961 uint32 freq = Util::muldiv_unsigned(nC5Speed, LinearSlideUpTable[(note % 12u) * 16u] << (note / 12u), 65536 << 5);
5962 LimitMax(freq, static_cast<uint32>(int32_max));
5963 return freq;
5964 } else if(m_SongFlags[SONG_LINEARSLIDES])
5965 {
5966 return (FreqS3MTable[note % 12u] << 5) >> (note / 12);
5967 } else
5968 {
5969 LimitMax(nC5Speed, uint32_max >> (note / 12u));
5970 //(a*b)/c
5971 return Util::muldiv_unsigned(8363, (FreqS3MTable[note % 12u] << 5), nC5Speed << (note / 12u));
5972 //8363 * freq[note%12] / nC5Speed * 2^(5-note/12)
5973 }
5974 } else if(GetType() & (MOD_TYPE_XM | MOD_TYPE_MTM))
5975 {
5976 if (note < 12) note = 12;
5977 note -= 12;
5978
5979 if(GetType() == MOD_TYPE_MTM)
5980 {
5981 nFineTune *= 16;
5982 } else if(m_playBehaviour[kFT2FinetunePrecision])
5983 {
5984 // FT2 Compatibility: The lower three bits of the finetune are truncated.
5985 // Test case: Finetune-Precision.xm
5986 nFineTune &= ~7;
5987 }
5988
5989 if(m_SongFlags[SONG_LINEARSLIDES])
5990 {
5991 int l = ((NOTE_MAX - note) << 6) - (nFineTune / 2);
5992 if (l < 1) l = 1;
5993 return static_cast<uint32>(l);
5994 } else
5995 {
5996 int finetune = nFineTune;
5997 uint32 rnote = (note % 12) << 3;
5998 uint32 roct = note / 12;
5999 int rfine = finetune / 16;
6000 int i = rnote + rfine + 8;
6001 Limit(i , 0, 103);
6002 uint32 per1 = XMPeriodTable[i];
6003 if(finetune < 0)
6004 {
6005 rfine--;
6006 finetune = -finetune;
6007 } else rfine++;
6008 i = rnote+rfine+8;
6009 if (i < 0) i = 0;
6010 if (i >= 104) i = 103;
6011 uint32 per2 = XMPeriodTable[i];
6012 rfine = finetune & 0x0F;
6013 per1 *= 16-rfine;
6014 per2 *= rfine;
6015 return ((per1 + per2) << 1) >> roct;
6016 }
6017 } else
6018 {
6019 nFineTune = XM2MODFineTune(nFineTune);
6020 if ((nFineTune) || (note < 24) || (note >= 24 + std::size(ProTrackerPeriodTable)))
6021 return (ProTrackerTunedPeriods[nFineTune * 12u + note % 12u] << 5) >> (note / 12u);
6022 else
6023 return (ProTrackerPeriodTable[note - 24] << 2);
6024 }
6025 }
6026
6027
6028 // Converts period value to sample frequency. Return value is fixed point, with FREQ_FRACBITS fractional bits.
GetFreqFromPeriod(uint32 period,uint32 c5speed,int32 nPeriodFrac) const6029 uint32 CSoundFile::GetFreqFromPeriod(uint32 period, uint32 c5speed, int32 nPeriodFrac) const
6030 {
6031 if (!period) return 0;
6032 if (GetType() & (MOD_TYPE_XM | MOD_TYPE_MTM))
6033 {
6034 if(m_playBehaviour[kFT2Periods])
6035 {
6036 // FT2 compatibility: Period is a 16-bit value in FT2, and it overflows happily.
6037 // Test case: FreqWraparound.xm
6038 period &= 0xFFFF;
6039 }
6040 if(m_SongFlags[SONG_LINEARSLIDES])
6041 {
6042 uint32 octave;
6043 if(m_playBehaviour[kFT2Periods])
6044 {
6045 // Under normal circumstances, this calculation returns the same values as the non-compatible one.
6046 // However, once the 12 octaves are exceeded (through portamento slides), the octave shift goes
6047 // crazy in FT2, meaning that the frequency wraps around randomly...
6048 // The entries in FT2's conversion table are four times as big, hence we have to do an additional shift by two bits.
6049 // Test case: FreqWraparound.xm
6050 // 12 octaves * (12 * 64) LUT entries = 9216, add 767 for rounding
6051 uint32 div = ((9216u + 767u - period) / 768);
6052 octave = ((14 - div) & 0x1F);
6053 } else
6054 {
6055 octave = (period / 768) + 2;
6056 }
6057 return (XMLinearTable[period % 768] << (FREQ_FRACBITS + 2)) >> octave;
6058 } else
6059 {
6060 if(!period) period = 1;
6061 return ((8363 * 1712L) << FREQ_FRACBITS) / period;
6062 }
6063 } else if(UseFinetuneAndTranspose())
6064 {
6065 return ((3546895L * 4) << FREQ_FRACBITS) / period;
6066 } else if(GetType() == MOD_TYPE_669)
6067 {
6068 // We only really use c5speed for the finetune pattern command. All samples in 669 files have the same middle-C speed (imported as 8363 Hz).
6069 return (period + c5speed - 8363) << FREQ_FRACBITS;
6070 } else if(GetType() & (MOD_TYPE_MDL | MOD_TYPE_DTM))
6071 {
6072 LimitMax(period, Util::MaxValueOfType(period) >> 8);
6073 if (!c5speed) c5speed = 8363;
6074 return Util::muldiv_unsigned(c5speed, (1712L << 7) << FREQ_FRACBITS, (period << 8) + nPeriodFrac);
6075 } else
6076 {
6077 LimitMax(period, Util::MaxValueOfType(period) >> 8);
6078 if(PeriodsAreFrequencies())
6079 {
6080 // Input is already a frequency in Hertz, not a period.
6081 static_assert(FREQ_FRACBITS <= 8, "Check this shift operator");
6082 return uint32(((uint64(period) << 8) + nPeriodFrac) >> (8 - FREQ_FRACBITS));
6083 } else if(m_SongFlags[SONG_LINEARSLIDES])
6084 {
6085 if(!c5speed)
6086 c5speed = 8363;
6087 return Util::muldiv_unsigned(c5speed, (1712L << 8) << FREQ_FRACBITS, (period << 8) + nPeriodFrac);
6088 } else
6089 {
6090 return Util::muldiv_unsigned(8363, (1712L << 8) << FREQ_FRACBITS, (period << 8) + nPeriodFrac);
6091 }
6092 }
6093 }
6094
6095
GetBestPlugin(CHANNELINDEX nChn,PluginPriority priority,PluginMutePriority respectMutes) const6096 PLUGINDEX CSoundFile::GetBestPlugin(CHANNELINDEX nChn, PluginPriority priority, PluginMutePriority respectMutes) const
6097 {
6098 if (nChn >= MAX_CHANNELS) //Check valid channel number
6099 {
6100 return 0;
6101 }
6102
6103 //Define search source order
6104 PLUGINDEX plugin = 0;
6105 switch (priority)
6106 {
6107 case ChannelOnly:
6108 plugin = GetChannelPlugin(nChn, respectMutes);
6109 break;
6110 case InstrumentOnly:
6111 plugin = GetActiveInstrumentPlugin(nChn, respectMutes);
6112 break;
6113 case PrioritiseInstrument:
6114 plugin = GetActiveInstrumentPlugin(nChn, respectMutes);
6115 if(!plugin || plugin > MAX_MIXPLUGINS)
6116 {
6117 plugin = GetChannelPlugin(nChn, respectMutes);
6118 }
6119 break;
6120 case PrioritiseChannel:
6121 plugin = GetChannelPlugin(nChn, respectMutes);
6122 if(!plugin || plugin > MAX_MIXPLUGINS)
6123 {
6124 plugin = GetActiveInstrumentPlugin(nChn, respectMutes);
6125 }
6126 break;
6127 }
6128
6129 return plugin; // 0 Means no plugin found.
6130 }
6131
6132
GetChannelPlugin(CHANNELINDEX nChn,PluginMutePriority respectMutes) const6133 PLUGINDEX CSoundFile::GetChannelPlugin(CHANNELINDEX nChn, PluginMutePriority respectMutes) const
6134 {
6135 const ModChannel &channel = m_PlayState.Chn[nChn];
6136
6137 PLUGINDEX plugin;
6138 if((respectMutes == RespectMutes && channel.dwFlags[CHN_MUTE | CHN_SYNCMUTE]) || channel.dwFlags[CHN_NOFX])
6139 {
6140 plugin = 0;
6141 } else
6142 {
6143 // If it looks like this is an NNA channel, we need to find the master channel.
6144 // This ensures we pick up the right ChnSettings.
6145 // NB: nMasterChn == 0 means no master channel, so we need to -1 to get correct index.
6146 if (nChn >= m_nChannels && channel.nMasterChn > 0)
6147 {
6148 nChn = channel.nMasterChn - 1;
6149 }
6150
6151 if(nChn < MAX_BASECHANNELS)
6152 {
6153 plugin = ChnSettings[nChn].nMixPlugin;
6154 } else
6155 {
6156 plugin = 0;
6157 }
6158 }
6159 return plugin;
6160 }
6161
6162
GetActiveInstrumentPlugin(CHANNELINDEX nChn,PluginMutePriority respectMutes) const6163 PLUGINDEX CSoundFile::GetActiveInstrumentPlugin(CHANNELINDEX nChn, PluginMutePriority respectMutes) const
6164 {
6165 // Unlike channel settings, pModInstrument is copied from the original chan to the NNA chan,
6166 // so we don't need to worry about finding the master chan.
6167
6168 PLUGINDEX plug = 0;
6169 if(m_PlayState.Chn[nChn].pModInstrument != nullptr)
6170 {
6171 if(respectMutes == RespectMutes && m_PlayState.Chn[nChn].pModSample && m_PlayState.Chn[nChn].pModSample->uFlags[CHN_MUTE])
6172 {
6173 plug = 0;
6174 } else
6175 {
6176 plug = m_PlayState.Chn[nChn].pModInstrument->nMixPlug;
6177 }
6178 }
6179 return plug;
6180 }
6181
6182
6183 // Retrieve the plugin that is associated with the channel's current instrument.
6184 // No plugin is returned if the channel is muted or if the instrument doesn't have a MIDI channel set up,
6185 // As this is meant to be used with instrument plugins.
GetChannelInstrumentPlugin(CHANNELINDEX chn) const6186 IMixPlugin *CSoundFile::GetChannelInstrumentPlugin(CHANNELINDEX chn) const
6187 {
6188 #ifndef NO_PLUGINS
6189 if(m_PlayState.Chn[chn].dwFlags[CHN_MUTE | CHN_SYNCMUTE])
6190 {
6191 // Don't process portamento on muted channels. Note that this might have a side-effect
6192 // on other channels which trigger notes on the same MIDI channel of the same plugin,
6193 // as those won't be pitch-bent anymore.
6194 return nullptr;
6195 }
6196
6197 if(m_PlayState.Chn[chn].HasMIDIOutput())
6198 {
6199 const ModInstrument *pIns = m_PlayState.Chn[chn].pModInstrument;
6200 // Instrument sends to a MIDI channel
6201 if(pIns->nMixPlug != 0 && pIns->nMixPlug <= MAX_MIXPLUGINS)
6202 {
6203 return m_MixPlugins[pIns->nMixPlug - 1].pMixPlugin;
6204 }
6205 }
6206 #else
6207 MPT_UNREFERENCED_PARAMETER(chn);
6208 #endif // NO_PLUGINS
6209 return nullptr;
6210 }
6211
6212
6213 #ifdef MODPLUG_TRACKER
HandlePatternTransitionEvents()6214 void CSoundFile::HandlePatternTransitionEvents()
6215 {
6216 // MPT sequence override
6217 if(m_PlayState.m_nSeqOverride != ORDERINDEX_INVALID && m_PlayState.m_nSeqOverride < Order().size())
6218 {
6219 if(m_SongFlags[SONG_PATTERNLOOP])
6220 {
6221 m_PlayState.m_nPattern = Order()[m_PlayState.m_nSeqOverride];
6222 }
6223 m_PlayState.m_nCurrentOrder = m_PlayState.m_nSeqOverride;
6224 m_PlayState.m_nSeqOverride = ORDERINDEX_INVALID;
6225 }
6226
6227 // Channel mutes
6228 for (CHANNELINDEX chan = 0; chan < GetNumChannels(); chan++)
6229 {
6230 if (m_bChannelMuteTogglePending[chan])
6231 {
6232 if(GetpModDoc())
6233 {
6234 GetpModDoc()->MuteChannel(chan, !GetpModDoc()->IsChannelMuted(chan));
6235 }
6236 m_bChannelMuteTogglePending[chan] = false;
6237 }
6238 }
6239 }
6240 #endif // MODPLUG_TRACKER
6241
6242
6243 // Update time signatures (global or pattern-specific). Don't forget to call this when changing the RPB/RPM settings anywhere!
UpdateTimeSignature()6244 void CSoundFile::UpdateTimeSignature()
6245 {
6246 if(!Patterns.IsValidIndex(m_PlayState.m_nPattern) || !Patterns[m_PlayState.m_nPattern].GetOverrideSignature())
6247 {
6248 m_PlayState.m_nCurrentRowsPerBeat = m_nDefaultRowsPerBeat;
6249 m_PlayState.m_nCurrentRowsPerMeasure = m_nDefaultRowsPerMeasure;
6250 } else
6251 {
6252 m_PlayState.m_nCurrentRowsPerBeat = Patterns[m_PlayState.m_nPattern].GetRowsPerBeat();
6253 m_PlayState.m_nCurrentRowsPerMeasure = Patterns[m_PlayState.m_nPattern].GetRowsPerMeasure();
6254 }
6255 }
6256
6257
PortamentoMPT(ModChannel & chn,int param)6258 void CSoundFile::PortamentoMPT(ModChannel &chn, int param)
6259 {
6260 //Behavior: Modifies portamento by param-steps on every tick.
6261 //Note that step meaning depends on tuning.
6262
6263 chn.m_PortamentoFineSteps += param;
6264 chn.m_CalculateFreq = true;
6265 }
6266
6267
PortamentoFineMPT(ModChannel & chn,int param)6268 void CSoundFile::PortamentoFineMPT(ModChannel &chn, int param)
6269 {
6270 //Behavior: Divides portamento change between ticks/row. For example
6271 //if Ticks/row == 6, and param == +-6, portamento goes up/down by one tuning-dependent
6272 //fine step every tick.
6273
6274 if(m_PlayState.m_nTickCount == 0)
6275 chn.nOldFinePortaUpDown = 0;
6276
6277 const int tickParam = static_cast<int>((m_PlayState.m_nTickCount + 1.0) * param / m_PlayState.m_nMusicSpeed);
6278 chn.m_PortamentoFineSteps += (param >= 0) ? tickParam - chn.nOldFinePortaUpDown : tickParam + chn.nOldFinePortaUpDown;
6279 if(m_PlayState.m_nTickCount + 1 == m_PlayState.m_nMusicSpeed)
6280 chn.nOldFinePortaUpDown = static_cast<int8>(std::abs(param));
6281 else
6282 chn.nOldFinePortaUpDown = static_cast<int8>(std::abs(tickParam));
6283
6284 chn.m_CalculateFreq = true;
6285 }
6286
6287
PortamentoExtraFineMPT(ModChannel & chn,int param)6288 void CSoundFile::PortamentoExtraFineMPT(ModChannel &chn, int param)
6289 {
6290 // This kinda behaves like regular fine portamento.
6291 // It changes the pitch by n finetune steps on the first tick.
6292
6293 if(chn.isFirstTick)
6294 {
6295 chn.m_PortamentoFineSteps += param;
6296 chn.m_CalculateFreq = true;
6297 }
6298 }
6299
6300
6301 OPENMPT_NAMESPACE_END
6302