#include "stdafx.h"
#include "Sndfile.h"
#include "mod_specifications.h"
#ifdef MODPLUG_TRACKER
#include "../mptrack/Moddoc.h"
#endif #include "tuning.h"
#include "Tables.h"
#include "modsmp_ctrl.h"
#include "plugins/PlugInterface.h"
#include "OPL.h"
#include "MIDIEvents.h"
OPENMPT_NAMESPACE_BEGIN
#ifdef MODPLUG_TRACKER
#define GLOBALVOL_7BIT_FORMATS_EXT (MOD_TYPE_MT2)
#else
#define GLOBALVOL_7BIT_FORMATS_EXT (MOD_TYPE_NONE)
#endif #define 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)
static uint32 GetLinearSlideDownTable (const CSoundFile *sndFile, uint32 i) { MPT_ASSERT(i < CountOf(LinearSlideDownTable)); return sndFile->m_playBehaviour[kHertzInLinearMode] ? LinearSlideDownTable[i] : LinearSlideUpTable[i]; }
static uint32 GetLinearSlideUpTable (const CSoundFile *sndFile, uint32 i) { MPT_ASSERT(i < CountOf(LinearSlideDownTable)); return sndFile->m_playBehaviour[kHertzInLinearMode] ? LinearSlideUpTable[i] : LinearSlideDownTable[i]; }
static uint32 GetFineLinearSlideDownTable(const CSoundFile *sndFile, uint32 i) { MPT_ASSERT(i < CountOf(FineLinearSlideDownTable)); return sndFile->m_playBehaviour[kHertzInLinearMode] ? FineLinearSlideDownTable[i] : FineLinearSlideUpTable[i]; }
static uint32 GetFineLinearSlideUpTable (const CSoundFile *sndFile, uint32 i) { MPT_ASSERT(i < CountOf(FineLinearSlideDownTable)); return sndFile->m_playBehaviour[kHertzInLinearMode] ? FineLinearSlideUpTable[i] : FineLinearSlideDownTable[i]; }
class GetLengthMemory
{
protected:
const CSoundFile &sndFile;
public:
std::unique_ptr<CSoundFile::PlayState> state;
struct ChnSettings
{
double patLoop = 0.0;
CSoundFile::samplecount_t patLoopSmp = 0;
ROWINDEX patLoopStart = 0;
uint32 ticksToRender = 0; bool incChanged = false; uint8 vol = 0xFF;
};
#ifndef NO_PLUGINS
typedef std::map<std::pair<ModCommand::INSTR, uint16>, uint16> PlugParamMap;
PlugParamMap plugParams;
#endif
std::vector<ChnSettings> chnSettings;
double elapsedTime;
static const uint32 IGNORE_CHANNEL = uint32_max;
GetLengthMemory(const CSoundFile &sf)
: sndFile(sf)
, state(std::make_unique<CSoundFile::PlayState>(sf.m_PlayState))
{
Reset();
}
void Reset()
{
#ifndef NO_PLUGINS
plugParams.clear();
#endif
elapsedTime = 0.0;
state->m_lTotalSampleCount = 0;
state->m_nMusicSpeed = sndFile.m_nDefaultSpeed;
state->m_nMusicTempo = sndFile.m_nDefaultTempo;
state->m_nGlobalVolume = sndFile.m_nDefaultGlobalVolume;
chnSettings.assign(sndFile.GetNumChannels(), ChnSettings());
for(CHANNELINDEX chn = 0; chn < sndFile.GetNumChannels(); chn++)
{
state->Chn[chn].Reset(ModChannel::resetTotal, sndFile, chn);
state->Chn[chn].nOldGlobalVolSlide = 0;
state->Chn[chn].nOldChnVolSlide = 0;
state->Chn[chn].nNote = state->Chn[chn].nNewNote = state->Chn[chn].nLastNote = NOTE_NONE;
}
}
void RenderChannel(CHANNELINDEX channel, uint32 tickDuration, uint32 portaStart = uint32_max)
{
ModChannel &chn = state->Chn[channel];
uint32 numTicks = chnSettings[channel].ticksToRender;
if(numTicks == IGNORE_CHANNEL || numTicks == 0 || (!chn.IsSamplePlaying() && !chnSettings[channel].incChanged) || chn.pModSample == nullptr)
{
return;
}
const SmpLength sampleEnd = chn.dwFlags[CHN_LOOP] ? chn.nLoopEnd : chn.nLength;
const SmpLength loopLength = chn.nLoopEnd - chn.nLoopStart;
const bool itEnvMode = sndFile.m_playBehaviour[kITEnvelopePositionHandling];
const bool updatePitchEnv = (chn.PitchEnv.flags & (ENV_ENABLED | ENV_FILTER)) == ENV_ENABLED;
bool stopNote = false;
SamplePosition inc = chn.increment * tickDuration;
if(chn.dwFlags[CHN_PINGPONGFLAG]) inc.Negate();
for(uint32 i = 0; i < numTicks; i++)
{
bool updateInc = (chn.PitchEnv.flags & (ENV_ENABLED | ENV_FILTER)) == ENV_ENABLED;
if(i >= portaStart)
{
chn.isFirstTick = false;
const ModCommand &p = *sndFile.Patterns[state->m_nPattern].GetpModCommand(state->m_nRow, channel);
if(p.command == CMD_TONEPORTAMENTO) sndFile.TonePortamento(chn, p.param);
else if(p.command == CMD_TONEPORTAVOL) sndFile.TonePortamento(chn, 0);
if(p.volcmd == VOLCMD_TONEPORTAMENTO)
{
uint32 param = p.vol;
if(sndFile.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))
{
param = ImpulseTrackerPortaVolCmd[param & 0x0F];
} else
{
param <<= 4;
}
sndFile.TonePortamento(chn, param);
}
updateInc = true;
}
int period = chn.nPeriod;
if(itEnvMode) sndFile.IncrementEnvelopePositions(chn);
if(updatePitchEnv)
{
sndFile.ProcessPitchFilterEnvelope(chn, period);
updateInc = true;
}
if(!itEnvMode) sndFile.IncrementEnvelopePositions(chn);
int vol = 0;
sndFile.ProcessInstrumentFade(chn, vol);
if(updateInc || chnSettings[channel].incChanged)
{
chn.increment = sndFile.GetChannelIncrement(chn, period, 0);
chnSettings[channel].incChanged = false;
inc = chn.increment * tickDuration;
if(chn.dwFlags[CHN_PINGPONGFLAG]) inc.Negate();
}
chn.position += inc;
if(chn.position.GetUInt() >= sampleEnd)
{
if(chn.dwFlags[CHN_LOOP])
{
if(chn.dwFlags[CHN_PINGPONGLOOP])
{
if(chn.position < SamplePosition(chn.nLoopStart, 0))
{
chn.position = SamplePosition(chn.nLoopStart + chn.nLoopStart, 0) - chn.position;
chn.dwFlags.flip(CHN_PINGPONGFLAG);
inc.Negate();
}
SmpLength posInt = chn.position.GetUInt() - chn.nLoopStart;
SmpLength pingpongLength = loopLength * 2;
if(sndFile.m_playBehaviour[kITPingPongMode]) pingpongLength--;
posInt %= pingpongLength;
bool forward = (posInt < loopLength);
if(forward)
chn.position.SetInt(chn.nLoopStart + posInt);
else
chn.position.SetInt(chn.nLoopEnd - (posInt - loopLength));
if(forward == chn.dwFlags[CHN_PINGPONGFLAG])
{
chn.dwFlags.flip(CHN_PINGPONGFLAG);
inc.Negate();
}
} else
{
SmpLength posInt = chn.position.GetUInt();
if(posInt >= chn.nLoopEnd + loopLength)
{
const SmpLength overshoot = posInt - chn.nLoopEnd;
posInt -= (overshoot / loopLength) * loopLength;
}
while(posInt >= chn.nLoopEnd)
{
posInt -= loopLength;
}
chn.position.SetInt(posInt);
}
} else
{
stopNote = true;
break;
}
}
}
if(stopNote)
{
chn.Stop();
chn.nPortamentoDest = 0;
}
chnSettings[channel].ticksToRender = 0;
}
};
std::vector<GetLengthType> CSoundFile::GetLength(enmGetLengthResetMode adjustMode, GetLengthTarget target)
{
std::vector<GetLengthType> results;
GetLengthType retval;
retval.startOrder = target.startOrder;
retval.startRow = target.startRow;
const bool hasSearchTarget = target.mode != GetLengthTarget::NoTarget;
const bool adjustSamplePos = (adjustMode & eAdjustSamplePositions) == eAdjustSamplePositions;
SEQUENCEINDEX sequence = target.sequence;
if(sequence >= Order.GetNumSequences()) sequence = Order.GetCurrentSequenceIndex();
const ModSequence &orderList = Order(sequence);
GetLengthMemory memory(*this);
CSoundFile::PlayState &playState = *memory.state;
RowVisitor visitedRows(*this, sequence);
playState.m_nNextRow = playState.m_nRow = target.startRow;
playState.m_nNextOrder = playState.m_nCurrentOrder = target.startOrder;
std::bitset<MAX_EFFECTS> forbiddenCommands;
std::bitset<MAX_VOLCMDS> forbiddenVolCommands;
if(adjustSamplePos)
{
forbiddenCommands.set(CMD_ARPEGGIO); forbiddenCommands.set(CMD_PORTAMENTOUP);
forbiddenCommands.set(CMD_PORTAMENTODOWN); forbiddenCommands.set(CMD_XFINEPORTAUPDOWN);
forbiddenCommands.set(CMD_NOTESLIDEUP); forbiddenCommands.set(CMD_NOTESLIDEUPRETRIG);
forbiddenCommands.set(CMD_NOTESLIDEDOWN); forbiddenCommands.set(CMD_NOTESLIDEDOWNRETRIG);
forbiddenVolCommands.set(VOLCMD_PORTAUP); forbiddenVolCommands.set(VOLCMD_PORTADOWN);
for(CHANNELINDEX i = 0; i < GetNumChannels(); i++)
{
if(ChnSettings[i].dwFlags[CHN_MUTE]) memory.chnSettings[i].ticksToRender = GetLengthMemory::IGNORE_CHANNEL;
}
if(target.mode == GetLengthTarget::SeekPosition && target.pos.order < orderList.size())
{
const PATTERNINDEX seekPat = orderList[target.pos.order];
if(Patterns.IsValidPat(seekPat) && Patterns[seekPat].IsValidRow(target.pos.row))
{
const ModCommand *m = Patterns[seekPat].GetRow(target.pos.row);
for(CHANNELINDEX i = 0; i < GetNumChannels(); i++, m++)
{
if(m->note == NOTE_NOTECUT || m->note == NOTE_KEYOFF || (m->note == NOTE_FADE && GetNumInstruments())
|| (m->IsNote() && !m->IsPortamento()))
{
memory.chnSettings[i].ticksToRender = GetLengthMemory::IGNORE_CHANNEL;
}
}
}
}
}
uint32 oldTickDuration = 0;
for (;;)
{
if(target.mode == GetLengthTarget::SeekSeconds && memory.elapsedTime >= target.time)
{
retval.targetReached = true;
break;
}
uint32 rowDelay = 0, tickDelay = 0;
playState.m_nRow = playState.m_nNextRow;
playState.m_nCurrentOrder = playState.m_nNextOrder;
if(orderList.IsValidPat(playState.m_nCurrentOrder) && playState.m_nRow >= Patterns[orderList[playState.m_nCurrentOrder]].GetNumRows())
{
playState.m_nRow = 0;
if(m_playBehaviour[kFT2LoopE60Restart])
{
playState.m_nRow = playState.m_nNextPatStartRow;
playState.m_nNextPatStartRow = 0;
}
playState.m_nCurrentOrder = ++playState.m_nNextOrder;
}
playState.m_nPattern = playState.m_nCurrentOrder < orderList.size() ? orderList[playState.m_nCurrentOrder] : orderList.GetInvalidPatIndex();
bool positionJumpOnThisRow = false;
bool patternBreakOnThisRow = false;
bool patternLoopEndedOnThisRow = false, patternLoopStartedOnThisRow = false;
if(!Patterns.IsValidPat(playState.m_nPattern) && playState.m_nPattern != orderList.GetInvalidPatIndex() && target.mode == GetLengthTarget::SeekPosition && playState.m_nCurrentOrder == target.pos.order)
{
retval.targetReached = true;
break;
}
while(playState.m_nPattern >= Patterns.Size())
{
if((playState.m_nPattern == orderList.GetInvalidPatIndex()) || (playState.m_nCurrentOrder >= orderList.size()))
{
if(playState.m_nCurrentOrder == orderList.GetRestartPos())
break;
else
playState.m_nCurrentOrder = orderList.GetRestartPos();
} else
{
playState.m_nCurrentOrder++;
}
playState.m_nPattern = (playState.m_nCurrentOrder < orderList.size()) ? orderList[playState.m_nCurrentOrder] : orderList.GetInvalidPatIndex();
playState.m_nNextOrder = playState.m_nCurrentOrder;
if((!Patterns.IsValidPat(playState.m_nPattern)) && visitedRows.IsVisited(playState.m_nCurrentOrder, 0, true))
{
if(!hasSearchTarget || !visitedRows.GetFirstUnvisitedRow(playState.m_nNextOrder, playState.m_nRow, true))
{
break;
} else
{
retval.duration = memory.elapsedTime;
results.push_back(retval);
retval.startRow = playState.m_nRow;
retval.startOrder = playState.m_nNextOrder;
memory.Reset();
playState.m_nCurrentOrder = playState.m_nNextOrder;
playState.m_nPattern = orderList[playState.m_nCurrentOrder];
playState.m_nNextRow = playState.m_nRow;
break;
}
}
}
if(playState.m_nNextOrder == ORDERINDEX_INVALID)
{
break;
}
if(!Patterns.IsValidPat(playState.m_nPattern))
{
if(playState.m_nCurrentOrder == orderList.GetRestartPos())
{
if(!hasSearchTarget || !visitedRows.GetFirstUnvisitedRow(playState.m_nNextOrder, playState.m_nRow, true))
{
break;
} else
{
retval.duration = memory.elapsedTime;
results.push_back(retval);
retval.startRow = playState.m_nRow;
retval.startOrder = playState.m_nNextOrder;
memory.Reset();
playState.m_nNextRow = playState.m_nRow;
continue;
}
}
playState.m_nNextOrder = playState.m_nCurrentOrder + 1;
continue;
}
if(playState.m_nRow >= Patterns[playState.m_nPattern].GetNumRows())
playState.m_nRow = 0;
if(target.mode == GetLengthTarget::SeekPosition && playState.m_nCurrentOrder == target.pos.order && playState.m_nRow == target.pos.row)
{
retval.targetReached = true;
break;
}
if(visitedRows.IsVisited(playState.m_nCurrentOrder, playState.m_nRow, true))
{
if(!hasSearchTarget || !visitedRows.GetFirstUnvisitedRow(playState.m_nNextOrder, playState.m_nRow, true))
{
break;
} else
{
retval.duration = memory.elapsedTime;
results.push_back(retval);
retval.startRow = playState.m_nRow;
retval.startOrder = playState.m_nNextOrder;
memory.Reset();
playState.m_nNextRow = playState.m_nRow;
continue;
}
}
retval.endOrder = playState.m_nCurrentOrder;
retval.endRow = playState.m_nRow;
playState.m_nNextRow = playState.m_nRow + 1;
if(playState.m_nRow >= Patterns[playState.m_nPattern].GetNumRows())
{
playState.m_nRow = 0;
}
if(!playState.m_nRow)
{
for(CHANNELINDEX chn = 0; chn < GetNumChannels(); chn++)
{
memory.chnSettings[chn].patLoop = memory.elapsedTime;
memory.chnSettings[chn].patLoopSmp = playState.m_lTotalSampleCount;
}
}
const ModCommand *p = Patterns[playState.m_nPattern].GetpModCommand(playState.m_nRow, 0);
for(CHANNELINDEX nChn = 0; nChn < GetNumChannels(); nChn++, p++)
{
ModChannel &chn = playState.Chn[nChn];
if(m_playBehaviour[kST3NoMutedChannels] && ChnSettings[nChn].dwFlags[CHN_MUTE]) continue;
if(p->IsPcNote())
{
#ifndef NO_PLUGINS
if((adjustMode & eAdjust) && p->instr > 0 && p->instr <= MAX_MIXPLUGINS)
{
memory.plugParams[std::make_pair(p->instr, p->GetValueVolCol())] = p->GetValueEffectCol();
}
#endif chn.rowCommand.Clear();
continue;
}
chn.rowCommand = *p;
switch(p->command)
{
case CMD_SPEED:
SetSpeed(playState, p->param);
break;
case CMD_TEMPO:
if(m_playBehaviour[kMODVBlankTiming])
{
if(p->param != 0) SetSpeed(playState, p->param);
}
break;
case CMD_S3MCMDEX:
if((p->param & 0xF0) == 0x60)
{
tickDelay += (p->param & 0x0F);
} else if((p->param & 0xF0) == 0xE0 && !rowDelay)
{
if(!(GetType() & MOD_TYPE_S3M) || (p->param & 0x0F) != 0)
{
rowDelay = 1 + (p->param & 0x0F);
}
}
break;
case CMD_MODCMDEX:
if((p->param & 0xF0) == 0xE0)
{
rowDelay = 1 + (p->param & 0x0F);
}
break;
}
}
if(rowDelay == 0) rowDelay = 1;
const uint32 numTicks = (playState.m_nMusicSpeed + tickDelay) * rowDelay;
const uint32 nonRowTicks = numTicks - rowDelay;
for(CHANNELINDEX nChn = 0; nChn < GetNumChannels(); nChn++)
{
ModChannel &chn = playState.Chn[nChn];
if(chn.rowCommand.IsEmpty())
continue;
if(m_playBehaviour[kST3NoMutedChannels] && ChnSettings[nChn].dwFlags[CHN_MUTE]) continue;
ModCommand::COMMAND command = chn.rowCommand.command;
ModCommand::PARAM param = chn.rowCommand.param;
ModCommand::NOTE note = chn.rowCommand.note;
if (chn.rowCommand.instr)
{
chn.nNewIns = chn.rowCommand.instr;
chn.nLastNote = NOTE_NONE;
memory.chnSettings[nChn].vol = 0xFF;
}
if (chn.rowCommand.IsNote()) chn.nLastNote = note;
if(chn.rowCommand.IsNote() || chn.rowCommand.instr)
{
SAMPLEINDEX smp = 0;
if(GetNumInstruments())
{
ModInstrument *pIns;
if(chn.nNewIns <= GetNumInstruments() && (pIns = Instruments[chn.nNewIns]) != nullptr)
{
if(pIns->dwFlags[INS_SETPANNING])
chn.nPan = pIns->nPan;
if(ModCommand::IsNote(note))
smp = pIns->Keyboard[note - NOTE_MIN];
}
} else
{
smp = chn.nNewIns;
}
if(smp > 0 && smp <= GetNumSamples())
{
if(Samples[smp].uFlags[CHN_PANNING])
chn.nPan = Samples[smp].nPan;
if(Samples[smp].uFlags[CHN_ADLIB])
{
memory.state->Chn[nChn].Stop();
memory.chnSettings[nChn].ticksToRender = 0;
}
}
}
switch(chn.rowCommand.volcmd)
{
case VOLCMD_VOLUME:
memory.chnSettings[nChn].vol = chn.rowCommand.vol;
break;
case VOLCMD_VOLSLIDEUP:
case VOLCMD_VOLSLIDEDOWN:
if(chn.rowCommand.vol != 0)
chn.nOldVolParam = chn.rowCommand.vol;
break;
}
switch(command)
{
case CMD_POSITIONJUMP:
positionJumpOnThisRow = true;
playState.m_nNextOrder = static_cast<ORDERINDEX>(CalculateXParam(playState.m_nPattern, playState.m_nRow, nChn));
playState.m_nNextPatStartRow = 0; if(!patternBreakOnThisRow || (GetType() & (MOD_TYPE_MOD | MOD_TYPE_XM)))
playState.m_nNextRow = 0;
if (adjustMode & eAdjust)
{
chn.nPatternLoopCount = 0;
chn.nPatternLoop = 0;
}
break;
case CMD_PATTERNBREAK:
{
ROWINDEX row = PatternBreak(playState, nChn, param);
if(row != ROWINDEX_INVALID)
{
patternBreakOnThisRow = true;
playState.m_nNextRow = row;
if(!positionJumpOnThisRow)
{
playState.m_nNextOrder = playState.m_nCurrentOrder + 1;
}
if(adjustMode & eAdjust)
{
chn.nPatternLoopCount = 0;
chn.nPatternLoop = 0;
}
}
}
break;
case CMD_TEMPO:
if(!m_playBehaviour[kMODVBlankTiming])
{
TEMPO tempo(CalculateXParam(playState.m_nPattern, playState.m_nRow, nChn), 0);
if ((adjustMode & eAdjust) && (GetType() & (MOD_TYPE_S3M | MOD_TYPE_IT | MOD_TYPE_MPT)))
{
if (tempo.GetInt()) chn.nOldTempo = static_cast<uint8>(tempo.GetInt()); else tempo.Set(chn.nOldTempo);
}
if (tempo.GetInt() >= 0x20) playState.m_nMusicTempo = tempo;
else
{
TEMPO tempoDiff((tempo.GetInt() & 0x0F) * nonRowTicks, 0);
if ((tempo.GetInt() & 0xF0) == 0x10)
{
playState.m_nMusicTempo += tempoDiff;
} else
{
if(tempoDiff < playState.m_nMusicTempo)
playState.m_nMusicTempo -= tempoDiff;
else
playState.m_nMusicTempo.Set(0);
}
}
TEMPO tempoMin = GetModSpecifications().GetTempoMin(), tempoMax = GetModSpecifications().GetTempoMax();
if(m_playBehaviour[kTempoClamp]) {
tempoMax.Set(255);
}
Limit(playState.m_nMusicTempo, tempoMin, tempoMax);
}
break;
case CMD_S3MCMDEX:
switch(param & 0xF0)
{
case 0x90:
if(param <= 0x91)
{
chn.dwFlags.set(CHN_SURROUND, param == 0x91);
}
break;
case 0xA0:
chn.nOldHiOffset = param & 0x0F;
break;
case 0xB0:
if (param & 0x0F)
{
patternLoopEndedOnThisRow = true;
} else
{
CHANNELINDEX firstChn = nChn, lastChn = nChn;
if(GetType() == MOD_TYPE_S3M)
{
firstChn = 0;
lastChn = GetNumChannels() - 1;
}
for(CHANNELINDEX c = firstChn; c <= lastChn; c++)
{
memory.chnSettings[c].patLoop = memory.elapsedTime;
memory.chnSettings[c].patLoopSmp = playState.m_lTotalSampleCount;
memory.chnSettings[c].patLoopStart = playState.m_nRow;
}
patternLoopStartedOnThisRow = true;
}
break;
case 0xF0:
chn.nActiveMacro = param & 0x0F;
break;
}
break;
case CMD_MODCMDEX:
switch(param & 0xF0)
{
case 0x60:
if (param & 0x0F)
{
playState.m_nNextPatStartRow = memory.chnSettings[nChn].patLoopStart; patternLoopEndedOnThisRow = true;
} else
{
patternLoopStartedOnThisRow = true;
memory.chnSettings[nChn].patLoop = memory.elapsedTime;
memory.chnSettings[nChn].patLoopSmp = playState.m_lTotalSampleCount;
memory.chnSettings[nChn].patLoopStart = playState.m_nRow;
}
break;
case 0xF0:
chn.nActiveMacro = param & 0x0F;
break;
}
break;
case CMD_XFINEPORTAUPDOWN:
if(((param & 0xF0) == 0xA0) && !m_playBehaviour[kFT2RestrictXCommand]) chn.nOldHiOffset = param & 0x0F;
break;
}
if (!(adjustMode & eAdjust)) continue;
switch(command)
{
case CMD_PORTAMENTOUP:
if(param)
{
if(!m_playBehaviour[kFT2PortaUpDownMemory])
chn.nOldPortaDown = param;
chn.nOldPortaUp = param;
}
break;
case CMD_PORTAMENTODOWN:
if(param)
{
if(!m_playBehaviour[kFT2PortaUpDownMemory])
chn.nOldPortaUp = param;
chn.nOldPortaDown = param;
}
break;
case CMD_TONEPORTAMENTO:
if (param) chn.nPortamentoSlide = param << 2;
break;
case CMD_OFFSET:
if (param) chn.oldOffset = param << 8;
break;
case CMD_VOLUMESLIDE:
case CMD_TONEPORTAVOL:
if (param) chn.nOldVolumeSlide = param;
break;
case CMD_VOLUME:
memory.chnSettings[nChn].vol = param;
break;
case CMD_GLOBALVOLUME:
if(!(GetType() & GLOBALVOL_7BIT_FORMATS) && param < 128) param *= 2;
if(param <= 128)
{
playState.m_nGlobalVolume = param * 2;
} else if(!(GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT | MOD_TYPE_S3M)))
{
playState.m_nGlobalVolume = 256;
}
break;
case CMD_GLOBALVOLSLIDE:
if(m_playBehaviour[kPerChannelGlobalVolSlide])
{
if (param) chn.nOldGlobalVolSlide = param; else param = chn.nOldGlobalVolSlide;
} else
{
if (param) playState.Chn[0].nOldGlobalVolSlide = param; else param = playState.Chn[0].nOldGlobalVolSlide;
}
if (((param & 0x0F) == 0x0F) && (param & 0xF0))
{
param >>= 4;
if (!(GetType() & GLOBALVOL_7BIT_FORMATS)) param <<= 1;
playState.m_nGlobalVolume += param << 1;
} else if (((param & 0xF0) == 0xF0) && (param & 0x0F))
{
param = (param & 0x0F) << 1;
if (!(GetType() & GLOBALVOL_7BIT_FORMATS)) param <<= 1;
playState.m_nGlobalVolume -= param;
} else if (param & 0xF0)
{
param >>= 4;
param <<= 1;
if (!(GetType() & GLOBALVOL_7BIT_FORMATS)) param <<= 1;
playState.m_nGlobalVolume += param * nonRowTicks;
} else
{
param = (param & 0x0F) << 1;
if (!(GetType() & GLOBALVOL_7BIT_FORMATS)) param <<= 1;
playState.m_nGlobalVolume -= param * nonRowTicks;
}
Limit(playState.m_nGlobalVolume, 0, 256);
break;
case CMD_CHANNELVOLUME:
if (param <= 64) chn.nGlobalVol = param;
break;
case CMD_CHANNELVOLSLIDE:
{
if (param) chn.nOldChnVolSlide = param; else param = chn.nOldChnVolSlide;
int32 volume = chn.nGlobalVol;
if((param & 0x0F) == 0x0F && (param & 0xF0))
volume += (param >> 4); else if((param & 0xF0) == 0xF0 && (param & 0x0F))
volume -= (param & 0x0F); else if(param & 0x0F) volume -= (param & 0x0F) * nonRowTicks;
else volume += ((param & 0xF0) >> 4) * nonRowTicks;
Limit(volume, 0, 64);
chn.nGlobalVol = volume;
}
break;
case CMD_PANNING8:
Panning(chn, param, Pan8bit);
break;
case CMD_MODCMDEX:
if(param < 0x10)
{
for(CHANNELINDEX channel = 0; channel < GetNumChannels(); channel++)
{
playState.Chn[channel].dwFlags.set(CHN_AMIGAFILTER, !(param & 1));
}
}
MPT_FALLTHROUGH;
case CMD_S3MCMDEX:
if((param & 0xF0) == 0x80)
{
Panning(chn, (param & 0x0F), Pan4bit);
}
break;
case CMD_VIBRATOVOL:
if (param) chn.nOldVolumeSlide = param;
param = 0;
MPT_FALLTHROUGH;
case CMD_VIBRATO:
Vibrato(chn, param);
break;
case CMD_FINEVIBRATO:
FineVibrato(chn, param);
break;
case CMD_TREMOLO:
Tremolo(chn, param);
break;
case CMD_PANBRELLO:
Panbrello(chn, param);
break;
}
switch(chn.rowCommand.volcmd)
{
case VOLCMD_PANNING:
Panning(chn, chn.rowCommand.vol, Pan6bit);
break;
case VOLCMD_VIBRATOSPEED:
if(m_playBehaviour[kFT2VolColVibrato])
chn.nVibratoSpeed = chn.rowCommand.vol & 0x0F;
else
Vibrato(chn, chn.rowCommand.vol << 4);
break;
case VOLCMD_VIBRATODEPTH:
Vibrato(chn, chn.rowCommand.vol);
break;
}
switch(chn.rowCommand.command)
{
case CMD_VIBRATO:
case CMD_FINEVIBRATO:
case CMD_VIBRATOVOL:
if(adjustMode & eAdjust)
{
uint32 vibTicks = ((GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT)) && !m_SongFlags[SONG_ITOLDEFFECTS]) ? numTicks : nonRowTicks;
uint32 inc = chn.nVibratoSpeed * vibTicks;
if(m_playBehaviour[kITVibratoTremoloPanbrello])
inc *= 4;
chn.nVibratoPos += static_cast<uint8>(inc);
}
break;
case CMD_TREMOLO:
if(adjustMode & eAdjust)
{
uint32 tremTicks = ((GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT)) && !m_SongFlags[SONG_ITOLDEFFECTS]) ? numTicks : nonRowTicks;
uint32 inc = chn.nTremoloSpeed * tremTicks;
if(m_playBehaviour[kITVibratoTremoloPanbrello])
inc *= 4;
chn.nTremoloPos += static_cast<uint8>(inc);
}
break;
case CMD_PANBRELLO:
if(adjustMode & eAdjust)
{
chn.nPanbrelloPos += static_cast<uint8>(chn.nPanbrelloSpeed * (numTicks - 1));
ProcessPanbrello(chn);
}
break;
}
}
if(GetType() == MOD_TYPE_XM && playState.m_nMusicSpeed == uint16_max)
{
break;
}
playState.m_nCurrentRowsPerBeat = m_nDefaultRowsPerBeat;
if(Patterns[playState.m_nPattern].GetOverrideSignature())
{
playState.m_nCurrentRowsPerBeat = Patterns[playState.m_nPattern].GetRowsPerBeat();
}
const uint32 tickDuration = GetTickDuration(playState);
const uint32 rowDuration = tickDuration * numTicks;
memory.elapsedTime += static_cast<double>(rowDuration) / static_cast<double>(m_MixerSettings.gdwMixingFreq);
playState.m_lTotalSampleCount += rowDuration;
if(adjustSamplePos)
{
for(CHANNELINDEX nChn = 0; nChn < GetNumChannels(); nChn++)
{
if(memory.chnSettings[nChn].ticksToRender == GetLengthMemory::IGNORE_CHANNEL)
continue;
ModChannel &chn = playState.Chn[nChn];
const ModCommand &m = chn.rowCommand;
uint32 paramHi = m.param >> 4, paramLo = m.param & 0x0F;
uint32 startTick = 0;
bool porta = m.command == CMD_TONEPORTAMENTO || m.command == CMD_TONEPORTAVOL || m.volcmd == VOLCMD_TONEPORTAMENTO;
bool stopNote = patternLoopStartedOnThisRow;
if(m.instr) chn.prevNoteOffset = 0;
if(m.IsNote())
{
if(porta && memory.chnSettings[nChn].incChanged)
{
chn.increment = GetChannelIncrement(chn, chn.nPeriod, 0);
}
int32 setPan = chn.nPan;
chn.nNewNote = chn.nLastNote;
if(chn.nNewIns != 0) InstrumentChange(chn, chn.nNewIns, porta);
NoteChange(chn, m.note, porta);
memory.chnSettings[nChn].incChanged = true;
if((m.command == CMD_MODCMDEX || m.command == CMD_S3MCMDEX) && (m.param & 0xF0) == 0xD0 && paramLo < numTicks)
{
startTick = paramLo;
} else if(m.command == CMD_DELAYCUT && paramHi < numTicks)
{
startTick = paramHi;
}
if(rowDelay > 1 && startTick != 0 && (GetType() & (MOD_TYPE_S3M | MOD_TYPE_IT | MOD_TYPE_MPT)))
{
startTick += (playState.m_nMusicSpeed + tickDelay) * (rowDelay - 1);
}
if(!porta) memory.chnSettings[nChn].ticksToRender = 0;
if(m.command == CMD_PANNING8
|| ((m.command == CMD_MODCMDEX || m.command == CMD_S3MCMDEX) && paramHi == 0x8)
|| m.volcmd == VOLCMD_PANNING)
{
chn.nPan = setPan;
}
if(m.command == CMD_OFFSET)
{
bool isExtended = false;
SmpLength offset = CalculateXParam(playState.m_nPattern, playState.m_nRow, nChn, &isExtended);
if(!isExtended)
{
offset <<= 8;
if(offset == 0) offset = chn.oldOffset;
offset += static_cast<SmpLength>(chn.nOldHiOffset) << 16;
}
SampleOffset(chn, offset);
} else if(m.command == CMD_OFFSETPERCENTAGE)
{
SampleOffset(chn, Util::muldiv_unsigned(chn.nLength, m.param, 255));
} else if(m.command == CMD_REVERSEOFFSET && chn.pModSample != nullptr)
{
memory.RenderChannel(nChn, oldTickDuration); ReverseSampleOffset(chn, m.param);
startTick = playState.m_nMusicSpeed - 1;
} else if(m.volcmd == VOLCMD_OFFSET)
{
if(m.vol <= CountOf(chn.pModSample->cues) && chn.pModSample != nullptr)
{
SmpLength offset;
if(m.vol == 0)
offset = chn.oldOffset;
else
offset = chn.oldOffset = chn.pModSample->cues[m.vol - 1];
SampleOffset(chn, offset);
}
}
}
if(m.note == NOTE_KEYOFF || m.note == NOTE_NOTECUT || (m.note == NOTE_FADE && GetNumInstruments())
|| ((m.command == CMD_MODCMDEX || m.command == CMD_S3MCMDEX) && (m.param & 0xF0) == 0xC0 && paramLo < numTicks)
|| (m.command == CMD_DELAYCUT && paramLo != 0 && startTick + paramLo < numTicks))
{
stopNote = true;
}
if(m.command == CMD_VOLUME)
{
chn.nVolume = m.param * 4;
} else if(m.volcmd == VOLCMD_VOLUME)
{
chn.nVolume = m.vol * 4;
}
if(chn.pModSample && !stopNote)
{
if(m.command < MAX_EFFECTS)
{
if(forbiddenCommands[m.command])
{
stopNote = true;
} else if(m.command == CMD_MODCMDEX)
{
switch(m.param & 0xF0)
{
case 0x10:
case 0x20:
stopNote = true;
}
}
}
if(m.volcmd < forbiddenVolCommands.size() && forbiddenVolCommands[m.volcmd])
{
stopNote = true;
}
}
if(stopNote)
{
chn.Stop();
memory.chnSettings[nChn].ticksToRender = 0;
} else
{
if(oldTickDuration != tickDuration && oldTickDuration != 0)
{
memory.RenderChannel(nChn, oldTickDuration); }
switch(m.command)
{
case CMD_TONEPORTAVOL:
case CMD_VOLUMESLIDE:
case CMD_VIBRATOVOL:
if(m.param || (GetType() != MOD_TYPE_MOD))
{
for(uint32 i = 0; i < numTicks; i++)
{
chn.isFirstTick = (i == 0);
VolumeSlide(chn, m.param);
}
}
break;
case CMD_MODCMDEX:
if((m.param & 0x0F) || (GetType() & (MOD_TYPE_XM | MOD_TYPE_MT2)))
{
chn.isFirstTick = true;
switch(m.param & 0xF0)
{
case 0xA0: FineVolumeUp(chn, m.param & 0x0F, false); break;
case 0xB0: FineVolumeDown(chn, m.param & 0x0F, false); break;
}
}
break;
case CMD_S3MCMDEX:
if(m.param == 0x9E)
{
memory.RenderChannel(nChn, oldTickDuration); chn.dwFlags.reset(CHN_PINGPONGFLAG);
} else if(m.param == 0x9F)
{
memory.RenderChannel(nChn, oldTickDuration); chn.dwFlags.set(CHN_PINGPONGFLAG);
if(!chn.position.GetInt() && chn.nLength && (m.IsNote() || !chn.dwFlags[CHN_LOOP]))
{
chn.position.Set(chn.nLength - 1, SamplePosition::fractMax);
}
} else if((m.param & 0xF0) == 0x70)
{
}
break;
}
chn.isFirstTick = true;
switch(m.volcmd)
{
case VOLCMD_FINEVOLUP: FineVolumeUp(chn, m.vol, m_playBehaviour[kITVolColMemory]); break;
case VOLCMD_FINEVOLDOWN: FineVolumeDown(chn, m.vol, m_playBehaviour[kITVolColMemory]); break;
case VOLCMD_VOLSLIDEUP:
case VOLCMD_VOLSLIDEDOWN:
{
ModCommand::VOL vol = m.vol;
if(vol == 0 && m_playBehaviour[kITVolColMemory])
{
vol = chn.nOldVolParam;
if(vol == 0)
break;
}
if(m.volcmd == VOLCMD_VOLSLIDEUP)
vol <<= 4;
for(uint32 i = 0; i < numTicks; i++)
{
chn.isFirstTick = (i == 0);
VolumeSlide(chn, vol);
}
}
break;
}
if(porta)
{
uint32 portaTick = memory.chnSettings[nChn].ticksToRender + startTick + 1;
memory.chnSettings[nChn].ticksToRender += numTicks;
memory.RenderChannel(nChn, tickDuration, portaTick);
} else
{
memory.chnSettings[nChn].ticksToRender += (numTicks - startTick);
}
}
}
}
oldTickDuration = tickDuration;
if(patternLoopEndedOnThisRow
&& (!m_playBehaviour[kFT2PatternLoopWithJumps] || !(positionJumpOnThisRow || patternBreakOnThisRow))
&& (!m_playBehaviour[kITPatternLoopWithJumps] || !positionJumpOnThisRow))
{
std::map<double, int> startTimes;
for(CHANNELINDEX nChn = 0; nChn < GetNumChannels(); nChn++)
{
ModChannel &chn = playState.Chn[nChn];
ModCommand::COMMAND command = chn.rowCommand.command;
ModCommand::PARAM param = chn.rowCommand.param;
if((command == CMD_S3MCMDEX && param >= 0xB1 && param <= 0xBF)
|| (command == CMD_MODCMDEX && param >= 0x61 && param <= 0x6F))
{
const double start = memory.chnSettings[nChn].patLoop;
if(!startTimes[start]) startTimes[start] = 1;
startTimes[start] = mpt::lcm(startTimes[start], 1 + (param & 0x0F));
}
}
for(const auto &i : startTimes)
{
memory.elapsedTime += (memory.elapsedTime - i.first) * (double)(i.second - 1);
for(CHANNELINDEX nChn = 0; nChn < GetNumChannels(); nChn++)
{
if(memory.chnSettings[nChn].patLoop == i.first)
{
playState.m_lTotalSampleCount += (playState.m_lTotalSampleCount - memory.chnSettings[nChn].patLoopSmp) * (i.second - 1);
if(m_playBehaviour[kITPatternLoopTargetReset] || (GetType() == MOD_TYPE_S3M))
{
memory.chnSettings[nChn].patLoop = memory.elapsedTime;
memory.chnSettings[nChn].patLoopSmp = playState.m_lTotalSampleCount;
memory.chnSettings[nChn].patLoopStart = playState.m_nRow + 1;
}
break;
}
}
}
if(GetType() == MOD_TYPE_IT)
{
for(CHANNELINDEX nChn = 0; nChn < GetNumChannels(); nChn++)
{
ModChannel &chn = playState.Chn[nChn];
if((chn.rowCommand.command == CMD_S3MCMDEX && chn.rowCommand.param >= 0xB1 && chn.rowCommand.param <= 0xBF))
{
memory.chnSettings[nChn].patLoop = memory.elapsedTime;
memory.chnSettings[nChn].patLoopSmp = playState.m_lTotalSampleCount;
}
}
}
}
}
if(adjustSamplePos)
{
for(CHANNELINDEX nChn = 0; nChn < GetNumChannels(); nChn++)
{
if(memory.chnSettings[nChn].ticksToRender != GetLengthMemory::IGNORE_CHANNEL)
{
memory.RenderChannel(nChn, oldTickDuration);
}
}
}
if(retval.targetReached || target.mode == GetLengthTarget::NoTarget)
{
retval.lastOrder = playState.m_nCurrentOrder;
retval.lastRow = playState.m_nRow;
}
retval.duration = memory.elapsedTime;
results.push_back(retval);
if(adjustMode & eAdjust)
{
if(retval.targetReached || target.mode == GetLengthTarget::NoTarget)
{
m_PlayState = std::move(playState);
m_PlayState.m_nNextRow = m_PlayState.m_nRow;
m_PlayState.m_nFrameDelay = m_PlayState.m_nPatternDelay = 0;
m_PlayState.m_nTickCount = Util::MaxValueOfType(m_PlayState.m_nTickCount) - 1;
m_PlayState.m_bPositionChanged = true;
for(CHANNELINDEX n = 0; n < GetNumChannels(); n++)
{
if(m_PlayState.Chn[n].nLastNote != NOTE_NONE)
{
m_PlayState.Chn[n].nNewNote = m_PlayState.Chn[n].nLastNote;
}
if(memory.chnSettings[n].vol != 0xFF && !adjustSamplePos)
{
m_PlayState.Chn[n].nVolume = std::min(memory.chnSettings[n].vol, uint8(64)) * 4;
}
}
if(m_opl != nullptr) m_opl->Reset();
#ifndef NO_PLUGINS
std::bitset<MAX_MIXPLUGINS> plugSetProgram;
for(const auto ¶m : memory.plugParams)
{
PLUGINDEX plug = param.first.first - 1;
IMixPlugin *plugin = m_MixPlugins[plug].pMixPlugin;
if(plugin != nullptr)
{
if(!plugSetProgram[plug])
{
plugSetProgram.set(plug);
plugin->BeginSetProgram();
}
plugin->SetParameter(param.first.second, param.second / PlugParamValue(ModCommand::maxColumnValue));
}
}
if(plugSetProgram.any())
{
for(PLUGINDEX i = 0; i < MAX_MIXPLUGINS; i++)
{
if(plugSetProgram[i])
{
m_MixPlugins[i].pMixPlugin->EndSetProgram();
}
}
}
#endif } else if(adjustMode != eAdjustOnSuccess)
{
m_PlayState.m_nMusicSpeed = m_nDefaultSpeed;
m_PlayState.m_nMusicTempo = m_nDefaultTempo;
m_PlayState.m_nGlobalVolume = m_nDefaultGlobalVolume;
}
if(sequence != Order.GetCurrentSequenceIndex())
{
Order.SetSequence(sequence);
}
visitedSongRows = std::move(visitedRows);
}
return results;
}
void CSoundFile::InstrumentChange(ModChannel &chn, uint32 instr, bool bPorta, bool bUpdVol, bool bResetEnv) const
{
const ModInstrument *pIns = instr <= GetNumInstruments() ? Instruments[instr] : nullptr;
const ModSample *pSmp = &Samples[instr];
const auto oldInsVol = chn.nInsVol;
ModCommand::NOTE note = chn.nNewNote;
if(note == NOTE_NONE && m_playBehaviour[kITInstrWithoutNote]) return;
if(pIns != nullptr && ModCommand::IsNote(note))
{
if(pIns->Keyboard[note - NOTE_MIN] == 0 && m_playBehaviour[kITEmptyNoteMapSlot] && !pIns->HasValidMIDIChannel())
{
chn.pModInstrument = pIns;
return;
}
if(pIns->NoteMap[note - NOTE_MIN] > NOTE_MAX) return;
uint32 n = pIns->Keyboard[note - NOTE_MIN];
pSmp = ((n) && (n < MAX_SAMPLES)) ? &Samples[n] : nullptr;
} else if(GetNumInstruments())
{
if (note >= NOTE_MIN_SPECIAL) return;
if(m_playBehaviour[kITEmptyNoteMapSlot] && (pIns == nullptr || !pIns->HasValidMIDIChannel()))
{
chn.pModInstrument = nullptr;
chn.nNewIns = 0;
return;
}
pSmp = nullptr;
}
bool returnAfterVolumeAdjust = false;
bool instrumentChanged = (pIns != chn.pModInstrument);
const bool sampleChanged = (chn.pModSample != nullptr) && (pSmp != chn.pModSample);
const bool newTuning = (GetType() == MOD_TYPE_MPT && pIns && pIns->pTuning);
if(bPorta && newTuning && pIns == chn.pModInstrument && sampleChanged)
return;
if(sampleChanged && bPorta)
{
if(m_playBehaviour[kITPortamentoInstrument] && m_SongFlags[SONG_ITCOMPATGXX] && !chn.increment.IsZero())
{
pSmp = chn.pModSample;
}
if((!instrumentChanged && (GetType() & (MOD_TYPE_XM | MOD_TYPE_MT2)) && pIns)
|| (GetType() == MOD_TYPE_PLM)
|| (GetType() == MOD_TYPE_MOD && chn.IsSamplePlaying())
|| m_playBehaviour[kST3PortaSampleChange])
{
returnAfterVolumeAdjust = true;
}
}
if(m_nInstruments && !instrumentChanged && sampleChanged && chn.pCurrentSample != nullptr && m_playBehaviour[kITMultiSampleInstrumentNumber] && !chn.rowCommand.IsNote())
{
returnAfterVolumeAdjust = true;
}
if(!chn.IsSamplePlaying() && (GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT)) && (!pIns || !pIns->HasValidMIDIChannel()))
{
instrumentChanged = true;
}
if(instrumentChanged && bPorta && m_playBehaviour[kFT2PortaIgnoreInstr] && (chn.pModInstrument != nullptr || chn.pModSample != nullptr))
{
pIns = chn.pModInstrument;
pSmp = chn.pModSample;
instrumentChanged = false;
} else
{
chn.pModInstrument = pIns;
}
if (bUpdVol && (!(GetType() & (MOD_TYPE_MOD | MOD_TYPE_S3M)) || ((pSmp != nullptr && pSmp->HasSampleData()) || chn.HasMIDIOutput())))
{
if(pSmp)
{
if(!pSmp->uFlags[SMP_NODEFAULTVOLUME])
chn.nVolume = pSmp->nVolume;
} else if(pIns && pIns->nMixPlug)
{
chn.nVolume = chn.GetVSTVolume();
} else
{
chn.nVolume = 0;
}
}
if(returnAfterVolumeAdjust && sampleChanged && m_playBehaviour[kMODSampleSwap] && pSmp != nullptr)
{
chn.nFineTune = pSmp->nFineTune;
}
if(returnAfterVolumeAdjust) return;
chn.nNewIns = 0;
if (pIns && ((!m_playBehaviour[kITNNAReset] && pSmp) || pIns->nMixPlug || instrumentChanged))
chn.nNNA = pIns->nNNA;
chn.UpdateInstrumentVolume(pSmp, pIns);
if((bUpdVol || !(GetType() & (MOD_TYPE_XM | MOD_TYPE_MT2))) && !m_playBehaviour[kITPanningReset])
{
ApplyInstrumentPanning(chn, pIns, pSmp);
}
if(bResetEnv)
{
bool reset, resetAlways;
if(m_playBehaviour[kITEnvelopeReset])
{
const bool insNumber = (instr != 0);
reset = (!chn.nLength
|| (insNumber && bPorta && m_SongFlags[SONG_ITCOMPATGXX])
|| (insNumber && !bPorta && chn.dwFlags[CHN_NOTEFADE | CHN_KEYOFF] && m_SongFlags[SONG_ITOLDEFFECTS]));
resetAlways = (!chn.nFadeOutVol || instrumentChanged || chn.dwFlags[CHN_KEYOFF]);
} else
{
reset = (!bPorta || !(GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT | MOD_TYPE_DBM)) || m_SongFlags[SONG_ITCOMPATGXX]
|| !chn.nLength || (chn.dwFlags[CHN_NOTEFADE] && !chn.nFadeOutVol));
resetAlways = !(GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT | MOD_TYPE_DBM)) || instrumentChanged || pIns == nullptr || chn.dwFlags[CHN_KEYOFF | CHN_NOTEFADE];
}
if(reset)
{
chn.dwFlags.set(CHN_FASTVOLRAMP);
if(pIns != nullptr)
{
if(resetAlways)
{
chn.ResetEnvelopes();
} else
{
if(!pIns->VolEnv.dwFlags[ENV_CARRY]) chn.VolEnv.Reset();
if(!pIns->PanEnv.dwFlags[ENV_CARRY]) chn.PanEnv.Reset();
if(!pIns->PitchEnv.dwFlags[ENV_CARRY]) chn.PitchEnv.Reset();
}
}
if(!m_playBehaviour[kITVibratoTremoloPanbrello])
{
chn.nAutoVibDepth = 0;
chn.nAutoVibPos = 0;
}
} else if(pIns != nullptr && !pIns->VolEnv.dwFlags[ENV_ENABLED])
{
if(m_playBehaviour[kITPortamentoInstrument])
{
chn.VolEnv.Reset();
} else
{
chn.ResetEnvelopes();
}
}
}
if(pSmp == nullptr && (pIns == nullptr || !pIns->HasValidMIDIChannel()))
{
chn.pModSample = nullptr;
chn.nInsVol = 0;
return;
}
if(bPorta && pSmp == chn.pModSample && pSmp != nullptr)
{
if(GetType() & (MOD_TYPE_S3M|MOD_TYPE_IT|MOD_TYPE_MPT) && chn.nLength != 0) return;
chn.dwFlags.reset(CHN_KEYOFF | CHN_NOTEFADE);
chn.dwFlags = (chn.dwFlags & (CHN_CHANNELFLAGS | CHN_PINGPONGFLAG));
} else {
chn.dwFlags.reset(CHN_KEYOFF | CHN_NOTEFADE);
if((m_playBehaviour[kITPingPongNoReset] || !(GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT))) && pSmp == chn.pModSample && !instrumentChanged)
chn.dwFlags = (chn.dwFlags & (CHN_CHANNELFLAGS | CHN_PINGPONGFLAG));
else
chn.dwFlags = (chn.dwFlags & CHN_CHANNELFLAGS);
if(pIns)
{
chn.VolEnv.flags = pIns->VolEnv.dwFlags;
chn.PanEnv.flags = pIns->PanEnv.dwFlags;
chn.PitchEnv.flags = pIns->PitchEnv.dwFlags;
if((pIns->PitchEnv.dwFlags & (ENV_ENABLED | ENV_FILTER)) == (ENV_ENABLED | ENV_FILTER) && !m_playBehaviour[kITFilterBehaviour])
{
if(!chn.nCutOff) chn.nCutOff = 0x7F;
}
if(pIns->IsCutoffEnabled()) chn.nCutOff = pIns->GetCutoff();
if(pIns->IsResonanceEnabled()) chn.nResonance = pIns->GetResonance();
}
}
if(pSmp == nullptr)
{
chn.pModSample = nullptr;
chn.nLength = 0;
return;
}
if(bPorta && chn.nLength == 0 && (m_playBehaviour[kFT2PortaNoNote] || m_playBehaviour[kITPortaNoNote]))
{
chn.increment.Set(0);
}
if(chn.rowCommand.note == NOTE_KEYOFF && m_playBehaviour[kITInstrWithNoteOffOldEffects] && m_SongFlags[SONG_ITOLDEFFECTS] && sampleChanged)
{
if(chn.pModSample)
{
chn.dwFlags |= (chn.pModSample->uFlags & CHN_SAMPLEFLAGS);
}
chn.nInsVol = oldInsVol;
chn.nVolume = pSmp->nVolume;
if(pSmp->uFlags[CHN_PANNING]) chn.nPan = pSmp->nPan;
return;
}
chn.pModSample = pSmp;
chn.nLength = pSmp->nLength;
chn.nLoopStart = pSmp->nLoopStart;
chn.nLoopEnd = pSmp->nLoopEnd;
if(m_playBehaviour[kMODOneShotLoops] && chn.nLoopStart == 0) chn.nLoopEnd = pSmp->nLength;
chn.dwFlags |= (pSmp->uFlags & CHN_SAMPLEFLAGS);
if(m_playBehaviour[kITVibratoTremoloPanbrello])
{
chn.nAutoVibDepth = 0;
chn.nAutoVibPos = 0;
}
if(newTuning)
{
chn.nC5Speed = pSmp->nC5Speed;
chn.m_CalculateFreq = true;
chn.nFineTune = 0;
} else if(!bPorta || sampleChanged || !(GetType() & (MOD_TYPE_MOD | MOD_TYPE_XM)))
{
chn.nC5Speed = pSmp->nC5Speed;
chn.nFineTune = pSmp->nFineTune;
}
chn.nTranspose = pSmp->RelativeTone;
if(!m_playBehaviour[kFT2PortaTargetNoReset] && GetType() != MOD_TYPE_MOD)
{
chn.nPortamentoDest = 0;
}
chn.m_PortamentoFineSteps = 0;
if(chn.dwFlags[CHN_SUSTAINLOOP])
{
chn.nLoopStart = pSmp->nSustainStart;
chn.nLoopEnd = pSmp->nSustainEnd;
if(chn.dwFlags[CHN_PINGPONGSUSTAIN]) chn.dwFlags.set(CHN_PINGPONGLOOP);
chn.dwFlags.set(CHN_LOOP);
}
if(chn.dwFlags[CHN_LOOP] && chn.nLoopEnd < chn.nLength) chn.nLength = chn.nLoopEnd;
if(chn.position.GetUInt() >= chn.nLength)
{
if((GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT)))
{
chn.position.Set(0);
}
}
}
void CSoundFile::NoteChange(ModChannel &chn, int note, bool bPorta, bool bResetEnv, bool bManual, CHANNELINDEX channelHint) const
{
if (note < NOTE_MIN) return;
const ModSample *pSmp = chn.pModSample;
const ModInstrument *pIns = chn.pModInstrument;
const bool newTuning = (GetType() == MOD_TYPE_MPT && pIns != nullptr && pIns->pTuning);
const int realnote = note;
if((pIns) && (note - NOTE_MIN < (int)CountOf(pIns->Keyboard)))
{
uint32 n = pIns->Keyboard[note - NOTE_MIN];
if((n) && (n < MAX_SAMPLES))
{
pSmp = &Samples[n];
} else if(m_playBehaviour[kITEmptyNoteMapSlot] && !chn.HasMIDIOutput())
{
return;
}
note = pIns->NoteMap[note - NOTE_MIN];
}
if(note > NOTE_MAX)
{
if(note == NOTE_KEYOFF || !(GetType() & (MOD_TYPE_IT|MOD_TYPE_MPT)))
{
KeyOff(chn);
if(!bPorta && m_playBehaviour[kITInstrWithNoteOffOldEffects] && m_SongFlags[SONG_ITOLDEFFECTS] && chn.rowCommand.instr)
chn.dwFlags.reset(CHN_NOTEFADE | CHN_KEYOFF);
} else {
if( GetNumInstruments())
chn.dwFlags.set(CHN_NOTEFADE);
}
if (note == NOTE_NOTECUT)
{
if(chn.dwFlags[CHN_ADLIB] && GetType() == MOD_TYPE_S3M)
{
chn.dwFlags.set(CHN_KEYOFF);
} else
{
chn.dwFlags.set(CHN_NOTEFADE | CHN_FASTVOLRAMP);
if ((!(GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT))) || (m_nInstruments != 0 && !m_playBehaviour[kITInstrWithNoteOff])) chn.nVolume = 0;
if (m_playBehaviour[kITInstrWithNoteOff]) chn.increment.Set(0);
chn.nFadeOutVol = 0;
}
}
if(m_playBehaviour[kITClearOldNoteAfterCut])
{
chn.nNote = chn.nNewNote = NOTE_NONE;
}
return;
}
if(newTuning)
{
if(!bPorta || chn.nNote == NOTE_NONE)
chn.nPortamentoDest = 0;
else
{
chn.nPortamentoDest = pIns->pTuning->GetStepDistance(chn.nNote, chn.m_PortamentoFineSteps, static_cast<Tuning::NOTEINDEXTYPE>(note), 0);
chn.m_PortamentoFineSteps = -chn.nPortamentoDest;
}
}
if(!bPorta && (GetType() & (MOD_TYPE_XM | MOD_TYPE_MED | MOD_TYPE_MT2)))
{
if(pSmp)
{
chn.nTranspose = pSmp->RelativeTone;
chn.nFineTune = pSmp->nFineTune;
}
}
if(!bPorta && pSmp && m_playBehaviour[kITMultiSampleBehaviour])
chn.nC5Speed = pSmp->nC5Speed;
if(bPorta && !chn.IsSamplePlaying())
{
if(m_playBehaviour[kFT2PortaNoNote])
{
chn.nPeriod = 0;
return;
} else if(m_playBehaviour[kITPortaNoNote])
{
bPorta = false;
}
}
if(UseFinetuneAndTranspose())
{
note += chn.nTranspose;
Limit(note, NOTE_MIN + 11, NOTE_MIN + 130); } else
{
Limit(note, NOTE_MIN, NOTE_MAX);
}
if(m_playBehaviour[kITRealNoteMapping])
{
chn.nNote = static_cast<ModCommand::NOTE>(Clamp(realnote, NOTE_MIN, NOTE_MAX));
} else
{
chn.nNote = static_cast<ModCommand::NOTE>(note);
}
chn.m_CalculateFreq = true;
if ((!bPorta) || (GetType() & (MOD_TYPE_S3M|MOD_TYPE_IT|MOD_TYPE_MPT)))
chn.nNewIns = 0;
uint32 period = GetPeriodFromNote(note, chn.nFineTune, chn.nC5Speed);
chn.nPanbrelloOffset = 0;
if(m_playBehaviour[kITPanningReset]) ApplyInstrumentPanning(chn, pIns, pSmp);
if(bResetEnv && !bPorta)
{
chn.nVolSwing = chn.nPanSwing = 0;
chn.nResSwing = chn.nCutSwing = 0;
if(pIns)
{
if(m_playBehaviour[kITNNAReset]) chn.nNNA = pIns->nNNA;
if(!pIns->VolEnv.dwFlags[ENV_CARRY]) chn.VolEnv.Reset();
if(!pIns->PanEnv.dwFlags[ENV_CARRY]) chn.PanEnv.Reset();
if(!pIns->PitchEnv.dwFlags[ENV_CARRY]) chn.PitchEnv.Reset();
if(pIns->nVolSwing)
{
chn.nVolSwing = static_cast<int16>(((mpt::random<int8>(AccessPRNG()) * pIns->nVolSwing) / 64 + 1) * (m_playBehaviour[kITSwingBehaviour] ? chn.nInsVol : ((chn.nVolume + 1) / 2)) / 199);
}
if(pIns->nPanSwing)
{
chn.nPanSwing = static_cast<int16>(((mpt::random<int8>(AccessPRNG()) * pIns->nPanSwing * 4) / 128));
if(!m_playBehaviour[kITSwingBehaviour])
{
chn.nRestorePanOnNewNote = static_cast<int16>(chn.nPan + 1);
}
}
if(pIns->nCutSwing)
{
int32 d = ((int32)pIns->nCutSwing * (int32)(static_cast<int32>(mpt::random<int8>(AccessPRNG())) + 1)) / 128;
chn.nCutSwing = static_cast<int16>((d * chn.nCutOff + 1) / 128);
chn.nRestoreCutoffOnNewNote = chn.nCutOff + 1;
}
if(pIns->nResSwing)
{
int32 d = ((int32)pIns->nResSwing * (int32)(static_cast<int32>(mpt::random<int8>(AccessPRNG())) + 1)) / 128;
chn.nResSwing = static_cast<int16>((d * chn.nResonance + 1) / 128);
chn.nRestoreResonanceOnNewNote = chn.nResonance + 1;
}
}
}
if(!pSmp) return;
if(period)
{
if((!bPorta) || (!chn.nPeriod)) chn.nPeriod = period;
if(!newTuning)
{
if(bPorta || !(m_playBehaviour[kFT2PortaTargetNoReset] || m_playBehaviour[kITClearPortaTarget] || GetType() == MOD_TYPE_MOD))
{
chn.nPortamentoDest = period;
}
}
if(!bPorta || (!chn.nLength && !(GetType() & MOD_TYPE_S3M)))
{
chn.pModSample = pSmp;
chn.nLength = pSmp->nLength;
chn.nLoopEnd = pSmp->nLength;
chn.nLoopStart = 0;
chn.position.Set(0);
if((m_SongFlags[SONG_PT_MODE] || m_playBehaviour[kST3OffsetWithoutInstrument]) && !chn.rowCommand.instr)
{
chn.position.SetInt(std::min<SmpLength>(chn.prevNoteOffset, chn.nLength - 1));
} else
{
chn.prevNoteOffset = 0;
}
chn.dwFlags = (chn.dwFlags & CHN_CHANNELFLAGS) | (pSmp->uFlags & CHN_SAMPLEFLAGS);
chn.dwFlags.reset(CHN_PORTAMENTO);
if(chn.dwFlags[CHN_SUSTAINLOOP])
{
chn.nLoopStart = pSmp->nSustainStart;
chn.nLoopEnd = pSmp->nSustainEnd;
chn.dwFlags.set(CHN_PINGPONGLOOP, chn.dwFlags[CHN_PINGPONGSUSTAIN]);
chn.dwFlags.set(CHN_LOOP);
if (chn.nLength > chn.nLoopEnd) chn.nLength = chn.nLoopEnd;
} else if(chn.dwFlags[CHN_LOOP])
{
chn.nLoopStart = pSmp->nLoopStart;
chn.nLoopEnd = pSmp->nLoopEnd;
if (chn.nLength > chn.nLoopEnd) chn.nLength = chn.nLoopEnd;
}
if(m_playBehaviour[kMODOneShotLoops] && chn.nLoopStart == 0) chn.nLoopEnd = chn.nLength = pSmp->nLength;
if(chn.dwFlags[CHN_REVERSE])
{
chn.dwFlags.set(CHN_PINGPONGFLAG);
chn.position.SetInt(chn.nLength - 1);
}
if(chn.nVibratoType < 4)
{
if(!m_playBehaviour[kITVibratoTremoloPanbrello] && (GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT)) && !m_SongFlags[SONG_ITOLDEFFECTS])
chn.nVibratoPos = 0x10;
else if(GetType() == MOD_TYPE_MTM)
chn.nVibratoPos = 0x20;
else if(!(GetType() & (MOD_TYPE_DIGI | MOD_TYPE_DBM)))
chn.nVibratoPos = 0;
}
if(!m_playBehaviour[kITVibratoTremoloPanbrello] && chn.nTremoloType < 4)
{
chn.nTremoloPos = 0;
}
}
if(chn.position.GetUInt() >= chn.nLength) chn.position.SetInt(chn.nLoopStart);
} else
{
bPorta = false;
}
if (!bPorta
|| (!(GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT | MOD_TYPE_DBM)))
|| (chn.dwFlags[CHN_NOTEFADE] && !chn.nFadeOutVol)
|| (m_SongFlags[SONG_ITCOMPATGXX] && chn.rowCommand.instr != 0))
{
if((GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT | MOD_TYPE_DBM)) && chn.dwFlags[CHN_NOTEFADE] && !chn.nFadeOutVol)
{
chn.ResetEnvelopes();
if(!m_playBehaviour[kITVibratoTremoloPanbrello])
{
chn.nAutoVibDepth = 0;
chn.nAutoVibPos = 0;
}
chn.dwFlags.reset(CHN_NOTEFADE);
chn.nFadeOutVol = 65536;
}
if ((!bPorta) || (!m_SongFlags[SONG_ITCOMPATGXX]) || (chn.rowCommand.instr))
{
if ((!(GetType() & (MOD_TYPE_XM|MOD_TYPE_MT2))) || (chn.rowCommand.instr))
{
chn.dwFlags.reset(CHN_NOTEFADE);
chn.nFadeOutVol = 65536;
}
}
}
if(m_playBehaviour[kITDontResetNoteOffOnPorta] && bPorta && (!m_SongFlags[SONG_ITCOMPATGXX] || chn.rowCommand.instr == 0))
chn.dwFlags.reset(CHN_EXTRALOUD);
else
chn.dwFlags.reset(CHN_EXTRALOUD | CHN_KEYOFF);
if(!bPorta)
{
chn.nLeftVU = chn.nRightVU = 0xFF;
chn.dwFlags.reset(CHN_FILTER);
chn.dwFlags.set(CHN_FASTVOLRAMP);
if(!m_playBehaviour[kITRetrigger] && !m_playBehaviour[kITTremor])
{
if(!m_playBehaviour[kFT2Retrigger] && !m_playBehaviour[kFT2Tremor])
{
chn.nRetrigCount = 0;
chn.nTremorCount = 0;
}
}
if(bResetEnv)
{
chn.nAutoVibDepth = 0;
chn.nAutoVibPos = 0;
}
chn.rightVol = chn.leftVol = 0;
bool useFilter = !m_SongFlags[SONG_MPTFILTERMODE];
if(pIns)
{
if(pIns->IsResonanceEnabled())
{
chn.nResonance = pIns->GetResonance();
useFilter = true;
}
if(pIns->IsCutoffEnabled())
{
chn.nCutOff = pIns->GetCutoff();
useFilter = true;
}
if(useFilter && (pIns->nFilterMode != FLTMODE_UNCHANGED))
{
chn.nFilterMode = pIns->nFilterMode;
}
} else
{
chn.nVolSwing = chn.nPanSwing = 0;
chn.nCutSwing = chn.nResSwing = 0;
}
if((chn.nCutOff < 0x7F || m_playBehaviour[kITFilterBehaviour]) && useFilter)
{
int cutoff = SetupChannelFilter(chn, true);
if(cutoff >= 0 && chn.dwFlags[CHN_ADLIB] && m_opl)
m_opl->Volume(channelHint, chn.nCutOff / 2u, true);
}
}
if (bManual) chn.dwFlags.reset(CHN_MUTE);
if((chn.dwFlags[CHN_MUTE] && (m_MixerSettings.MixerFlags & SNDMIX_MUTECHNMODE))
|| (chn.pModSample != nullptr && chn.pModSample->uFlags[CHN_MUTE] && !bManual)
|| (chn.pModInstrument != nullptr && chn.pModInstrument->dwFlags[INS_MUTE] && !bManual))
{
if (!bManual) chn.nPeriod = 0;
}
if(!bPorta)
{
chn.paulaState.Reset();
}
}
void CSoundFile::ApplyInstrumentPanning(ModChannel &chn, const ModInstrument *instr, const ModSample *smp) const
{
int32 newPan = int32_min;
if(instr != nullptr && instr->dwFlags[INS_SETPANNING])
newPan = instr->nPan;
if(smp != nullptr && smp->uFlags[CHN_PANNING])
newPan = smp->nPan;
if(newPan != int32_min)
{
chn.nPan = newPan;
if(m_playBehaviour[kPanOverride] && !m_SongFlags[SONG_SURROUNDPAN])
{
chn.dwFlags.reset(CHN_SURROUND);
}
}
}
CHANNELINDEX CSoundFile::GetNNAChannel(CHANNELINDEX nChn) const
{
for (CHANNELINDEX i = m_nChannels; i < MAX_CHANNELS; i++)
{
const ModChannel &c = m_PlayState.Chn[i];
if(!c.nLength && !c.HasMIDIOutput())
return i;
if(!c.nLength && c.dwFlags[CHN_KEYOFF | CHN_NOTEFADE])
return i;
}
uint32 vol = 0x800000;
if(nChn < MAX_CHANNELS)
{
const ModChannel &srcChn = m_PlayState.Chn[nChn];
if(!srcChn.nFadeOutVol && srcChn.nLength) return 0;
vol = (srcChn.nRealVolume << 9) | srcChn.nVolume;
}
CHANNELINDEX result = 0;
uint32 envpos = 0;
for (CHANNELINDEX i = m_nChannels; i < MAX_CHANNELS; i++)
{
const ModChannel &c = m_PlayState.Chn[i];
if(c.nLength && !c.nFadeOutVol)
return i;
uint32 v = (c.nRealVolume << 9) | c.nVolume;
if(c.dwFlags[CHN_LOOP]) v /= 2;
if((v < vol) || ((v == vol) && (c.VolEnv.nEnvPosition > envpos)))
{
envpos = c.VolEnv.nEnvPosition;
vol = v;
result = i;
}
}
return result;
}
CHANNELINDEX CSoundFile::CheckNNA(CHANNELINDEX nChn, uint32 instr, int note, bool forceCut)
{
CHANNELINDEX nnaChn = CHANNELINDEX_INVALID;
ModChannel &srcChn = m_PlayState.Chn[nChn];
const ModInstrument *pIns = nullptr;
if(!ModCommand::IsNote(static_cast<ModCommand::NOTE>(note)))
{
return nnaChn;
}
if((!(GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT | MOD_TYPE_MT2)) || !m_nInstruments || forceCut) && !srcChn.HasMIDIOutput())
{
if(!srcChn.nLength || srcChn.dwFlags[CHN_MUTE] || !(srcChn.rightVol | srcChn.leftVol))
{
return CHANNELINDEX_INVALID;
}
nnaChn = GetNNAChannel(nChn);
if(!nnaChn) return CHANNELINDEX_INVALID;
ModChannel &chn = m_PlayState.Chn[nnaChn];
chn = srcChn;
chn.dwFlags.reset(CHN_VIBRATO | CHN_TREMOLO | CHN_MUTE | CHN_PORTAMENTO);
chn.nPanbrelloOffset = 0;
chn.nMasterChn = nChn + 1;
chn.nCommand = CMD_NONE;
chn.rowCommand.Clear();
chn.nFadeOutVol = 0;
chn.dwFlags.set(CHN_NOTEFADE | CHN_FASTVOLRAMP);
srcChn.nLength = 0;
srcChn.position.Set(0);
srcChn.nROfs = srcChn.nLOfs = 0;
srcChn.rightVol = srcChn.leftVol = 0;
if(srcChn.dwFlags[CHN_ADLIB] && m_opl)
{
m_opl->NoteCut(nChn);
}
return nnaChn;
}
if(instr > GetNumInstruments()) instr = 0;
const ModSample *pSample = srcChn.pModSample;
pIns = instr > 0 ? Instruments[instr] : srcChn.pModInstrument;
if(pIns != nullptr)
{
uint32 n = pIns->Keyboard[note - NOTE_MIN];
note = pIns->NoteMap[note - NOTE_MIN];
if ((n) && (n < MAX_SAMPLES))
{
pSample = &Samples[n];
} else if(m_playBehaviour[kITEmptyNoteMapSlot] && !pIns->HasValidMIDIChannel())
{
return CHANNELINDEX_INVALID;
}
}
if (srcChn.dwFlags[CHN_MUTE])
return CHANNELINDEX_INVALID;
for(CHANNELINDEX i = nChn; i < MAX_CHANNELS; i++)
if(i >= m_nChannels || i == nChn)
{
ModChannel &chn = m_PlayState.Chn[i];
bool applyDNAtoPlug = false;
if((chn.nMasterChn == nChn + 1 || i == nChn) && chn.pModInstrument != nullptr)
{
bool bOk = false;
switch(chn.pModInstrument->nDCT)
{
case DCT_NONE:
break;
case DCT_NOTE:
if(note && chn.nNote == note && pIns == chn.pModInstrument) bOk = true;
if(pIns && pIns->nMixPlug) applyDNAtoPlug = true;
break;
case DCT_SAMPLE:
if(pSample != nullptr && pSample == chn.pModSample) bOk = true;
break;
case DCT_INSTRUMENT:
if(pIns == chn.pModInstrument) bOk = true;
if(pIns && pIns->nMixPlug) applyDNAtoPlug = true;
break;
case DCT_PLUGIN:
if(pIns && (pIns->nMixPlug) && (pIns->nMixPlug == chn.pModInstrument->nMixPlug))
{
applyDNAtoPlug = true;
bOk = true;
}
break;
}
if (bOk)
{
#ifndef NO_PLUGINS
if (applyDNAtoPlug && chn.nNote != NOTE_NONE)
{
switch(chn.pModInstrument->nDNA)
{
case DNA_NOTECUT:
case DNA_NOTEOFF:
case DNA_NOTEFADE:
SendMIDINote(i, chn.GetPluginNote(m_playBehaviour[kITRealNoteMapping]) + NOTE_MAX_SPECIAL, 0);
chn.nArpeggioLastNote = NOTE_NONE;
break;
}
}
#endif
switch(chn.pModInstrument->nDNA)
{
case DNA_NOTECUT:
KeyOff(chn);
chn.nVolume = 0;
if(chn.dwFlags[CHN_ADLIB] && m_opl)
m_opl->NoteCut(i);
break;
case DNA_NOTEOFF:
KeyOff(chn);
if(chn.dwFlags[CHN_ADLIB] && m_opl)
m_opl->NoteOff(i);
break;
case DNA_NOTEFADE:
chn.dwFlags.set(CHN_NOTEFADE);
if(chn.dwFlags[CHN_ADLIB] && m_opl)
m_opl->NoteOff(i);
break;
}
if(!chn.nVolume)
{
chn.nFadeOutVol = 0;
chn.dwFlags.set(CHN_NOTEFADE | CHN_FASTVOLRAMP);
}
}
}
}
bool applyNNAtoPlug = false;
#ifndef NO_PLUGINS
IMixPlugin *pPlugin = nullptr;
if(srcChn.HasMIDIOutput() && ModCommand::IsNote(srcChn.nNote)) {
PLUGINDEX plugin = GetBestPlugin(nChn, PrioritiseInstrument, RespectMutes);
if(plugin > 0 && plugin <= MAX_MIXPLUGINS)
{
pPlugin = m_MixPlugins[plugin - 1].pMixPlugin;
if(pPlugin)
{
applyNNAtoPlug = pPlugin->IsNotePlaying(srcChn.GetPluginNote(m_playBehaviour[kITRealNoteMapping]), nChn);
}
}
}
#endif
if(srcChn.IsSamplePlaying() || applyNNAtoPlug)
{
nnaChn = GetNNAChannel(nChn);
if(nnaChn != 0)
{
ModChannel &chn = m_PlayState.Chn[nnaChn];
chn = srcChn;
chn.dwFlags.reset(CHN_VIBRATO | CHN_TREMOLO | CHN_PORTAMENTO);
chn.nPanbrelloOffset = 0;
chn.nMasterChn = nChn < GetNumChannels() ? nChn + 1 : 0;
chn.nCommand = CMD_NONE;
#ifndef NO_PLUGINS
if(applyNNAtoPlug && pPlugin)
{
switch(srcChn.nNNA)
{
case NNA_NOTEOFF:
case NNA_NOTECUT:
case NNA_NOTEFADE:
SendMIDINote(nChn, NOTE_KEYOFF, 0);
srcChn.nArpeggioLastNote = NOTE_NONE;
break;
case NNA_CONTINUE:
break;
}
}
#endif
switch(srcChn.nNNA)
{
case NNA_NOTEOFF:
KeyOff(chn);
if(chn.dwFlags[CHN_ADLIB] && m_opl)
m_opl->NoteOff(nChn);
break;
case NNA_NOTECUT:
chn.nFadeOutVol = 0;
chn.dwFlags.set(CHN_NOTEFADE);
if(chn.dwFlags[CHN_ADLIB] && m_opl)
m_opl->NoteCut(nChn);
break;
case NNA_NOTEFADE:
chn.dwFlags.set(CHN_NOTEFADE);
if(chn.dwFlags[CHN_ADLIB] && m_opl)
m_opl->NoteOff(nChn);
break;
case NNA_CONTINUE:
if(chn.dwFlags[CHN_ADLIB] && m_opl)
m_opl->MoveChannel(nChn, nnaChn);
break;
}
if(!chn.nVolume)
{
chn.nFadeOutVol = 0;
chn.dwFlags.set(CHN_NOTEFADE | CHN_FASTVOLRAMP);
}
srcChn.nLength = 0;
srcChn.position.Set(0);
srcChn.nROfs = srcChn.nLOfs = 0;
}
}
return nnaChn;
}
bool CSoundFile::ProcessEffects()
{
ROWINDEX nBreakRow = ROWINDEX_INVALID; ROWINDEX nPatLoopRow = ROWINDEX_INVALID; ORDERINDEX nPosJump = ORDERINDEX_INVALID;
for(CHANNELINDEX nChn = 0; nChn < GetNumChannels(); nChn++)
{
ModChannel &chn = m_PlayState.Chn[nChn];
const uint32 tickCount = m_PlayState.m_nTickCount % (m_PlayState.m_nMusicSpeed + m_PlayState.m_nFrameDelay);
uint32 instr = chn.rowCommand.instr;
ModCommand::VOLCMD volcmd = chn.rowCommand.volcmd;
uint32 vol = chn.rowCommand.vol;
ModCommand::COMMAND cmd = chn.rowCommand.command;
uint32 param = chn.rowCommand.param;
bool bPorta = chn.rowCommand.IsPortamento();
uint32 nStartTick = 0;
chn.isFirstTick = m_SongFlags[SONG_FIRSTTICK];
if(chn.rowCommand.note == NOTE_PC)
{
#ifndef NO_PLUGINS
const PLUGINDEX plug = chn.rowCommand.instr;
const PlugParamIndex plugparam = chn.rowCommand.GetValueVolCol();
const PlugParamValue value = chn.rowCommand.GetValueEffectCol() / PlugParamValue(ModCommand::maxColumnValue);
if(plug > 0 && plug <= MAX_MIXPLUGINS && m_MixPlugins[plug - 1].pMixPlugin)
m_MixPlugins[plug-1].pMixPlugin->SetParameter(plugparam, value);
#endif }
if(chn.rowCommand.note == NOTE_PCS || (cmd == CMD_NONE && chn.m_plugParamValueStep != 0))
{
#ifndef NO_PLUGINS
const bool isFirstTick = m_SongFlags[SONG_FIRSTTICK];
if(isFirstTick)
chn.m_RowPlug = chn.rowCommand.instr;
const PLUGINDEX plugin = chn.m_RowPlug;
const bool hasValidPlug = (plugin > 0 && plugin <= MAX_MIXPLUGINS && m_MixPlugins[plugin - 1].pMixPlugin);
if(hasValidPlug)
{
if(isFirstTick)
chn.m_RowPlugParam = ModCommand::GetValueVolCol(chn.rowCommand.volcmd, chn.rowCommand.vol);
const PlugParamIndex plugparam = chn.m_RowPlugParam;
if(isFirstTick)
{
PlugParamValue targetvalue = ModCommand::GetValueEffectCol(chn.rowCommand.command, chn.rowCommand.param) / PlugParamValue(ModCommand::maxColumnValue);
chn.m_plugParamTargetValue = targetvalue;
chn.m_plugParamValueStep = (targetvalue - m_MixPlugins[plugin - 1].pMixPlugin->GetParameter(plugparam)) / PlugParamValue(GetNumTicksOnCurrentRow());
}
if(m_PlayState.m_nTickCount + 1 == GetNumTicksOnCurrentRow())
{ m_MixPlugins[plugin - 1].pMixPlugin->SetParameter(plugparam, chn.m_plugParamTargetValue);
}
else
m_MixPlugins[plugin - 1].pMixPlugin->ModifyParameter(plugparam, chn.m_plugParamValueStep);
}
#endif }
if(ModCommand::IsPcNote(chn.rowCommand.note))
{
chn.ClearRowCmd();
instr = 0;
volcmd = VOLCMD_NONE;
vol = 0;
cmd = CMD_NONE;
param = 0;
bPorta = false;
}
if(!m_SongFlags[SONG_FIRSTTICK])
{
InvertLoop(m_PlayState.Chn[nChn]);
} else
{
if(instr) m_PlayState.Chn[nChn].nEFxOffset = 0;
}
if (cmd == CMD_DELAYCUT)
{
nStartTick = (param & 0xF0) >> 4;
const uint32 cutAtTick = nStartTick + (param & 0x0F);
NoteCut(nChn, cutAtTick, m_playBehaviour[kITSCxStopsSample]);
} else if ((cmd == CMD_MODCMDEX) || (cmd == CMD_S3MCMDEX))
{
if ((!param) && (GetType() & (MOD_TYPE_S3M|MOD_TYPE_IT|MOD_TYPE_MPT)))
param = chn.nOldCmdEx;
else
chn.nOldCmdEx = static_cast<ModCommand::PARAM>(param);
if ((param & 0xF0) == 0xD0)
{
nStartTick = param & 0x0F;
if(nStartTick == 0)
{
if(GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT))
nStartTick = 1;
else if(GetType() == MOD_TYPE_S3M)
continue;
} else if(nStartTick >= (m_PlayState.m_nMusicSpeed + m_PlayState.m_nFrameDelay) && m_playBehaviour[kITOutOfRangeDelay])
{
if(instr)
{
chn.nNewIns = static_cast<ModCommand::INSTR>(instr);
}
continue;
}
} else if(m_SongFlags[SONG_FIRSTTICK])
{
if((((param & 0xF0) == 0x60 && cmd == CMD_MODCMDEX)
|| ((param & 0xF0) == 0xB0 && cmd == CMD_S3MCMDEX))
&& !(m_playBehaviour[kST3NoMutedChannels] && ChnSettings[nChn].dwFlags[CHN_MUTE])) {
ROWINDEX nloop = PatternLoop(chn, param & 0x0F);
if (nloop != ROWINDEX_INVALID)
{
if(nBreakRow != ROWINDEX_INVALID && m_playBehaviour[kFT2PatternLoopWithJumps])
{
nBreakRow = nloop;
}
nPatLoopRow = nloop;
}
if(GetType() == MOD_TYPE_S3M)
{
for (CHANNELINDEX i = 0; i < GetNumChannels(); i++)
{
m_PlayState.Chn[i].nPatternLoop = chn.nPatternLoop;
m_PlayState.Chn[i].nPatternLoopCount = chn.nPatternLoopCount;
}
}
} else if ((param & 0xF0) == 0xE0)
{
if(!(GetType() & (MOD_TYPE_S3M | MOD_TYPE_IT | MOD_TYPE_MPT)) || !m_PlayState.m_nPatternDelay)
{
if(!(GetType() & (MOD_TYPE_S3M)) || (param & 0x0F) != 0)
{
m_PlayState.m_nPatternDelay = 1 + (param & 0x0F);
}
}
}
}
}
if(GetType() == MOD_TYPE_MTM && cmd == CMD_MODCMDEX && (param & 0xF0) == 0xD0)
{
nStartTick = 0;
param = 0x90 | (param & 0x0F);
}
if(nStartTick != 0 && chn.rowCommand.note == NOTE_KEYOFF && chn.rowCommand.volcmd == VOLCMD_PANNING && m_playBehaviour[kFT2PanWithDelayedNoteOff])
{
chn.rowCommand.volcmd = VOLCMD_NONE;
}
bool triggerNote = (m_PlayState.m_nTickCount == nStartTick); if(m_playBehaviour[kFT2OutOfRangeDelay] && nStartTick >= m_PlayState.m_nMusicSpeed)
{
triggerNote = false;
} else if(m_playBehaviour[kRowDelayWithNoteDelay] && nStartTick > 0 && tickCount == nStartTick)
{
triggerNote = true;
}
if(m_playBehaviour[kITFirstTickHandling])
{
chn.isFirstTick = tickCount == nStartTick;
}
if(m_playBehaviour[kFT2PortaDelay] && nStartTick != 0)
{
bPorta = false;
}
if(m_SongFlags[SONG_PT_MODE] && instr && !m_PlayState.m_nTickCount)
{
chn.prevNoteOffset = 0;
if(!triggerNote && chn.IsSamplePlaying())
{
chn.nNewIns = static_cast<ModCommand::INSTR>(instr);
if(instr <= GetNumSamples())
{
chn.nVolume = Samples[instr].nVolume;
chn.nFineTune = Samples[instr].nFineTune;
}
}
}
if(triggerNote)
{
ModCommand::NOTE note = chn.rowCommand.note;
if(instr) chn.nNewIns = static_cast<ModCommand::INSTR>(instr);
if(ModCommand::IsNote(note) && m_playBehaviour[kFT2Transpose])
{
int transpose = chn.nTranspose;
if(instr && !bPorta)
{
SAMPLEINDEX sample = SAMPLEINDEX_INVALID;
if(GetNumInstruments())
{
if(instr <= GetNumInstruments() && Instruments[instr] != nullptr)
{
sample = Instruments[instr]->Keyboard[note - NOTE_MIN];
}
} else
{
sample = static_cast<SAMPLEINDEX>(instr);
}
if(sample <= GetNumSamples())
{
transpose = GetSample(sample).RelativeTone;
}
}
const int computedNote = note + transpose;
if((computedNote < NOTE_MIN + 11 || computedNote > NOTE_MIN + 130))
{
note = NOTE_NONE;
}
} else if((GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT)) && GetNumInstruments() != 0 && ModCommand::IsNoteOrEmpty(static_cast<ModCommand::NOTE>(note)))
{
INSTRUMENTINDEX instrToCheck = static_cast<INSTRUMENTINDEX>((instr != 0) ? instr : chn.nOldIns);
if(instrToCheck != 0 && (instrToCheck > GetNumInstruments() || Instruments[instrToCheck] == nullptr))
{
note = NOTE_NONE;
instr = 0;
}
}
if(cmd == CMD_KEYOFF && param == 0 && m_playBehaviour[kFT2KeyOff])
{
note = NOTE_NONE;
instr = 0;
}
bool retrigEnv = note == NOTE_NONE && instr != 0;
bool reloadSampleSettings = (m_playBehaviour[kFT2ReloadSampleSettings] && instr != 0);
bool keepInstr = (GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT)) || (m_playBehaviour[kMODSampleSwap] && !chn.IsSamplePlaying() && chn.pModSample != nullptr && !chn.pModSample->HasSampleData());
if (GetType() & (MOD_TYPE_XM | MOD_TYPE_MT2))
{
if(note == NOTE_KEYOFF
&& ((!instr && volcmd != VOLCMD_VOLUME && cmd != CMD_VOLUME) || !m_playBehaviour[kFT2KeyOff])
&& (chn.pModInstrument == nullptr || !chn.pModInstrument->VolEnv.dwFlags[ENV_ENABLED]))
{
chn.dwFlags.set(CHN_FASTVOLRAMP);
chn.nVolume = 0;
note = NOTE_NONE;
instr = 0;
retrigEnv = false;
if(m_SongFlags[SONG_FIRSTTICK] && m_playBehaviour[kFT2NoteOffFlags])
chn.dwFlags.set(CHN_NOTEFADE);
} else if(m_playBehaviour[kFT2RetrigWithNoteDelay] && !m_SongFlags[SONG_FIRSTTICK])
{
retrigEnv = true;
bPorta = false;
if(note == NOTE_NONE)
{
note = static_cast<ModCommand::NOTE>(chn.nNote - chn.nTranspose);
} else if(note >= NOTE_MIN_SPECIAL)
{
note = NOTE_NONE;
keepInstr = false;
reloadSampleSettings = true;
} else if(instr || !m_playBehaviour[kFT2NoteDelayWithoutInstr])
{
keepInstr = true;
reloadSampleSettings = true;
}
}
}
if((retrigEnv && !m_playBehaviour[kFT2ReloadSampleSettings]) || reloadSampleSettings)
{
const ModSample *oldSample = nullptr;
if(GetNumInstruments())
{
oldSample = chn.pModSample;
} else if (instr <= GetNumSamples())
{
oldSample = &Samples[instr];
}
if(oldSample != nullptr)
{
if(!oldSample->uFlags[SMP_NODEFAULTVOLUME])
chn.nVolume = oldSample->nVolume;
if(reloadSampleSettings)
{
chn.nPan = oldSample->nPan;
}
}
}
if(m_playBehaviour[kFT2Tremor] && instr != 0)
{
chn.nTremorCount = 0x20;
}
if(GetNumInstruments() && m_playBehaviour[kITInstrWithNoteOffOldEffects]
&& instr && !ModCommand::IsNote(note))
{
if((bPorta && m_SongFlags[SONG_ITCOMPATGXX])
|| (!bPorta && m_SongFlags[SONG_ITOLDEFFECTS]))
{
chn.ResetEnvelopes();
chn.dwFlags.set(CHN_FASTVOLRAMP);
chn.nFadeOutVol = 65536;
}
}
if(retrigEnv) {
if(m_playBehaviour[kITInstrWithoutNote] || GetType() == MOD_TYPE_PLM)
{
bool triggerAfterSmpEnd = m_playBehaviour[kITMultiSampleInstrumentNumber] && !chn.IsSamplePlaying();
if(GetNumInstruments())
{
if(instr <= GetNumInstruments() && (chn.pModInstrument != Instruments[instr] || triggerAfterSmpEnd))
note = chn.nNote;
} else
{
if(instr < MAX_SAMPLES && (chn.pModSample != &Samples[instr] || triggerAfterSmpEnd))
note = chn.nNote;
}
}
if (GetNumInstruments() && (GetType() & (MOD_TYPE_XM|MOD_TYPE_MT2)))
{
chn.ResetEnvelopes();
chn.dwFlags.set(CHN_FASTVOLRAMP);
chn.dwFlags.reset(CHN_NOTEFADE);
chn.nAutoVibDepth = 0;
chn.nAutoVibPos = 0;
chn.nFadeOutVol = 65536;
if(m_playBehaviour[kFT2NoteOffFlags])
chn.dwFlags.reset(CHN_KEYOFF);
}
if (!keepInstr) instr = 0;
}
if (note >= NOTE_MIN_SPECIAL)
{
if(m_playBehaviour[kITInstrWithNoteOff] && instr)
{
SAMPLEINDEX smp = static_cast<SAMPLEINDEX>(instr);
if(GetNumInstruments())
{
smp = 0;
if(instr <= GetNumInstruments() && Instruments[instr] != nullptr && ModCommand::IsNote(chn.nLastNote))
{
smp = Instruments[instr]->Keyboard[chn.nLastNote - NOTE_MIN];
}
}
if(smp > 0 && smp <= GetNumSamples() && !Samples[smp].uFlags[SMP_NODEFAULTVOLUME])
chn.nVolume = Samples[smp].nVolume;
}
if(!m_playBehaviour[kITInstrWithNoteOffOldEffects] || !m_SongFlags[SONG_ITOLDEFFECTS])
instr = 0;
}
if(ModCommand::IsNote(note))
{
chn.nNewNote = chn.nLastNote = note;
if(!bPorta)
{
CheckNNA(nChn, instr, note, false);
}
}
if(note)
{
if(chn.nRestorePanOnNewNote > 0)
{
chn.nPan = chn.nRestorePanOnNewNote - 1;
chn.nRestorePanOnNewNote = 0;
}
if(chn.nRestoreResonanceOnNewNote > 0)
{
chn.nResonance = chn.nRestoreResonanceOnNewNote - 1;
chn.nRestoreResonanceOnNewNote = 0;
}
if(chn.nRestoreCutoffOnNewNote > 0)
{
chn.nCutOff = chn.nRestoreCutoffOnNewNote - 1;
chn.nRestoreCutoffOnNewNote = 0;
}
}
if(instr)
{
const ModSample *oldSample = chn.pModSample;
InstrumentChange(chn, instr, bPorta, true);
if(chn.pModSample != nullptr && chn.pModSample->uFlags[CHN_ADLIB] && m_opl)
{
m_opl->Patch(nChn, chn.pModSample->adlib);
}
if(GetType() == MOD_TYPE_MOD)
{
if(!bPorta || !m_playBehaviour[kMODSampleSwap]) chn.nNewIns = 0;
} else
{
if(!m_playBehaviour[kITInstrWithNoteOff] || ModCommand::IsNote(note)) chn.nNewIns = 0;
}
if(m_playBehaviour[kITPortamentoSwapResetsPos])
{
if(ModCommand::IsNote(note) && oldSample != chn.pModSample)
{
chn.position.Set(0);
}
} else if ((GetType() & (MOD_TYPE_S3M | MOD_TYPE_IT | MOD_TYPE_MPT) && oldSample != chn.pModSample && ModCommand::IsNote(note)))
{
bPorta = false;
} else if(m_playBehaviour[kMODSampleSwap] && chn.increment.IsZero())
{
chn.position.Set(0);
}
}
if (note != NOTE_NONE)
{
const bool instrChange = (!instr) && (chn.nNewIns) && ModCommand::IsNote(note);
if(instrChange)
{
InstrumentChange(chn, chn.nNewIns, bPorta, chn.pModSample == nullptr && chn.pModInstrument == nullptr, !(GetType() & (MOD_TYPE_XM|MOD_TYPE_MT2)));
chn.nNewIns = 0;
}
if(chn.pModSample != nullptr && chn.pModSample->uFlags[CHN_ADLIB] && m_opl && (instrChange || !m_opl->IsActive(nChn)))
{
m_opl->Patch(nChn, chn.pModSample->adlib);
}
NoteChange(chn, note, bPorta, !(GetType() & (MOD_TYPE_XM | MOD_TYPE_MT2)), false, nChn);
if ((bPorta) && (GetType() & (MOD_TYPE_XM|MOD_TYPE_MT2)) && (instr))
{
chn.dwFlags.set(CHN_FASTVOLRAMP);
chn.ResetEnvelopes();
chn.nAutoVibDepth = 0;
chn.nAutoVibPos = 0;
}
if(chn.dwFlags[CHN_ADLIB] && m_opl
&& ((note == NOTE_NOTECUT || note == NOTE_KEYOFF) || (note == NOTE_FADE && !m_playBehaviour[kOPLFlexibleNoteOff])))
{
m_opl->NoteOff(nChn);
}
}
if (volcmd == VOLCMD_VOLUME)
{
if (vol > 64) vol = 64;
chn.nVolume = vol << 2;
chn.dwFlags.set(CHN_FASTVOLRAMP);
} else
if (volcmd == VOLCMD_PANNING)
{
Panning(chn, vol, Pan6bit);
}
#ifndef NO_PLUGINS
if (m_nInstruments) ProcessMidiOut(nChn);
#endif }
if(m_playBehaviour[kST3NoMutedChannels] && ChnSettings[nChn].dwFlags[CHN_MUTE]) continue;
bool doVolumeColumn = m_PlayState.m_nTickCount >= nStartTick;
if(m_playBehaviour[kFT2VolColDelay] && nStartTick != 0)
{
doVolumeColumn = m_PlayState.m_nTickCount != 0 && (m_PlayState.m_nTickCount != nStartTick || (chn.rowCommand.instr == 0 && volcmd != VOLCMD_TONEPORTAMENTO));
}
if(volcmd > VOLCMD_PANNING && doVolumeColumn)
{
if (volcmd == VOLCMD_TONEPORTAMENTO)
{
uint32 porta = 0;
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))
{
porta = ImpulseTrackerPortaVolCmd[vol & 0x0F];
} else
{
if(cmd == CMD_TONEPORTAMENTO && GetType() == MOD_TYPE_XM)
{
cmd = CMD_NONE;
vol *= 2;
}
porta = vol << 4;
if(m_playBehaviour[kFT2PortaDelay] && nStartTick != 0)
{
porta = 0;
}
}
TonePortamento(chn, porta);
} else
{
if(m_playBehaviour[kFT2VolColMemory] && vol == 0)
{
switch(volcmd)
{
case VOLCMD_VOLUME:
case VOLCMD_PANNING:
case VOLCMD_VIBRATODEPTH:
break;
case VOLCMD_PANSLIDELEFT:
if(!m_SongFlags[SONG_FIRSTTICK])
{
chn.nPan = 0;
}
MPT_FALLTHROUGH;
default:
volcmd = VOLCMD_NONE;
}
} else if(!m_playBehaviour[kITVolColMemory])
{
if(vol) chn.nOldVolParam = static_cast<ModCommand::PARAM>(vol); else vol = chn.nOldVolParam;
}
switch(volcmd)
{
case VOLCMD_VOLSLIDEUP:
case VOLCMD_VOLSLIDEDOWN:
if(vol == 0 && m_playBehaviour[kITVolColMemory])
{
vol = chn.nOldVolParam;
if(vol == 0)
break;
} else
{
chn.nOldVolParam = static_cast<ModCommand::PARAM>(vol);
}
VolumeSlide(chn, static_cast<ModCommand::PARAM>(volcmd == VOLCMD_VOLSLIDEUP ? (vol << 4) : vol));
break;
case VOLCMD_FINEVOLUP:
if(m_PlayState.m_nTickCount == nStartTick || !m_playBehaviour[kITVolColMemory])
{
FineVolumeUp(chn, static_cast<ModCommand::PARAM>(vol), m_playBehaviour[kITVolColMemory]);
}
break;
case VOLCMD_FINEVOLDOWN:
if(m_PlayState.m_nTickCount == nStartTick || !m_playBehaviour[kITVolColMemory])
{
FineVolumeDown(chn, static_cast<ModCommand::PARAM>(vol), m_playBehaviour[kITVolColMemory]);
}
break;
case VOLCMD_VIBRATOSPEED:
if(m_playBehaviour[kFT2VolColVibrato])
chn.nVibratoSpeed = vol & 0x0F;
else
Vibrato(chn, vol << 4);
break;
case VOLCMD_VIBRATODEPTH:
Vibrato(chn, vol);
break;
case VOLCMD_PANSLIDELEFT:
PanningSlide(chn, static_cast<ModCommand::PARAM>(vol), !m_playBehaviour[kFT2VolColMemory]);
break;
case VOLCMD_PANSLIDERIGHT:
PanningSlide(chn, static_cast<ModCommand::PARAM>(vol << 4), !m_playBehaviour[kFT2VolColMemory]);
break;
case VOLCMD_PORTAUP:
PortamentoUp(nChn, static_cast<ModCommand::PARAM>(vol << 2), m_playBehaviour[kITVolColFinePortamento]);
break;
case VOLCMD_PORTADOWN:
PortamentoDown(nChn, static_cast<ModCommand::PARAM>(vol << 2), m_playBehaviour[kITVolColFinePortamento]);
break;
case VOLCMD_OFFSET:
if (triggerNote && chn.pModSample && vol <= CountOf(chn.pModSample->cues))
{
SmpLength offset;
if(vol == 0)
offset = chn.oldOffset;
else
offset = chn.oldOffset = chn.pModSample->cues[vol - 1];
SampleOffset(chn, offset);
}
break;
}
}
}
if(cmd != CMD_NONE) switch (cmd)
{
case CMD_VOLUME:
if(m_SongFlags[SONG_FIRSTTICK])
{
chn.nVolume = (param < 64) ? param * 4 : 256;
chn.dwFlags.set(CHN_FASTVOLRAMP);
}
break;
case CMD_PORTAMENTOUP:
if ((!param) && (GetType() & MOD_TYPE_MOD)) break;
PortamentoUp(nChn, static_cast<ModCommand::PARAM>(param));
break;
case CMD_PORTAMENTODOWN:
if ((!param) && (GetType() & MOD_TYPE_MOD)) break;
PortamentoDown(nChn, static_cast<ModCommand::PARAM>(param));
break;
case CMD_VOLUMESLIDE:
if (param || (GetType() != MOD_TYPE_MOD)) VolumeSlide(chn, static_cast<ModCommand::PARAM>(param));
break;
case CMD_TONEPORTAMENTO:
TonePortamento(chn, param);
break;
case CMD_TONEPORTAVOL:
if ((param) || (GetType() != MOD_TYPE_MOD)) VolumeSlide(chn, static_cast<ModCommand::PARAM>(param));
TonePortamento(chn, 0);
break;
case CMD_VIBRATO:
Vibrato(chn, param);
break;
case CMD_VIBRATOVOL:
if ((param) || (GetType() != MOD_TYPE_MOD)) VolumeSlide(chn, static_cast<ModCommand::PARAM>(param));
Vibrato(chn, 0);
break;
case CMD_SPEED:
if(m_SongFlags[SONG_FIRSTTICK])
SetSpeed(m_PlayState, param);
break;
case CMD_TEMPO:
if(m_playBehaviour[kMODVBlankTiming])
{
if(m_SongFlags[SONG_FIRSTTICK] && param != 0) SetSpeed(m_PlayState, param);
break;
}
{
param = CalculateXParam(m_PlayState.m_nPattern, m_PlayState.m_nRow, nChn);
if (GetType() & (MOD_TYPE_S3M|MOD_TYPE_IT|MOD_TYPE_MPT))
{
if (param) chn.nOldTempo = static_cast<ModCommand::PARAM>(param); else param = chn.nOldTempo;
}
TEMPO t(param, 0);
LimitMax(t, GetModSpecifications().GetTempoMax());
SetTempo(t);
}
break;
case CMD_OFFSET:
if (triggerNote)
{
if(bPorta && GetType() == MOD_TYPE_XM)
{
break;
}
bool isExtended = false;
SmpLength offset = CalculateXParam(m_PlayState.m_nPattern, m_PlayState.m_nRow, nChn, &isExtended);
if(!isExtended)
{
offset <<= 8;
if (offset) chn.oldOffset = offset; else offset = chn.oldOffset;
offset += static_cast<SmpLength>(chn.nOldHiOffset) << 16;
}
SampleOffset(chn, offset);
}
break;
case CMD_OFFSETPERCENTAGE:
if(triggerNote)
{
SampleOffset(chn, Util::muldiv_unsigned(chn.nLength, param, 255));
}
break;
case CMD_ARPEGGIO:
if(m_PlayState.m_nTickCount) break;
if((!chn.nPeriod || !chn.nNote)
&& (chn.pModInstrument == nullptr || !chn.pModInstrument->HasValidMIDIChannel()) && !m_playBehaviour[kITArpeggio] && (GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT))) break;
if (!param && (GetType() & (MOD_TYPE_XM | MOD_TYPE_MOD))) break; chn.nCommand = CMD_ARPEGGIO;
if (param) chn.nArpeggio = static_cast<ModCommand::PARAM>(param);
break;
case CMD_RETRIG:
if (GetType() & (MOD_TYPE_XM|MOD_TYPE_MT2))
{
if (!(param & 0xF0)) param |= chn.nRetrigParam & 0xF0;
if (!(param & 0x0F)) param |= chn.nRetrigParam & 0x0F;
param |= 0x100; }
if(m_playBehaviour[kITRetrigger])
{
if (param) chn.nRetrigParam = static_cast<uint8>(param & 0xFF);
RetrigNote(nChn, chn.nRetrigParam, (volcmd == VOLCMD_OFFSET) ? vol + 1 : 0);
} else
{
if (param) chn.nRetrigParam = static_cast<uint8>(param & 0xFF); else param = chn.nRetrigParam;
RetrigNote(nChn, param, (volcmd == VOLCMD_OFFSET) ? vol + 1 : 0);
}
break;
case CMD_TREMOR:
if(!m_SongFlags[SONG_FIRSTTICK])
{
break;
}
if(m_playBehaviour[kITTremor])
{
if(param && !m_SongFlags[SONG_ITOLDEFFECTS])
{
if(param & 0xF0) param -= 0x10;
if(param & 0x0F) param -= 0x01;
}
chn.nTremorCount |= 0x80; } else if(m_playBehaviour[kFT2Tremor])
{
chn.nTremorCount |= 0x80; }
chn.nCommand = CMD_TREMOR;
if (param) chn.nTremorParam = static_cast<ModCommand::PARAM>(param);
break;
case CMD_GLOBALVOLUME:
if(!m_SongFlags[SONG_FIRSTTICK])
break;
if (!(GetType() & GLOBALVOL_7BIT_FORMATS)) param *= 2;
if(param <= 128)
{
m_PlayState.m_nGlobalVolume = param * 2;
} else if(!(GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT | MOD_TYPE_S3M)))
{
m_PlayState.m_nGlobalVolume = 256;
}
break;
case CMD_GLOBALVOLSLIDE:
if(m_playBehaviour[kPerChannelGlobalVolSlide])
GlobalVolSlide(static_cast<ModCommand::PARAM>(param), chn.nOldGlobalVolSlide);
else
GlobalVolSlide(static_cast<ModCommand::PARAM>(param), m_PlayState.Chn[0].nOldGlobalVolSlide);
break;
case CMD_PANNING8:
if(m_SongFlags[SONG_FIRSTTICK])
{
Panning(chn, param, Pan8bit);
}
break;
case CMD_PANNINGSLIDE:
PanningSlide(chn, static_cast<ModCommand::PARAM>(param));
break;
case CMD_TREMOLO:
Tremolo(chn, param);
break;
case CMD_FINEVIBRATO:
FineVibrato(chn, param);
break;
case CMD_MODCMDEX:
ExtendedMODCommands(nChn, static_cast<ModCommand::PARAM>(param));
break;
case CMD_S3MCMDEX:
if(m_playBehaviour[kST3EffectMemory] && param == 0)
{
param = chn.nArpeggio; }
ExtendedS3MCommands(nChn, static_cast<ModCommand::PARAM>(param));
break;
case CMD_KEYOFF:
if(m_playBehaviour[kFT2KeyOff])
{
if (m_PlayState.m_nTickCount == param)
{
if(chn.pModInstrument == nullptr || !chn.pModInstrument->VolEnv.dwFlags[ENV_ENABLED])
{
if(param == 0 && (chn.rowCommand.instr || chn.rowCommand.volcmd != VOLCMD_NONE)) {
chn.dwFlags.set(CHN_NOTEFADE);
}
else
{
chn.dwFlags.set(CHN_FASTVOLRAMP);
chn.nVolume = 0;
}
}
KeyOff(chn);
}
}
else
{
if(m_SongFlags[SONG_FIRSTTICK])
KeyOff(chn);
}
break;
case CMD_XFINEPORTAUPDOWN:
switch(param & 0xF0)
{
case 0x10: ExtraFinePortamentoUp(chn, param & 0x0F); break;
case 0x20: ExtraFinePortamentoDown(chn, param & 0x0F); break;
case 0x50:
case 0x60:
case 0x70:
case 0x90:
case 0xA0:
if(!m_playBehaviour[kFT2RestrictXCommand]) ExtendedS3MCommands(nChn, static_cast<ModCommand::PARAM>(param));
break;
}
break;
case CMD_CHANNELVOLUME:
if(!m_SongFlags[SONG_FIRSTTICK]) break;
if (param <= 64)
{
chn.nGlobalVol = param;
chn.dwFlags.set(CHN_FASTVOLRAMP);
}
break;
case CMD_CHANNELVOLSLIDE:
ChannelVolSlide(chn, static_cast<ModCommand::PARAM>(param));
break;
case CMD_PANBRELLO:
Panbrello(chn, param);
break;
case CMD_SETENVPOSITION:
if(m_SongFlags[SONG_FIRSTTICK])
{
chn.VolEnv.nEnvPosition = param;
if(!m_playBehaviour[kFT2SetPanEnvPos] || chn.VolEnv.flags[ENV_SUSTAIN])
{
chn.PanEnv.nEnvPosition = param;
chn.PitchEnv.nEnvPosition = param;
}
}
break;
case CMD_POSITIONJUMP:
m_PlayState.m_nNextPatStartRow = 0; nPosJump = static_cast<ORDERINDEX>(CalculateXParam(m_PlayState.m_nPattern, m_PlayState.m_nRow, nChn));
if((GetType() & (MOD_TYPE_MOD | MOD_TYPE_XM)) && nBreakRow != ROWINDEX_INVALID)
{
nBreakRow = 0;
}
break;
case CMD_PATTERNBREAK:
{
ROWINDEX row = PatternBreak(m_PlayState, nChn, static_cast<ModCommand::PARAM>(param));
if(row != ROWINDEX_INVALID)
{
nBreakRow = row;
if(m_SongFlags[SONG_PATTERNLOOP])
{
nPosJump = m_PlayState.m_nCurrentOrder;
}
}
}
break;
case CMD_NOTESLIDEUP:
case CMD_NOTESLIDEDOWN:
case CMD_NOTESLIDEUPRETRIG:
case CMD_NOTESLIDEDOWNRETRIG:
NoteSlide(chn, param, cmd == CMD_NOTESLIDEUP || cmd == CMD_NOTESLIDEUPRETRIG, cmd == CMD_NOTESLIDEUPRETRIG || cmd == CMD_NOTESLIDEDOWNRETRIG);
break;
case CMD_REVERSEOFFSET:
ReverseSampleOffset(chn, static_cast<ModCommand::PARAM>(param));
break;
#ifndef NO_PLUGINS
case CMD_DBMECHO:
if(m_PlayState.m_nTickCount == 0)
{
uint32 chns = (param >> 4), enable = (param & 0x0F);
if(chns > 1 || enable > 2)
{
break;
}
CHANNELINDEX firstChn = nChn, lastChn = nChn;
if(chns == 1)
{
firstChn = 0;
lastChn = m_nChannels - 1;
}
for(CHANNELINDEX c = firstChn; c <= lastChn; c++)
{
ChnSettings[c].dwFlags.set(CHN_NOFX, enable == 1);
m_PlayState.Chn[c].dwFlags.set(CHN_NOFX, enable == 1);
}
}
break;
#endif }
if(m_playBehaviour[kST3EffectMemory] && param != 0)
{
UpdateS3MEffectMemory(chn, static_cast<ModCommand::PARAM>(param));
}
if(chn.rowCommand.instr)
{
chn.nOldIns = chn.rowCommand.instr;
}
}
if(m_SongFlags[SONG_FIRSTTICK])
{
const bool doPatternLoop = (nPatLoopRow != ROWINDEX_INVALID);
const bool doBreakRow = (nBreakRow != ROWINDEX_INVALID);
const bool doPosJump = (nPosJump != ORDERINDEX_INVALID);
if((doBreakRow || doPosJump)
&& (!doPatternLoop || m_playBehaviour[kFT2PatternLoopWithJumps] || (m_playBehaviour[kITPatternLoopWithJumps] && doPosJump)))
{
if(!doPosJump) nPosJump = m_PlayState.m_nCurrentOrder + 1;
if(!doBreakRow) nBreakRow = 0;
m_SongFlags.set(SONG_BREAKTOROW);
if(nPosJump >= Order().size())
{
nPosJump = Order().GetRestartPos();
}
if(nPosJump != m_PlayState.m_nCurrentOrder
&& !m_playBehaviour[kITPatternLoopBreak] && !m_playBehaviour[kFT2PatternLoopWithJumps] && GetType() != MOD_TYPE_MOD)
{
for(CHANNELINDEX i = 0; i < GetNumChannels(); i++)
{
m_PlayState.Chn[i].nPatternLoopCount = 0;
}
}
m_PlayState.m_nNextRow = nBreakRow;
if(!m_SongFlags[SONG_PATTERNLOOP])
m_PlayState.m_nNextOrder = nPosJump;
} else if(doPatternLoop)
{
m_PlayState.m_nNextOrder = m_PlayState.m_nCurrentOrder;
m_PlayState.m_nNextRow = nPatLoopRow;
if(m_PlayState.m_nPatternDelay)
{
m_PlayState.m_nNextRow++;
}
if(nPatLoopRow >= Patterns[m_PlayState.m_nPattern].GetNumRows())
{
m_PlayState.m_nNextOrder++;
m_PlayState.m_nNextRow = 0;
}
visitedSongRows.ResetPatternLoop(m_PlayState.m_nCurrentOrder, nPatLoopRow);
}
}
return true;
}
void CSoundFile::UpdateS3MEffectMemory(ModChannel &chn, ModCommand::PARAM param) const
{
chn.nOldVolumeSlide = param; chn.nOldPortaUp = param; chn.nOldPortaDown = param; chn.nTremorParam = param; chn.nArpeggio = param; chn.nRetrigParam = param; chn.nTremoloDepth = (param & 0x0F) << 2; chn.nTremoloSpeed = (param >> 4) & 0x0F; }
uint32 CSoundFile::CalculateXParam(PATTERNINDEX pat, ROWINDEX row, CHANNELINDEX chn, bool *isExtended) const
{
if(isExtended != nullptr) *isExtended = false;
ROWINDEX maxCommands = 4;
const ModCommand *m = Patterns[pat].GetpModCommand(row, chn);
uint32 val = m->param;
switch(m->command)
{
case CMD_OFFSET:
maxCommands = 2;
break;
case CMD_TEMPO:
case CMD_PATTERNBREAK:
case CMD_POSITIONJUMP:
maxCommands = 1;
break;
default:
return val;
}
const bool xmTempoFix = m->command == CMD_TEMPO && GetType() == MOD_TYPE_XM;
ROWINDEX numRows = std::min(Patterns[pat].GetNumRows() - row - 1, maxCommands);
while(numRows > 0)
{
m += Patterns[pat].GetNumChannels();
if(m->command != CMD_XPARAM)
{
break;
}
if(xmTempoFix && val < 256)
{
val -= 0x20;
}
val = (val << 8) | m->param;
numRows--;
if(isExtended != nullptr) *isExtended = true;
}
return val;
}
ROWINDEX CSoundFile::PatternBreak(PlayState &state, CHANNELINDEX chn, uint8 param) const
{
if(param >= 64 && (GetType() & MOD_TYPE_S3M))
{
return ROWINDEX_INVALID;
}
state.m_nNextPatStartRow = 0;
return static_cast<ROWINDEX>(CalculateXParam(state.m_nPattern, state.m_nRow, chn));
}
void CSoundFile::PortamentoUp(CHANNELINDEX nChn, ModCommand::PARAM param, const bool doFinePortamentoAsRegular)
{
ModChannel &chn = m_PlayState.Chn[nChn];
if(param)
{
if(!m_playBehaviour[kFT2PortaUpDownMemory])
chn.nOldPortaDown = param;
chn.nOldPortaUp = param;
} else
{
param = chn.nOldPortaUp;
}
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));
MidiPortamento(nChn, param, doFineSlides);
if(GetType() == MOD_TYPE_MPT && chn.pModInstrument && chn.pModInstrument->pTuning)
{
if(param >= 0xF0 && !doFinePortamentoAsRegular)
PortamentoFineMPT(chn, param - 0xF0);
else if(param >= 0xE0 && !doFinePortamentoAsRegular)
PortamentoExtraFineMPT(chn, param - 0xE0);
else
PortamentoMPT(chn, param);
return;
} else if(GetType() == MOD_TYPE_PLM)
{
chn.nPortamentoDest = 1;
}
if (doFineSlides && param >= 0xE0)
{
if (param & 0x0F)
{
if ((param & 0xF0) == 0xF0)
{
FinePortamentoUp(chn, param & 0x0F);
return;
} else if ((param & 0xF0) == 0xE0 && GetType() != MOD_TYPE_DBM)
{
ExtraFinePortamentoUp(chn, param & 0x0F);
return;
}
}
if(GetType() != MOD_TYPE_DBM)
{
return;
}
}
if(!chn.isFirstTick || (m_PlayState.m_nMusicSpeed == 1 && m_playBehaviour[kSlidesAtSpeed1]) || GetType() == MOD_TYPE_669)
{
DoFreqSlide(chn, -int(param) * 4);
}
}
void CSoundFile::PortamentoDown(CHANNELINDEX nChn, ModCommand::PARAM param, const bool doFinePortamentoAsRegular)
{
ModChannel &chn = m_PlayState.Chn[nChn];
if(param)
{
if(!m_playBehaviour[kFT2PortaUpDownMemory])
chn.nOldPortaUp = param;
chn.nOldPortaDown = param;
} else
{
param = chn.nOldPortaDown;
}
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));
MidiPortamento(nChn, -static_cast<int>(param), doFineSlides);
if(GetType() == MOD_TYPE_MPT && chn.pModInstrument && chn.pModInstrument->pTuning)
{
if(param >= 0xF0 && !doFinePortamentoAsRegular)
PortamentoFineMPT(chn, -static_cast<int>(param - 0xF0));
else if(param >= 0xE0 && !doFinePortamentoAsRegular)
PortamentoExtraFineMPT(chn, -static_cast<int>(param - 0xE0));
else
PortamentoMPT(chn, -static_cast<int>(param));
return;
} else if(GetType() == MOD_TYPE_PLM)
{
chn.nPortamentoDest = 65535;
}
if(doFineSlides && param >= 0xE0)
{
if (param & 0x0F)
{
if ((param & 0xF0) == 0xF0)
{
FinePortamentoDown(chn, param & 0x0F);
return;
} else if ((param & 0xF0) == 0xE0 && GetType() != MOD_TYPE_DBM)
{
ExtraFinePortamentoDown(chn, param & 0x0F);
return;
}
}
if(GetType() != MOD_TYPE_DBM)
{
return;
}
}
if(!chn.isFirstTick || (m_PlayState.m_nMusicSpeed == 1 && m_playBehaviour[kSlidesAtSpeed1]) || GetType() == MOD_TYPE_669)
{
DoFreqSlide(chn, int(param) * 4);
}
}
void CSoundFile::MidiPortamento(CHANNELINDEX nChn, int param, bool doFineSlides)
{
int actualParam = mpt::abs(param);
int pitchBend = 0;
if(doFineSlides && actualParam >= 0xE0 && !m_playBehaviour[kOldMIDIPitchBends])
{
if(m_PlayState.Chn[nChn].isFirstTick)
{
pitchBend = (actualParam & 0x0F) * sgn(param);
if(actualParam >= 0xF0)
{
pitchBend *= 4;
}
}
} else if(!m_PlayState.Chn[nChn].isFirstTick || m_playBehaviour[kOldMIDIPitchBends])
{
pitchBend = param * 4;
}
if(pitchBend)
{
#ifndef NO_PLUGINS
IMixPlugin *plugin = GetChannelInstrumentPlugin(nChn);
if(plugin != nullptr)
{
int8 pwd = 13; if(m_PlayState.Chn[nChn].pModInstrument != nullptr)
{
pwd = m_PlayState.Chn[nChn].pModInstrument->midiPWD;
}
plugin->MidiPitchBend(pitchBend, pwd, nChn);
}
#endif }
}
void CSoundFile::FinePortamentoUp(ModChannel &chn, ModCommand::PARAM param) const
{
if(GetType() == MOD_TYPE_XM)
{
if(param) chn.nOldFinePortaUpDown = (chn.nOldFinePortaUpDown & 0x0F) | (param << 4); else param = (chn.nOldFinePortaUpDown >> 4);
} else if(GetType() == MOD_TYPE_MT2)
{
if(param) chn.nOldFinePortaUpDown = param; else param = chn.nOldFinePortaUpDown;
}
if(chn.isFirstTick)
{
if ((chn.nPeriod) && (param))
{
if(m_SongFlags[SONG_LINEARSLIDES] && GetType() != MOD_TYPE_XM)
{
const auto oldPeriod = chn.nPeriod;
chn.nPeriod = Util::muldivr(chn.nPeriod, GetLinearSlideUpTable(this, param & 0x0F), 65536);
if(oldPeriod == chn.nPeriod)
{
if(m_playBehaviour[kHertzInLinearMode] && chn.nPeriod < Util::MaxValueOfType(chn.nPeriod))
chn.nPeriod++;
else if(!m_playBehaviour[kHertzInLinearMode] && chn.nPeriod > 1)
chn.nPeriod--;
}
} else
{
chn.nPeriod -= (int)(param * 4);
if (chn.nPeriod < 1)
{
chn.nPeriod = 1;
if(GetType() == MOD_TYPE_S3M)
{
chn.nFadeOutVol = 0;
chn.dwFlags.set(CHN_NOTEFADE | CHN_FASTVOLRAMP);
}
}
}
}
}
}
void CSoundFile::FinePortamentoDown(ModChannel &chn, ModCommand::PARAM param) const
{
if(GetType() == MOD_TYPE_XM)
{
if(param) chn.nOldFinePortaUpDown = (chn.nOldFinePortaUpDown & 0xF0) | (param & 0x0F); else param = (chn.nOldFinePortaUpDown & 0x0F);
} else if(GetType() == MOD_TYPE_MT2)
{
if(param) chn.nOldFinePortaUpDown = param; else param = chn.nOldFinePortaUpDown;
}
if(chn.isFirstTick)
{
if ((chn.nPeriod) && (param))
{
if (m_SongFlags[SONG_LINEARSLIDES] && GetType() != MOD_TYPE_XM)
{
const auto oldPeriod = chn.nPeriod;
chn.nPeriod = Util::muldivr(chn.nPeriod, GetLinearSlideDownTable(this, param & 0x0F), 65536);
if(oldPeriod == chn.nPeriod)
{
if(!m_playBehaviour[kHertzInLinearMode] && chn.nPeriod < Util::MaxValueOfType(chn.nPeriod))
chn.nPeriod++;
else if(m_playBehaviour[kHertzInLinearMode] && chn.nPeriod > 1)
chn.nPeriod--;
}
} else
{
chn.nPeriod += (int)(param * 4);
if (chn.nPeriod > 0xFFFF) chn.nPeriod = 0xFFFF;
}
}
}
}
void CSoundFile::ExtraFinePortamentoUp(ModChannel &chn, ModCommand::PARAM param) const
{
if(GetType() == MOD_TYPE_XM)
{
if(param) chn.nOldExtraFinePortaUpDown = (chn.nOldExtraFinePortaUpDown & 0x0F) | (param << 4); else param = (chn.nOldExtraFinePortaUpDown >> 4);
} else if(GetType() == MOD_TYPE_MT2)
{
if(param) chn.nOldFinePortaUpDown = param; else param = chn.nOldFinePortaUpDown;
}
if(chn.isFirstTick)
{
if ((chn.nPeriod) && (param))
{
if(m_SongFlags[SONG_LINEARSLIDES] && GetType() != MOD_TYPE_XM)
{
int oldPeriod = chn.nPeriod;
chn.nPeriod = Util::muldivr(chn.nPeriod, GetFineLinearSlideUpTable(this, param & 0x0F), 65536);
if(oldPeriod == chn.nPeriod) chn.nPeriod++;
} else
{
chn.nPeriod -= (int)(param);
if (chn.nPeriod < 1)
{
chn.nPeriod = 1;
if(GetType() == MOD_TYPE_S3M)
{
chn.nFadeOutVol = 0;
chn.dwFlags.set(CHN_NOTEFADE | CHN_FASTVOLRAMP);
}
}
}
}
}
}
void CSoundFile::ExtraFinePortamentoDown(ModChannel &chn, ModCommand::PARAM param) const
{
if(GetType() == MOD_TYPE_XM)
{
if(param) chn.nOldExtraFinePortaUpDown = (chn.nOldExtraFinePortaUpDown & 0xF0) | (param & 0x0F); else param = (chn.nOldExtraFinePortaUpDown & 0x0F);
} else if(GetType() == MOD_TYPE_MT2)
{
if(param) chn.nOldFinePortaUpDown = param; else param = chn.nOldFinePortaUpDown;
}
if(chn.isFirstTick)
{
if ((chn.nPeriod) && (param))
{
if(m_SongFlags[SONG_LINEARSLIDES] && GetType() != MOD_TYPE_XM)
{
int oldPeriod = chn.nPeriod;
chn.nPeriod = Util::muldivr(chn.nPeriod, GetFineLinearSlideDownTable(this, param & 0x0F), 65536);
if(oldPeriod == chn.nPeriod) chn.nPeriod--;
} else
{
chn.nPeriod += (int)(param);
if (chn.nPeriod > 0xFFFF) chn.nPeriod = 0xFFFF;
}
}
}
}
void CSoundFile::NoteSlide(ModChannel &chn, uint32 param, bool slideUp, bool retrig) const
{
uint8 x, y;
if(m_SongFlags[SONG_FIRSTTICK])
{
x = param & 0xF0;
if (x)
chn.nNoteSlideSpeed = (x >> 4);
y = param & 0x0F;
if (y)
chn.nNoteSlideStep = y;
chn.nNoteSlideCounter = chn.nNoteSlideSpeed;
} else
{
if (--chn.nNoteSlideCounter == 0)
{
chn.nNoteSlideCounter = chn.nNoteSlideSpeed;
chn.nPeriod = GetPeriodFromNote
((slideUp ? 1 : -1) * chn.nNoteSlideStep + GetNoteFromPeriod(chn.nPeriod), 8363, 0);
if(retrig)
{
chn.position.Set(0);
}
}
}
}
void CSoundFile::TonePortamento(ModChannel &chn, uint32 param) const
{
chn.dwFlags.set(CHN_PORTAMENTO);
if((!m_SongFlags[SONG_ITCOMPATGXX] && m_playBehaviour[kITPortaMemoryShare]) || GetType() == MOD_TYPE_PLM)
{
if(param == 0) param = chn.nOldPortaUp;
chn.nOldPortaUp = chn.nOldPortaDown = static_cast<uint8>(param);
}
if(GetType() == MOD_TYPE_MPT && chn.pModInstrument && chn.pModInstrument->pTuning)
{
const int32 old_PortamentoTickSlide = (m_PlayState.m_nTickCount != 0) ? chn.m_PortamentoTickSlide : 0;
if(param)
chn.nPortamentoSlide = param;
else
if(chn.nPortamentoSlide == 0)
return;
if((chn.nPortamentoDest > 0 && chn.nPortamentoSlide < 0) ||
(chn.nPortamentoDest < 0 && chn.nPortamentoSlide > 0))
chn.nPortamentoSlide = -chn.nPortamentoSlide;
chn.m_PortamentoTickSlide = static_cast<int32>((m_PlayState.m_nTickCount + 1.0) * chn.nPortamentoSlide / m_PlayState.m_nMusicSpeed);
if(chn.dwFlags[CHN_GLISSANDO])
{
chn.m_PortamentoTickSlide *= chn.pModInstrument->pTuning->GetFineStepCount() + 1;
}
const int32 slide = chn.m_PortamentoTickSlide - old_PortamentoTickSlide;
if(mpt::abs(chn.nPortamentoDest) <= mpt::abs(slide))
{
if(chn.nPortamentoDest != 0)
{
chn.m_PortamentoFineSteps += chn.nPortamentoDest;
chn.nPortamentoDest = 0;
chn.m_CalculateFreq = true;
}
} else
{
chn.m_PortamentoFineSteps += slide;
chn.nPortamentoDest -= slide;
chn.m_CalculateFreq = true;
}
return;
}
bool doPorta = !chn.isFirstTick || (GetType() & (MOD_TYPE_DBM | MOD_TYPE_669)) || (m_PlayState.m_nMusicSpeed == 1 && m_playBehaviour[kSlidesAtSpeed1]);
if(GetType() == MOD_TYPE_PLM && param >= 0xF0)
{
param -= 0xF0;
doPorta = chn.isFirstTick;
}
if(param)
{
if(GetType() == MOD_TYPE_669)
{
param *= 10;
}
chn.nPortamentoSlide = param * 4;
}
if(chn.nPeriod && chn.nPortamentoDest && doPorta)
{
if (chn.nPeriod < chn.nPortamentoDest)
{
int32 delta = chn.nPortamentoSlide;
if(m_SongFlags[SONG_LINEARSLIDES] && GetType() != MOD_TYPE_XM)
{
uint32 n = chn.nPortamentoSlide / 4;
if (n > 255) n = 255;
delta = Util::muldivr(chn.nPeriod, LinearSlideUpTable[n], 65536) - chn.nPeriod;
if (delta < 1) delta = 1;
}
chn.nPeriod += delta;
if (chn.nPeriod > chn.nPortamentoDest) chn.nPeriod = chn.nPortamentoDest;
} else
if (chn.nPeriod > chn.nPortamentoDest)
{
int32 delta = -chn.nPortamentoSlide;
if(m_SongFlags[SONG_LINEARSLIDES] && GetType() != MOD_TYPE_XM)
{
uint32 n = chn.nPortamentoSlide / 4;
if (n > 255) n = 255;
delta = Util::muldivr(chn.nPeriod, LinearSlideDownTable[n], 65536) - chn.nPeriod;
if (delta > -1) delta = -1;
}
chn.nPeriod += delta;
if (chn.nPeriod < chn.nPortamentoDest) chn.nPeriod = chn.nPortamentoDest;
}
}
if(chn.nPeriod == chn.nPortamentoDest && (m_playBehaviour[kITPortaTargetReached] || GetType() == MOD_TYPE_MOD))
chn.nPortamentoDest = 0;
}
void CSoundFile::Vibrato(ModChannel &chn, uint32 param) const
{
if (param & 0x0F) chn.nVibratoDepth = (param & 0x0F) * 4;
if (param & 0xF0) chn.nVibratoSpeed = (param >> 4) & 0x0F;
chn.dwFlags.set(CHN_VIBRATO);
}
void CSoundFile::FineVibrato(ModChannel &chn, uint32 param) const
{
if (param & 0x0F) chn.nVibratoDepth = param & 0x0F;
if (param & 0xF0) chn.nVibratoSpeed = (param >> 4) & 0x0F;
chn.dwFlags.set(CHN_VIBRATO);
if(m_playBehaviour[kST3VibratoMemory] && (param & 0x0F))
{
chn.nVibratoDepth *= 4u;
}
}
void CSoundFile::Panbrello(ModChannel &chn, uint32 param) const
{
if (param & 0x0F) chn.nPanbrelloDepth = param & 0x0F;
if (param & 0xF0) chn.nPanbrelloSpeed = (param >> 4) & 0x0F;
}
void CSoundFile::Panning(ModChannel &chn, uint32 param, PanningType panBits) const
{
if(m_playBehaviour[kMODIgnorePanning])
{
return;
}
if (!m_SongFlags[SONG_SURROUNDPAN] && (panBits == Pan8bit || m_playBehaviour[kPanOverride]))
{
chn.dwFlags.reset(CHN_SURROUND);
}
if(panBits == Pan4bit)
{
chn.nPan = (param * 256 + 8) / 15;
} else if(panBits == Pan6bit)
{
if(param > 64) param = 64;
chn.nPan = param * 4;
} else
{
if(!(GetType() & (MOD_TYPE_S3M | MOD_TYPE_DSM | MOD_TYPE_AMF0 | MOD_TYPE_AMF | MOD_TYPE_MTM)))
{
chn.nPan = param;
} else
{
if(param <= 0x80)
{
chn.nPan = param << 1;
} else if(param == 0xA4)
{
chn.dwFlags.set(CHN_SURROUND);
chn.nPan = 0x80;
}
}
}
chn.dwFlags.set(CHN_FASTVOLRAMP);
chn.nRestorePanOnNewNote = 0;
if(m_playBehaviour[kPanOverride])
{
chn.nPanSwing = 0;
chn.nPanbrelloOffset = 0;
}
}
void CSoundFile::VolumeSlide(ModChannel &chn, ModCommand::PARAM param)
{
if (param)
chn.nOldVolumeSlide = param;
else
param = chn.nOldVolumeSlide;
if((GetType() & (MOD_TYPE_MOD | MOD_TYPE_XM | MOD_TYPE_MT2 | MOD_TYPE_MED | MOD_TYPE_DIGI | MOD_TYPE_STP | MOD_TYPE_DTM)))
{
if((param & 0xF0) != 0)
{
param &= 0xF0;
} else
{
param &= 0x0F;
}
}
int newVolume = chn.nVolume;
if(!(GetType() & (MOD_TYPE_MOD | MOD_TYPE_XM | MOD_TYPE_AMF0 | MOD_TYPE_MED | MOD_TYPE_DIGI)))
{
if ((param & 0x0F) == 0x0F) {
if (param & 0xF0) {
FineVolumeUp(chn, (param >> 4), false);
return;
} else {
if(chn.isFirstTick && !m_SongFlags[SONG_FASTVOLSLIDES])
{
newVolume -= 0x0F * 4;
}
}
} else
if ((param & 0xF0) == 0xF0) {
if (param & 0x0F) {
FineVolumeDown(chn, (param & 0x0F), false);
return;
} else {
if(chn.isFirstTick && !m_SongFlags[SONG_FASTVOLSLIDES])
{
newVolume += 0x0F * 4;
}
}
}
}
if(!chn.isFirstTick || m_SongFlags[SONG_FASTVOLSLIDES] || (m_PlayState.m_nMusicSpeed == 1 && GetType() == MOD_TYPE_DBM))
{
if (param & 0x0F)
{
if(!(GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT)) || (param & 0xF0) == 0)
newVolume -= (int)((param & 0x0F) * 4);
}
else
{
newVolume += (int)((param & 0xF0) >> 2);
}
if (GetType() == MOD_TYPE_MOD) chn.dwFlags.set(CHN_FASTVOLRAMP);
}
newVolume = Clamp(newVolume, 0, 256);
chn.nVolume = newVolume;
}
void CSoundFile::PanningSlide(ModChannel &chn, ModCommand::PARAM param, bool memory)
{
if(memory)
{
if(param)
chn.nOldPanSlide = param;
else
param = chn.nOldPanSlide;
}
if((GetType() & (MOD_TYPE_XM | MOD_TYPE_MT2)))
{
if((param & 0xF0) != 0)
{
param &= 0xF0;
} else
{
param &= 0x0F;
}
}
int32 nPanSlide = 0;
if(!(GetType() & (MOD_TYPE_XM | MOD_TYPE_MT2)))
{
if (((param & 0x0F) == 0x0F) && (param & 0xF0))
{
if(m_SongFlags[SONG_FIRSTTICK])
{
param = (param & 0xF0) / 4u;
nPanSlide = - (int)param;
}
} else if (((param & 0xF0) == 0xF0) && (param & 0x0F))
{
if(m_SongFlags[SONG_FIRSTTICK])
{
nPanSlide = (param & 0x0F) * 4u;
}
} else if(!m_SongFlags[SONG_FIRSTTICK])
{
if (param & 0x0F)
{
if(!(GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT)) || (param & 0xF0) == 0)
nPanSlide = (int)((param & 0x0F) * 4u);
} else
{
nPanSlide = -(int)((param & 0xF0) / 4u);
}
}
} else
{
if(!m_SongFlags[SONG_FIRSTTICK])
{
if (param & 0xF0)
{
nPanSlide = (int)((param & 0xF0) / 4u);
} else
{
nPanSlide = -(int)((param & 0x0F) * 4u);
}
if(m_playBehaviour[kFT2PanSlide])
nPanSlide /= 4;
}
}
if (nPanSlide)
{
nPanSlide += chn.nPan;
nPanSlide = Clamp(nPanSlide, 0, 256);
chn.nPan = nPanSlide;
chn.nRestorePanOnNewNote = 0;
}
}
void CSoundFile::FineVolumeUp(ModChannel &chn, ModCommand::PARAM param, bool volCol) const
{
if(GetType() == MOD_TYPE_XM)
{
if(param) chn.nOldFineVolUpDown = (param << 4) | (chn.nOldFineVolUpDown & 0x0F); else param = (chn.nOldFineVolUpDown >> 4);
} else if(volCol)
{
if(param) chn.nOldVolParam = param; else param = chn.nOldVolParam;
} else
{
if(param) chn.nOldFineVolUpDown = param; else param = chn.nOldFineVolUpDown;
}
if(chn.isFirstTick)
{
chn.nVolume += param * 4;
if(chn.nVolume > 256) chn.nVolume = 256;
if(GetType() & MOD_TYPE_MOD) chn.dwFlags.set(CHN_FASTVOLRAMP);
}
}
void CSoundFile::FineVolumeDown(ModChannel &chn, ModCommand::PARAM param, bool volCol) const
{
if(GetType() == MOD_TYPE_XM)
{
if(param) chn.nOldFineVolUpDown = param | (chn.nOldFineVolUpDown & 0xF0); else param = (chn.nOldFineVolUpDown & 0x0F);
} else if(volCol)
{
if(param) chn.nOldVolParam = param; else param = chn.nOldVolParam;
} else
{
if(param) chn.nOldFineVolUpDown = param; else param = chn.nOldFineVolUpDown;
}
if(chn.isFirstTick)
{
chn.nVolume -= param * 4;
if(chn.nVolume < 0) chn.nVolume = 0;
if(GetType() & MOD_TYPE_MOD) chn.dwFlags.set(CHN_FASTVOLRAMP);
}
}
void CSoundFile::Tremolo(ModChannel &chn, uint32 param) const
{
if (param & 0x0F) chn.nTremoloDepth = (param & 0x0F) << 2;
if (param & 0xF0) chn.nTremoloSpeed = (param >> 4) & 0x0F;
chn.dwFlags.set(CHN_TREMOLO);
}
void CSoundFile::ChannelVolSlide(ModChannel &chn, ModCommand::PARAM param) const
{
int32 nChnSlide = 0;
if (param) chn.nOldChnVolSlide = param; else param = chn.nOldChnVolSlide;
if (((param & 0x0F) == 0x0F) && (param & 0xF0))
{
if(m_SongFlags[SONG_FIRSTTICK]) nChnSlide = param >> 4;
} else if (((param & 0xF0) == 0xF0) && (param & 0x0F))
{
if(m_SongFlags[SONG_FIRSTTICK]) nChnSlide = - (int)(param & 0x0F);
} else
{
if(!m_SongFlags[SONG_FIRSTTICK])
{
if (param & 0x0F)
{
if(!(GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT | MOD_TYPE_J2B | MOD_TYPE_DBM)) || (param & 0xF0) == 0)
nChnSlide = -(int)(param & 0x0F);
} else
{
nChnSlide = (int)((param & 0xF0) >> 4);
}
}
}
if (nChnSlide)
{
nChnSlide += chn.nGlobalVol;
nChnSlide = Clamp(nChnSlide, 0, 64);
chn.nGlobalVol = nChnSlide;
}
}
void CSoundFile::ExtendedMODCommands(CHANNELINDEX nChn, ModCommand::PARAM param)
{
ModChannel &chn = m_PlayState.Chn[nChn];
uint8 command = param & 0xF0;
param &= 0x0F;
switch(command)
{
case 0x00:
for(CHANNELINDEX channel = 0; channel < GetNumChannels(); channel++)
{
m_PlayState.Chn[channel].dwFlags.set(CHN_AMIGAFILTER, !(param & 1));
}
break;
case 0x10: if ((param) || (GetType() & (MOD_TYPE_XM|MOD_TYPE_MT2))) FinePortamentoUp(chn, param); break;
case 0x20: if ((param) || (GetType() & (MOD_TYPE_XM|MOD_TYPE_MT2))) FinePortamentoDown(chn, param); break;
case 0x30: chn.dwFlags.set(CHN_GLISSANDO, param != 0); break;
case 0x40: chn.nVibratoType = param & 0x07; break;
case 0x50: if(!m_SongFlags[SONG_FIRSTTICK])
{
break;
}
if(GetType() & (MOD_TYPE_MOD | MOD_TYPE_DIGI | MOD_TYPE_AMF0 | MOD_TYPE_MED))
{
chn.nFineTune = MOD2XMFineTune(param);
if(chn.nPeriod && chn.rowCommand.IsNote()) chn.nPeriod = GetPeriodFromNote(chn.nNote, chn.nFineTune, chn.nC5Speed);
} else if(chn.rowCommand.IsNote())
{
chn.nFineTune = MOD2XMFineTune(param - 8);
if(chn.nPeriod) chn.nPeriod = GetPeriodFromNote(chn.nNote, chn.nFineTune, chn.nC5Speed);
}
break;
case 0x70: chn.nTremoloType = param & 0x07; break;
case 0x80:
if(m_SongFlags[SONG_FIRSTTICK])
{
Panning(chn, param, Pan4bit);
}
break;
case 0x90: RetrigNote(nChn, param); break;
case 0xA0: if ((param) || (GetType() & (MOD_TYPE_XM|MOD_TYPE_MT2))) FineVolumeUp(chn, param, false); break;
case 0xB0: if ((param) || (GetType() & (MOD_TYPE_XM|MOD_TYPE_MT2))) FineVolumeDown(chn, param, false); break;
case 0xC0: NoteCut(nChn, param, false); break;
case 0xF0:
if(GetType() == MOD_TYPE_MOD) {
chn.nEFxSpeed = param;
if(m_SongFlags[SONG_FIRSTTICK]) InvertLoop(chn);
} else {
chn.nActiveMacro = param;
}
break;
}
}
void CSoundFile::ExtendedS3MCommands(CHANNELINDEX nChn, ModCommand::PARAM param)
{
ModChannel &chn = m_PlayState.Chn[nChn];
uint8 command = param & 0xF0;
param &= 0x0F;
switch(command)
{
case 0x10: chn.dwFlags.set(CHN_GLISSANDO, param != 0); break;
case 0x20: if(!m_SongFlags[SONG_FIRSTTICK]) break;
if(GetType() != MOD_TYPE_669)
{
chn.nC5Speed = S3MFineTuneTable[param];
chn.nFineTune = MOD2XMFineTune(param);
if (chn.nPeriod) chn.nPeriod = GetPeriodFromNote(chn.nNote, chn.nFineTune, chn.nC5Speed);
} else if(chn.pModSample != nullptr)
{
chn.nC5Speed = chn.pModSample->nC5Speed + param * 80;
}
break;
case 0x30: if(GetType() == MOD_TYPE_S3M)
{
chn.nVibratoType = param & 0x03;
} else
{
if(m_playBehaviour[kITVibratoTremoloPanbrello])
chn.nVibratoType = (param < 0x04) ? param : 0;
else
chn.nVibratoType = param & 0x07;
}
break;
case 0x40: if(GetType() == MOD_TYPE_S3M)
{
chn.nTremoloType = param & 0x03;
} else
{
if(m_playBehaviour[kITVibratoTremoloPanbrello])
chn.nTremoloType = (param < 0x04) ? param : 0;
else
chn.nTremoloType = param & 0x07;
}
break;
case 0x50:
if(m_playBehaviour[kITVibratoTremoloPanbrello])
{
chn.nPanbrelloType = (param < 0x04) ? param : 0;
chn.nPanbrelloPos = 0;
} else
{
chn.nPanbrelloType = param & 0x07;
}
break;
case 0x60:
if(m_SongFlags[SONG_FIRSTTICK] && m_PlayState.m_nTickCount == 0)
{
m_PlayState.m_nFrameDelay += param;
}
break;
case 0x70: if(!m_SongFlags[SONG_FIRSTTICK]) break;
switch(param)
{
case 0:
case 1:
case 2:
{
for (CHANNELINDEX i = m_nChannels; i < MAX_CHANNELS; i++)
{
ModChannel &bkChn = m_PlayState.Chn[i];
if (bkChn.nMasterChn == nChn + 1)
{
if (param == 1)
{
KeyOff(bkChn);
if(bkChn.dwFlags[CHN_ADLIB] && m_opl)
m_opl->NoteOff(i);
} else if (param == 2)
{
bkChn.dwFlags.set(CHN_NOTEFADE);
if(bkChn.dwFlags[CHN_ADLIB] && m_opl)
m_opl->NoteOff(i);
} else
{
bkChn.dwFlags.set(CHN_NOTEFADE);
bkChn.nFadeOutVol = 0;
if(bkChn.dwFlags[CHN_ADLIB] && m_opl)
m_opl->NoteCut(i);
}
#ifndef NO_PLUGINS
const ModInstrument *pIns = bkChn.pModInstrument;
IMixPlugin *pPlugin;
if(pIns != nullptr && pIns->nMixPlug && (pPlugin = m_MixPlugins[pIns->nMixPlug - 1].pMixPlugin) != nullptr)
{
pPlugin->MidiCommand(*pIns, bkChn.nNote + NOTE_MAX_SPECIAL, 0, nChn);
}
#endif }
}
}
break;
case 3: chn.nNNA = NNA_NOTECUT; break;
case 4: chn.nNNA = NNA_CONTINUE; break;
case 5: chn.nNNA = NNA_NOTEOFF; break;
case 6: chn.nNNA = NNA_NOTEFADE; break;
case 7: chn.VolEnv.flags.reset(ENV_ENABLED); break;
case 8: chn.VolEnv.flags.set(ENV_ENABLED); break;
case 9: chn.PanEnv.flags.reset(ENV_ENABLED); break;
case 10: chn.PanEnv.flags.set(ENV_ENABLED); break;
case 11: chn.PitchEnv.flags.reset(ENV_ENABLED); break;
case 12: chn.PitchEnv.flags.set(ENV_ENABLED); break;
case 13: case 14: if(GetType() == MOD_TYPE_MPT)
{
chn.PitchEnv.flags.set(ENV_ENABLED);
chn.PitchEnv.flags.set(ENV_FILTER, param != 13);
}
break;
}
break;
case 0x80:
if(m_SongFlags[SONG_FIRSTTICK])
{
Panning(chn, param, Pan4bit);
}
break;
case 0x90: ExtendedChannelEffect(chn, param); break;
case 0xA0: if(m_SongFlags[SONG_FIRSTTICK])
{
chn.nOldHiOffset = static_cast<uint8>(param);
if (!m_playBehaviour[kITHighOffsetNoRetrig] && chn.rowCommand.IsNote())
{
SmpLength pos = param << 16;
if (pos < chn.nLength) chn.position.SetInt(pos);
}
}
break;
case 0xC0:
if(param == 0)
{
if(GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT))
param = 1;
else if(GetType() == MOD_TYPE_S3M)
return;
}
NoteCut(nChn, param, m_playBehaviour[kITSCxStopsSample] || GetType() == MOD_TYPE_S3M);
break;
case 0xF0:
if(GetType() != MOD_TYPE_S3M)
{
chn.nActiveMacro = static_cast<uint8>(param);
}
break;
}
}
void CSoundFile::ExtendedChannelEffect(ModChannel &chn, uint32 param)
{
if(!m_SongFlags[SONG_FIRSTTICK]) return;
switch(param & 0x0F)
{
case 0x00: chn.dwFlags.reset(CHN_SURROUND); break;
case 0x01: chn.dwFlags.set(CHN_SURROUND); chn.nPan = 128; break;
case 0x08:
chn.dwFlags.reset(CHN_REVERB);
chn.dwFlags.set(CHN_NOREVERB);
break;
case 0x09:
chn.dwFlags.reset(CHN_NOREVERB);
chn.dwFlags.set(CHN_REVERB);
break;
case 0x0A:
m_SongFlags.reset(SONG_SURROUNDPAN);
break;
case 0x0B:
m_SongFlags.set(SONG_SURROUNDPAN);
break;
case 0x0C:
m_SongFlags.reset(SONG_MPTFILTERMODE);
break;
case 0x0D:
m_SongFlags.set(SONG_MPTFILTERMODE);
break;
case 0x0E:
chn.dwFlags.reset(CHN_PINGPONGFLAG);
break;
case 0x0F:
if(chn.position.IsZero() && chn.nLength && (chn.rowCommand.IsNote() || !chn.dwFlags[CHN_LOOP]))
{
chn.position.Set(chn.nLength - 1, SamplePosition::fractMax);
}
chn.dwFlags.set(CHN_PINGPONGFLAG);
break;
}
}
void CSoundFile::InvertLoop(ModChannel &chn)
{
if(GetType() != MOD_TYPE_MOD || chn.nEFxSpeed == 0) return;
ModSample *pModSample = const_cast<ModSample *>(chn.pModSample);
if(pModSample == nullptr || !pModSample->HasSampleData() || !pModSample->uFlags[CHN_LOOP] || pModSample->uFlags[CHN_16BIT]) return;
chn.nEFxDelay += ModEFxTable[chn.nEFxSpeed & 0x0F];
if((chn.nEFxDelay & 0x80) == 0) return; chn.nEFxDelay = 0;
if (++chn.nEFxOffset >= pModSample->nLoopEnd - pModSample->nLoopStart)
chn.nEFxOffset = 0;
uint8 &sample = mpt::byte_cast<uint8 *>(pModSample->sampleb())[pModSample->nLoopStart + chn.nEFxOffset];
sample = ~sample;
ctrlSmp::PrecomputeLoops(*pModSample, *this, false);
}
void CSoundFile::ProcessMIDIMacro(CHANNELINDEX nChn, bool isSmooth, const char *macro, uint8 param, PLUGINDEX plugin)
{
ModChannel &chn = m_PlayState.Chn[nChn];
const ModInstrument *pIns = GetNumInstruments() ? chn.pModInstrument : nullptr;
uint8 out[MACRO_LENGTH];
uint32 outPos = 0; const uint8 lastZxxParam = chn.lastZxxParam;
bool firstNibble = true;
for(uint32 pos = 0; pos < (MACRO_LENGTH - 1) && macro[pos]; pos++)
{
bool isNibble = false; uint8 data = 0;
if(macro[pos] >= '0' && macro[pos] <= '9')
{
isNibble = true;
data = static_cast<uint8>(macro[pos] - '0');
}
else if(macro[pos] >= 'A' && macro[pos] <= 'F')
{
isNibble = true;
data = static_cast<uint8>(macro[pos] - 'A' + 0x0A);
} else if(macro[pos] == 'c')
{
isNibble = true;
data = GetBestMidiChannel(nChn);
} else if(macro[pos] == 'n')
{
if(ModCommand::IsNote(chn.nLastNote))
{
data = chn.nLastNote - NOTE_MIN;
}
} else if(macro[pos] == 'v')
{
const int swing = (m_playBehaviour[kITSwingBehaviour] || m_playBehaviour[kMPTOldSwingBehaviour]) ? chn.nVolSwing : 0;
const int vol = Util::muldiv((chn.nVolume + swing) * m_PlayState.m_nGlobalVolume, chn.nGlobalVol * chn.nInsVol, 1 << 20);
data = static_cast<uint8>(Clamp(vol / 2, 1, 127));
} else if(macro[pos] == 'u')
{
const int vol = Util::muldiv(chn.nCalcVolume * m_PlayState.m_nGlobalVolume, chn.nGlobalVol * chn.nInsVol, 1 << 26);
data = static_cast<uint8>(Clamp(vol / 2, 1, 127));
} else if(macro[pos] == 'x')
{
data = static_cast<uint8>(std::min<int>(chn.nPan / 2, 127));
} else if(macro[pos] == 'y')
{
data = static_cast<uint8>(std::min<int>(chn.nRealPan / 2, 127));
} else if(macro[pos] == 'a')
{
if(pIns && pIns->wMidiBank)
{
data = static_cast<uint8>(((pIns->wMidiBank - 1) >> 7) & 0x7F);
}
} else if(macro[pos] == 'b')
{
if(pIns && pIns->wMidiBank)
{
data = static_cast<uint8>((pIns->wMidiBank - 1) & 0x7F);
}
} else if(macro[pos] == 'o')
{
data = static_cast<uint8>((chn.oldOffset >> 8) & 0xFF);
} else if(macro[pos] == 'h')
{
data = static_cast<uint8>((nChn >= GetNumChannels() ? (chn.nMasterChn - 1) : nChn) & 0x7F);
} else if(macro[pos] == 'm')
{
data = chn.dwFlags[CHN_PINGPONGFLAG] ? 1 : 0;
} else if(macro[pos] == 'p')
{
if(pIns && pIns->nMidiProgram)
{
data = static_cast<uint8>((pIns->nMidiProgram - 1) & 0x7F);
}
} else if(macro[pos] == 'z')
{
data = param & 0x7F;
if(isSmooth && chn.lastZxxParam < 0x80
&& (outPos < 3 || out[outPos - 3] != 0xF0 || out[outPos - 2] < 0xF0))
{
data = static_cast<uint8>(CalculateSmoothParamChange(lastZxxParam, data));
}
chn.lastZxxParam = data;
} else if(macro[pos] == 's')
{
uint32 startPos = outPos;
while(startPos > 0 && out[--startPos] != 0xF0);
if(outPos - startPos < 5 || out[startPos] != 0xF0)
{
continue;
}
for(uint32 p = startPos + 5; p != outPos; p++)
{
data += out[p];
}
data = (~data + 1) & 0x7F;
} else
{
continue;
}
if(isNibble) {
if(firstNibble)
{
out[outPos] = data;
} else
{
out[outPos] = (out[outPos] << 4) | data;
outPos++;
}
firstNibble = !firstNibble;
} else {
if(!firstNibble) {
outPos++;
}
out[outPos++] = data;
firstNibble = true;
}
}
if(!firstNibble)
{
outPos++;
}
uint32 sendPos = 0;
uint8 runningStatus = 0;
while(sendPos < outPos)
{
uint32 sendLen = 0;
if(out[sendPos] == 0xF0)
{
if((outPos - sendPos >= 4) && (out[sendPos + 1] == 0xF0 || out[sendPos + 1] == 0xF1))
{
sendLen = 4;
} else
{
for(uint32 i = sendPos + 1; i < outPos; i++)
{
if(out[i] == 0xF7)
{
sendLen = i - sendPos + 1;
break;
}
}
if(sendLen == 0)
{
out[outPos++] = 0xF7;
sendLen = outPos - sendPos;
}
}
} else if(!(out[sendPos] & 0x80))
{
if(runningStatus != 0)
{
sendPos--;
out[sendPos] = runningStatus;
} else
{
sendPos++;
}
continue;
} else
{
sendLen = std::min<uint32>(MIDIEvents::GetEventLength(out[sendPos]), outPos - sendPos);
}
if(sendLen == 0)
{
break;
}
if(out[sendPos] < 0xF0)
{
runningStatus = out[sendPos];
}
uint32 bytesSent = SendMIDIData(nChn, isSmooth, out + sendPos, sendLen, plugin);
if(bytesSent > 0)
{
sendPos += bytesSent;
} else
{
sendPos += sendLen;
}
}
}
float CSoundFile::CalculateSmoothParamChange(float currentValue, float param) const
{
MPT_ASSERT(GetNumTicksOnCurrentRow() > m_PlayState.m_nTickCount);
const uint32 ticksLeft = GetNumTicksOnCurrentRow() - m_PlayState.m_nTickCount;
if(ticksLeft > 1)
{
const float step = (param - currentValue) / (float)ticksLeft;
return (currentValue + step);
} else
{
return param;
}
}
uint32 CSoundFile::SendMIDIData(CHANNELINDEX nChn, bool isSmooth, const unsigned char *macro, uint32 macroLen, PLUGINDEX plugin)
{
if(macroLen < 1)
{
return 0;
}
if(macro[0] == 0xFA || macro[0] == 0xFC || macro[0] == 0xFF)
{
for(CHANNELINDEX chn = 0; chn < GetNumChannels(); chn++)
{
m_PlayState.Chn[chn].nCutOff = 0x7F;
m_PlayState.Chn[chn].nResonance = 0x00;
}
}
ModChannel &chn = m_PlayState.Chn[nChn];
if(macro[0] == 0xF0 && (macro[1] == 0xF0 || macro[1] == 0xF1))
{
if(macroLen < 4)
{
return 0;
}
const bool isExtended = (macro[1] == 0xF1);
const uint8 macroCode = macro[2];
const uint8 param = macro[3];
if(macroCode == 0x00 && !isExtended && param < 0x80)
{
if(!isSmooth)
{
chn.nCutOff = param;
} else
{
chn.nCutOff = mpt::saturate_round<uint8>(CalculateSmoothParamChange(chn.nCutOff, param));
}
chn.nRestoreCutoffOnNewNote = 0;
int cutoff = SetupChannelFilter(chn, !chn.dwFlags[CHN_FILTER]);
if(cutoff >= 0 && chn.dwFlags[CHN_ADLIB] && m_opl)
{
m_opl->Volume(nChn, static_cast<uint8>(cutoff / 4), true);
}
return 4;
} else if(macroCode == 0x01 && !isExtended && param < 0x80)
{
if(!isSmooth)
{
chn.nResonance = param;
} else
{
chn.nResonance = (uint8)CalculateSmoothParamChange((float)chn.nResonance, (float)param);
}
chn.nRestoreResonanceOnNewNote = 0;
SetupChannelFilter(chn, !chn.dwFlags[CHN_FILTER]);
return 4;
} else if(macroCode == 0x02 && !isExtended)
{
if(param < 0x20)
{
chn.nFilterMode = (param >> 4);
SetupChannelFilter(chn, !chn.dwFlags[CHN_FILTER]);
}
return 4;
#ifndef NO_PLUGINS
} else if(macroCode == 0x03 && !isExtended)
{
const PLUGINDEX plug = (plugin != 0) ? plugin : GetBestPlugin(nChn, PrioritiseChannel, EvenIfMuted);
if(plug > 0 && plug <= MAX_MIXPLUGINS && param < 0x80)
{
const float newRatio = (0x7F - (param & 0x7F)) / 127.0f;
if(!isSmooth)
{
m_MixPlugins[plug - 1].fDryRatio = newRatio;
} else
{
m_MixPlugins[plug - 1].fDryRatio = CalculateSmoothParamChange(m_MixPlugins[plug - 1].fDryRatio, newRatio);
}
}
return 4;
} else if((macroCode & 0x80) || isExtended)
{
const PLUGINDEX plug = (plugin != 0) ? plugin : GetBestPlugin(nChn, PrioritiseChannel, EvenIfMuted);
const uint32 plugParam = isExtended ? (0x80 + macroCode) : (macroCode & 0x7F);
if(plug > 0 && plug <= MAX_MIXPLUGINS)
{
IMixPlugin *pPlugin = m_MixPlugins[plug - 1].pMixPlugin;
if(pPlugin && param < 0x80)
{
const float fParam = param / 127.0f;
if(!isSmooth)
{
pPlugin->SetParameter(plugParam, fParam);
} else
{
pPlugin->SetParameter(plugParam, CalculateSmoothParamChange(pPlugin->GetParameter(plugParam), fParam));
}
}
}
return 4;
#endif }
} else
{
#ifndef NO_PLUGINS
const CHANNELINDEX plugChannel = (nChn < GetNumChannels()) ? nChn + 1 : chn.nMasterChn;
if(plugChannel > 0 && plugChannel <= GetNumChannels()) {
PLUGINDEX plug = 0;
if(!chn.dwFlags[CHN_NOFX])
{
plug = (plugin != 0) ? plugin : GetBestPlugin(nChn, PrioritiseChannel, EvenIfMuted);
}
if(plug > 0 && plug <= MAX_MIXPLUGINS)
{
IMixPlugin *pPlugin = m_MixPlugins[plug - 1].pMixPlugin;
if (pPlugin != nullptr)
{
if(macro[0] == 0xF0)
{
pPlugin->MidiSysexSend(mpt::as_span(mpt::byte_cast<const mpt::byte*>(macro), macroLen));
} else
{
uint32 len = std::min<uint32>(MIDIEvents::GetEventLength(macro[0]), macroLen);
uint32 curData = 0;
memcpy(&curData, macro, len);
pPlugin->MidiSend(curData);
}
}
}
}
#else
MPT_UNREFERENCED_PARAMETER(plugin);
#endif
return macroLen;
}
return 0;
}
void CSoundFile::SendMIDINote(CHANNELINDEX chn, uint16 note, uint16 volume)
{
#ifndef NO_PLUGINS
auto &channel = m_PlayState.Chn[chn];
const ModInstrument *pIns = channel.pModInstrument;
if (pIns && pIns->HasValidMIDIChannel())
{
PLUGINDEX plug = pIns->nMixPlug;
if(plug > 0 && plug <= MAX_MIXPLUGINS)
{
IMixPlugin *pPlug = m_MixPlugins[plug - 1].pMixPlugin;
if (pPlug != nullptr)
{
pPlug->MidiCommand(*pIns, note, volume, chn);
if(note < NOTE_MIN_SPECIAL)
channel.nLeftVU = channel.nRightVU = 0xFF;
}
}
}
#endif }
void CSoundFile::SampleOffset(ModChannel &chn, SmpLength param) const
{
if(m_playBehaviour[kST3OffsetWithoutInstrument])
chn.prevNoteOffset = 0;
chn.prevNoteOffset += param;
if(param >= chn.nLoopEnd && GetType() == MOD_TYPE_MTM && chn.dwFlags[CHN_LOOP] && chn.nLoopEnd > 0)
{
param = (param - chn.nLoopStart) % (chn.nLoopEnd - chn.nLoopStart) + chn.nLoopStart;
}
if(GetType() == MOD_TYPE_MDL && chn.dwFlags[CHN_16BIT])
{
param /= 2u;
}
if(chn.rowCommand.IsNote())
{
if(chn.pModInstrument != nullptr)
{
SAMPLEINDEX smp = chn.pModInstrument->Keyboard[chn.rowCommand.note - NOTE_MIN];
if(smp == 0 || smp > GetNumSamples())
return;
}
if(m_SongFlags[SONG_PT_MODE])
{
chn.position.Set(chn.prevNoteOffset);
chn.prevNoteOffset += param;
} else
{
chn.position.Set(param);
}
if (chn.position.GetUInt() >= chn.nLength || (chn.dwFlags[CHN_LOOP] && chn.position.GetUInt() >= chn.nLoopEnd))
{
if (!(GetType() & (MOD_TYPE_XM | MOD_TYPE_MT2 | MOD_TYPE_MOD | MOD_TYPE_MTM)))
{
if(m_playBehaviour[kITOffset])
{
if(m_SongFlags[SONG_ITOLDEFFECTS])
chn.position.Set(chn.nLength); else
chn.position.Set(0); } else
{
chn.position.Set(chn.nLoopStart);
if(m_SongFlags[SONG_ITOLDEFFECTS] && chn.nLength > 4)
{
chn.position.Set(chn.nLength - 2);
}
}
} else if(m_playBehaviour[kFT2OffsetOutOfRange] || GetType() == MOD_TYPE_MTM)
{
chn.dwFlags.set(CHN_FASTVOLRAMP);
chn.nPeriod = 0;
} else if(GetType() == MOD_TYPE_MOD && chn.dwFlags[CHN_LOOP])
{
chn.position.Set(chn.nLoopStart);
}
}
} else if ((param < chn.nLength) && (GetType() & (MOD_TYPE_MTM | MOD_TYPE_DMF | MOD_TYPE_MDL | MOD_TYPE_PLM)))
{
chn.position.Set(param);
}
}
void CSoundFile::ReverseSampleOffset(ModChannel &chn, ModCommand::PARAM param) const
{
if(chn.pModSample != nullptr)
{
chn.dwFlags.set(CHN_PINGPONGFLAG);
chn.dwFlags.reset(CHN_LOOP);
chn.nLength = chn.pModSample->nLength; chn.position.Set((chn.nLength - 1) - std::min<SmpLength>(SmpLength(param) << 8, chn.nLength - 1), 0);
}
}
void CSoundFile::RetrigNote(CHANNELINDEX nChn, int param, int offset)
{
ModChannel &chn = m_PlayState.Chn[nChn];
int retrigSpeed = param & 0x0F;
int16 retrigCount = chn.nRetrigCount;
bool doRetrig = false;
if(m_playBehaviour[kITRetrigger])
{
if(m_PlayState.m_nTickCount == 0 && chn.rowCommand.note)
{
chn.nRetrigCount = param & 0xf;
} else if(!chn.nRetrigCount || !--chn.nRetrigCount)
{
chn.nRetrigCount = param & 0xf;
doRetrig = true;
}
} else if(m_playBehaviour[kFT2Retrigger] && (param & 0x100))
{
if(m_SongFlags[SONG_FIRSTTICK])
{
if(chn.rowCommand.instr > 0 && chn.rowCommand.IsNoteOrEmpty()) retrigCount = 1;
if(chn.rowCommand.volcmd == VOLCMD_VOLUME && chn.rowCommand.vol != 0)
{
chn.nRetrigCount = retrigCount;
return;
}
}
if(retrigCount >= retrigSpeed)
{
if(!m_SongFlags[SONG_FIRSTTICK] || !chn.rowCommand.IsNote())
{
doRetrig = true;
retrigCount = 0;
}
}
} else
{
if (GetType() & (MOD_TYPE_S3M|MOD_TYPE_IT|MOD_TYPE_MPT))
{
if (!retrigSpeed) retrigSpeed = 1;
if ((retrigCount) && (!(retrigCount % retrigSpeed))) doRetrig = true;
retrigCount++;
} else if(GetType() == MOD_TYPE_MTM)
{
doRetrig = m_PlayState.m_nTickCount == static_cast<uint32>(param & 0x0F) && retrigSpeed != 0;
} else
{
int realspeed = retrigSpeed;
if ((param & 0x100) && (chn.rowCommand.volcmd == VOLCMD_VOLUME) && (chn.rowCommand.param & 0xF0)) realspeed++;
if(!m_SongFlags[SONG_FIRSTTICK] || (param & 0x100))
{
if (!realspeed) realspeed = 1;
if ((!(param & 0x100)) && (m_PlayState.m_nMusicSpeed) && (!(m_PlayState.m_nTickCount % realspeed))) doRetrig = true;
retrigCount++;
} else if (GetType() & (MOD_TYPE_XM|MOD_TYPE_MT2)) retrigCount = 0;
if (retrigCount >= realspeed)
{
if ((m_PlayState.m_nTickCount) || ((param & 0x100) && (!chn.rowCommand.note))) doRetrig = true;
}
if(m_playBehaviour[kFT2Retrigger] && param == 0)
{
doRetrig = (m_PlayState.m_nTickCount == 0);
}
}
}
if(chn.nLength == 0 && m_playBehaviour[kITShortSampleRetrig] && !chn.HasMIDIOutput())
{
return;
}
if(doRetrig)
{
uint32 dv = (param >> 4) & 0x0F;
int vol = chn.nVolume;
if (dv)
{
if(!m_playBehaviour[kFT2Retrigger] || !(chn.rowCommand.volcmd == VOLCMD_VOLUME))
{
if (retrigTable1[dv])
vol = (vol * retrigTable1[dv]) >> 4;
else
vol += ((int)retrigTable2[dv]) << 2;
}
Limit(vol, 0, 256);
chn.dwFlags.set(CHN_FASTVOLRAMP);
}
uint32 note = chn.nNewNote;
int32 oldPeriod = chn.nPeriod;
if (note >= NOTE_MIN && note <= NOTE_MAX && chn.nLength)
CheckNNA(nChn, 0, note, true);
bool resetEnv = false;
if(GetType() & (MOD_TYPE_XM | MOD_TYPE_MT2))
{
if((chn.rowCommand.instr) && (param < 0x100))
{
InstrumentChange(chn, chn.rowCommand.instr, false, false);
resetEnv = true;
}
if (param < 0x100) resetEnv = true;
}
bool fading = chn.dwFlags[CHN_NOTEFADE];
NoteChange(chn, note, m_playBehaviour[kITRetrigger], resetEnv);
if(fading && GetType() == MOD_TYPE_XM)
chn.dwFlags.set(CHN_NOTEFADE);
chn.nVolume = vol;
if(m_nInstruments)
{
chn.rowCommand.note = static_cast<ModCommand::NOTE>(note); #ifndef NO_PLUGINS
ProcessMidiOut(nChn); #endif }
if ((GetType() & (MOD_TYPE_IT|MOD_TYPE_MPT)) && (!chn.rowCommand.note) && (oldPeriod)) chn.nPeriod = oldPeriod;
if (!(GetType() & (MOD_TYPE_S3M|MOD_TYPE_IT|MOD_TYPE_MPT))) retrigCount = 0;
if(m_playBehaviour[kITRetrigger]) chn.position.Set(0);
offset--;
if(offset >= 0 && offset <= static_cast<int>(CountOf(chn.pModSample->cues)) && chn.pModSample != nullptr)
{
if(offset == 0) offset = chn.oldOffset;
else offset = chn.oldOffset = chn.pModSample->cues[offset - 1];
SampleOffset(chn, offset);
}
}
if(m_playBehaviour[kFT2Retrigger] && (param & 0x100)) retrigCount++;
if(!m_playBehaviour[kITRetrigger])
chn.nRetrigCount = retrigCount;
}
void CSoundFile::DoFreqSlide(ModChannel &chn, int32 nFreqSlide) const
{
if(!chn.nPeriod) return;
if(GetType() == MOD_TYPE_669)
{
nFreqSlide *= -20;
}
if(m_SongFlags[SONG_LINEARSLIDES] && GetType() != MOD_TYPE_XM)
{
const auto nOldPeriod = chn.nPeriod;
uint32 n = mpt::abs(nFreqSlide) / 4u;
LimitMax(n, 255u);
if(n != 0)
{
chn.nPeriod = Util::muldivr(chn.nPeriod, nFreqSlide < 0 ? GetLinearSlideUpTable(this, n) : GetLinearSlideDownTable(this, n), 65536);
if(chn.nPeriod == nOldPeriod)
{
const bool incPeriod = m_playBehaviour[kHertzInLinearMode] == (nFreqSlide < 0);
if(incPeriod && chn.nPeriod < Util::MaxValueOfType(chn.nPeriod))
chn.nPeriod++;
else if(!incPeriod && chn.nPeriod > 1)
chn.nPeriod--;
}
}
} else
{
chn.nPeriod += nFreqSlide;
}
if (chn.nPeriod < 1)
{
chn.nPeriod = 1;
if(GetType() == MOD_TYPE_S3M)
{
chn.nFadeOutVol = 0;
chn.dwFlags.set(CHN_NOTEFADE | CHN_FASTVOLRAMP);
}
}
}
void CSoundFile::NoteCut(CHANNELINDEX nChn, uint32 nTick, bool cutSample)
{
if (m_PlayState.m_nTickCount == nTick)
{
ModChannel &chn = m_PlayState.Chn[nChn];
if(cutSample)
{
chn.increment.Set(0);
chn.nFadeOutVol = 0;
chn.dwFlags.set(CHN_NOTEFADE);
} else
{
chn.nVolume = 0;
}
chn.dwFlags.set(CHN_FASTVOLRAMP);
SendMIDINote(nChn, NOTE_MAX_SPECIAL, 0);
if(chn.dwFlags[CHN_ADLIB] && m_opl)
{
m_opl->NoteCut(nChn);
}
}
}
void CSoundFile::KeyOff(ModChannel &chn) const
{
const bool keyIsOn = !chn.dwFlags[CHN_KEYOFF];
chn.dwFlags.set(CHN_KEYOFF);
if(chn.pModInstrument != nullptr && !chn.VolEnv.flags[ENV_ENABLED])
{
chn.dwFlags.set(CHN_NOTEFADE);
}
if (!chn.nLength) return;
if (chn.dwFlags[CHN_SUSTAINLOOP] && chn.pModSample && keyIsOn)
{
const ModSample *pSmp = chn.pModSample;
if(pSmp->uFlags[CHN_LOOP])
{
if (pSmp->uFlags[CHN_PINGPONGLOOP])
chn.dwFlags.set(CHN_PINGPONGLOOP);
else
chn.dwFlags.reset(CHN_PINGPONGLOOP | CHN_PINGPONGFLAG);
chn.dwFlags.set(CHN_LOOP);
chn.nLength = pSmp->nLength;
chn.nLoopStart = pSmp->nLoopStart;
chn.nLoopEnd = pSmp->nLoopEnd;
if (chn.nLength > chn.nLoopEnd) chn.nLength = chn.nLoopEnd;
if(chn.position.GetUInt() > chn.nLength)
{
chn.position.Set(chn.position.GetInt() - chn.nLength + chn.nLoopStart);
}
} else
{
chn.dwFlags.reset(CHN_LOOP | CHN_PINGPONGLOOP | CHN_PINGPONGFLAG);
chn.nLength = pSmp->nLength;
}
}
if (chn.pModInstrument)
{
const ModInstrument *pIns = chn.pModInstrument;
if((pIns->VolEnv.dwFlags[ENV_LOOP] || (GetType() & (MOD_TYPE_XM | MOD_TYPE_MT2 | MOD_TYPE_MDL))) && pIns->nFadeOut != 0)
{
chn.dwFlags.set(CHN_NOTEFADE);
}
if (pIns->VolEnv.nReleaseNode != ENV_RELEASE_NODE_UNSET && chn.VolEnv.nEnvValueAtReleaseJump == NOT_YET_RELEASED)
{
chn.VolEnv.nEnvValueAtReleaseJump = pIns->VolEnv.GetValueFromPosition(chn.VolEnv.nEnvPosition, 256);
chn.VolEnv.nEnvPosition = pIns->VolEnv[pIns->VolEnv.nReleaseNode].tick;
}
}
}
void CSoundFile::SetSpeed(PlayState &playState, uint32 param) const
{
#ifdef MODPLUG_TRACKER
if(GetType() == MOD_TYPE_XM && !param)
{
playState.m_nMusicSpeed = uint16_max;
}
#endif if(param > 0) playState.m_nMusicSpeed = param;
if(GetType() == MOD_TYPE_STM && param > 0)
{
playState.m_nMusicSpeed = std::max<uint32>(param >> 4u, 1);
playState.m_nMusicTempo = ConvertST2Tempo(static_cast<uint8>(param));
}
}
TEMPO CSoundFile::ConvertST2Tempo(uint8 tempo)
{
static const uint8 ST2TempoFactor[] = { 140, 50, 25, 15, 10, 7, 6, 4, 3, 3, 2, 2, 2, 2, 1, 1 };
static const uint32 st2MixingRate = 23863;
int32 samplesPerTick = st2MixingRate / (49 - ((ST2TempoFactor[tempo >> 4u] * (tempo & 0x0F)) >> 4u));
if(samplesPerTick <= 0)
samplesPerTick += 65536;
return TEMPO().SetRaw(Util::muldivrfloor(st2MixingRate, 5 * TEMPO::fractFact, samplesPerTick * 2));
}
void CSoundFile::SetTempo(TEMPO param, bool setFromUI)
{
const CModSpecifications &specs = GetModSpecifications();
const TEMPO minTempo = (GetType() == MOD_TYPE_MDL) ? TEMPO(1, 0) : TEMPO(32, 0);
if(setFromUI)
{
m_PlayState.m_nMusicTempo = Clamp(param, specs.GetTempoMin(), specs.GetTempoMax());
} else if(param >= minTempo && m_SongFlags[SONG_FIRSTTICK] == !m_playBehaviour[kMODTempoOnSecondTick])
{
#if MPT_MSVC_AT_LEAST(2017,8)
m_PlayState.m_nMusicTempo.SetRaw(std::min(param.GetRaw(), specs.GetTempoMax().GetRaw()));
#else
m_PlayState.m_nMusicTempo = std::min(param, specs.GetTempoMax());
#endif
} else if(param < minTempo && !m_SongFlags[SONG_FIRSTTICK])
{
TEMPO tempDiff(param.GetInt() & 0x0F, 0);
if((param.GetInt() & 0xF0) == 0x10)
m_PlayState.m_nMusicTempo += tempDiff;
else
m_PlayState.m_nMusicTempo -= tempDiff;
TEMPO tempoMin = specs.GetTempoMin(), tempoMax = specs.GetTempoMax();
if(m_playBehaviour[kTempoClamp]) {
tempoMax.Set(255);
}
Limit(m_PlayState.m_nMusicTempo, tempoMin, tempoMax);
}
}
ROWINDEX CSoundFile::PatternLoop(ModChannel &chn, uint32 param)
{
if (param)
{
if(chn.nPatternLoopCount)
{
chn.nPatternLoopCount--;
if(!chn.nPatternLoopCount)
{
if(m_playBehaviour[kITPatternLoopTargetReset] || (GetType() == MOD_TYPE_S3M))
{
chn.nPatternLoop = m_PlayState.m_nRow + 1;
}
return ROWINDEX_INVALID;
}
} else
{
if(!m_playBehaviour[kITFT2PatternLoop] && !(GetType() & (MOD_TYPE_MOD | MOD_TYPE_S3M)))
{
ModChannel *p = m_PlayState.Chn;
for(CHANNELINDEX i = 0; i < GetNumChannels(); i++, p++) if (p != &chn)
{
if(p->nPatternLoopCount) return ROWINDEX_INVALID;
}
}
chn.nPatternLoopCount = static_cast<uint8>(param);
}
m_PlayState.m_nNextPatStartRow = chn.nPatternLoop; return chn.nPatternLoop;
} else
{
chn.nPatternLoop = m_PlayState.m_nRow;
}
return ROWINDEX_INVALID;
}
void CSoundFile::GlobalVolSlide(ModCommand::PARAM param, uint8 &nOldGlobalVolSlide)
{
int32 nGlbSlide = 0;
if (param) nOldGlobalVolSlide = param; else param = nOldGlobalVolSlide;
if((GetType() & (MOD_TYPE_XM | MOD_TYPE_MT2)))
{
if((param & 0xF0) != 0)
{
param &= 0xF0;
} else
{
param &= 0x0F;
}
}
if (((param & 0x0F) == 0x0F) && (param & 0xF0))
{
if(m_SongFlags[SONG_FIRSTTICK]) nGlbSlide = (param >> 4) * 2;
} else
if (((param & 0xF0) == 0xF0) && (param & 0x0F))
{
if(m_SongFlags[SONG_FIRSTTICK]) nGlbSlide = - (int)((param & 0x0F) * 2);
} else
{
if(!m_SongFlags[SONG_FIRSTTICK])
{
if (param & 0xF0)
{
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)
nGlbSlide = (int)((param & 0xF0) >> 4) * 2;
} else
{
nGlbSlide = -(int)((param & 0x0F) * 2);
}
}
}
if (nGlbSlide)
{
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;
nGlbSlide += m_PlayState.m_nGlobalVolume;
Limit(nGlbSlide, 0, 256);
m_PlayState.m_nGlobalVolume = nGlbSlide;
}
}
uint32 CSoundFile::GetNoteFromPeriod(uint32 period, int32 nFineTune, uint32 nC5Speed) const
{
if(!period) return 0;
if(m_playBehaviour[kFT2Periods])
{
nFineTune += 64;
}
uint32 minNote = NOTE_MIN, maxNote = NOTE_MAX, count = maxNote - minNote + 1;
const bool periodIsFreq = PeriodsAreFrequencies();
while(count > 0)
{
const uint32 step = count / 2, midNote = minNote + step;
uint32 n = GetPeriodFromNote(midNote, nFineTune, nC5Speed);
if((n > period && !periodIsFreq) || (n < period && periodIsFreq) || !n)
{
minNote = midNote + 1;
count -= step + 1;
} else
{
count = step;
}
}
return minNote;
}
uint32 CSoundFile::GetPeriodFromNote(uint32 note, int32 nFineTune, uint32 nC5Speed) const
{
if (note == NOTE_NONE || (note >= NOTE_MIN_SPECIAL)) return 0;
note -= NOTE_MIN;
if (!UseFinetuneAndTranspose())
{
if(GetType() & (MOD_TYPE_MDL | MOD_TYPE_DTM))
{
return (FreqS3MTable[note % 12u] << 4) >> (note / 12);
}
if(m_SongFlags[SONG_LINEARSLIDES] || GetType() == MOD_TYPE_669)
{
if(m_playBehaviour[kHertzInLinearMode] || GetType() == MOD_TYPE_669)
return Util::muldiv_unsigned(nC5Speed, LinearSlideUpTable[(note % 12u) * 16u] << (note / 12u), 65536 << 5);
else
return (FreqS3MTable[note % 12u] << 5) >> (note / 12);
} else
{
if (!nC5Speed)
nC5Speed = 8363;
LimitMax(nC5Speed, uint32_max >> (note / 12u));
return Util::muldiv_unsigned(8363, (FreqS3MTable[note % 12u] << 5), nC5Speed << (note / 12u));
}
} else if (GetType() == MOD_TYPE_XM)
{
if (note < 12) note = 12;
note -= 12;
if(m_playBehaviour[kFT2FinetunePrecision])
{
nFineTune &= ~7;
}
if(m_SongFlags[SONG_LINEARSLIDES])
{
int l = ((NOTE_MAX - note) << 6) - (nFineTune / 2);
if (l < 1) l = 1;
return static_cast<uint32>(l);
} else
{
int finetune = nFineTune;
uint32 rnote = (note % 12) << 3;
uint32 roct = note / 12;
int rfine = finetune / 16;
int i = rnote + rfine + 8;
Limit(i , 0, 103);
uint32 per1 = XMPeriodTable[i];
if(finetune < 0)
{
rfine--;
finetune = -finetune;
} else rfine++;
i = rnote+rfine+8;
if (i < 0) i = 0;
if (i >= 104) i = 103;
uint32 per2 = XMPeriodTable[i];
rfine = finetune & 0x0F;
per1 *= 16-rfine;
per2 *= rfine;
return ((per1 + per2) << 1) >> roct;
}
} else
{
nFineTune = XM2MODFineTune(nFineTune);
if ((nFineTune) || (note < 24) || (note >= 24 + mpt::size(ProTrackerPeriodTable)))
return (ProTrackerTunedPeriods[nFineTune * 12u + note % 12u] << 5) >> (note / 12u);
else
return (ProTrackerPeriodTable[note - 24] << 2);
}
}
uint32 CSoundFile::GetFreqFromPeriod(uint32 period, uint32 c5speed, int32 nPeriodFrac) const
{
if (!period) return 0;
if (GetType() == MOD_TYPE_XM)
{
if(m_playBehaviour[kFT2Periods])
{
period &= 0xFFFF;
}
if(m_SongFlags[SONG_LINEARSLIDES])
{
uint32 octave;
if(m_playBehaviour[kFT2Periods])
{
uint32 div = ((9216u + 767u - period) / 768);
octave = ((14 - div) & 0x1F);
} else
{
octave = (period / 768) + 2;
}
return (XMLinearTable[period % 768] << (FREQ_FRACBITS + 2)) >> octave;
} else
{
if(!period) period = 1;
return ((8363 * 1712L) << FREQ_FRACBITS) / period;
}
} else if (UseFinetuneAndTranspose())
{
return ((3546895L * 4) << FREQ_FRACBITS) / period;
} else if(GetType() == MOD_TYPE_669)
{
return (period + c5speed - 8363) << FREQ_FRACBITS;
} else if(GetType() & (MOD_TYPE_MDL | MOD_TYPE_DTM))
{
LimitMax(period, Util::MaxValueOfType(period) >> 8);
if (!c5speed) c5speed = 8363;
return Util::muldiv_unsigned(c5speed, (1712L << 7) << FREQ_FRACBITS, (period << 8) + nPeriodFrac);
} else
{
LimitMax(period, Util::MaxValueOfType(period) >> 8);
if(m_SongFlags[SONG_LINEARSLIDES])
{
if(m_playBehaviour[kHertzInLinearMode])
{
static_assert(FREQ_FRACBITS <= 8, "Check this shift operator");
return uint32(((uint64(period) << 8) + nPeriodFrac) >> (8 - FREQ_FRACBITS));
} else
{
if (!c5speed) c5speed = 8363;
return Util::muldiv_unsigned(c5speed, (1712L << 8) << FREQ_FRACBITS, (period << 8) + nPeriodFrac);
}
} else
{
return Util::muldiv_unsigned(8363, (1712L << 8) << FREQ_FRACBITS, (period << 8) + nPeriodFrac);
}
}
}
PLUGINDEX CSoundFile::GetBestPlugin(CHANNELINDEX nChn, PluginPriority priority, PluginMutePriority respectMutes) const
{
if (nChn >= MAX_CHANNELS) {
return 0;
}
PLUGINDEX plugin = 0;
switch (priority)
{
case ChannelOnly:
plugin = GetChannelPlugin(nChn, respectMutes);
break;
case InstrumentOnly:
plugin = GetActiveInstrumentPlugin(nChn, respectMutes);
break;
case PrioritiseInstrument:
plugin = GetActiveInstrumentPlugin(nChn, respectMutes);
if(!plugin || plugin > MAX_MIXPLUGINS)
{
plugin = GetChannelPlugin(nChn, respectMutes);
}
break;
case PrioritiseChannel:
plugin = GetChannelPlugin(nChn, respectMutes);
if(!plugin || plugin > MAX_MIXPLUGINS)
{
plugin = GetActiveInstrumentPlugin(nChn, respectMutes);
}
break;
}
return plugin; }
PLUGINDEX CSoundFile::GetChannelPlugin(CHANNELINDEX nChn, PluginMutePriority respectMutes) const
{
const ModChannel &channel = m_PlayState.Chn[nChn];
PLUGINDEX plugin;
if((respectMutes == RespectMutes && channel.dwFlags[CHN_MUTE]) || channel.dwFlags[CHN_NOFX])
{
plugin = 0;
} else
{
if (nChn >= m_nChannels && channel.nMasterChn > 0)
{
nChn = channel.nMasterChn - 1;
}
if(nChn < MAX_BASECHANNELS)
{
plugin = ChnSettings[nChn].nMixPlugin;
} else
{
plugin = 0;
}
}
return plugin;
}
PLUGINDEX CSoundFile::GetActiveInstrumentPlugin(CHANNELINDEX nChn, PluginMutePriority respectMutes) const
{
PLUGINDEX plug = 0;
if(m_PlayState.Chn[nChn].pModInstrument != nullptr)
{
if(respectMutes == RespectMutes && m_PlayState.Chn[nChn].pModSample && m_PlayState.Chn[nChn].pModSample->uFlags[CHN_MUTE])
{
plug = 0;
} else
{
plug = m_PlayState.Chn[nChn].pModInstrument->nMixPlug;
}
}
return plug;
}
IMixPlugin *CSoundFile::GetChannelInstrumentPlugin(CHANNELINDEX chn) const
{
#ifndef NO_PLUGINS
if(m_PlayState.Chn[chn].dwFlags[CHN_MUTE | CHN_SYNCMUTE])
{
return nullptr;
}
if(m_PlayState.Chn[chn].HasMIDIOutput())
{
const ModInstrument *pIns = m_PlayState.Chn[chn].pModInstrument;
if(pIns->nMixPlug != 0 && pIns->nMixPlug <= MAX_MIXPLUGINS)
{
return m_MixPlugins[pIns->nMixPlug - 1].pMixPlugin;
}
}
#else
MPT_UNREFERENCED_PARAMETER(chn);
#endif return nullptr;
}
uint8 CSoundFile::GetBestMidiChannel(CHANNELINDEX trackerChn) const
{
if(trackerChn >= mpt::size(m_PlayState.Chn))
{
return 0;
}
const ModChannel &chn = m_PlayState.Chn[trackerChn];
const ModInstrument *ins = chn.pModInstrument;
if(ins != nullptr)
{
if(ins->nMidiChannel == MidiMappedChannel)
{
return static_cast<uint8>((chn.nMasterChn ? (chn.nMasterChn - 1u) : trackerChn) % 16u);
} else if(ins->HasValidMIDIChannel())
{
return (ins->nMidiChannel - 1u) % 16u;
}
}
return 0;
}
#ifdef MODPLUG_TRACKER
void CSoundFile::HandlePatternTransitionEvents()
{
if(m_PlayState.m_nSeqOverride != ORDERINDEX_INVALID && m_PlayState.m_nSeqOverride < Order().size())
{
if(m_SongFlags[SONG_PATTERNLOOP])
{
m_PlayState.m_nPattern = Order()[m_PlayState.m_nSeqOverride];
}
m_PlayState.m_nCurrentOrder = m_PlayState.m_nSeqOverride;
m_PlayState.m_nSeqOverride = ORDERINDEX_INVALID;
}
for (CHANNELINDEX chan = 0; chan < GetNumChannels(); chan++)
{
if (m_bChannelMuteTogglePending[chan])
{
if(GetpModDoc())
{
GetpModDoc()->MuteChannel(chan, !GetpModDoc()->IsChannelMuted(chan));
}
m_bChannelMuteTogglePending[chan] = false;
}
}
}
#endif
void CSoundFile::UpdateTimeSignature()
{
if(!Patterns.IsValidIndex(m_PlayState.m_nPattern) || !Patterns[m_PlayState.m_nPattern].GetOverrideSignature())
{
m_PlayState.m_nCurrentRowsPerBeat = m_nDefaultRowsPerBeat;
m_PlayState.m_nCurrentRowsPerMeasure = m_nDefaultRowsPerMeasure;
} else
{
m_PlayState.m_nCurrentRowsPerBeat = Patterns[m_PlayState.m_nPattern].GetRowsPerBeat();
m_PlayState.m_nCurrentRowsPerMeasure = Patterns[m_PlayState.m_nPattern].GetRowsPerMeasure();
}
}
void CSoundFile::PortamentoMPT(ModChannel &chn, int param)
{
chn.m_PortamentoFineSteps += param;
chn.m_CalculateFreq = true;
}
void CSoundFile::PortamentoFineMPT(ModChannel &chn, int param)
{
if(m_PlayState.m_nTickCount == 0)
chn.nOldFinePortaUpDown = 0;
const int tickParam = static_cast<int>((m_PlayState.m_nTickCount + 1.0) * param / m_PlayState.m_nMusicSpeed);
chn.m_PortamentoFineSteps += (param >= 0) ? tickParam - chn.nOldFinePortaUpDown : tickParam + chn.nOldFinePortaUpDown;
if(m_PlayState.m_nTickCount + 1 == m_PlayState.m_nMusicSpeed)
chn.nOldFinePortaUpDown = static_cast<int8>(mpt::abs(param));
else
chn.nOldFinePortaUpDown = static_cast<int8>(mpt::abs(tickParam));
chn.m_CalculateFreq = true;
}
void CSoundFile::PortamentoExtraFineMPT(ModChannel &chn, int param)
{
if(chn.isFirstTick)
{
chn.m_PortamentoFineSteps += param;
chn.m_CalculateFreq = true;
}
}
OPENMPT_NAMESPACE_END