1 /*
2  * ModChannel.cpp
3  * --------------
4  * Purpose: Module Channel header class and helpers
5  * Notes  : (currently none)
6  * Authors: OpenMPT Devs
7  * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
8  */
9 
10 
11 #include "stdafx.h"
12 #include "Sndfile.h"
13 #include "ModChannel.h"
14 #include "tuning.h"
15 
16 OPENMPT_NAMESPACE_BEGIN
17 
Reset(ResetFlags resetMask,const CSoundFile & sndFile,CHANNELINDEX sourceChannel,ChannelFlags muteFlag)18 void ModChannel::Reset(ResetFlags resetMask, const CSoundFile &sndFile, CHANNELINDEX sourceChannel, ChannelFlags muteFlag)
19 {
20 	if(resetMask & resetSetPosBasic)
21 	{
22 		nNote = nNewNote = NOTE_NONE;
23 		nNewIns = nOldIns = 0;
24 		pModSample = nullptr;
25 		pModInstrument = nullptr;
26 		nPortamentoDest = 0;
27 		nCommand = CMD_NONE;
28 		nPatternLoopCount = 0;
29 		nPatternLoop = 0;
30 		nFadeOutVol = 0;
31 		dwFlags.set(CHN_KEYOFF | CHN_NOTEFADE);
32 		dwOldFlags.reset();
33 		//IT compatibility 15. Retrigger
34 		if(sndFile.m_playBehaviour[kITRetrigger])
35 		{
36 			nRetrigParam = 1;
37 			nRetrigCount = 0;
38 		}
39 		microTuning = 0;
40 		nTremorCount = 0;
41 		nEFxSpeed = 0;
42 		prevNoteOffset = 0;
43 		lastZxxParam = 0xFF;
44 		isFirstTick = false;
45 		triggerNote = false;
46 		isPreviewNote = false;
47 		isPaused = false;
48 		portaTargetReached = false;
49 		rowCommand.Clear();
50 	}
51 
52 	if(resetMask & resetSetPosAdvanced)
53 	{
54 		increment = SamplePosition(0);
55 		nPeriod = 0;
56 		position.Set(0);
57 		nLength = 0;
58 		nLoopStart = 0;
59 		nLoopEnd = 0;
60 		nROfs = nLOfs = 0;
61 		pModSample = nullptr;
62 		pModInstrument = nullptr;
63 		nCutOff = 0x7F;
64 		nResonance = 0;
65 		nFilterMode = FilterMode::LowPass;
66 		rightVol = leftVol = 0;
67 		newRightVol = newLeftVol = 0;
68 		rightRamp = leftRamp = 0;
69 		nVolume = 0;  // Needs to be 0 for SMP_NODEFAULTVOLUME flag
70 		nVibratoPos = nTremoloPos = nPanbrelloPos = 0;
71 		nOldHiOffset = 0;
72 		nLeftVU = nRightVU = 0;
73 
74 		// Custom tuning related
75 		m_ReCalculateFreqOnFirstTick = false;
76 		m_CalculateFreq = false;
77 		m_PortamentoFineSteps = 0;
78 		m_PortamentoTickSlide = 0;
79 	}
80 
81 	if(resetMask & resetChannelSettings)
82 	{
83 		if(sourceChannel < MAX_BASECHANNELS)
84 		{
85 			dwFlags = sndFile.ChnSettings[sourceChannel].dwFlags;
86 			nPan = sndFile.ChnSettings[sourceChannel].nPan;
87 			nGlobalVol = sndFile.ChnSettings[sourceChannel].nVolume;
88 			if(dwFlags[CHN_MUTE])
89 			{
90 				dwFlags.reset(CHN_MUTE);
91 				dwFlags.set(muteFlag);
92 			}
93 		} else
94 		{
95 			dwFlags.reset();
96 			nPan = 128;
97 			nGlobalVol = 64;
98 		}
99 		nRestorePanOnNewNote = 0;
100 		nRestoreCutoffOnNewNote = 0;
101 		nRestoreResonanceOnNewNote = 0;
102 	}
103 }
104 
105 
Stop()106 void ModChannel::Stop()
107 {
108 	nPeriod = 0;
109 	increment.Set(0);
110 	position.Set(0);
111 	nLeftVU = nRightVU = 0;
112 	nVolume = 0;
113 	pCurrentSample = nullptr;
114 }
115 
116 
UpdateInstrumentVolume(const ModSample * smp,const ModInstrument * ins)117 void ModChannel::UpdateInstrumentVolume(const ModSample *smp, const ModInstrument *ins)
118 {
119 	nInsVol = 64;
120 	if(smp != nullptr)
121 		nInsVol = smp->nGlobalVol;
122 	if(ins != nullptr)
123 		nInsVol = (nInsVol * ins->nGlobalVol) / 64;
124 }
125 
126 
GetPluginNote(bool realNoteMapping) const127 ModCommand::NOTE ModChannel::GetPluginNote(bool realNoteMapping) const
128 {
129 	if(nArpeggioLastNote != NOTE_NONE)
130 	{
131 		// If an arpeggio is playing, this definitely the last playing note, which may be different from the arpeggio base note stored in nNote.
132 		return nArpeggioLastNote;
133 	}
134 	ModCommand::NOTE plugNote = mpt::saturate_cast<ModCommand::NOTE>(nNote - nTranspose);
135 	// Caution: When in compatible mode, ModChannel::nNote stores the "real" note, not the mapped note!
136 	if(realNoteMapping && pModInstrument != nullptr && plugNote >= NOTE_MIN && plugNote < (std::size(pModInstrument->NoteMap) + NOTE_MIN))
137 	{
138 		plugNote = pModInstrument->NoteMap[plugNote - NOTE_MIN];
139 	}
140 	return plugNote;
141 }
142 
143 
SetInstrumentPan(int32 pan,const CSoundFile & sndFile)144 void ModChannel::SetInstrumentPan(int32 pan, const CSoundFile &sndFile)
145 {
146 	// IT compatibility: Instrument and sample panning does not override channel panning
147 	// Test case: PanResetInstr.it
148 	if(sndFile.m_playBehaviour[kITDoNotOverrideChannelPan])
149 	{
150 		nRestorePanOnNewNote = static_cast<uint16>(nPan + 1);
151 		if(dwFlags[CHN_SURROUND])
152 			nRestorePanOnNewNote |= 0x8000;
153 	}
154 	nPan = pan;
155 }
156 
157 
RecalcTuningFreq(Tuning::RATIOTYPE vibratoFactor,Tuning::NOTEINDEXTYPE arpeggioSteps,const CSoundFile & sndFile)158 void ModChannel::RecalcTuningFreq(Tuning::RATIOTYPE vibratoFactor, Tuning::NOTEINDEXTYPE arpeggioSteps, const CSoundFile &sndFile)
159 {
160 	if(!HasCustomTuning())
161 		return;
162 
163 	ModCommand::NOTE note = ModCommand::IsNote(nNote) ? nNote : nLastNote;
164 
165 	if(sndFile.m_playBehaviour[kITRealNoteMapping] && note >= NOTE_MIN && note <= NOTE_MAX)
166 		note = pModInstrument->NoteMap[note - NOTE_MIN];
167 
168 	nPeriod = mpt::saturate_round<uint32>(nC5Speed * vibratoFactor * pModInstrument->pTuning->GetRatio(note - NOTE_MIDDLEC + arpeggioSteps, nFineTune + m_PortamentoFineSteps) * (1 << FREQ_FRACBITS));
169 }
170 
171 
172 // IT command S73-S7E
InstrumentControl(uint8 param,const CSoundFile & sndFile)173 void ModChannel::InstrumentControl(uint8 param, const CSoundFile &sndFile)
174 {
175 	param &= 0x0F;
176 	switch(param)
177 	{
178 		case 0x3: nNNA = NewNoteAction::NoteCut; break;
179 		case 0x4: nNNA = NewNoteAction::Continue; break;
180 		case 0x5: nNNA = NewNoteAction::NoteOff; break;
181 		case 0x6: nNNA = NewNoteAction::NoteFade; break;
182 		case 0x7: VolEnv.flags.reset(ENV_ENABLED); break;
183 		case 0x8: VolEnv.flags.set(ENV_ENABLED); break;
184 		case 0x9: PanEnv.flags.reset(ENV_ENABLED); break;
185 		case 0xA: PanEnv.flags.set(ENV_ENABLED); break;
186 		case 0xB: PitchEnv.flags.reset(ENV_ENABLED); break;
187 		case 0xC: PitchEnv.flags.set(ENV_ENABLED); break;
188 		case 0xD:  // S7D: Enable pitch envelope, force to play as pitch envelope
189 		case 0xE:  // S7E: Enable pitch envelope, force to play as filter envelope
190 			if(sndFile.GetType() == MOD_TYPE_MPT)
191 			{
192 				PitchEnv.flags.set(ENV_ENABLED);
193 				PitchEnv.flags.set(ENV_FILTER, param != 0xD);
194 			}
195 			break;
196 	}
197 }
198 
199 
200 OPENMPT_NAMESPACE_END
201