And reordered all the source files in the projects according to name sort. And removed all the deleted files, including some which were forgotten in previous updates, but left as 0 byte files. Finally, updated the project to use C23 / C++23 language standards. Signed-off-by: Christopher Snowhill <kode54@gmail.com>
1059 lines
34 KiB
C++
1059 lines
34 KiB
C++
/*
|
|
* InstrumentSynth.cpp
|
|
* -------------------
|
|
* Purpose: "Script" / "Synth" processor for various file formats (MED, GT2, Puma, His Master's Noise, Face The Music, Future Composer)
|
|
* Notes : (currently none)
|
|
* Authors: OpenMPT Devs
|
|
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
|
|
*/
|
|
|
|
|
|
#include "stdafx.h"
|
|
#include "InstrumentSynth.h"
|
|
#include "ModChannel.h"
|
|
#include "Sndfile.h"
|
|
#include "Tables.h"
|
|
|
|
OPENMPT_NAMESPACE_BEGIN
|
|
|
|
struct InstrumentSynth::States::State
|
|
{
|
|
static constexpr uint16 STOP_ROW = uint16_max;
|
|
|
|
enum Flags
|
|
{
|
|
kJumpConditionSet,
|
|
kGTKTremorEnabled,
|
|
kGTKTremorMute,
|
|
kGTKTremoloEnabled,
|
|
kGTKVibratoEnabled,
|
|
kFCVibratoDelaySet,
|
|
kFCVibratoStep,
|
|
kFCPitchBendStep,
|
|
kFCVolumeBendStep,
|
|
kNumFlags,
|
|
};
|
|
|
|
std::bitset<kNumFlags> m_flags;
|
|
|
|
uint16 m_currentRow = STOP_ROW;
|
|
uint16 m_nextRow = 0;
|
|
uint16 m_ticksRemain = 0;
|
|
uint8 m_stepSpeed = 1;
|
|
uint8 m_stepsRemain = 0;
|
|
|
|
uint16 m_volumeFactor = 16384;
|
|
int16 m_volumeAdd = int16_min;
|
|
uint16 m_panning = 2048;
|
|
int16 m_linearPitchFactor = 0;
|
|
int16 m_periodFreqSlide = 0;
|
|
int16 m_periodAdd = 0;
|
|
uint16 m_loopCount = 0;
|
|
|
|
uint16 m_gtkKeyOffOffset = STOP_ROW;
|
|
int16 m_gtkVolumeStep = 0, m_gtkPitchStep = 0, m_gtkPanningStep = 0;
|
|
uint16 m_gtkPitch = 4096;
|
|
uint8 m_gtkSpeed = 1, m_gtkSpeedRemain = 1;
|
|
uint8 m_gtkTremorOnTime = 3, m_gtkTremorOffTime = 3, m_gtkTremorPos = 0;
|
|
uint8 m_gtkVibratoWidth = 0, m_gtkVibratoSpeed = 0, m_gtkVibratoPos = 0;
|
|
|
|
uint8 m_pumaStartWaveform = 0, m_pumaEndWaveform = 0, m_pumaWaveform = 0;
|
|
int8 m_pumaWaveformStep = 0;
|
|
|
|
uint8 m_medVibratoEnvelope = uint8_max, m_medVibratoSpeed = 0, m_medVibratoDepth = 0;
|
|
int8 m_medVibratoValue = 0;
|
|
uint16 m_medVibratoPos = 0;
|
|
int16 m_medVolumeStep = 0;
|
|
int16 m_medPeriodStep = 0;
|
|
uint16 m_medArpOffset = STOP_ROW;
|
|
uint8 m_medArpPos = 0;
|
|
uint8 m_medHold = uint8_max;
|
|
uint16 m_medDecay = STOP_ROW;
|
|
uint8 m_medVolumeEnv = uint8_max, m_medVolumeEnvPos = 0;
|
|
|
|
uint32 m_ftmSampleStart = 0;
|
|
int16 m_ftmDetune = 1;
|
|
uint16 m_ftmVolumeChangeJump = STOP_ROW;
|
|
uint16 m_ftmPitchChangeJump = STOP_ROW;
|
|
uint16 m_ftmSampleChangeJump = STOP_ROW;
|
|
uint16 m_ftmReleaseJump = STOP_ROW;
|
|
uint16 m_ftmVolumeDownJump = STOP_ROW;
|
|
uint16 m_ftmPortamentoJump = STOP_ROW;
|
|
struct LFO
|
|
{
|
|
uint8 targetWaveform = 0, speed = 0, depth = 0, position = 0;
|
|
};
|
|
std::array<LFO, 4> m_ftmLFO;
|
|
uint8 m_ftmWorkTrack = 0;
|
|
|
|
int8 m_fcPitch = 0;
|
|
int16 m_fcVibratoValue = 0;
|
|
uint8 m_fcVibratoDelay = 0, m_fcVibratoSpeed = 0, m_fcVibratoDepth = 0;
|
|
int8 m_fcVolumeBendSpeed = 0, m_fcPitchBendSpeed = 0;
|
|
uint8 m_fcVolumeBendRemain = 0, m_fcPitchBendRemain = 0;
|
|
|
|
void JumpToPosition(const Events &events, uint16 position);
|
|
void NextTick(const Events &events, PlayState &playState, CHANNELINDEX channel, const CSoundFile &sndFile, States &states);
|
|
void ApplyChannelState(ModChannel &chn, int32 &period, const CSoundFile &sndFile);
|
|
bool EvaluateEvent(const Event &event, PlayState &playState, CHANNELINDEX channel, const CSoundFile &sndFile, States &states);
|
|
void EvaluateRunningEvent(const Event &event);
|
|
void HandleFTMInterrupt(uint16 &target, const bool condition);
|
|
bool HandleFCVolumeBend(bool forceRun = false);
|
|
|
|
CHANNELINDEX FTMRealChannel(CHANNELINDEX channel, const CSoundFile &sndFile) const noexcept
|
|
{
|
|
if(m_ftmWorkTrack)
|
|
return static_cast<CHANNELINDEX>(m_ftmWorkTrack - 1) % sndFile.GetNumChannels();
|
|
else
|
|
return channel;
|
|
}
|
|
};
|
|
|
|
|
|
static int32 ApplyLinearPitchSlide(int32 target, const int32 totalAmount, const bool periodsAreFrequencies)
|
|
{
|
|
const auto &table = (periodsAreFrequencies ^ (totalAmount < 0)) ? LinearSlideUpTable : LinearSlideDownTable;
|
|
size_t value = std::abs(totalAmount);
|
|
while(value > 0)
|
|
{
|
|
const size_t amount = std::min(value, std::size(table) - size_t(1));
|
|
target = Util::muldivr(target, table[amount], 65536);
|
|
value -= amount;
|
|
}
|
|
return target;
|
|
}
|
|
|
|
|
|
static int16 TranslateGT2Pitch(uint16 pitch)
|
|
{
|
|
// 4096 = normal, 8192 = one octave up
|
|
return mpt::saturate_round<int16>((mpt::log2(8192.0 / std::max(pitch, uint16(1))) - 1.0) * (16 * 12));
|
|
}
|
|
|
|
|
|
static int32 TranslateFTMPitch(uint16 pitch, ModChannel &chn, const CSoundFile &sndFile)
|
|
{
|
|
int32 period = sndFile.GetPeriodFromNote(NOTE_MIDDLEC - 12 + pitch / 16, chn.nFineTune, chn.nC5Speed);
|
|
sndFile.DoFreqSlide(chn, period, (pitch % 16) * 4);
|
|
return period;
|
|
}
|
|
|
|
|
|
static int8 MEDEnvelopeFromSample(const ModInstrument &instr, const CSoundFile &sndFile, uint8 envelope, uint16 envelopePos)
|
|
{
|
|
SAMPLEINDEX smp = instr.Keyboard[NOTE_MIDDLEC - NOTE_MIN] + envelope;
|
|
if(smp < 1 || smp > sndFile.GetNumSamples())
|
|
return 0;
|
|
|
|
const auto &mptSmp = sndFile.GetSample(smp);
|
|
if(envelopePos >= mptSmp.nLength || mptSmp.uFlags[CHN_16BIT] || !mptSmp.sample8())
|
|
return 0;
|
|
|
|
return mptSmp.sample8()[envelopePos];
|
|
}
|
|
|
|
|
|
static void ChannelSetSample(ModChannel &chn, const CSoundFile &sndFile, SAMPLEINDEX smp, bool swapAtEnd = true)
|
|
{
|
|
if(smp < 1 || smp > sndFile.GetNumSamples())
|
|
return;
|
|
const bool channelIsActive = chn.pCurrentSample && chn.nLength;
|
|
if(sndFile.m_playBehaviour[kMODSampleSwap] && smp <= uint8_max && swapAtEnd && channelIsActive)
|
|
{
|
|
chn.swapSampleIndex = smp;
|
|
return;
|
|
}
|
|
const ModSample &sample = sndFile.GetSample(smp);
|
|
if(chn.pModSample == &sample && channelIsActive)
|
|
return;
|
|
if(chn.increment.IsZero() && chn.nLength == 0 && chn.nVolume == 0)
|
|
chn.nVolume = 256;
|
|
chn.pModSample = &sample;
|
|
chn.pCurrentSample = sample.samplev();
|
|
chn.dwFlags = (chn.dwFlags & CHN_CHANNELFLAGS) | sample.uFlags;
|
|
chn.nLength = sample.uFlags[CHN_LOOP] ? sample.nLoopEnd : sample.nLength;
|
|
chn.nLoopStart = sample.nLoopStart;
|
|
chn.nLoopEnd = sample.nLoopEnd;
|
|
if(chn.position.GetUInt() >= chn.nLength)
|
|
chn.position.Set(0);
|
|
}
|
|
|
|
|
|
// To allow State to be forward-declared
|
|
InstrumentSynth::States::States() = default;
|
|
InstrumentSynth::States::States(const States &other) = default;
|
|
InstrumentSynth::States::States(States &&other) noexcept = default;
|
|
InstrumentSynth::States::~States() = default;
|
|
InstrumentSynth::States &InstrumentSynth::States::operator=(const States &other) = default;
|
|
InstrumentSynth::States &InstrumentSynth::States::operator=(States &&other) noexcept = default;
|
|
|
|
|
|
void InstrumentSynth::States::Stop()
|
|
{
|
|
for(auto &state : states)
|
|
state.m_currentRow = state.m_nextRow = State::STOP_ROW;
|
|
}
|
|
|
|
|
|
void InstrumentSynth::States::NextTick(PlayState &playState, CHANNELINDEX channel, const CSoundFile &sndFile)
|
|
{
|
|
ModChannel &chn = playState.Chn[channel];
|
|
if(!chn.pModInstrument || !chn.pModInstrument->synth.HasScripts())
|
|
return;
|
|
|
|
const auto &scripts = chn.pModInstrument->synth.m_scripts;
|
|
states.resize(scripts.size());
|
|
for(size_t i = 0; i < scripts.size(); i++)
|
|
{
|
|
auto &state = states[i];
|
|
if(chn.triggerNote)
|
|
mpt::reconstruct(state);
|
|
if(i == 1 && chn.rowCommand.command == CMD_MED_SYNTH_JUMP && chn.isFirstTick)
|
|
state.JumpToPosition(scripts[i], chn.rowCommand.param);
|
|
|
|
state.NextTick(scripts[i], playState, channel, sndFile, *this);
|
|
}
|
|
}
|
|
|
|
|
|
void InstrumentSynth::States::ApplyChannelState(ModChannel &chn, int32 &period, const CSoundFile &sndFile)
|
|
{
|
|
if(!chn.pModInstrument || !chn.pModInstrument->synth.HasScripts())
|
|
return;
|
|
|
|
for(auto &state : states)
|
|
{
|
|
state.ApplyChannelState(chn, period, sndFile);
|
|
}
|
|
}
|
|
|
|
|
|
void InstrumentSynth::States::State::JumpToPosition(const Events &events, uint16 position)
|
|
{
|
|
for(size_t pos = 0; pos < events.size(); pos++)
|
|
{
|
|
if(events[pos].type == Event::Type::JumpMarker && events[pos].u16 >= position)
|
|
{
|
|
m_nextRow = static_cast<uint16>(pos);
|
|
m_ticksRemain = 0;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void InstrumentSynth::States::State::NextTick(const Events &events, PlayState &playState, CHANNELINDEX channel, const CSoundFile &sndFile, States &states)
|
|
{
|
|
if(events.empty())
|
|
return;
|
|
|
|
const CHANNELINDEX origChannel = channel;
|
|
channel = FTMRealChannel(channel, sndFile);
|
|
ModChannel *chn = &playState.Chn[channel];
|
|
|
|
if(m_gtkKeyOffOffset != STOP_ROW && chn->dwFlags[CHN_KEYOFF])
|
|
{
|
|
m_nextRow = m_gtkKeyOffOffset;
|
|
m_ticksRemain = 0;
|
|
m_gtkKeyOffOffset = STOP_ROW;
|
|
}
|
|
if(m_pumaWaveformStep)
|
|
{
|
|
m_pumaWaveform = static_cast<uint8>(Clamp(m_pumaWaveform + m_pumaWaveformStep, m_pumaStartWaveform, m_pumaEndWaveform));
|
|
if(m_pumaWaveform <= m_pumaStartWaveform || m_pumaWaveform >= m_pumaEndWaveform)
|
|
m_pumaWaveformStep = -m_pumaWaveformStep;
|
|
ChannelSetSample(*chn, sndFile, m_pumaWaveform);
|
|
}
|
|
|
|
if(m_medHold != uint8_max)
|
|
{
|
|
if(!m_medHold--)
|
|
m_nextRow = m_medDecay;
|
|
}
|
|
|
|
const ModCommand &m = chn->rowCommand;
|
|
HandleFTMInterrupt(m_ftmPitchChangeJump, m.IsNote());
|
|
HandleFTMInterrupt(m_ftmVolumeChangeJump, m.command == CMD_CHANNELVOLUME);
|
|
HandleFTMInterrupt(m_ftmSampleChangeJump, m.instr != 0);
|
|
HandleFTMInterrupt(m_ftmReleaseJump, m.note == NOTE_KEYOFF);
|
|
HandleFTMInterrupt(m_ftmVolumeDownJump, m.command == CMD_VOLUMEDOWN_DURATION);
|
|
HandleFTMInterrupt(m_ftmPortamentoJump, m.command == CMD_TONEPORTA_DURATION);
|
|
|
|
if(!HandleFCVolumeBend() && m_stepSpeed && !m_stepsRemain--)
|
|
{
|
|
// Yep, MED executes this before a potential SPD command may change the step speed on this very row...
|
|
m_stepsRemain = m_stepSpeed - 1;
|
|
|
|
if(m_medVolumeStep)
|
|
m_volumeFactor = static_cast<int16>(std::clamp(m_volumeFactor + m_medVolumeStep, 0, 16384));
|
|
if(m_medPeriodStep)
|
|
m_periodAdd = mpt::saturate_cast<int16>(m_periodAdd - m_medPeriodStep);
|
|
if(m_medVolumeEnv != uint8_max && chn->pModInstrument)
|
|
{
|
|
m_volumeFactor = static_cast<uint16>(std::clamp((MEDEnvelopeFromSample(*chn->pModInstrument, sndFile, m_medVolumeEnv & 0x7F, m_medVolumeEnvPos) + 128) * 64, 0, 16384));
|
|
if(m_medVolumeEnvPos < 127)
|
|
m_medVolumeEnvPos++;
|
|
else if(m_medVolumeEnv & 0x80)
|
|
m_medVolumeEnvPos = 0;
|
|
}
|
|
|
|
if(m_ticksRemain)
|
|
{
|
|
if(m_currentRow < events.size())
|
|
EvaluateRunningEvent(events[m_currentRow]);
|
|
m_ticksRemain--;
|
|
} else
|
|
{
|
|
uint8 jumpCount = 0;
|
|
while(!m_ticksRemain)
|
|
{
|
|
m_currentRow = m_nextRow;
|
|
if(m_currentRow >= std::min(events.size(), static_cast<size_t>(STOP_ROW)))
|
|
break;
|
|
m_nextRow++;
|
|
if(EvaluateEvent(events[m_currentRow], playState, channel, sndFile, states))
|
|
break;
|
|
|
|
MPT_ASSERT(m_nextRow == STOP_ROW || m_nextRow == m_currentRow + 1 || events[m_currentRow].IsJumpEvent());
|
|
if(events[m_currentRow].IsJumpEvent())
|
|
{
|
|
// This smells like an infinite loop
|
|
if(jumpCount++ > 10)
|
|
break;
|
|
}
|
|
|
|
channel = FTMRealChannel(origChannel, sndFile);
|
|
chn = &playState.Chn[channel];
|
|
}
|
|
}
|
|
}
|
|
|
|
// MED stuff
|
|
if(m_medArpOffset < events.size() && events[m_medArpOffset].u16)
|
|
{
|
|
m_linearPitchFactor = 16 * events[m_medArpOffset + m_medArpPos].u8;
|
|
m_medArpPos = static_cast<uint8>((m_medArpPos + 1) % events[m_medArpOffset].u16);
|
|
}
|
|
if(m_medVibratoDepth)
|
|
{
|
|
static_assert(std::size(ModSinusTable) == 64);
|
|
uint16 offset = m_medVibratoPos / 16u;
|
|
if(m_medVibratoEnvelope == uint8_max)
|
|
m_medVibratoValue = ModSinusTable[(offset * 2) % std::size(ModSinusTable)];
|
|
else if(chn->pModInstrument)
|
|
m_medVibratoValue = MEDEnvelopeFromSample(*chn->pModInstrument, sndFile, m_medVibratoEnvelope, offset);
|
|
m_medVibratoPos = (m_medVibratoPos + m_medVibratoSpeed) % (32u * 16u);
|
|
}
|
|
|
|
// GTK stuff
|
|
if(m_currentRow < events.size() && m_gtkSpeed && !--m_gtkSpeedRemain)
|
|
{
|
|
m_gtkSpeedRemain = m_gtkSpeed;
|
|
if(m_gtkVolumeStep)
|
|
m_volumeFactor = static_cast<uint16>(std::clamp(m_volumeFactor + m_gtkVolumeStep, 0, 16384));
|
|
if(m_gtkPanningStep)
|
|
m_panning = static_cast<uint16>(std::clamp(m_panning + m_gtkPanningStep, 0, 4096));
|
|
if(m_gtkPitchStep)
|
|
{
|
|
m_gtkPitch = static_cast<uint16>(std::clamp(m_gtkPitch + m_gtkPitchStep, 0, 32768));
|
|
m_linearPitchFactor = TranslateGT2Pitch(m_gtkPitch);
|
|
}
|
|
}
|
|
if(m_flags[kGTKTremorEnabled])
|
|
{
|
|
if(m_gtkTremorPos >= m_gtkTremorOnTime + m_gtkTremorOffTime)
|
|
m_gtkTremorPos = 0;
|
|
m_flags.set(kGTKTremorMute, m_gtkTremorPos >= m_gtkTremorOnTime);
|
|
m_gtkTremorPos++;
|
|
}
|
|
if(m_flags[kGTKTremoloEnabled])
|
|
{
|
|
m_volumeAdd = static_cast<int16>(ModSinusTable[(m_gtkVibratoPos / 4u) % std::size(ModSinusTable)] * m_gtkVibratoWidth / 2);
|
|
m_gtkVibratoPos += m_gtkVibratoSpeed;
|
|
}
|
|
if(m_flags[kGTKVibratoEnabled])
|
|
{
|
|
m_periodFreqSlide = static_cast<int16>(-ModSinusTable[(m_gtkVibratoPos / 4u) % std::size(ModSinusTable)] * m_gtkVibratoWidth / 96);
|
|
m_gtkVibratoPos += m_gtkVibratoSpeed;
|
|
}
|
|
|
|
// FTM LFOs
|
|
for(auto &lfo : m_ftmLFO)
|
|
{
|
|
if(!lfo.speed && !lfo.depth)
|
|
continue;
|
|
|
|
const uint8 lutPos = static_cast<uint8>(Util::muldivr_unsigned(lfo.position, 256, 192));
|
|
int32 value = 0;
|
|
switch(lfo.targetWaveform & 0x07)
|
|
{
|
|
case 0: value = ITSinusTable[lutPos]; break;
|
|
case 1: value = lutPos < 128 ? 64 : -64; break;
|
|
case 2: value = 64 - std::abs(((lutPos + 64) % 256) - 128); break;
|
|
case 3: value = 64 - lutPos / 2; break;
|
|
case 4: value = lutPos / 2 - 64; break;
|
|
}
|
|
if((lfo.targetWaveform & 0xF0) < 0xA0)
|
|
value += 64;
|
|
value *= lfo.depth; // -8192...+8192 or 0...16384 for LFO targets
|
|
|
|
switch(lfo.targetWaveform & 0xF0)
|
|
{
|
|
case 0x10: m_ftmLFO[0].speed = static_cast<uint8>(value / 64); break;
|
|
case 0x20: m_ftmLFO[1].speed = static_cast<uint8>(value / 64); break;
|
|
case 0x30: m_ftmLFO[2].speed = static_cast<uint8>(value / 64); break;
|
|
case 0x40: m_ftmLFO[3].speed = static_cast<uint8>(value / 64); break;
|
|
case 0x50: m_ftmLFO[0].depth = static_cast<uint8>(value / 64); break;
|
|
case 0x60: m_ftmLFO[1].depth = static_cast<uint8>(value / 64); break;
|
|
case 0x70: m_ftmLFO[2].depth = static_cast<uint8>(value / 64); break;
|
|
case 0x80: m_ftmLFO[3].depth = static_cast<uint8>(value / 64); break;
|
|
case 0xA0: m_volumeAdd = mpt::saturate_cast<int16>(value * 4); break;
|
|
case 0xF0: m_periodFreqSlide = static_cast<int16>(value / 8); break;
|
|
}
|
|
|
|
uint16 newPos = lfo.position + lfo.speed;
|
|
if(newPos >= 192)
|
|
{
|
|
newPos -= 192;
|
|
if(lfo.targetWaveform & 0x08)
|
|
{
|
|
lfo.speed = 0;
|
|
newPos = 191;
|
|
}
|
|
}
|
|
lfo.position = static_cast<uint8>(newPos);
|
|
}
|
|
|
|
// Future Composer stuff
|
|
if(m_flags[kFCVibratoDelaySet] && m_fcVibratoDelay > 0)
|
|
{
|
|
m_fcVibratoDelay--;
|
|
} else if(m_fcVibratoDepth)
|
|
{
|
|
if(m_flags[kFCVibratoStep])
|
|
{
|
|
int16 delta = m_fcVibratoDepth * 2;
|
|
m_fcVibratoValue += m_fcVibratoSpeed;
|
|
if(m_fcVibratoValue > delta)
|
|
{
|
|
m_fcVibratoValue = delta;
|
|
m_flags.flip(kFCVibratoStep);
|
|
}
|
|
} else
|
|
{
|
|
m_fcVibratoValue -= m_fcVibratoSpeed;
|
|
if(m_fcVibratoValue < 0)
|
|
{
|
|
m_fcVibratoValue = 0;
|
|
m_flags.flip(kFCVibratoStep);
|
|
}
|
|
}
|
|
}
|
|
if(m_fcPitchBendRemain)
|
|
{
|
|
m_flags.flip(kFCPitchBendStep);
|
|
if(m_flags[kFCPitchBendStep])
|
|
{
|
|
m_fcPitchBendRemain--;
|
|
m_periodAdd -= static_cast<int16>(m_fcPitchBendSpeed * 4);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void InstrumentSynth::States::State::ApplyChannelState(ModChannel &chn, int32 &period, const CSoundFile &sndFile)
|
|
{
|
|
if(m_volumeFactor != 16384)
|
|
chn.nRealVolume = Util::muldivr(chn.nRealVolume, m_volumeFactor, 16384);
|
|
if(m_volumeAdd != int16_min)
|
|
chn.nRealVolume = std::clamp(chn.nRealVolume + m_volumeAdd, int32(0), int32(16384));
|
|
if(m_flags[kGTKTremorEnabled] && m_flags[kGTKTremorMute])
|
|
chn.nRealVolume = 0;
|
|
|
|
if(m_panning != 2048)
|
|
{
|
|
if(chn.nRealPan >= 128)
|
|
chn.nRealPan += ((m_panning - 2048) * (256 - chn.nRealPan)) / 2048;
|
|
else
|
|
chn.nRealPan += ((m_panning - 2048) * (chn.nRealPan)) / 2048;
|
|
}
|
|
|
|
const bool periodsAreFrequencies = sndFile.PeriodsAreFrequencies();
|
|
if(m_linearPitchFactor != 0)
|
|
period = ApplyLinearPitchSlide(period, m_linearPitchFactor, periodsAreFrequencies);
|
|
if(m_periodFreqSlide != 0)
|
|
sndFile.DoFreqSlide(chn, period, m_periodFreqSlide);
|
|
if(periodsAreFrequencies)
|
|
period -= m_periodAdd;
|
|
else
|
|
period += m_periodAdd;
|
|
if(m_medVibratoDepth)
|
|
period += m_medVibratoValue * m_medVibratoDepth / 64;
|
|
|
|
int16 vibratoFC = m_fcVibratoValue - m_fcVibratoDepth;
|
|
const bool doVibratoFC = vibratoFC != 0 && m_fcVibratoDelay < 1;
|
|
if(m_fcPitch || doVibratoFC)
|
|
{
|
|
uint8 fcNote = static_cast<uint8>(m_fcPitch >= 0 ? m_fcPitch + chn.nLastNote - NOTE_MIN : m_fcPitch) & 0x7F;
|
|
static_assert(mpt::array_size<decltype(chn.pModInstrument->NoteMap)>::size > 0x7F);
|
|
if(m_fcPitch && ModCommand::IsNote(chn.nLastNote))
|
|
period += (sndFile.GetPeriodFromNote(chn.pModInstrument->NoteMap[fcNote], chn.nFineTune, chn.nC5Speed) - sndFile.GetPeriodFromNote(chn.pModInstrument->NoteMap[chn.nLastNote - NOTE_MIN], chn.nFineTune, chn.nC5Speed));
|
|
|
|
if(doVibratoFC)
|
|
{
|
|
int note = (fcNote * 2) + 160;
|
|
while(note < 256)
|
|
{
|
|
vibratoFC *= 2;
|
|
note += 24;
|
|
}
|
|
period += vibratoFC * 4;
|
|
}
|
|
}
|
|
|
|
if((m_linearPitchFactor || m_periodFreqSlide || m_periodAdd) && !sndFile.PeriodsAreFrequencies())
|
|
{
|
|
if(period < sndFile.m_nMinPeriod)
|
|
period = sndFile.m_nMinPeriod;
|
|
else if(period > sndFile.m_nMaxPeriod && sndFile.m_playBehaviour[kApplyUpperPeriodLimit])
|
|
period = sndFile.m_nMaxPeriod;
|
|
}
|
|
if(period < 1)
|
|
period = 1;
|
|
|
|
if(m_ftmDetune != 1)
|
|
chn.microTuning = m_ftmDetune;
|
|
}
|
|
|
|
|
|
bool InstrumentSynth::States::State::EvaluateEvent(const Event &event, PlayState &playState, CHANNELINDEX channel, const CSoundFile &sndFile, States &states)
|
|
{
|
|
// Return true to indicate end of processing for this tick
|
|
ModChannel &chn = playState.Chn[channel];
|
|
switch(event.type)
|
|
{
|
|
case Event::Type::StopScript:
|
|
m_nextRow = STOP_ROW;
|
|
return true;
|
|
case Event::Type::Jump:
|
|
m_nextRow = event.u16;
|
|
return false;
|
|
case Event::Type::JumpIfTrue:
|
|
if(m_flags[kJumpConditionSet])
|
|
m_nextRow = event.u16;
|
|
return false;
|
|
case Event::Type::Delay:
|
|
m_ticksRemain = event.u16;
|
|
return true;
|
|
case Event::Type::SetStepSpeed:
|
|
m_stepSpeed = event.u8;
|
|
if(event.Byte1())
|
|
m_stepsRemain = m_stepSpeed - 1;
|
|
return false;
|
|
case Event::Type::JumpMarker:
|
|
return false;
|
|
case Event::Type::SampleOffset:
|
|
case Event::Type::SampleOffsetAdd:
|
|
case Event::Type::SampleOffsetSub:
|
|
{
|
|
int64 pos = event.Value24Bit();
|
|
if(event.type == Event::Type::SampleOffsetAdd)
|
|
pos += chn.position.GetInt();
|
|
else if(event.type == Event::Type::SampleOffsetSub)
|
|
pos = chn.position.GetInt() - pos;
|
|
else
|
|
pos += m_ftmSampleStart;
|
|
chn.position.Set(std::min(chn.nLength, mpt::saturate_cast<SmpLength>(pos)), 0);
|
|
}
|
|
return false;
|
|
case Event::Type::SetLoopCounter:
|
|
if(!m_loopCount || event.u8)
|
|
m_loopCount = 1 + std::min(event.u16, uint16(0xFFFE));
|
|
return false;
|
|
case Event::Type::EvaluateLoopCounter:
|
|
if(m_loopCount > 1)
|
|
m_nextRow = event.u16;
|
|
if(m_loopCount)
|
|
m_loopCount--;
|
|
return false;
|
|
case Event::Type::NoteCut:
|
|
chn.nFadeOutVol = 0;
|
|
chn.dwFlags.set(CHN_NOTEFADE);
|
|
return false;
|
|
|
|
case Event::Type::GTK_KeyOff:
|
|
m_gtkKeyOffOffset = event.u16;
|
|
return false;
|
|
case Event::Type::GTK_SetVolume:
|
|
m_volumeFactor = event.u16;
|
|
chn.dwFlags.set(CHN_FASTVOLRAMP);
|
|
return false;
|
|
case Event::Type::GTK_SetPitch:
|
|
m_gtkPitch = event.u16;
|
|
m_linearPitchFactor = TranslateGT2Pitch(event.u16);
|
|
m_periodAdd = 0;
|
|
return false;
|
|
case Event::Type::GTK_SetPanning:
|
|
m_panning = event.u16;
|
|
return false;
|
|
case Event::Type::GTK_SetVolumeStep:
|
|
m_gtkVolumeStep = event.i16;
|
|
return false;
|
|
case Event::Type::GTK_SetPitchStep:
|
|
m_gtkPitchStep = event.i16;
|
|
return false;
|
|
case Event::Type::GTK_SetPanningStep:
|
|
m_gtkPanningStep = event.i16;
|
|
return false;
|
|
case Event::Type::GTK_SetSpeed:
|
|
m_gtkSpeed = m_gtkSpeedRemain = event.u8;
|
|
return false;
|
|
case Event::Type::GTK_EnableTremor:
|
|
m_flags.set(kGTKTremorEnabled, event.u8 != 0);
|
|
return false;
|
|
case Event::Type::GTK_SetTremorTime:
|
|
if(event.Byte0())
|
|
m_gtkTremorOnTime = event.Byte0();
|
|
if(event.Byte1())
|
|
m_gtkTremorOffTime = event.Byte1();
|
|
m_gtkTremorPos = 0;
|
|
return false;
|
|
case Event::Type::GTK_EnableTremolo:
|
|
m_flags.set(kGTKTremoloEnabled, event.u8 != 0);
|
|
m_gtkVibratoPos = 0;
|
|
if(!m_gtkVibratoWidth)
|
|
m_gtkVibratoWidth = 8;
|
|
if(!m_gtkVibratoSpeed)
|
|
m_gtkVibratoSpeed = 16;
|
|
return false;
|
|
case Event::Type::GTK_EnableVibrato:
|
|
m_flags.set(kGTKVibratoEnabled, event.u8 != 0);
|
|
m_periodFreqSlide = 0;
|
|
m_gtkVibratoPos = 0;
|
|
if(!m_gtkVibratoWidth)
|
|
m_gtkVibratoWidth = 3;
|
|
if(!m_gtkVibratoSpeed)
|
|
m_gtkVibratoSpeed = 8;
|
|
return false;
|
|
case Event::Type::GTK_SetVibratoParams:
|
|
if(event.Byte0())
|
|
m_gtkVibratoWidth = event.Byte0();
|
|
if(event.Byte1())
|
|
m_gtkVibratoSpeed = event.Byte1();
|
|
return false;
|
|
|
|
case Event::Type::Puma_SetWaveform:
|
|
m_pumaWaveform = m_pumaStartWaveform = event.Byte0() + 1;
|
|
if(event.Byte0() < 10)
|
|
{
|
|
m_pumaWaveformStep = 0;
|
|
} else
|
|
{
|
|
m_pumaWaveformStep = static_cast<int8>(event.Byte1());
|
|
m_pumaEndWaveform = event.Byte2() + m_pumaStartWaveform;
|
|
}
|
|
ChannelSetSample(chn, sndFile, m_pumaWaveform);
|
|
return false;
|
|
case Event::Type::Puma_VolumeRamp:
|
|
m_ticksRemain = event.Byte2();
|
|
m_volumeAdd = static_cast<int16>(event.Byte0() * 256 - 16384);
|
|
chn.dwFlags.set(CHN_FASTVOLRAMP);
|
|
return true;
|
|
case Event::Type::Puma_StopVoice:
|
|
chn.Stop();
|
|
m_nextRow = STOP_ROW;
|
|
return true;
|
|
case Event::Type::Puma_SetPitch:
|
|
m_linearPitchFactor = event.i8 * 8;
|
|
m_periodAdd = 0;
|
|
m_ticksRemain = std::max(event.Byte2(), uint8(1)) - 1;
|
|
return true;
|
|
case Event::Type::Puma_PitchRamp:
|
|
m_linearPitchFactor = 0;
|
|
m_periodAdd = event.i8 * 4;
|
|
m_ticksRemain = std::max(event.Byte2(), uint8(1)) - 1;
|
|
return true;
|
|
|
|
case Event::Type::Mupp_SetWaveform:
|
|
ChannelSetSample(chn, sndFile, static_cast<SAMPLEINDEX>(32 + event.Byte0() * 28 + event.Byte1()));
|
|
m_volumeFactor = static_cast<uint16>(std::min(event.Byte2() & 0x7F, 64) * 256u);
|
|
chn.dwFlags.set(CHN_FASTVOLRAMP);
|
|
return true;
|
|
|
|
case Event::Type::MED_DefineArpeggio:
|
|
if(!event.u16)
|
|
return false;
|
|
m_nextRow = m_currentRow + event.u16;
|
|
m_medArpOffset = m_currentRow;
|
|
m_medArpPos = 0;
|
|
return true;
|
|
case Event::Type::MED_JumpScript:
|
|
if(event.u8 < chn.synthState.states.size() && chn.pModInstrument && event.u8 < chn.pModInstrument->synth.m_scripts.size())
|
|
{
|
|
chn.synthState.states[event.u8].JumpToPosition(chn.pModInstrument->synth.m_scripts[event.u8], event.u16);
|
|
chn.synthState.states[event.u8].m_stepsRemain = 0;
|
|
}
|
|
return false;
|
|
case Event::Type::MED_SetEnvelope:
|
|
if(event.Byte2())
|
|
m_medVolumeEnv = (event.Byte0() & 0x3F) | (event.Byte1() ? 0x80 : 0x00);
|
|
else
|
|
m_medVibratoEnvelope = event.Byte0();
|
|
m_medVolumeEnvPos = 0;
|
|
return false;
|
|
case Event::Type::MED_SetVolume:
|
|
m_volumeFactor = event.u8 * 256u;
|
|
chn.dwFlags.set(CHN_FASTVOLRAMP);
|
|
return true;
|
|
case Event::Type::MED_SetWaveform:
|
|
if(chn.pModInstrument)
|
|
ChannelSetSample(chn, sndFile, chn.pModInstrument->Keyboard[NOTE_MIDDLEC - NOTE_MIN] + event.u8);
|
|
return true;
|
|
case Event::Type::MED_SetVibratoSpeed:
|
|
m_medVibratoSpeed = event.u8;
|
|
return false;
|
|
case Event::Type::MED_SetVibratoDepth:
|
|
m_medVibratoDepth = event.u8;
|
|
return false;
|
|
case Event::Type::MED_SetVolumeStep:
|
|
m_medVolumeStep = static_cast<int16>(event.i16 * 256);
|
|
return false;
|
|
case Event::Type::MED_SetPeriodStep:
|
|
m_medPeriodStep = static_cast<int16>(event.i16 * 4);
|
|
return false;
|
|
case Event::Type::MED_HoldDecay:
|
|
m_medHold = event.u8;
|
|
m_medDecay = event.u16;
|
|
return false;
|
|
|
|
case Event::Type::FTM_SetCondition:
|
|
{
|
|
MPT_ASSERT(!sndFile.PeriodsAreFrequencies());
|
|
const int32 threshold = (event.u8 < 3) ? int32_max - TranslateFTMPitch(event.u16, chn, sndFile) : event.u16;
|
|
const int32 compare = (event.u8 < 3) ? int32_max - chn.nPeriod : chn.nGlobalVol;
|
|
switch(event.u8 % 3u)
|
|
{
|
|
case 0: m_flags.set(kJumpConditionSet, compare == threshold); break;
|
|
case 1: m_flags.set(kJumpConditionSet, compare < threshold); break;
|
|
case 2: m_flags.set(kJumpConditionSet, compare > threshold); break;
|
|
}
|
|
}
|
|
return false;
|
|
case Event::Type::FTM_SetInterrupt:
|
|
if(event.u8 & 0x01) m_ftmPitchChangeJump = event.u16;
|
|
if(event.u8 & 0x02) m_ftmVolumeChangeJump = event.u16;
|
|
if(event.u8 & 0x04) m_ftmSampleChangeJump = event.u16;
|
|
if(event.u8 & 0x08) m_ftmReleaseJump = event.u16;
|
|
if(event.u8 & 0x10) m_ftmPortamentoJump = event.u16;
|
|
if(event.u8 & 0x20) m_ftmVolumeDownJump = event.u16;
|
|
return false;
|
|
case Event::Type::FTM_PlaySample:
|
|
if(chn.nNewIns > 0 && chn.nNewIns <= sndFile.GetNumSamples())
|
|
chn.pModSample = &sndFile.GetSample(chn.nNewIns);
|
|
if(chn.pModSample)
|
|
{
|
|
const ModSample &sample = *chn.pModSample;
|
|
chn.nVolume = sample.nVolume;
|
|
chn.UpdateInstrumentVolume(&sample, nullptr);
|
|
chn.nC5Speed = sample.nC5Speed;
|
|
chn.dwFlags = (chn.dwFlags & (CHN_CHANNELFLAGS ^ CHN_NOTEFADE)) | sample.uFlags;
|
|
chn.nLength = chn.pModSample->uFlags[CHN_LOOP] ? sample.nLoopEnd : sample.nLength;
|
|
chn.nLoopStart = sample.nLoopStart;
|
|
chn.nLoopEnd = sample.nLoopEnd;
|
|
}
|
|
chn.position.Set(0);
|
|
return false;
|
|
case Event::Type::FTM_SetPitch:
|
|
chn.nPeriod = TranslateFTMPitch(event.u16 * 2, chn, sndFile);
|
|
return false;
|
|
case Event::Type::FTM_SetDetune:
|
|
// Detune always applies to the first channel of a channel pair (and only if the other channel is playing a sample)
|
|
states.states[channel & ~1].m_ftmDetune = static_cast<int16>(event.u16 * -8);
|
|
return false;
|
|
case Event::Type::FTM_AddDetune:
|
|
states.states[channel & ~1].m_ftmDetune -= static_cast<int16>(event.i16 * 8);
|
|
return false;
|
|
case Event::Type::FTM_AddPitch:
|
|
if(event.i16)
|
|
{
|
|
sndFile.DoFreqSlide(chn, chn.nPeriod, event.i16 * 8);
|
|
const int32 limit = TranslateFTMPitch((event.i16 < 0) ? 0 : 0x21E, chn, sndFile);
|
|
if((event.i16 > 0) == sndFile.PeriodsAreFrequencies())
|
|
chn.nPeriod = std::min(chn.nPeriod, limit);
|
|
else
|
|
chn.nPeriod = std::max(chn.nPeriod, limit);
|
|
}
|
|
return false;
|
|
case Event::Type::FTM_SetVolume:
|
|
chn.nGlobalVol = std::min(event.u8, uint8(64));
|
|
chn.dwFlags.set(CHN_FASTVOLRAMP);
|
|
return false;
|
|
case Event::Type::FTM_AddVolume:
|
|
chn.nGlobalVol = static_cast<uint8>(std::clamp(chn.nGlobalVol + event.i16, 0, 64));
|
|
return false;
|
|
case Event::Type::FTM_SetSample:
|
|
chn.swapSampleIndex = event.u8 + 1;
|
|
return false;
|
|
case Event::Type::FTM_SetSampleStart:
|
|
// Documentation says this should be in words, but it really appears to work with bytes.
|
|
// The relative variants appear to be completely broken.
|
|
if(event.u8 == 1)
|
|
m_ftmSampleStart += std::min(static_cast<uint32>(event.u16), Util::MaxValueOfType(m_ftmSampleStart) - m_ftmSampleStart);
|
|
else if(event.u8 == 2)
|
|
m_ftmSampleStart -= std::min(static_cast<uint32>(event.u16), m_ftmSampleStart);
|
|
else
|
|
m_ftmSampleStart = event.u16 * 2u;
|
|
return false;
|
|
case Event::Type::FTM_SetOneshotLength:
|
|
if(chn.pModSample)
|
|
{
|
|
const SmpLength loopLength = chn.nLoopEnd - chn.nLoopStart;
|
|
int64 loopStart = event.u16 * 2;
|
|
if(event.u8 == 1)
|
|
loopStart += chn.nLoopStart;
|
|
else if(event.u8 == 2)
|
|
loopStart = chn.nLoopStart - loopStart;
|
|
loopStart = std::clamp(loopStart, int64(0), static_cast<int64>(chn.pModSample->nLength));
|
|
chn.nLoopStart = static_cast<SmpLength>(loopStart);
|
|
chn.nLoopEnd = chn.nLoopStart + loopLength;
|
|
LimitMax(chn.nLoopEnd, chn.pModSample->nLength);
|
|
chn.nLength = chn.nLoopEnd;
|
|
chn.dwFlags.set(CHN_LOOP, chn.nLoopEnd > chn.nLoopStart);
|
|
if(chn.position.GetUInt() >= chn.nLength && chn.dwFlags[CHN_LOOP])
|
|
chn.position.SetInt(chn.nLoopStart);
|
|
}
|
|
return false;
|
|
case Event::Type::FTM_SetRepeatLength:
|
|
if(chn.pModSample)
|
|
{
|
|
int64 loopEnd = chn.nLoopStart + event.u16 * 2;
|
|
if(event.u8 == 1)
|
|
loopEnd = chn.nLoopEnd + event.u16 * 2;
|
|
else if(event.u8 == 2)
|
|
loopEnd = chn.nLoopEnd - event.u16 * 2;
|
|
loopEnd = std::clamp(loopEnd, static_cast<int64>(chn.nLoopStart), static_cast<int64>(chn.pModSample->nLength));
|
|
chn.nLoopEnd = static_cast<SmpLength>(loopEnd);
|
|
chn.nLength = chn.nLoopEnd;
|
|
chn.dwFlags.set(CHN_LOOP, chn.nLoopEnd > chn.nLoopStart);
|
|
if(chn.position.GetUInt() >= chn.nLength && chn.dwFlags[CHN_LOOP])
|
|
chn.position.SetInt(chn.nLoopStart);
|
|
}
|
|
return false;
|
|
case Event::Type::FTM_CloneTrack:
|
|
if(event.Byte0() < sndFile.GetNumChannels())
|
|
{
|
|
const ModChannel &srcChn = playState.Chn[event.Byte0()];
|
|
if(event.Byte1() & (0x01 | 0x08))
|
|
chn.nPeriod = srcChn.nPeriod;
|
|
if(event.Byte1() & (0x02 | 0x08))
|
|
chn.nGlobalVol = srcChn.nGlobalVol;
|
|
if(event.Byte1() & (0x04 | 0x08))
|
|
{
|
|
chn.nNewIns = srcChn.nNewIns;
|
|
chn.swapSampleIndex = srcChn.swapSampleIndex;
|
|
chn.pModSample = srcChn.pModSample;
|
|
chn.position = srcChn.position;
|
|
chn.dwFlags = (chn.dwFlags & CHN_CHANNELFLAGS) | (srcChn.dwFlags & CHN_SAMPLEFLAGS);
|
|
chn.nLength = srcChn.nLength;
|
|
chn.nLoopStart = srcChn.nLoopStart;
|
|
chn.nLoopEnd = srcChn.nLoopEnd;
|
|
}
|
|
if(event.Byte1() & 0x08)
|
|
{
|
|
// Note: This does not appear to behave entirely as documented.
|
|
// When the command is triggered, it copies frequency, volume, sample and the state of running slide commands.
|
|
// Running LFOs are not copied. But any notes and effects (including newly triggered LFOs) on the source track on following rows are copied.
|
|
// There appears to be no way to stop this cloning once it has started.
|
|
// As no FTM in the wild makes use of this command, we will glance over this ugly detail.
|
|
chn.position = srcChn.position;
|
|
chn.portamentoSlide = srcChn.portamentoSlide;
|
|
chn.nPortamentoDest = srcChn.nPortamentoDest;
|
|
chn.volSlideDownStart = srcChn.volSlideDownStart;
|
|
chn.volSlideDownTotal = srcChn.volSlideDownTotal;
|
|
chn.volSlideDownRemain = srcChn.volSlideDownRemain;
|
|
chn.autoSlide.SetActive(AutoSlideCommand::TonePortamentoWithDuration, srcChn.autoSlide.IsActive(AutoSlideCommand::TonePortamentoWithDuration));
|
|
chn.autoSlide.SetActive(AutoSlideCommand::VolumeDownWithDuration, srcChn.autoSlide.IsActive(AutoSlideCommand::VolumeDownWithDuration));
|
|
}
|
|
}
|
|
return false;
|
|
case Event::Type::FTM_StartLFO:
|
|
{
|
|
auto &lfo = m_ftmLFO[event.Byte0() & 3];
|
|
lfo.targetWaveform = event.Byte1();
|
|
lfo.speed = lfo.depth = lfo.position = 0;
|
|
}
|
|
return false;
|
|
case Event::Type::FTM_LFOAddSub:
|
|
{
|
|
auto &lfo = m_ftmLFO[event.Byte0() & 3];
|
|
int factor = (event.Byte0() & 4) ? -1 : 1;
|
|
lfo.speed = std::min(mpt::saturate_cast<uint8>(lfo.speed + event.Byte1() * factor), uint8(0xBF));
|
|
lfo.depth = std::min(mpt::saturate_cast<uint8>(lfo.depth + event.Byte2() * factor), uint8(0x7F));
|
|
}
|
|
return false;
|
|
case Event::Type::FTM_SetWorkTrack:
|
|
if(event.Byte0() == uint8_max)
|
|
{
|
|
m_ftmWorkTrack = 0;
|
|
} else if(const bool isRelative = event.Byte1() != 0; isRelative && event.Byte0())
|
|
{
|
|
if(!m_ftmWorkTrack)
|
|
m_ftmWorkTrack = static_cast<uint8>(channel + 1);
|
|
m_ftmWorkTrack = static_cast<uint8>((m_ftmWorkTrack - 1u + event.Byte0()) % sndFile.GetNumChannels() + 1);
|
|
} else if(!isRelative)
|
|
{
|
|
m_ftmWorkTrack = event.Byte0() + 1;
|
|
}
|
|
return false;
|
|
case Event::Type::FTM_SetGlobalVolume:
|
|
playState.m_nGlobalVolume = event.u16;
|
|
return false;
|
|
case Event::Type::FTM_SetTempo:
|
|
playState.m_nMusicTempo = TEMPO(1777517.482 / std::clamp(event.u16, uint16(0x1000), uint16(0x4FFF)));
|
|
return false;
|
|
case Event::Type::FTM_SetSpeed:
|
|
if(event.u16)
|
|
playState.m_nMusicSpeed = event.u16;
|
|
else
|
|
playState.m_nMusicSpeed = uint16_max;
|
|
return false;
|
|
case Event::Type::FTM_SetPlayPosition:
|
|
if(ORDERINDEX playPos = sndFile.Order().FindOrder(event.u16, event.u16); playPos != ORDERINDEX_INVALID)
|
|
{
|
|
playState.m_nNextOrder = playPos;
|
|
playState.m_nNextRow = event.u8;
|
|
}
|
|
return false;
|
|
|
|
case Event::Type::FC_SetWaveform:
|
|
{
|
|
uint8 waveform = event.Byte1() + 1;
|
|
if(event.Byte0() == 0xE9)
|
|
waveform += static_cast<uint8>(event.Byte2() * 10 + 90);
|
|
ChannelSetSample(chn, sndFile, waveform, event.Byte0() == 0xE4);
|
|
}
|
|
return false;
|
|
case Event::Type::FC_SetPitch:
|
|
m_fcPitch = event.i8;
|
|
return true;
|
|
case Event::Type::FC_SetVibrato:
|
|
m_fcVibratoSpeed = event.Byte0();
|
|
m_fcVibratoDepth = event.Byte1();
|
|
if(!m_flags[kFCVibratoDelaySet])
|
|
{
|
|
m_flags.set(kFCVibratoDelaySet);
|
|
m_fcVibratoDelay = event.Byte2();
|
|
m_fcVibratoValue = m_fcVibratoDepth;
|
|
}
|
|
return false;
|
|
case Event::Type::FC_PitchSlide:
|
|
m_fcPitchBendSpeed = event.Byte0();
|
|
m_fcPitchBendRemain = event.Byte1();
|
|
return false;
|
|
case Event::Type::FC_VolumeSlide:
|
|
m_fcVolumeBendSpeed = event.Byte0();
|
|
m_fcVolumeBendRemain = event.Byte1();
|
|
HandleFCVolumeBend(true);
|
|
return true;
|
|
}
|
|
|
|
MPT_ASSERT_NOTREACHED();
|
|
return false;
|
|
}
|
|
|
|
|
|
void InstrumentSynth::States::State::EvaluateRunningEvent(const Event &event)
|
|
{
|
|
switch(event.type)
|
|
{
|
|
case Event::Type::Puma_VolumeRamp:
|
|
if(event.Byte2() > 0)
|
|
m_volumeAdd = static_cast<int16>((event.Byte1() + Util::muldivr(event.Byte0() - event.Byte1(), m_ticksRemain, event.Byte2())) * 256 - 16384);
|
|
break;
|
|
case Event::Type::Puma_PitchRamp:
|
|
if(event.Byte2() > 0)
|
|
m_periodAdd = static_cast<int16>((static_cast<int8>(event.Byte1()) + Util::muldivr(static_cast<int8>(event.Byte0()) - static_cast<int8>(event.Byte1()), m_ticksRemain, event.Byte2())) * 4);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
void InstrumentSynth::States::State::HandleFTMInterrupt(uint16 &target, const bool condition)
|
|
{
|
|
if(target == STOP_ROW || !condition)
|
|
return;
|
|
m_nextRow = target;
|
|
m_ticksRemain = 0;
|
|
m_stepsRemain = 0;
|
|
target = STOP_ROW;
|
|
}
|
|
|
|
|
|
bool InstrumentSynth::States::State::HandleFCVolumeBend(bool forceRun)
|
|
{
|
|
if(!m_fcVolumeBendRemain && !forceRun)
|
|
return false;
|
|
|
|
m_flags.flip(kFCVolumeBendStep);
|
|
if(m_flags[kFCVolumeBendStep])
|
|
{
|
|
m_fcVolumeBendRemain--;
|
|
int32 target = m_volumeFactor + m_fcVolumeBendSpeed * 256;
|
|
if(target < 0 || target >= 32768)
|
|
m_fcVolumeBendRemain = 0;
|
|
m_volumeFactor = static_cast<uint16>(std::clamp(target, int32(0), int32(16384)));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
void GlobalScriptState::Initialize(const CSoundFile &sndFile)
|
|
{
|
|
if(!sndFile.m_globalScript.empty())
|
|
states.assign(sndFile.GetNumChannels(), {});
|
|
}
|
|
|
|
|
|
void GlobalScriptState::NextTick(PlayState &playState, const CSoundFile &sndFile)
|
|
{
|
|
if(sndFile.m_globalScript.empty())
|
|
return;
|
|
states.resize(sndFile.GetNumChannels());
|
|
for(CHANNELINDEX chn = 0; chn < sndFile.GetNumChannels(); chn++)
|
|
{
|
|
auto &state = states[chn];
|
|
auto &modChn = playState.Chn[chn];
|
|
if(modChn.rowCommand.command == CMD_MED_SYNTH_JUMP && !playState.m_nTickCount)
|
|
states[chn].JumpToPosition(sndFile.m_globalScript, modChn.rowCommand.param);
|
|
state.NextTick(sndFile.m_globalScript, playState, chn, sndFile, *this);
|
|
}
|
|
}
|
|
|
|
|
|
void GlobalScriptState::ApplyChannelState(PlayState &playState, CHANNELINDEX chn, int32 &period, const CSoundFile &sndFile)
|
|
{
|
|
if(sndFile.m_globalScript.empty())
|
|
return;
|
|
for(CHANNELINDEX s = 0; s < states.size(); s++)
|
|
{
|
|
if(states[s].FTMRealChannel(s, sndFile) == chn)
|
|
{
|
|
states[s].ApplyChannelState(playState.Chn[chn], period, sndFile);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void InstrumentSynth::Sanitize()
|
|
{
|
|
for(auto &script : m_scripts)
|
|
{
|
|
if(script.size() >= States::State::STOP_ROW)
|
|
script.resize(States::State::STOP_ROW - 1);
|
|
}
|
|
}
|
|
|
|
|
|
OPENMPT_NAMESPACE_END
|