2021-12-26 11:29:43 +00:00
/*
* Sndmix . cpp
* - - - - - - - - - - -
* Purpose : Pattern playback , effect processing
* Notes : ( currently none )
* Authors : Olivier Lapicque
* OpenMPT Devs
* The OpenMPT source code is released under the BSD license . Read LICENSE for more details .
*/
# include "stdafx.h"
# include "Sndfile.h"
# include "MixerLoops.h"
# include "MIDIEvents.h"
# include "Tables.h"
# ifdef MODPLUG_TRACKER
# include "../mptrack/TrackerSettings.h"
# endif // MODPLUG_TRACKER
# ifndef NO_PLUGINS
# include "plugins/PlugInterface.h"
# endif // NO_PLUGINS
# include "OPL.h"
OPENMPT_NAMESPACE_BEGIN
// Log tables for pre-amp
// Pre-amp (or more precisely: Pre-attenuation) depends on the number of channels,
// Which this table takes care of.
static constexpr uint8 PreAmpTable [ 16 ] =
{
0x60 , 0x60 , 0x60 , 0x70 , // 0-7
0x80 , 0x88 , 0x90 , 0x98 , // 8-15
0xA0 , 0xA4 , 0xA8 , 0xAC , // 16-23
0xB0 , 0xB4 , 0xB8 , 0xBC , // 24-31
} ;
# ifndef NO_AGC
static constexpr uint8 PreAmpAGCTable [ 16 ] =
{
0x60 , 0x60 , 0x60 , 0x64 ,
0x68 , 0x70 , 0x78 , 0x80 ,
0x84 , 0x88 , 0x8C , 0x90 ,
0x92 , 0x94 , 0x96 , 0x98 ,
} ;
# endif
// Compensate frequency slide LUTs depending on whether we are handling periods or frequency - "up" and "down" in function name are seen from frequency perspective.
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 ] ; }
void CSoundFile : : SetMixerSettings ( const MixerSettings & mixersettings )
{
SetPreAmp ( mixersettings . m_nPreAmp ) ; // adjust agc
bool reset = false ;
if (
( mixersettings . gdwMixingFreq ! = m_MixerSettings . gdwMixingFreq )
| |
( mixersettings . gnChannels ! = m_MixerSettings . gnChannels )
| |
( mixersettings . MixerFlags ! = m_MixerSettings . MixerFlags ) )
reset = true ;
m_MixerSettings = mixersettings ;
InitPlayer ( reset ) ;
}
void CSoundFile : : SetResamplerSettings ( const CResamplerSettings & resamplersettings )
{
m_Resampler . m_Settings = resamplersettings ;
m_Resampler . UpdateTables ( ) ;
InitAmigaResampler ( ) ;
}
void CSoundFile : : InitPlayer ( bool bReset )
{
if ( bReset )
{
ResetMixStat ( ) ;
m_dryLOfsVol = m_dryROfsVol = 0 ;
m_surroundLOfsVol = m_surroundROfsVol = 0 ;
InitAmigaResampler ( ) ;
}
m_Resampler . UpdateTables ( ) ;
# ifndef NO_REVERB
m_Reverb . Initialize ( bReset , m_MixerSettings . gdwMixingFreq ) ;
# endif
# ifndef NO_DSP
m_Surround . Initialize ( bReset , m_MixerSettings . gdwMixingFreq ) ;
# endif
# ifndef NO_DSP
m_MegaBass . Initialize ( bReset , m_MixerSettings . gdwMixingFreq ) ;
# endif
# ifndef NO_EQ
m_EQ . Initialize ( bReset , m_MixerSettings . gdwMixingFreq ) ;
# endif
# ifndef NO_AGC
m_AGC . Initialize ( bReset , m_MixerSettings . gdwMixingFreq ) ;
# endif
# ifndef NO_DSP
m_BitCrush . Initialize ( bReset , m_MixerSettings . gdwMixingFreq ) ;
# endif
if ( m_opl )
{
m_opl - > Initialize ( m_MixerSettings . gdwMixingFreq ) ;
}
}
bool CSoundFile : : FadeSong ( uint32 msec )
{
samplecount_t nsamples = Util : : muldiv ( msec , m_MixerSettings . gdwMixingFreq , 1000 ) ;
if ( nsamples < = 0 ) return false ;
if ( nsamples > 0x100000 ) nsamples = 0x100000 ;
m_PlayState . m_nBufferCount = nsamples ;
int32 nRampLength = static_cast < int32 > ( m_PlayState . m_nBufferCount ) ;
// Ramp everything down
for ( uint32 noff = 0 ; noff < m_nMixChannels ; noff + + )
{
ModChannel & pramp = m_PlayState . Chn [ m_PlayState . ChnMix [ noff ] ] ;
pramp . newRightVol = pramp . newLeftVol = 0 ;
pramp . leftRamp = - pramp . leftVol * ( 1 < < VOLUMERAMPPRECISION ) / nRampLength ;
pramp . rightRamp = - pramp . rightVol * ( 1 < < VOLUMERAMPPRECISION ) / nRampLength ;
pramp . rampLeftVol = pramp . leftVol * ( 1 < < VOLUMERAMPPRECISION ) ;
pramp . rampRightVol = pramp . rightVol * ( 1 < < VOLUMERAMPPRECISION ) ;
pramp . nRampLength = nRampLength ;
pramp . dwFlags . set ( CHN_VOLUMERAMP ) ;
}
return true ;
}
// Apply stereo separation factor on an interleaved stereo/quad stream.
// count = Number of stereo sample pairs to process
// separation = -256...256 (negative values = swap L/R, 0 = mono, 128 = normal)
static void ApplyStereoSeparation ( mixsample_t * mixBuf , std : : size_t count , int32 separation )
{
# ifdef MPT_INTMIXER
const mixsample_t factor_num = separation ; // 128 =^= 1.0f
const mixsample_t factor_den = MixerSettings : : StereoSeparationScale ; // 128
const mixsample_t normalize_den = 2 ; // mid/side pre/post normalization
const mixsample_t mid_den = normalize_den ;
const mixsample_t side_num = factor_num ;
const mixsample_t side_den = factor_den * normalize_den ;
# else
const float normalize_factor = 0.5f ; // cumulative mid/side normalization factor (1/sqrt(2))*(1/sqrt(2))
const float factor = static_cast < float > ( separation ) / static_cast < float > ( MixerSettings : : StereoSeparationScale ) ; // sep / 128
const float mid_factor = normalize_factor ;
const float side_factor = factor * normalize_factor ;
# endif
for ( std : : size_t i = 0 ; i < count ; i + + )
{
mixsample_t l = mixBuf [ 0 ] ;
mixsample_t r = mixBuf [ 1 ] ;
mixsample_t m = l + r ;
mixsample_t s = l - r ;
# ifdef MPT_INTMIXER
m / = mid_den ;
s = Util : : muldiv ( s , side_num , side_den ) ;
# else
m * = mid_factor ;
s * = side_factor ;
# endif
l = m + s ;
r = m - s ;
mixBuf [ 0 ] = l ;
mixBuf [ 1 ] = r ;
mixBuf + = 2 ;
}
}
static void ApplyStereoSeparation ( mixsample_t * SoundFrontBuffer , mixsample_t * SoundRearBuffer , std : : size_t channels , std : : size_t countChunk , int32 separation )
{
if ( separation = = MixerSettings : : StereoSeparationScale )
{ // identity
return ;
}
if ( channels > = 2 ) ApplyStereoSeparation ( SoundFrontBuffer , countChunk , separation ) ;
if ( channels > = 4 ) ApplyStereoSeparation ( SoundRearBuffer , countChunk , separation ) ;
}
void CSoundFile : : ProcessInputChannels ( IAudioSource & source , std : : size_t countChunk )
{
for ( std : : size_t channel = 0 ; channel < NUMMIXINPUTBUFFERS ; + + channel )
{
std : : fill ( & ( MixInputBuffer [ channel ] [ 0 ] ) , & ( MixInputBuffer [ channel ] [ countChunk ] ) , 0 ) ;
}
mixsample_t * buffers [ NUMMIXINPUTBUFFERS ] ;
for ( std : : size_t channel = 0 ; channel < NUMMIXINPUTBUFFERS ; + + channel )
{
buffers [ channel ] = MixInputBuffer [ channel ] ;
}
source . FillCallback ( buffers , m_MixerSettings . NumInputChannels , countChunk ) ;
}
CSoundFile : : samplecount_t CSoundFile : : Read ( samplecount_t count , IAudioReadTarget & target , IAudioSource & source )
{
MPT_ASSERT_ALWAYS ( m_MixerSettings . IsValid ( ) ) ;
samplecount_t countRendered = 0 ;
samplecount_t countToRender = count ;
while ( ! m_SongFlags [ SONG_ENDREACHED ] & & countToRender > 0 )
{
// Update Channel Data
if ( ! m_PlayState . m_nBufferCount )
{
// Last tick or fade completely processed, find out what to do next
if ( m_SongFlags [ SONG_FADINGSONG ] )
{
// Song was faded out
m_SongFlags . set ( SONG_ENDREACHED ) ;
} else if ( ReadNote ( ) )
{
// Render next tick (normal progress)
MPT_ASSERT ( m_PlayState . m_nBufferCount > 0 ) ;
# ifdef MODPLUG_TRACKER
// Save pattern cue points for WAV rendering here (if we reached a new pattern, that is.)
if ( m_PatternCuePoints ! = nullptr & & ( m_PatternCuePoints - > empty ( ) | | m_PlayState . m_nCurrentOrder ! = m_PatternCuePoints - > back ( ) . order ) )
{
PatternCuePoint cue ;
cue . offset = countRendered ;
cue . order = m_PlayState . m_nCurrentOrder ;
cue . processed = false ; // We don't know the base offset in the file here. It has to be added in the main conversion loop.
m_PatternCuePoints - > push_back ( cue ) ;
}
# endif
} else
{
// No new pattern data
# ifdef MODPLUG_TRACKER
if ( ( m_nMaxOrderPosition ) & & ( m_PlayState . m_nCurrentOrder > = m_nMaxOrderPosition ) )
{
m_SongFlags . set ( SONG_ENDREACHED ) ;
}
# endif // MODPLUG_TRACKER
if ( IsRenderingToDisc ( ) )
{
// Disable song fade when rendering or when requested in libopenmpt.
m_SongFlags . set ( SONG_ENDREACHED ) ;
} else
{ // end of song reached, fade it out
if ( FadeSong ( FADESONGDELAY ) ) // sets m_nBufferCount xor returns false
{ // FadeSong sets m_nBufferCount here
MPT_ASSERT ( m_PlayState . m_nBufferCount > 0 ) ;
m_SongFlags . set ( SONG_FADINGSONG ) ;
} else
{
m_SongFlags . set ( SONG_ENDREACHED ) ;
}
}
}
}
if ( m_SongFlags [ SONG_ENDREACHED ] )
{
// Mix done.
// If we decide to continue the mix (possible in libopenmpt), the tick count
// is valid right now (0), meaning that no new row data will be processed.
// This would effectively prolong the last played row.
m_PlayState . m_nTickCount = GetNumTicksOnCurrentRow ( ) ;
break ;
}
MPT_ASSERT ( m_PlayState . m_nBufferCount > 0 ) ; // assert that we have actually something to do
const samplecount_t countChunk = std : : min ( { static_cast < samplecount_t > ( MIXBUFFERSIZE ) , static_cast < samplecount_t > ( m_PlayState . m_nBufferCount ) , static_cast < samplecount_t > ( countToRender ) } ) ;
if ( m_MixerSettings . NumInputChannels > 0 )
{
ProcessInputChannels ( source , countChunk ) ;
}
CreateStereoMix ( countChunk ) ;
if ( m_opl )
{
m_opl - > Mix ( MixSoundBuffer , countChunk , m_OPLVolumeFactor * m_nVSTiVolume / 48 ) ;
}
# ifndef NO_REVERB
m_Reverb . Process ( MixSoundBuffer , countChunk ) ;
# endif // NO_REVERB
# ifndef NO_PLUGINS
if ( m_loadedPlugins )
{
ProcessPlugins ( countChunk ) ;
}
# endif // NO_PLUGINS
if ( m_MixerSettings . gnChannels = = 1 )
{
MonoFromStereo ( MixSoundBuffer , countChunk ) ;
}
if ( m_PlayConfig . getGlobalVolumeAppliesToMaster ( ) )
{
ProcessGlobalVolume ( countChunk ) ;
}
if ( m_MixerSettings . m_nStereoSeparation ! = MixerSettings : : StereoSeparationScale )
{
ProcessStereoSeparation ( countChunk ) ;
}
if ( m_MixerSettings . DSPMask )
{
ProcessDSP ( countChunk ) ;
}
if ( m_MixerSettings . gnChannels = = 4 )
{
InterleaveFrontRear ( MixSoundBuffer , MixRearBuffer , countChunk ) ;
}
target . DataCallback ( MixSoundBuffer , m_MixerSettings . gnChannels , countChunk ) ;
// Buffer ready
countRendered + = countChunk ;
countToRender - = countChunk ;
m_PlayState . m_nBufferCount - = countChunk ;
m_PlayState . m_lTotalSampleCount + = countChunk ; // increase sample count for VSTTimeInfo.
# ifdef MODPLUG_TRACKER
if ( IsRenderingToDisc ( ) )
{
// Stop playback on F00 if no more voices are active.
// F00 sets the tick count to 65536 in FT2, so it just generates a reaaaally long row.
// Usually this command can be found at the end of a song to effectively stop playback.
// Since we don't want to render hours of silence, we are going to check if there are
// still any channels playing, and if that is no longer the case, we stop playback at
// the end of the next tick.
if ( m_PlayState . m_nMusicSpeed = = uint16_max & & ( m_nMixStat = = 0 | | m_PlayState . m_nGlobalVolume = = 0 ) & & GetType ( ) = = MOD_TYPE_XM & & ! m_PlayState . m_nBufferCount )
{
m_SongFlags . set ( SONG_ENDREACHED ) ;
}
}
# endif // MODPLUG_TRACKER
}
// mix done
return countRendered ;
}
void CSoundFile : : ProcessDSP ( uint32 countChunk )
{
# ifndef NO_DSP
if ( m_MixerSettings . DSPMask & SNDDSP_SURROUND )
{
m_Surround . Process ( MixSoundBuffer , MixRearBuffer , countChunk , m_MixerSettings . gnChannels ) ;
}
# endif // NO_DSP
# ifndef NO_DSP
if ( m_MixerSettings . DSPMask & SNDDSP_MEGABASS )
{
m_MegaBass . Process ( MixSoundBuffer , MixRearBuffer , countChunk , m_MixerSettings . gnChannels ) ;
}
# endif // NO_DSP
# ifndef NO_EQ
if ( m_MixerSettings . DSPMask & SNDDSP_EQ )
{
m_EQ . Process ( MixSoundBuffer , MixRearBuffer , countChunk , m_MixerSettings . gnChannels ) ;
}
# endif // NO_EQ
# ifndef NO_AGC
if ( m_MixerSettings . DSPMask & SNDDSP_AGC )
{
m_AGC . Process ( MixSoundBuffer , MixRearBuffer , countChunk , m_MixerSettings . gnChannels ) ;
}
# endif // NO_AGC
# ifndef NO_DSP
if ( m_MixerSettings . DSPMask & SNDDSP_BITCRUSH )
{
m_BitCrush . Process ( MixSoundBuffer , MixRearBuffer , countChunk , m_MixerSettings . gnChannels ) ;
}
# endif // NO_DSP
# if defined(NO_DSP) && defined(NO_EQ) && defined(NO_AGC)
MPT_UNREFERENCED_PARAMETER ( countChunk ) ;
# endif
}
/////////////////////////////////////////////////////////////////////////////
// Handles navigation/effects
bool CSoundFile : : ProcessRow ( )
{
while ( + + m_PlayState . m_nTickCount > = GetNumTicksOnCurrentRow ( ) )
{
// When having an EEx effect on the same row as a Dxx jump, the target row is not played in ProTracker.
// Test case: DelayBreak.mod (based on condom_corruption by Travolta)
const bool ignoreRow = m_PlayState . m_nPatternDelay ! = 0 & & m_SongFlags [ SONG_BREAKTOROW ] & & GetType ( ) = = MOD_TYPE_MOD ;
// Done with the last row of the pattern or jumping somewhere else
const bool patternTransition = m_PlayState . m_nNextRow = = 0 | | m_SongFlags [ SONG_BREAKTOROW ] ;
if ( patternTransition )
{
if ( GetType ( ) = = MOD_TYPE_S3M )
{
// Reset pattern loop start
// Test case: LoopReset.s3m
for ( CHANNELINDEX i = 0 ; i < GetNumChannels ( ) ; i + + )
{
m_PlayState . Chn [ i ] . nPatternLoop = 0 ;
}
}
}
m_PlayState . m_nPatternDelay = 0 ;
m_PlayState . m_nFrameDelay = 0 ;
m_PlayState . m_nTickCount = 0 ;
m_PlayState . m_nRow = m_PlayState . m_nNextRow ;
// Reset Pattern Loop Effect
m_PlayState . m_nCurrentOrder = m_PlayState . m_nNextOrder ;
# ifdef MODPLUG_TRACKER
if ( patternTransition )
{
HandlePatternTransitionEvents ( ) ;
}
// "Lock row" editing feature
if ( m_lockRowStart ! = ROWINDEX_INVALID & & ( m_PlayState . m_nRow < m_lockRowStart | | m_PlayState . m_nRow > m_lockRowEnd ) & & ! IsRenderingToDisc ( ) )
{
m_PlayState . m_nRow = m_lockRowStart ;
}
// "Lock order" editing feature
if ( Order ( ) . IsPositionLocked ( m_PlayState . m_nCurrentOrder ) & & ! IsRenderingToDisc ( ) )
{
m_PlayState . m_nCurrentOrder = m_lockOrderStart ;
}
# endif // MODPLUG_TRACKER
// Check if pattern is valid
if ( ! m_SongFlags [ SONG_PATTERNLOOP ] )
{
m_PlayState . m_nPattern = ( m_PlayState . m_nCurrentOrder < Order ( ) . size ( ) ) ? Order ( ) [ m_PlayState . m_nCurrentOrder ] : Order . GetInvalidPatIndex ( ) ;
if ( m_PlayState . m_nPattern < Patterns . Size ( ) & & ! Patterns [ m_PlayState . m_nPattern ] . IsValid ( ) ) m_PlayState . m_nPattern = Order . GetIgnoreIndex ( ) ;
while ( m_PlayState . m_nPattern > = Patterns . Size ( ) )
{
// End of song?
if ( ( m_PlayState . m_nPattern = = Order . GetInvalidPatIndex ( ) ) | | ( m_PlayState . m_nCurrentOrder > = Order ( ) . size ( ) ) )
{
//if (!m_nRepeatCount) return false;
ORDERINDEX restartPosOverride = Order ( ) . GetRestartPos ( ) ;
if ( restartPosOverride = = 0 & & m_PlayState . m_nCurrentOrder < = Order ( ) . size ( ) & & m_PlayState . m_nCurrentOrder > 0 )
{
// Subtune detection. Subtunes are separated by "---" order items, so if we're in a
// subtune and there's no restart position, we go to the first order of the subtune
// (i.e. the first order after the previous "---" item)
for ( ORDERINDEX ord = m_PlayState . m_nCurrentOrder - 1 ; ord > 0 ; ord - - )
{
if ( Order ( ) [ ord ] = = Order . GetInvalidPatIndex ( ) )
{
// Jump back to first order of this subtune
restartPosOverride = ord + 1 ;
break ;
}
}
}
// If channel resetting is disabled in MPT, we will emulate a pattern break (and we always do it if we're not in MPT)
# ifdef MODPLUG_TRACKER
if ( ! ( TrackerSettings : : Instance ( ) . m_dwPatternSetup & PATTERN_RESETCHANNELS ) )
# endif // MODPLUG_TRACKER
{
m_SongFlags . set ( SONG_BREAKTOROW ) ;
}
if ( restartPosOverride = = 0 & & ! m_SongFlags [ SONG_BREAKTOROW ] )
{
//rewbs.instroVSTi: stop all VSTi at end of song, if looping.
StopAllVsti ( ) ;
m_PlayState . m_nMusicSpeed = m_nDefaultSpeed ;
m_PlayState . m_nMusicTempo = m_nDefaultTempo ;
m_PlayState . m_nGlobalVolume = m_nDefaultGlobalVolume ;
for ( CHANNELINDEX i = 0 ; i < MAX_CHANNELS ; i + + )
{
auto & chn = m_PlayState . Chn [ i ] ;
if ( chn . dwFlags [ CHN_ADLIB ] & & m_opl )
{
m_opl - > NoteCut ( i ) ;
}
chn . dwFlags . set ( CHN_NOTEFADE | CHN_KEYOFF ) ;
chn . nFadeOutVol = 0 ;
if ( i < m_nChannels )
{
chn . nGlobalVol = ChnSettings [ i ] . nVolume ;
chn . nVolume = ChnSettings [ i ] . nVolume ;
chn . nPan = ChnSettings [ i ] . nPan ;
chn . nPanSwing = chn . nVolSwing = 0 ;
chn . nCutSwing = chn . nResSwing = 0 ;
chn . nOldVolParam = 0 ;
chn . oldOffset = 0 ;
chn . nOldHiOffset = 0 ;
chn . nPortamentoDest = 0 ;
if ( ! chn . nLength )
{
chn . dwFlags = ChnSettings [ i ] . dwFlags ;
chn . nLoopStart = 0 ;
chn . nLoopEnd = 0 ;
chn . pModInstrument = nullptr ;
chn . pModSample = nullptr ;
}
}
}
}
//Handle Repeat position
//if (m_nRepeatCount > 0) m_nRepeatCount--;
m_PlayState . m_nCurrentOrder = restartPosOverride ;
m_SongFlags . reset ( SONG_BREAKTOROW ) ;
//If restart pos points to +++, move along
while ( m_PlayState . m_nCurrentOrder < Order ( ) . size ( ) & & Order ( ) [ m_PlayState . m_nCurrentOrder ] = = Order . GetIgnoreIndex ( ) )
{
m_PlayState . m_nCurrentOrder + + ;
}
//Check for end of song or bad pattern
if ( m_PlayState . m_nCurrentOrder > = Order ( ) . size ( )
| | ! Order ( ) . IsValidPat ( m_PlayState . m_nCurrentOrder ) )
{
visitedSongRows . Initialize ( true ) ;
return false ;
}
} else
{
m_PlayState . m_nCurrentOrder + + ;
}
if ( m_PlayState . m_nCurrentOrder < Order ( ) . size ( ) )
m_PlayState . m_nPattern = Order ( ) [ m_PlayState . m_nCurrentOrder ] ;
else
m_PlayState . m_nPattern = Order . GetInvalidPatIndex ( ) ;
if ( m_PlayState . m_nPattern < Patterns . Size ( ) & & ! Patterns [ m_PlayState . m_nPattern ] . IsValid ( ) )
m_PlayState . m_nPattern = Order . GetIgnoreIndex ( ) ;
}
m_PlayState . m_nNextOrder = m_PlayState . m_nCurrentOrder ;
# ifdef MODPLUG_TRACKER
if ( ( m_nMaxOrderPosition ) & & ( m_PlayState . m_nCurrentOrder > = m_nMaxOrderPosition ) ) return false ;
# endif // MODPLUG_TRACKER
}
// Weird stuff?
if ( ! Patterns . IsValidPat ( m_PlayState . m_nPattern ) )
return false ;
// Did we jump to an invalid row?
if ( m_PlayState . m_nRow > = Patterns [ m_PlayState . m_nPattern ] . GetNumRows ( ) ) m_PlayState . m_nRow = 0 ;
// Has this row been visited before? We might want to stop playback now.
// But: We will not mark the row as modified if the song is not in loop mode but
// the pattern loop (editor flag, not to be confused with the pattern loop effect)
// flag is set - because in that case, the module would stop after the first pattern loop...
const bool overrideLoopCheck = ( m_nRepeatCount ! = - 1 ) & & m_SongFlags [ SONG_PATTERNLOOP ] ;
// If a row is jumped over due to a ProTracker quirk, don't add it to the visited rows (unless we notice that we are stuck in a loop by repeatedly playing that row)
const bool checkIgnoreRow = ! ignoreRow | | visitedSongRows . GetLastVisitedRow ( ) = = m_PlayState . m_nRow ;
if ( ! overrideLoopCheck & & checkIgnoreRow & & visitedSongRows . IsVisited ( m_PlayState . m_nCurrentOrder , m_PlayState . m_nRow , true ) )
{
if ( m_nRepeatCount )
{
// repeat count == -1 means repeat infinitely.
if ( m_nRepeatCount > 0 )
{
m_nRepeatCount - - ;
}
// Forget all but the current row.
visitedSongRows . Initialize ( true ) ;
visitedSongRows . Visit ( m_PlayState . m_nCurrentOrder , m_PlayState . m_nRow ) ;
} else
{
# ifdef MODPLUG_TRACKER
// Let's check again if this really is the end of the song.
// The visited rows vector might have been screwed up while editing...
// This is of course not possible during rendering to WAV, so we ignore that case.
bool isReallyAtEnd = false ;
if ( IsRenderingToDisc ( ) )
{
isReallyAtEnd = true ;
} else
{
for ( const auto & t : GetLength ( eNoAdjust , GetLengthTarget ( true ) ) )
{
if ( t . lastOrder = = m_PlayState . m_nCurrentOrder & & t . lastRow = = m_PlayState . m_nRow )
{
isReallyAtEnd = true ;
break ;
}
}
}
if ( isReallyAtEnd )
{
// This is really the song's end!
visitedSongRows . Initialize ( true ) ;
return false ;
} else
{
// Ok, this is really dirty, but we have to update the visited rows vector...
GetLength ( eAdjustOnlyVisitedRows , GetLengthTarget ( m_PlayState . m_nCurrentOrder , m_PlayState . m_nRow ) ) ;
}
# else
if ( m_SongFlags [ SONG_PLAYALLSONGS ] )
{
// When playing all subsongs consecutively, first search for any hidden subsongs...
if ( ! visitedSongRows . GetFirstUnvisitedRow ( m_PlayState . m_nCurrentOrder , m_PlayState . m_nRow , true ) )
{
// ...and then try the next sequence.
m_PlayState . m_nNextOrder = m_PlayState . m_nCurrentOrder = 0 ;
m_PlayState . m_nNextRow = m_PlayState . m_nRow = 0 ;
if ( Order . GetCurrentSequenceIndex ( ) > = Order . GetNumSequences ( ) - 1 )
{
Order . SetSequence ( 0 ) ;
visitedSongRows . Initialize ( true ) ;
return false ;
}
Order . SetSequence ( Order . GetCurrentSequenceIndex ( ) + 1 ) ;
visitedSongRows . Initialize ( true ) ;
}
// When jumping to the next subsong, stop all playing notes from the previous song...
const auto muteFlag = CSoundFile : : GetChannelMuteFlag ( ) ;
for ( CHANNELINDEX i = 0 ; i < MAX_CHANNELS ; i + + )
m_PlayState . Chn [ i ] . Reset ( ModChannel : : resetSetPosFull , * this , i , muteFlag ) ;
StopAllVsti ( ) ;
// ...and the global playback information.
m_PlayState . m_nMusicSpeed = m_nDefaultSpeed ;
m_PlayState . m_nMusicTempo = m_nDefaultTempo ;
m_PlayState . m_nGlobalVolume = m_nDefaultGlobalVolume ;
m_PlayState . m_nNextOrder = m_PlayState . m_nCurrentOrder ;
m_PlayState . m_nNextRow = m_PlayState . m_nRow ;
if ( Order ( ) . size ( ) > m_PlayState . m_nCurrentOrder )
m_PlayState . m_nPattern = Order ( ) [ m_PlayState . m_nCurrentOrder ] ;
visitedSongRows . Visit ( m_PlayState . m_nCurrentOrder , m_PlayState . m_nRow ) ;
if ( ! Patterns . IsValidPat ( m_PlayState . m_nPattern ) )
return false ;
} else
{
visitedSongRows . Initialize ( true ) ;
return false ;
}
# endif // MODPLUG_TRACKER
}
}
m_PlayState . m_nNextRow = m_PlayState . m_nRow + 1 ;
if ( m_PlayState . m_nNextRow > = Patterns [ m_PlayState . m_nPattern ] . GetNumRows ( ) )
{
if ( ! m_SongFlags [ SONG_PATTERNLOOP ] ) m_PlayState . m_nNextOrder = m_PlayState . m_nCurrentOrder + 1 ;
m_PlayState . m_nNextRow = 0 ;
// FT2 idiosyncrasy: When E60 is used on a pattern row x, the following pattern also starts from row x
// instead of the beginning of the pattern, unless there was a Bxx or Dxx effect.
if ( m_playBehaviour [ kFT2LoopE60Restart ] )
{
m_PlayState . m_nNextRow = m_PlayState . m_nNextPatStartRow ;
m_PlayState . m_nNextPatStartRow = 0 ;
}
}
// Reset channel values
ModCommand * m = Patterns [ m_PlayState . m_nPattern ] . GetpModCommand ( m_PlayState . m_nRow , 0 ) ;
for ( ModChannel * pChn = m_PlayState . Chn , * pEnd = pChn + m_nChannels ; pChn ! = pEnd ; pChn + + , m + + )
{
// First, handle some quirks that happen after the last tick of the previous row...
if ( m_playBehaviour [ KST3PortaAfterArpeggio ]
& & pChn - > nCommand = = CMD_ARPEGGIO // Previous row state!
& & ( m - > command = = CMD_PORTAMENTOUP | | m - > command = = CMD_PORTAMENTODOWN ) )
{
// In ST3, a portamento immediately following an arpeggio continues where the arpeggio left off.
// Test case: PortaAfterArp.s3m
pChn - > nPeriod = GetPeriodFromNote ( pChn - > nArpeggioLastNote , pChn - > nFineTune , pChn - > nC5Speed ) ;
}
if ( m_playBehaviour [ kMODOutOfRangeNoteDelay ]
& & ! m - > IsNote ( )
& & pChn - > rowCommand . IsNote ( )
& & pChn - > rowCommand . command = = CMD_MODCMDEX & & ( pChn - > rowCommand . param & 0xF0 ) = = 0xD0
& & ( pChn - > rowCommand . param & 0x0Fu ) > = m_PlayState . m_nMusicSpeed )
{
// In ProTracker, a note triggered by an out-of-range note delay can be heard on the next row
// if there is no new note on that row.
// Test case: NoteDelay-NextRow.mod
pChn - > nPeriod = GetPeriodFromNote ( pChn - > rowCommand . note , pChn - > nFineTune , 0 ) ;
}
if ( m_playBehaviour [ kMODTempoOnSecondTick ] & & ! m_playBehaviour [ kMODVBlankTiming ] & & m_PlayState . m_nMusicSpeed = = 1 & & pChn - > rowCommand . command = = CMD_TEMPO )
{
// ProTracker sets the tempo after the first tick. This block handles the case of one tick per row.
// Test case: TempoChange.mod
m_PlayState . m_nMusicTempo = TEMPO ( pChn - > rowCommand . param , 0 ) ;
}
pChn - > rowCommand = * m ;
pChn - > rightVol = pChn - > newRightVol ;
pChn - > leftVol = pChn - > newLeftVol ;
pChn - > dwFlags . reset ( CHN_VIBRATO | CHN_TREMOLO ) ;
if ( ! m_playBehaviour [ kITVibratoTremoloPanbrello ] ) pChn - > nPanbrelloOffset = 0 ;
pChn - > nCommand = CMD_NONE ;
pChn - > m_plugParamValueStep = 0 ;
}
// Now that we know which pattern we're on, we can update time signatures (global or pattern-specific)
UpdateTimeSignature ( ) ;
if ( ignoreRow )
{
m_PlayState . m_nTickCount = m_PlayState . m_nMusicSpeed ;
continue ;
}
break ;
}
// Should we process tick0 effects?
if ( ! m_PlayState . m_nMusicSpeed ) m_PlayState . m_nMusicSpeed = 1 ;
//End of row? stop pattern step (aka "play row").
# ifdef MODPLUG_TRACKER
if ( m_PlayState . m_nTickCount > = GetNumTicksOnCurrentRow ( ) - 1 )
{
if ( m_SongFlags [ SONG_STEP ] )
{
m_SongFlags . reset ( SONG_STEP ) ;
m_SongFlags . set ( SONG_PAUSED ) ;
}
}
# endif // MODPLUG_TRACKER
if ( m_PlayState . m_nTickCount )
{
m_SongFlags . reset ( SONG_FIRSTTICK ) ;
if ( ! ( GetType ( ) & ( MOD_TYPE_XM | MOD_TYPE_MT2 ) )
& & ( GetType ( ) ! = MOD_TYPE_MOD | | m_SongFlags [ SONG_PT_MODE ] ) // Fix infinite loop in "GamerMan " by MrGamer, which was made with FT2
& & m_PlayState . m_nTickCount < GetNumTicksOnCurrentRow ( ) )
{
// Emulate first tick behaviour if Row Delay is set.
// Test cases: PatternDelaysRetrig.it, PatternDelaysRetrig.s3m, PatternDelaysRetrig.xm, PatternDelaysRetrig.mod
if ( ! ( m_PlayState . m_nTickCount % ( m_PlayState . m_nMusicSpeed + m_PlayState . m_nFrameDelay ) ) )
{
m_SongFlags . set ( SONG_FIRSTTICK ) ;
}
}
} else
{
m_SongFlags . set ( SONG_FIRSTTICK ) ;
m_SongFlags . reset ( SONG_BREAKTOROW ) ;
}
// Update Effects
return ProcessEffects ( ) ;
}
////////////////////////////////////////////////////////////////////////////////////////////
// Channel effect processing
// Calculate delta for Vibrato / Tremolo / Panbrello effect
int CSoundFile : : GetVibratoDelta ( int type , int position ) const
{
// IT compatibility: IT has its own, more precise tables
if ( m_playBehaviour [ kITVibratoTremoloPanbrello ] )
{
position & = 0xFF ;
switch ( type & 0x03 )
{
case 0 : // Sine
default :
return ITSinusTable [ position ] ;
case 1 : // Ramp down
return 64 - ( position + 1 ) / 2 ;
case 2 : // Square
return position < 128 ? 64 : 0 ;
case 3 : // Random
return mpt : : random < int , 7 > ( AccessPRNG ( ) ) - 0x40 ;
}
} else if ( GetType ( ) & ( MOD_TYPE_DIGI | MOD_TYPE_DBM ) )
{
// Other waveforms are not supported.
static constexpr int8 DBMSinus [ ] =
{
33 , 52 , 69 , 84 , 96 , 107 , 116 , 122 , 125 , 127 , 125 , 122 , 116 , 107 , 96 , 84 ,
69 , 52 , 33 , 13 , - 8 , - 31 , - 54 , - 79 , - 104 , - 128 , - 104 , - 79 , - 54 , - 31 , - 8 , 13 ,
} ;
return DBMSinus [ ( position / 2u ) & 0x1F ] ;
} else
{
position & = 0x3F ;
switch ( type & 0x03 )
{
case 0 : // Sine
default :
return ModSinusTable [ position ] ;
case 1 : // Ramp down
return ( position < 32 ? 0 : 255 ) - position * 4 ;
case 2 : // Square
return position < 32 ? 127 : - 127 ;
case 3 : // Random
return ModRandomTable [ position ] ;
}
}
}
void CSoundFile : : ProcessVolumeSwing ( ModChannel & chn , int & vol ) const
{
if ( m_playBehaviour [ kITSwingBehaviour ] )
{
vol + = chn . nVolSwing ;
Limit ( vol , 0 , 64 ) ;
} else if ( m_playBehaviour [ kMPTOldSwingBehaviour ] )
{
vol + = chn . nVolSwing ;
Limit ( vol , 0 , 256 ) ;
} else
{
chn . nVolume + = chn . nVolSwing ;
Limit ( chn . nVolume , 0 , 256 ) ;
vol = chn . nVolume ;
chn . nVolSwing = 0 ;
}
}
void CSoundFile : : ProcessPanningSwing ( ModChannel & chn ) const
{
if ( m_playBehaviour [ kITSwingBehaviour ] | | m_playBehaviour [ kMPTOldSwingBehaviour ] )
{
chn . nRealPan = chn . nPan + chn . nPanSwing ;
Limit ( chn . nRealPan , 0 , 256 ) ;
} else
{
chn . nPan + = chn . nPanSwing ;
Limit ( chn . nPan , 0 , 256 ) ;
chn . nPanSwing = 0 ;
chn . nRealPan = chn . nPan ;
}
}
void CSoundFile : : ProcessTremolo ( ModChannel & chn , int & vol ) const
{
if ( chn . dwFlags [ CHN_TREMOLO ] )
{
if ( m_SongFlags . test_all ( SONG_FIRSTTICK | SONG_PT_MODE ) )
{
// ProTracker doesn't apply tremolo nor advance on the first tick.
// Test case: VibratoReset.mod
return ;
}
// IT compatibility: Why would you not want to execute tremolo at volume 0?
if ( vol > 0 | | m_playBehaviour [ kITVibratoTremoloPanbrello ] )
{
// IT compatibility: We don't need a different attenuation here because of the different tables we're going to use
const uint8 attenuation = ( ( GetType ( ) & ( MOD_TYPE_XM | MOD_TYPE_MOD ) ) | | m_playBehaviour [ kITVibratoTremoloPanbrello ] ) ? 5 : 6 ;
int delta = GetVibratoDelta ( chn . nTremoloType , chn . nTremoloPos ) ;
if ( ( chn . nTremoloType & 0x03 ) = = 1 & & m_playBehaviour [ kFT2MODTremoloRampWaveform ] )
{
// FT2 compatibility: Tremolo ramp down / triangle implementation is weird and affected by vibrato position (copypaste bug)
// Test case: TremoloWaveforms.xm, TremoloVibrato.xm
uint8 ramp = ( chn . nTremoloPos * 4u ) & 0x7F ;
// Volume-colum vibrato gets executed first in FT2, so we may need to advance the vibrato position first
uint32 vibPos = chn . nVibratoPos ;
if ( ! m_SongFlags [ SONG_FIRSTTICK ] & & chn . dwFlags [ CHN_VIBRATO ] )
vibPos + = chn . nVibratoSpeed ;
if ( ( vibPos & 0x3F ) > = 32 )
ramp ^ = 0x7F ;
if ( ( chn . nTremoloPos & 0x3F ) > = 32 )
delta = - ramp ;
else
delta = ramp ;
}
if ( GetType ( ) ! = MOD_TYPE_DMF )
{
vol + = ( delta * chn . nTremoloDepth ) / ( 1 < < attenuation ) ;
} else
{
// Tremolo in DMF always attenuates by a percentage of the current note volume
vol - = ( vol * chn . nTremoloDepth * ( 64 - delta ) ) / ( 128 * 64 ) ;
}
}
if ( ! m_SongFlags [ SONG_FIRSTTICK ] | | ( ( GetType ( ) & ( MOD_TYPE_IT | MOD_TYPE_MPT ) ) & & ! m_SongFlags [ SONG_ITOLDEFFECTS ] ) )
{
// IT compatibility: IT has its own, more precise tables
if ( m_playBehaviour [ kITVibratoTremoloPanbrello ] )
chn . nTremoloPos + = 4 * chn . nTremoloSpeed ;
else
chn . nTremoloPos + = chn . nTremoloSpeed ;
}
}
}
void CSoundFile : : ProcessTremor ( CHANNELINDEX nChn , int & vol )
{
ModChannel & chn = m_PlayState . Chn [ nChn ] ;
if ( m_playBehaviour [ kFT2Tremor ] )
{
// FT2 Compatibility: Weird XM tremor.
// Test case: Tremor.xm
if ( chn . nTremorCount & 0x80 )
{
if ( ! m_SongFlags [ SONG_FIRSTTICK ] & & chn . nCommand = = CMD_TREMOR )
{
chn . nTremorCount & = ~ 0x20 ;
if ( chn . nTremorCount = = 0x80 )
{
// Reached end of off-time
chn . nTremorCount = ( chn . nTremorParam > > 4 ) | 0xC0 ;
} else if ( chn . nTremorCount = = 0xC0 )
{
// Reached end of on-time
chn . nTremorCount = ( chn . nTremorParam & 0x0F ) | 0x80 ;
} else
{
chn . nTremorCount - - ;
}
chn . dwFlags . set ( CHN_FASTVOLRAMP ) ;
}
if ( ( chn . nTremorCount & 0xE0 ) = = 0x80 )
{
vol = 0 ;
}
}
} else if ( chn . nCommand = = CMD_TREMOR )
{
// IT compatibility 12. / 13.: Tremor
if ( m_playBehaviour [ kITTremor ] )
{
if ( ( chn . nTremorCount & 0x80 ) & & chn . nLength )
{
if ( chn . nTremorCount = = 0x80 )
chn . nTremorCount = ( chn . nTremorParam > > 4 ) | 0xC0 ;
else if ( chn . nTremorCount = = 0xC0 )
chn . nTremorCount = ( chn . nTremorParam & 0x0F ) | 0x80 ;
else
chn . nTremorCount - - ;
}
if ( ( chn . nTremorCount & 0xC0 ) = = 0x80 )
vol = 0 ;
} else
{
uint8 ontime = chn . nTremorParam > > 4 ;
uint8 n = ontime + ( chn . nTremorParam & 0x0F ) ; // Total tremor cycle time (On + Off)
if ( ( ! ( GetType ( ) & ( MOD_TYPE_IT | MOD_TYPE_MPT ) ) ) | | m_SongFlags [ SONG_ITOLDEFFECTS ] )
{
n + = 2 ;
ontime + + ;
}
uint8 tremcount = chn . nTremorCount ;
if ( ! ( GetType ( ) & MOD_TYPE_XM ) )
{
if ( tremcount > = n ) tremcount = 0 ;
if ( tremcount > = ontime ) vol = 0 ;
chn . nTremorCount = tremcount + 1 ;
} else
{
if ( m_SongFlags [ SONG_FIRSTTICK ] )
{
// tremcount is only 0 on the first tremor tick after triggering a note.
if ( tremcount > 0 )
{
tremcount - - ;
}
} else
{
chn . nTremorCount = tremcount + 1 ;
}
if ( tremcount % n > = ontime ) vol = 0 ;
}
}
chn . dwFlags . set ( CHN_FASTVOLRAMP ) ;
}
# ifndef NO_PLUGINS
// Plugin tremor
if ( chn . nCommand = = CMD_TREMOR & & chn . pModInstrument & & chn . pModInstrument - > nMixPlug
& & ! chn . pModInstrument - > dwFlags [ INS_MUTE ]
& & ! chn . dwFlags [ CHN_MUTE | CHN_SYNCMUTE ]
& & ModCommand : : IsNote ( chn . nLastNote ) )
{
const ModInstrument * pIns = chn . pModInstrument ;
IMixPlugin * pPlugin = m_MixPlugins [ pIns - > nMixPlug - 1 ] . pMixPlugin ;
if ( pPlugin )
{
const bool isPlaying = pPlugin - > IsNotePlaying ( chn . nLastNote , nChn ) ;
if ( vol = = 0 & & isPlaying )
pPlugin - > MidiCommand ( * pIns , chn . nLastNote + NOTE_MAX_SPECIAL , 0 , nChn ) ;
else if ( vol ! = 0 & & ! isPlaying )
pPlugin - > MidiCommand ( * pIns , chn . nLastNote , static_cast < uint16 > ( chn . nVolume ) , nChn ) ;
}
}
# endif // NO_PLUGINS
}
bool CSoundFile : : IsEnvelopeProcessed ( const ModChannel & chn , EnvelopeType env ) const
{
if ( chn . pModInstrument = = nullptr )
{
return false ;
}
const InstrumentEnvelope & insEnv = chn . pModInstrument - > GetEnvelope ( env ) ;
// IT Compatibility: S77/S79/S7B do not disable the envelope, they just pause the counter
// Test cases: s77.it, EnvLoops.xm, PanSustainRelease.xm
bool playIfPaused = m_playBehaviour [ kITEnvelopePositionHandling ] | | m_playBehaviour [ kFT2PanSustainRelease ] ;
return ( ( chn . GetEnvelope ( env ) . flags [ ENV_ENABLED ] | | ( insEnv . dwFlags [ ENV_ENABLED ] & & playIfPaused ) )
& & ! insEnv . empty ( ) ) ;
}
void CSoundFile : : ProcessVolumeEnvelope ( ModChannel & chn , int & vol ) const
{
if ( IsEnvelopeProcessed ( chn , ENV_VOLUME ) )
{
const ModInstrument * pIns = chn . pModInstrument ;
if ( m_playBehaviour [ kITEnvelopePositionHandling ] & & chn . VolEnv . nEnvPosition = = 0 )
{
// If the envelope is disabled at the very same moment as it is triggered, we do not process anything.
return ;
}
const int envpos = chn . VolEnv . nEnvPosition - ( m_playBehaviour [ kITEnvelopePositionHandling ] ? 1 : 0 ) ;
// Get values in [0, 256]
int envval = pIns - > VolEnv . GetValueFromPosition ( envpos , 256 ) ;
// if we are in the release portion of the envelope,
// rescale envelope factor so that it is proportional to the release point
// and release envelope beginning.
if ( pIns - > VolEnv . nReleaseNode ! = ENV_RELEASE_NODE_UNSET
& & chn . VolEnv . nEnvValueAtReleaseJump ! = NOT_YET_RELEASED )
{
int envValueAtReleaseJump = chn . VolEnv . nEnvValueAtReleaseJump ;
int envValueAtReleaseNode = pIns - > VolEnv [ pIns - > VolEnv . nReleaseNode ] . value * 4 ;
//If we have just hit the release node, force the current env value
//to be that of the release node. This works around the case where
// we have another node at the same position as the release node.
if ( envpos = = pIns - > VolEnv [ pIns - > VolEnv . nReleaseNode ] . tick )
envval = envValueAtReleaseNode ;
if ( m_playBehaviour [ kLegacyReleaseNode ] )
{
// Old, hard to grasp release node behaviour (additive)
int relativeVolumeChange = ( envval - envValueAtReleaseNode ) * 2 ;
envval = envValueAtReleaseJump + relativeVolumeChange ;
} else
{
// New behaviour, truly relative to release node
if ( envValueAtReleaseNode > 0 )
envval = envValueAtReleaseJump * envval / envValueAtReleaseNode ;
else
envval = 0 ;
}
}
vol = ( vol * Clamp ( envval , 0 , 512 ) ) / 256 ;
}
}
void CSoundFile : : ProcessPanningEnvelope ( ModChannel & chn ) const
{
if ( IsEnvelopeProcessed ( chn , ENV_PANNING ) )
{
const ModInstrument * pIns = chn . pModInstrument ;
if ( m_playBehaviour [ kITEnvelopePositionHandling ] & & chn . PanEnv . nEnvPosition = = 0 )
{
// If the envelope is disabled at the very same moment as it is triggered, we do not process anything.
return ;
}
const int envpos = chn . PanEnv . nEnvPosition - ( m_playBehaviour [ kITEnvelopePositionHandling ] ? 1 : 0 ) ;
// Get values in [-32, 32]
const int envval = pIns - > PanEnv . GetValueFromPosition ( envpos , 64 ) - 32 ;
int pan = chn . nRealPan ;
if ( pan > = 128 )
{
pan + = ( envval * ( 256 - pan ) ) / 32 ;
} else
{
pan + = ( envval * ( pan ) ) / 32 ;
}
chn . nRealPan = Clamp ( pan , 0 , 256 ) ;
}
}
int CSoundFile : : ProcessPitchFilterEnvelope ( ModChannel & chn , int & period ) const
{
if ( IsEnvelopeProcessed ( chn , ENV_PITCH ) )
{
const ModInstrument * pIns = chn . pModInstrument ;
if ( m_playBehaviour [ kITEnvelopePositionHandling ] & & chn . PitchEnv . nEnvPosition = = 0 )
{
// If the envelope is disabled at the very same moment as it is triggered, we do not process anything.
return - 1 ;
}
const int envpos = chn . PitchEnv . nEnvPosition - ( m_playBehaviour [ kITEnvelopePositionHandling ] ? 1 : 0 ) ;
// Get values in [-256, 256]
# ifdef MODPLUG_TRACKER
const int32 range = ENVELOPE_MAX ;
const int32 amp = 512 ;
# else
// TODO: AMS2 envelopes behave differently when linear slides are off - emulate with 15 * (-128...127) >> 6
// Copy over vibrato behaviour for that?
const int32 range = GetType ( ) = = MOD_TYPE_AMS ? uint8_max : ENVELOPE_MAX ;
int32 amp ;
switch ( GetType ( ) )
{
case MOD_TYPE_AMS : amp = 64 ; break ;
case MOD_TYPE_MDL : amp = 192 ; break ;
default : amp = 512 ;
}
# endif
const int envval = pIns - > PitchEnv . GetValueFromPosition ( envpos , amp , range ) - amp / 2 ;
if ( chn . PitchEnv . flags [ ENV_FILTER ] )
{
// Filter Envelope: controls cutoff frequency
return SetupChannelFilter ( chn , ! chn . dwFlags [ CHN_FILTER ] , envval ) ;
} else
{
// Pitch Envelope
if ( chn . HasCustomTuning ( ) )
{
if ( chn . nFineTune ! = envval )
{
chn . nFineTune = envval ;
chn . m_CalculateFreq = true ;
//Preliminary tests indicated that this behavior
//is very close to original(with 12TET) when finestep count
//is 15.
}
} else //Original behavior
{
const bool useFreq = PeriodsAreFrequencies ( ) ;
const uint32 ( & upTable ) [ 256 ] = useFreq ? LinearSlideUpTable : LinearSlideDownTable ;
const uint32 ( & downTable ) [ 256 ] = useFreq ? LinearSlideDownTable : LinearSlideUpTable ;
int l = envval ;
if ( l < 0 )
{
l = - l ;
LimitMax ( l , 255 ) ;
period = Util : : muldiv ( period , downTable [ l ] , 65536 ) ;
} else
{
LimitMax ( l , 255 ) ;
period = Util : : muldiv ( period , upTable [ l ] , 65536 ) ;
}
} //End: Original behavior.
}
}
return - 1 ;
}
void CSoundFile : : IncrementEnvelopePosition ( ModChannel & chn , EnvelopeType envType ) const
{
ModChannel : : EnvInfo & chnEnv = chn . GetEnvelope ( envType ) ;
if ( chn . pModInstrument = = nullptr | | ! chnEnv . flags [ ENV_ENABLED ] )
{
return ;
}
// Increase position
uint32 position = chnEnv . nEnvPosition + ( m_playBehaviour [ kITEnvelopePositionHandling ] ? 0 : 1 ) ;
const InstrumentEnvelope & insEnv = chn . pModInstrument - > GetEnvelope ( envType ) ;
if ( insEnv . empty ( ) )
{
return ;
}
bool endReached = false ;
if ( ! m_playBehaviour [ kITEnvelopePositionHandling ] )
{
// FT2-style envelope processing.
if ( insEnv . dwFlags [ ENV_LOOP ] )
{
// Normal loop active
uint32 end = insEnv [ insEnv . nLoopEnd ] . tick ;
if ( ! ( GetType ( ) & ( MOD_TYPE_XM | MOD_TYPE_MT2 ) ) ) end + + ;
// FT2 compatibility: If the sustain point is at the loop end and the sustain loop has been released, don't loop anymore.
// Test case: EnvLoops.xm
const bool escapeLoop = ( insEnv . nLoopEnd = = insEnv . nSustainEnd & & insEnv . dwFlags [ ENV_SUSTAIN ] & & chn . dwFlags [ CHN_KEYOFF ] & & m_playBehaviour [ kFT2EnvelopeEscape ] ) ;
if ( position = = end & & ! escapeLoop )
{
position = insEnv [ insEnv . nLoopStart ] . tick ;
}
}
if ( insEnv . dwFlags [ ENV_SUSTAIN ] & & ! chn . dwFlags [ CHN_KEYOFF ] )
{
// Envelope sustained
if ( position = = insEnv [ insEnv . nSustainEnd ] . tick + 1u )
{
position = insEnv [ insEnv . nSustainStart ] . tick ;
// FT2 compatibility: If the panning envelope reaches its sustain point before key-off, it stays there forever.
// Test case: PanSustainRelease.xm
if ( m_playBehaviour [ kFT2PanSustainRelease ] & & envType = = ENV_PANNING & & ! chn . dwFlags [ CHN_KEYOFF ] )
{
chnEnv . flags . reset ( ENV_ENABLED ) ;
}
}
} else
{
// Limit to last envelope point
if ( position > insEnv . back ( ) . tick )
{
// Env of envelope
position = insEnv . back ( ) . tick ;
endReached = true ;
}
}
} else
{
// IT envelope processing.
// Test case: EnvLoops.it
uint32 start , end ;
// IT compatiblity: OpenMPT processes the key-off flag earlier than IT. Grab the flag from the previous tick instead.
// Test case: EnvOffLength.it
if ( insEnv . dwFlags [ ENV_SUSTAIN ] & & ! chn . dwOldFlags [ CHN_KEYOFF ] & & ( chnEnv . nEnvValueAtReleaseJump = = NOT_YET_RELEASED | | m_playBehaviour [ kReleaseNodePastSustainBug ] ) )
{
// Envelope sustained
start = insEnv [ insEnv . nSustainStart ] . tick ;
end = insEnv [ insEnv . nSustainEnd ] . tick + 1 ;
} else if ( insEnv . dwFlags [ ENV_LOOP ] )
{
// Normal loop active
start = insEnv [ insEnv . nLoopStart ] . tick ;
end = insEnv [ insEnv . nLoopEnd ] . tick + 1 ;
} else
{
// Limit to last envelope point
start = end = insEnv . back ( ) . tick ;
if ( position > end )
{
// Env of envelope
endReached = true ;
}
}
if ( position > = end )
{
position = start ;
}
}
if ( envType = = ENV_VOLUME & & endReached )
{
// Special handling for volume envelopes at end of envelope
if ( ( GetType ( ) & ( MOD_TYPE_IT | MOD_TYPE_MPT ) ) | | ( chn . dwFlags [ CHN_KEYOFF ] & & GetType ( ) ! = MOD_TYPE_MDL ) )
{
chn . dwFlags . set ( CHN_NOTEFADE ) ;
}
if ( insEnv . back ( ) . value = = 0 & & ( chn . nMasterChn > 0 | | ( GetType ( ) & ( MOD_TYPE_IT | MOD_TYPE_MPT ) ) ) )
{
// Stop channel if the last envelope node is silent anyway.
chn . dwFlags . set ( CHN_NOTEFADE ) ;
chn . nFadeOutVol = 0 ;
chn . nRealVolume = 0 ;
chn . nCalcVolume = 0 ;
}
}
chnEnv . nEnvPosition = position + ( m_playBehaviour [ kITEnvelopePositionHandling ] ? 1 : 0 ) ;
}
void CSoundFile : : IncrementEnvelopePositions ( ModChannel & chn ) const
{
if ( chn . isFirstTick & & GetType ( ) = = MOD_TYPE_MED )
return ;
IncrementEnvelopePosition ( chn , ENV_VOLUME ) ;
IncrementEnvelopePosition ( chn , ENV_PANNING ) ;
IncrementEnvelopePosition ( chn , ENV_PITCH ) ;
}
void CSoundFile : : ProcessInstrumentFade ( ModChannel & chn , int & vol ) const
{
// FadeOut volume
if ( chn . dwFlags [ CHN_NOTEFADE ] & & chn . pModInstrument ! = nullptr )
{
const ModInstrument * pIns = chn . pModInstrument ;
uint32 fadeout = pIns - > nFadeOut ;
if ( fadeout )
{
chn . nFadeOutVol - = fadeout * 2 ;
if ( chn . nFadeOutVol < = 0 ) chn . nFadeOutVol = 0 ;
vol = ( vol * chn . nFadeOutVol ) / 65536 ;
} else if ( ! chn . nFadeOutVol )
{
vol = 0 ;
}
}
}
void CSoundFile : : ProcessPitchPanSeparation ( ModChannel & chn ) const
{
const ModInstrument * pIns = chn . pModInstrument ;
if ( ( pIns - > nPPS ) & & ( chn . nNote ! = NOTE_NONE ) )
{
// with PPS = 16 / PPC = C-5, E-6 will pan hard right (and D#6 will not)
int pandelta = ( int ) chn . nRealPan + ( int ) ( ( int ) ( chn . nNote - pIns - > nPPC - NOTE_MIN ) * ( int ) pIns - > nPPS ) / 2 ;
chn . nRealPan = Clamp ( pandelta , 0 , 256 ) ;
}
}
void CSoundFile : : ProcessPanbrello ( ModChannel & chn ) const
{
int pdelta = chn . nPanbrelloOffset ;
if ( chn . rowCommand . command = = CMD_PANBRELLO )
{
uint32 panpos ;
// IT compatibility: IT has its own, more precise tables
if ( m_playBehaviour [ kITVibratoTremoloPanbrello ] )
panpos = chn . nPanbrelloPos ;
else
panpos = ( ( chn . nPanbrelloPos + 0x10 ) > > 2 ) ;
pdelta = GetVibratoDelta ( chn . nPanbrelloType , panpos ) ;
// IT compatibility: Sample-and-hold style random panbrello (tremolo and vibrato don't use this mechanism in IT)
// Test case: RandomWaveform.it
if ( m_playBehaviour [ kITSampleAndHoldPanbrello ] & & chn . nPanbrelloType = = 3 )
{
if ( chn . nPanbrelloPos = = 0 | | chn . nPanbrelloPos > = chn . nPanbrelloSpeed )
{
chn . nPanbrelloPos = 0 ;
chn . nPanbrelloRandomMemory = static_cast < int8 > ( pdelta ) ;
}
chn . nPanbrelloPos + + ;
pdelta = chn . nPanbrelloRandomMemory ;
} else
{
chn . nPanbrelloPos + = chn . nPanbrelloSpeed ;
}
// IT compatibility: Panbrello effect is active until next note or panning command.
// Test case: PanbrelloHold.it
if ( m_playBehaviour [ kITPanbrelloHold ] )
{
chn . nPanbrelloOffset = static_cast < int8 > ( pdelta ) ;
}
}
if ( pdelta )
{
pdelta = ( ( pdelta * ( int ) chn . nPanbrelloDepth ) + 2 ) / 8 ;
pdelta + = chn . nRealPan ;
chn . nRealPan = Clamp ( pdelta , 0 , 256 ) ;
}
}
void CSoundFile : : ProcessArpeggio ( CHANNELINDEX nChn , int & period , Tuning : : NOTEINDEXTYPE & arpeggioSteps )
{
ModChannel & chn = m_PlayState . Chn [ nChn ] ;
# ifndef NO_PLUGINS
// Plugin arpeggio
if ( chn . pModInstrument & & chn . pModInstrument - > nMixPlug
& & ! chn . pModInstrument - > dwFlags [ INS_MUTE ]
& & ! chn . dwFlags [ CHN_MUTE | CHN_SYNCMUTE ] )
{
const ModInstrument * pIns = chn . pModInstrument ;
IMixPlugin * pPlugin = m_MixPlugins [ pIns - > nMixPlug - 1 ] . pMixPlugin ;
if ( pPlugin )
{
uint8 step = 0 ;
const bool arpOnRow = ( chn . rowCommand . command = = CMD_ARPEGGIO ) ;
const ModCommand : : NOTE lastNote = ModCommand : : IsNote ( chn . nLastNote ) ? static_cast < ModCommand : : NOTE > ( pIns - > NoteMap [ chn . nLastNote - NOTE_MIN ] ) : static_cast < ModCommand : : NOTE > ( NOTE_NONE ) ;
if ( arpOnRow )
{
switch ( m_PlayState . m_nTickCount % 3 )
{
case 1 : step = chn . nArpeggio > > 4 ; break ;
case 2 : step = chn . nArpeggio & 0x0F ; break ;
}
chn . nArpeggioBaseNote = lastNote ;
}
// Trigger new note:
// - If there's an arpeggio on this row and
// - the note to trigger is not the same as the previous arpeggio note or
// - a pattern note has just been triggered on this tick
// - If there's no arpeggio
// - but an arpeggio note is still active and
// - there's no note stop or new note that would stop it anyway
if ( ( arpOnRow & & chn . nArpeggioLastNote ! = chn . nArpeggioBaseNote + step & & ( ! m_SongFlags [ SONG_FIRSTTICK ] | | ! chn . rowCommand . IsNote ( ) ) )
| | ( ! arpOnRow & & chn . rowCommand . note = = NOTE_NONE & & chn . nArpeggioLastNote ! = NOTE_NONE ) )
SendMIDINote ( nChn , chn . nArpeggioBaseNote + step , static_cast < uint16 > ( chn . nVolume ) ) ;
// Stop note:
// - If some arpeggio note is still registered or
// - When starting an arpeggio on a row with no other note on it, stop some possibly still playing note.
if ( chn . nArpeggioLastNote ! = NOTE_NONE )
SendMIDINote ( nChn , chn . nArpeggioLastNote + NOTE_MAX_SPECIAL , 0 ) ;
else if ( arpOnRow & & m_SongFlags [ SONG_FIRSTTICK ] & & ! chn . rowCommand . IsNote ( ) & & ModCommand : : IsNote ( lastNote ) )
SendMIDINote ( nChn , lastNote + NOTE_MAX_SPECIAL , 0 ) ;
if ( chn . rowCommand . command = = CMD_ARPEGGIO )
chn . nArpeggioLastNote = chn . nArpeggioBaseNote + step ;
else
chn . nArpeggioLastNote = NOTE_NONE ;
}
}
# endif // NO_PLUGINS
if ( chn . nCommand = = CMD_ARPEGGIO )
{
if ( chn . HasCustomTuning ( ) )
{
switch ( m_PlayState . m_nTickCount % 3 )
{
case 0 : arpeggioSteps = 0 ; break ;
case 1 : arpeggioSteps = chn . nArpeggio > > 4 ; break ;
case 2 : arpeggioSteps = chn . nArpeggio & 0x0F ; break ;
}
chn . m_CalculateFreq = true ;
chn . m_ReCalculateFreqOnFirstTick = true ;
} else
{
if ( GetType ( ) = = MOD_TYPE_MT2 & & m_SongFlags [ SONG_FIRSTTICK ] )
{
// MT2 resets any previous portamento when an arpeggio occurs.
chn . nPeriod = period = GetPeriodFromNote ( chn . nNote , chn . nFineTune , chn . nC5Speed ) ;
}
if ( m_playBehaviour [ kITArpeggio ] )
{
//IT playback compatibility 01 & 02
// Pattern delay restarts tick counting. Not quite correct yet!
const uint32 tick = m_PlayState . m_nTickCount % ( m_PlayState . m_nMusicSpeed + m_PlayState . m_nFrameDelay ) ;
if ( chn . nArpeggio ! = 0 )
{
uint32 arpRatio = 65536 ;
switch ( tick % 3 )
{
case 1 : arpRatio = LinearSlideUpTable [ ( chn . nArpeggio > > 4 ) * 16 ] ; break ;
case 2 : arpRatio = LinearSlideUpTable [ ( chn . nArpeggio & 0x0F ) * 16 ] ; break ;
}
if ( PeriodsAreFrequencies ( ) )
period = Util : : muldivr ( period , arpRatio , 65536 ) ;
else
period = Util : : muldivr ( period , 65536 , arpRatio ) ;
}
} else if ( m_playBehaviour [ kFT2Arpeggio ] )
{
// FastTracker 2: Swedish tracker logic (TM) arpeggio
if ( ! m_SongFlags [ SONG_FIRSTTICK ] )
{
// Arpeggio is added on top of current note, but cannot do it the IT way because of
// the behaviour in ArpeggioClamp.xm.
// Test case: ArpSlide.xm
uint32 note = 0 ;
// The fact that arpeggio behaves in a totally fucked up way at 16 ticks/row or more is that the arpeggio offset LUT only has 16 entries in FT2.
// At more than 16 ticks/row, FT2 reads into the vibrato table, which is placed right after the arpeggio table.
// Test case: Arpeggio.xm
int arpPos = m_PlayState . m_nMusicSpeed - ( m_PlayState . m_nTickCount % m_PlayState . m_nMusicSpeed ) ;
if ( arpPos > 16 )
arpPos = 2 ;
else if ( arpPos = = 16 )
arpPos = 0 ;
else
arpPos % = 3 ;
switch ( arpPos )
{
case 1 : note = ( chn . nArpeggio > > 4 ) ; break ;
case 2 : note = ( chn . nArpeggio & 0x0F ) ; break ;
}
if ( arpPos ! = 0 )
{
// Arpeggio is added on top of current note, but cannot do it the IT way because of
// the behaviour in ArpeggioClamp.xm.
// Test case: ArpSlide.xm
note + = GetNoteFromPeriod ( period , chn . nFineTune , chn . nC5Speed ) ;
period = GetPeriodFromNote ( note , chn . nFineTune , chn . nC5Speed ) ;
// FT2 compatibility: FT2 has a different note limit for Arpeggio.
// Test case: ArpeggioClamp.xm
if ( note > = 108 + NOTE_MIN )
{
period = std : : max ( static_cast < uint32 > ( period ) , GetPeriodFromNote ( 108 + NOTE_MIN , 0 , chn . nC5Speed ) ) ;
}
}
}
}
// Other trackers
else
{
uint32 tick = m_PlayState . m_nTickCount ;
// TODO other likely formats for MOD case: MED, OKT, etc
uint8 note = ( GetType ( ) ! = MOD_TYPE_MOD ) ? chn . nNote : static_cast < uint8 > ( GetNoteFromPeriod ( period , chn . nFineTune , chn . nC5Speed ) ) ;
if ( GetType ( ) & ( MOD_TYPE_DBM | MOD_TYPE_DIGI ) )
tick + = 2 ;
switch ( tick % 3 )
{
case 1 : note + = ( chn . nArpeggio > > 4 ) ; break ;
case 2 : note + = ( chn . nArpeggio & 0x0F ) ; break ;
}
if ( note ! = chn . nNote | | ( GetType ( ) & ( MOD_TYPE_DBM | MOD_TYPE_DIGI | MOD_TYPE_STM ) ) | | m_playBehaviour [ KST3PortaAfterArpeggio ] )
{
if ( m_SongFlags [ SONG_PT_MODE ] )
{
// Weird arpeggio wrap-around in ProTracker.
// Test case: ArpWraparound.mod, and the snare sound in "Jim is dead" by doh.
if ( note = = NOTE_MIDDLEC + 24 )
{
period = int32_max ;
return ;
} else if ( note > NOTE_MIDDLEC + 24 )
{
note - = 37 ;
}
}
period = GetPeriodFromNote ( note , chn . nFineTune , chn . nC5Speed ) ;
if ( GetType ( ) & ( MOD_TYPE_DBM | MOD_TYPE_DIGI | MOD_TYPE_PSM | MOD_TYPE_STM ) )
{
// The arpeggio note offset remains effective after the end of the current row in ScreamTracker 2.
// This fixes the flute lead in MORPH.STM by Skaven, pattern 27.
// Note that ScreamTracker 2.24 handles arpeggio slightly differently: It only considers the lower
// nibble, and switches to that note halfway through the row.
chn . nPeriod = period ;
} else if ( m_playBehaviour [ KST3PortaAfterArpeggio ] )
{
chn . nArpeggioLastNote = note ;
}
}
}
}
}
}
void CSoundFile : : ProcessVibrato ( CHANNELINDEX nChn , int & period , Tuning : : RATIOTYPE & vibratoFactor )
{
ModChannel & chn = m_PlayState . Chn [ nChn ] ;
if ( chn . dwFlags [ CHN_VIBRATO ] )
{
const bool advancePosition = ! m_SongFlags [ SONG_FIRSTTICK ] | | ( ( GetType ( ) & ( MOD_TYPE_IT | MOD_TYPE_MPT ) ) & & ! ( m_SongFlags [ SONG_ITOLDEFFECTS ] ) ) ;
if ( GetType ( ) = = MOD_TYPE_669 )
{
if ( chn . nVibratoPos % 2u )
{
period + = chn . nVibratoDepth * 167 ; // Already multiplied by 4, and it seems like the real factor here is 669... how original =)
}
chn . nVibratoPos + + ;
return ;
}
// IT compatibility: IT has its own, more precise tables and pre-increments the vibrato position
if ( advancePosition & & m_playBehaviour [ kITVibratoTremoloPanbrello ] )
chn . nVibratoPos + = 4 * chn . nVibratoSpeed ;
int vdelta = GetVibratoDelta ( chn . nVibratoType , chn . nVibratoPos ) ;
if ( chn . HasCustomTuning ( ) )
{
//Hack implementation: Scaling vibratofactor to [0.95; 1.05]
//using figure from above tables and vibratodepth parameter
vibratoFactor + = 0.05f * ( vdelta * chn . nVibratoDepth ) / ( 128.0f * 60.0f ) ;
chn . m_CalculateFreq = true ;
chn . m_ReCalculateFreqOnFirstTick = false ;
if ( m_PlayState . m_nTickCount + 1 = = m_PlayState . m_nMusicSpeed )
chn . m_ReCalculateFreqOnFirstTick = true ;
} else
{
// Original behaviour
if ( m_SongFlags . test_all ( SONG_FIRSTTICK | SONG_PT_MODE ) | | ( ( GetType ( ) & ( MOD_TYPE_DIGI | MOD_TYPE_DBM ) ) & & m_SongFlags [ SONG_FIRSTTICK ] ) )
{
// ProTracker doesn't apply vibrato nor advance on the first tick.
// Test case: VibratoReset.mod
return ;
} else if ( ( GetType ( ) & ( MOD_TYPE_XM | MOD_TYPE_MOD ) ) & & ( chn . nVibratoType & 0x03 ) = = 1 )
{
// FT2 compatibility: Vibrato ramp down table is upside down.
// Test case: VibratoWaveforms.xm
vdelta = - vdelta ;
}
uint32 vdepth ;
// IT compatibility: correct vibrato depth
if ( m_playBehaviour [ kITVibratoTremoloPanbrello ] )
{
// Yes, vibrato goes backwards with old effects enabled!
if ( m_SongFlags [ SONG_ITOLDEFFECTS ] )
{
// Test case: vibrato-oldfx.it
vdepth = 5 ;
} else
{
// Test case: vibrato.it
vdepth = 6 ;
vdelta = - vdelta ;
}
} else
{
if ( m_SongFlags [ SONG_S3MOLDVIBRATO ] )
vdepth = 5 ;
else if ( GetType ( ) = = MOD_TYPE_DTM )
vdepth = 8 ;
else if ( GetType ( ) & ( MOD_TYPE_DBM | MOD_TYPE_MTM ) )
vdepth = 7 ;
else if ( ( GetType ( ) & ( MOD_TYPE_IT | MOD_TYPE_MPT ) ) & & ! m_SongFlags [ SONG_ITOLDEFFECTS ] )
vdepth = 7 ;
else
vdepth = 6 ;
// ST3 compatibility: Do not distinguish between vibrato types in effect memory
// Test case: VibratoTypeChange.s3m
if ( m_playBehaviour [ kST3VibratoMemory ] & & chn . rowCommand . command = = CMD_FINEVIBRATO )
vdepth + = 2 ;
}
vdelta = ( vdelta * ( int ) chn . nVibratoDepth ) / ( 1 < < vdepth ) ;
# ifndef NO_PLUGINS
int16 midiDelta = static_cast < int16 > ( - vdelta ) ; // Periods are upside down
# endif // NO_PLUGINS
if ( m_SongFlags [ SONG_LINEARSLIDES ] & & GetType ( ) ! = MOD_TYPE_XM )
{
int l = vdelta ;
if ( l < 0 )
{
l = - l ;
vdelta = Util : : muldiv ( period , GetLinearSlideUpTable ( this , l / 4u ) , 65536 ) - period ;
if ( l & 0x03 ) vdelta + = Util : : muldiv ( period , GetFineLinearSlideUpTable ( this , l & 0x03 ) , 65536 ) - period ;
} else
{
vdelta = Util : : muldiv ( period , GetLinearSlideDownTable ( this , l / 4u ) , 65536 ) - period ;
if ( l & 0x03 ) vdelta + = Util : : muldiv ( period , GetFineLinearSlideDownTable ( this , l & 0x03 ) , 65536 ) - period ;
}
}
period + = vdelta ;
// Process MIDI vibrato for plugins:
# ifndef NO_PLUGINS
IMixPlugin * plugin = GetChannelInstrumentPlugin ( nChn ) ;
if ( plugin ! = nullptr )
{
// If the Pitch Wheel Depth is configured correctly (so it's the same as the plugin's PWD),
// MIDI vibrato will sound identical to vibrato with linear slides enabled.
int8 pwd = 2 ;
if ( chn . pModInstrument ! = nullptr )
{
pwd = chn . pModInstrument - > midiPWD ;
}
plugin - > MidiVibrato ( midiDelta , pwd , nChn ) ;
}
# endif // NO_PLUGINS
}
// Advance vibrato position - IT updates on every tick, unless "old effects" are enabled (in this case it only updates on non-first ticks like other trackers)
// IT compatibility: IT has its own, more precise tables and pre-increments the vibrato position
if ( advancePosition & & ! m_playBehaviour [ kITVibratoTremoloPanbrello ] )
chn . nVibratoPos + = chn . nVibratoSpeed ;
} else if ( chn . dwOldFlags [ CHN_VIBRATO ] )
{
// Stop MIDI vibrato for plugins:
# ifndef NO_PLUGINS
IMixPlugin * plugin = GetChannelInstrumentPlugin ( nChn ) ;
if ( plugin ! = nullptr )
{
plugin - > MidiVibrato ( 0 , 0 , nChn ) ;
}
# endif // NO_PLUGINS
}
}
void CSoundFile : : ProcessSampleAutoVibrato ( ModChannel & chn , int & period , Tuning : : RATIOTYPE & vibratoFactor , int & nPeriodFrac ) const
{
// Sample Auto-Vibrato
if ( chn . pModSample ! = nullptr & & chn . pModSample - > nVibDepth )
{
const ModSample * pSmp = chn . pModSample ;
const bool hasTuning = chn . HasCustomTuning ( ) ;
// In IT linear slide mode, we use frequencies, otherwise we use periods, which are upside down.
// In this context, the "up" tables refer to the tables that increase frequency, and the down tables are the ones that decrease frequency.
const bool useFreq = PeriodsAreFrequencies ( ) ;
const uint32 ( & upTable ) [ 256 ] = useFreq ? LinearSlideUpTable : LinearSlideDownTable ;
const uint32 ( & downTable ) [ 256 ] = useFreq ? LinearSlideDownTable : LinearSlideUpTable ;
const uint32 ( & fineUpTable ) [ 16 ] = useFreq ? FineLinearSlideUpTable : FineLinearSlideDownTable ;
const uint32 ( & fineDownTable ) [ 16 ] = useFreq ? FineLinearSlideDownTable : FineLinearSlideUpTable ;
// IT compatibility: Autovibrato is so much different in IT that I just put this in a separate code block, to get rid of a dozen IsCompatibilityMode() calls.
if ( m_playBehaviour [ kITVibratoTremoloPanbrello ] & & ! hasTuning & & GetType ( ) ! = MOD_TYPE_MT2 )
{
if ( ! pSmp - > nVibRate )
return ;
// Schism's autovibrato code
/*
X86 Assembler from ITTECH . TXT :
1 ) Mov AX , [ SomeVariableNameRelatingToVibrato ]
2 ) Add AL , Rate
3 ) AdC AH , 0
4 ) AH contains the depth of the vibrato as a fine - linear slide .
5 ) Mov [ SomeVariableNameRelatingToVibrato ] , AX ; For the next cycle .
*/
const int vibpos = chn . nAutoVibPos & 0xFF ;
int adepth = chn . nAutoVibDepth ; // (1)
adepth + = pSmp - > nVibSweep ; // (2 & 3)
LimitMax ( adepth , static_cast < int > ( pSmp - > nVibDepth * 256u ) ) ;
chn . nAutoVibDepth = adepth ; // (5)
adepth / = 256 ; // (4)
chn . nAutoVibPos + = pSmp - > nVibRate ;
int vdelta ;
switch ( pSmp - > nVibType )
{
case VIB_RANDOM :
vdelta = mpt : : random < int , 7 > ( AccessPRNG ( ) ) - 0x40 ;
break ;
case VIB_RAMP_DOWN :
vdelta = 64 - ( vibpos + 1 ) / 2 ;
break ;
case VIB_RAMP_UP :
vdelta = ( ( vibpos + 1 ) / 2 ) - 64 ;
break ;
case VIB_SQUARE :
vdelta = vibpos < 128 ? 64 : 0 ;
break ;
case VIB_SINE :
default :
vdelta = ITSinusTable [ vibpos ] ;
break ;
}
vdelta = ( vdelta * adepth ) / 64 ;
uint32 l = std : : abs ( vdelta ) ;
LimitMax ( period , Util : : MaxValueOfType ( period ) / 256 ) ;
period * = 256 ;
if ( vdelta < 0 )
{
vdelta = Util : : muldiv ( period , downTable [ l / 4u ] , 0x10000 ) - period ;
if ( l & 0x03 )
{
vdelta + = Util : : muldiv ( period , fineDownTable [ l & 0x03 ] , 0x10000 ) - period ;
}
} else
{
vdelta = Util : : muldiv ( period , upTable [ l / 4u ] , 0x10000 ) - period ;
if ( l & 0x03 )
{
vdelta + = Util : : muldiv ( period , fineUpTable [ l & 0x03 ] , 0x10000 ) - period ;
}
}
period = ( period + vdelta ) / 256 ;
nPeriodFrac = vdelta & 0xFF ;
} else
{
// MPT's autovibrato code
if ( pSmp - > nVibSweep = = 0 & & ! ( GetType ( ) & ( MOD_TYPE_IT | MOD_TYPE_MPT ) ) )
{
chn . nAutoVibDepth = pSmp - > nVibDepth * 256 ;
} else
{
// Calculate current autovibrato depth using vibsweep
if ( GetType ( ) & ( MOD_TYPE_IT | MOD_TYPE_MPT ) )
{
chn . nAutoVibDepth + = pSmp - > nVibSweep * 2u ;
} else
{
if ( ! chn . dwFlags [ CHN_KEYOFF ] )
{
chn . nAutoVibDepth + = ( pSmp - > nVibDepth * 256u ) / pSmp - > nVibSweep ;
}
}
LimitMax ( chn . nAutoVibDepth , static_cast < int > ( pSmp - > nVibDepth * 256u ) ) ;
}
chn . nAutoVibPos + = pSmp - > nVibRate ;
int vdelta ;
switch ( pSmp - > nVibType )
{
case VIB_RANDOM :
vdelta = ModRandomTable [ chn . nAutoVibPos & 0x3F ] ;
chn . nAutoVibPos + + ;
break ;
case VIB_RAMP_DOWN :
vdelta = ( ( 0x40 - ( chn . nAutoVibPos / 2u ) ) & 0x7F ) - 0x40 ;
break ;
case VIB_RAMP_UP :
vdelta = ( ( 0x40 + ( chn . nAutoVibPos / 2u ) ) & 0x7F ) - 0x40 ;
break ;
case VIB_SQUARE :
vdelta = ( chn . nAutoVibPos & 128 ) ? + 64 : - 64 ;
break ;
case VIB_SINE :
default :
if ( GetType ( ) ! = MOD_TYPE_MT2 )
{
vdelta = - ITSinusTable [ chn . nAutoVibPos & 0xFF ] ;
} else
{
// Fix flat-sounding pads in "another worlds" by Eternal Engine.
// Vibrato starts at the maximum amplitude of the sine wave
// and the vibrato frequency never decreases below the original note's frequency.
vdelta = ( - ITSinusTable [ ( chn . nAutoVibPos + 192 ) & 0xFF ] + 64 ) / 2 ;
}
}
int n = ( vdelta * chn . nAutoVibDepth ) / 256 ;
if ( hasTuning )
{
//Vib sweep is not taken into account here.
vibratoFactor + = 0.05F * pSmp - > nVibDepth * vdelta / 4096.0f ; //4096 == 64^2
//See vibrato for explanation.
chn . m_CalculateFreq = true ;
/*
Finestep vibrato :
const float autoVibDepth = pSmp - > nVibDepth * val / 4096.0f ; //4096 == 64^2
vibratoFineSteps + = static_cast < CTuning : : FINESTEPTYPE > ( chn . pModInstrument - > pTuning - > GetFineStepCount ( ) * autoVibDepth ) ;
chn . m_CalculateFreq = true ;
*/
}
else //Original behavior
{
if ( GetType ( ) ! = MOD_TYPE_XM )
{
int df1 , df2 ;
if ( n < 0 )
{
n = - n ;
uint32 n1 = n / 256 ;
df1 = downTable [ n1 ] ;
df2 = downTable [ n1 + 1 ] ;
} else
{
uint32 n1 = n / 256 ;
df1 = upTable [ n1 ] ;
df2 = upTable [ n1 + 1 ] ;
}
n / = 4 ;
period = Util : : muldiv ( period , df1 + ( ( df2 - df1 ) * ( n & 0x3F ) / 64 ) , 256 ) ;
nPeriodFrac = period & 0xFF ;
period / = 256 ;
} else
{
period + = ( n / 64 ) ;
}
} //Original MPT behavior
}
}
}
void CSoundFile : : ProcessRamping ( ModChannel & chn ) const
{
chn . leftRamp = chn . rightRamp = 0 ;
2022-01-30 23:19:36 +00:00
LimitMax ( chn . newLeftVol , int32_max > > VOLUMERAMPPRECISION ) ;
LimitMax ( chn . newRightVol , int32_max > > VOLUMERAMPPRECISION ) ;
2021-12-26 11:29:43 +00:00
if ( chn . dwFlags [ CHN_VOLUMERAMP ] & & ( chn . leftVol ! = chn . newLeftVol | | chn . rightVol ! = chn . newRightVol ) )
{
const bool rampUp = ( chn . newLeftVol > chn . leftVol ) | | ( chn . newRightVol > chn . rightVol ) ;
int32 rampLength , globalRampLength , instrRampLength = 0 ;
rampLength = globalRampLength = ( rampUp ? m_MixerSettings . GetVolumeRampUpSamples ( ) : m_MixerSettings . GetVolumeRampDownSamples ( ) ) ;
//XXXih: add real support for bidi ramping here
if ( m_playBehaviour [ kFT2VolumeRamping ] & & ( GetType ( ) & MOD_TYPE_XM ) )
{
// apply FT2-style super-soft volume ramping (5ms), overriding openmpt settings
rampLength = globalRampLength = Util : : muldivr ( 5 , m_MixerSettings . gdwMixingFreq , 1000 ) ;
}
if ( chn . pModInstrument ! = nullptr & & rampUp )
{
instrRampLength = chn . pModInstrument - > nVolRampUp ;
rampLength = instrRampLength ? ( m_MixerSettings . gdwMixingFreq * instrRampLength / 100000 ) : globalRampLength ;
}
const bool enableCustomRamp = ( instrRampLength > 0 ) ;
if ( ! rampLength )
{
rampLength = 1 ;
}
int32 leftDelta = ( ( chn . newLeftVol - chn . leftVol ) * ( 1 < < VOLUMERAMPPRECISION ) ) ;
int32 rightDelta = ( ( chn . newRightVol - chn . rightVol ) * ( 1 < < VOLUMERAMPPRECISION ) ) ;
if ( ! enableCustomRamp )
{
// Extra-smooth ramping, unless we're forced to use the default values
if ( ( chn . leftVol | chn . rightVol ) & & ( chn . newLeftVol | chn . newRightVol ) & & ! chn . dwFlags [ CHN_FASTVOLRAMP ] )
{
rampLength = m_PlayState . m_nBufferCount ;
Limit ( rampLength , globalRampLength , int32 ( 1 < < ( VOLUMERAMPPRECISION - 1 ) ) ) ;
}
}
chn . leftRamp = leftDelta / rampLength ;
chn . rightRamp = rightDelta / rampLength ;
chn . leftVol = chn . newLeftVol - ( ( chn . leftRamp * rampLength ) / ( 1 < < VOLUMERAMPPRECISION ) ) ;
chn . rightVol = chn . newRightVol - ( ( chn . rightRamp * rampLength ) / ( 1 < < VOLUMERAMPPRECISION ) ) ;
if ( chn . leftRamp | chn . rightRamp )
{
chn . nRampLength = rampLength ;
} else
{
chn . dwFlags . reset ( CHN_VOLUMERAMP ) ;
chn . leftVol = chn . newLeftVol ;
chn . rightVol = chn . newRightVol ;
}
} else
{
chn . dwFlags . reset ( CHN_VOLUMERAMP ) ;
chn . leftVol = chn . newLeftVol ;
chn . rightVol = chn . newRightVol ;
}
chn . rampLeftVol = chn . leftVol * ( 1 < < VOLUMERAMPPRECISION ) ;
chn . rampRightVol = chn . rightVol * ( 1 < < VOLUMERAMPPRECISION ) ;
chn . dwFlags . reset ( CHN_FASTVOLRAMP ) ;
}
SamplePosition CSoundFile : : GetChannelIncrement ( const ModChannel & chn , uint32 period , int periodFrac ) const
{
uint32 freq ;
if ( ! chn . HasCustomTuning ( ) )
freq = GetFreqFromPeriod ( period , chn . nC5Speed , periodFrac ) ;
else
freq = chn . nPeriod ;
// Applying Pitch/Tempo lock
const ModInstrument * ins = chn . pModInstrument ;
if ( ins & & ins - > pitchToTempoLock . GetRaw ( ) )
{
freq = Util : : muldivr ( freq , m_PlayState . m_nMusicTempo . GetRaw ( ) , ins - > pitchToTempoLock . GetRaw ( ) ) ;
}
// Avoid increment to overflow and become negative with unrealisticly high frequencies.
LimitMax ( freq , uint32 ( int32_max ) ) ;
return SamplePosition : : Ratio ( freq , m_MixerSettings . gdwMixingFreq < < FREQ_FRACBITS ) ;
}
////////////////////////////////////////////////////////////////////////////////////////////
// Handles envelopes & mixer setup
bool CSoundFile : : ReadNote ( )
{
# ifdef MODPLUG_TRACKER
// Checking end of row ?
if ( m_SongFlags [ SONG_PAUSED ] )
{
m_PlayState . m_nTickCount = 0 ;
if ( ! m_PlayState . m_nMusicSpeed ) m_PlayState . m_nMusicSpeed = 6 ;
if ( ! m_PlayState . m_nMusicTempo . GetRaw ( ) ) m_PlayState . m_nMusicTempo . Set ( 125 ) ;
} else
# endif // MODPLUG_TRACKER
{
if ( ! ProcessRow ( ) )
return false ;
}
////////////////////////////////////////////////////////////////////////////////////
if ( m_PlayState . m_nMusicTempo . GetRaw ( ) = = 0 ) return false ;
m_PlayState . m_nSamplesPerTick = GetTickDuration ( m_PlayState ) ;
m_PlayState . m_nBufferCount = m_PlayState . m_nSamplesPerTick ;
// Master Volume + Pre-Amplification / Attenuation setup
uint32 nMasterVol ;
{
CHANNELINDEX nchn32 = Clamp ( m_nChannels , CHANNELINDEX ( 1 ) , CHANNELINDEX ( 31 ) ) ;
uint32 mastervol ;
if ( m_PlayConfig . getUseGlobalPreAmp ( ) )
{
int realmastervol = m_MixerSettings . m_nPreAmp ;
if ( realmastervol > 0x80 )
{
//Attenuate global pre-amp depending on num channels
realmastervol = 0x80 + ( ( realmastervol - 0x80 ) * ( nchn32 + 4 ) ) / 16 ;
}
mastervol = ( realmastervol * ( m_nSamplePreAmp ) ) / 64 ;
} else
{
//Preferred option: don't use global pre-amp at all.
mastervol = m_nSamplePreAmp ;
}
if ( m_PlayConfig . getUseGlobalPreAmp ( ) )
{
uint32 attenuation =
# ifndef NO_AGC
( m_MixerSettings . DSPMask & SNDDSP_AGC ) ? PreAmpAGCTable [ nchn32 / 2u ] :
# endif
PreAmpTable [ nchn32 / 2u ] ;
if ( attenuation < 1 ) attenuation = 1 ;
nMasterVol = ( mastervol < < 7 ) / attenuation ;
} else
{
nMasterVol = mastervol ;
}
}
////////////////////////////////////////////////////////////////////////////////////
// Update channels data
m_nMixChannels = 0 ;
for ( CHANNELINDEX nChn = 0 ; nChn < MAX_CHANNELS ; nChn + + )
{
ModChannel & chn = m_PlayState . Chn [ nChn ] ;
// FT2 Compatibility: Prevent notes to be stopped after a fadeout. This way, a portamento effect can pick up a faded instrument which is long enough.
// This occurs for example in the bassline (channel 11) of jt_burn.xm. I hope this won't break anything else...
// I also suppose this could decrease mixing performance a bit, but hey, which CPU can't handle 32 muted channels these days... :-)
if ( chn . dwFlags [ CHN_NOTEFADE ] & & ( ! ( chn . nFadeOutVol | chn . leftVol | chn . rightVol ) ) & & ! m_playBehaviour [ kFT2ProcessSilentChannels ] )
{
chn . nLength = 0 ;
chn . nROfs = chn . nLOfs = 0 ;
}
// Check for unused channel
if ( chn . dwFlags [ CHN_MUTE ] | | ( nChn > = m_nChannels & & ! chn . nLength ) )
{
if ( nChn < m_nChannels )
{
// Process MIDI macros on channels that are currently muted.
ProcessMacroOnChannel ( nChn ) ;
}
chn . nLeftVU = chn . nRightVU = 0 ;
continue ;
}
// Reset channel data
chn . increment = SamplePosition ( 0 ) ;
chn . nRealVolume = 0 ;
chn . nCalcVolume = 0 ;
chn . nRampLength = 0 ;
//Aux variables
Tuning : : RATIOTYPE vibratoFactor = 1 ;
Tuning : : NOTEINDEXTYPE arpeggioSteps = 0 ;
const ModInstrument * pIns = chn . pModInstrument ;
// Calc Frequency
int period ;
// Also process envelopes etc. when there's a plugin on this channel, for possible fake automation using volume and pan data.
// We only care about master channels, though, since automation only "happens" on them.
const bool samplePlaying = ( chn . nPeriod & & chn . nLength ) ;
const bool plugAssigned = ( nChn < m_nChannels ) & & ( ChnSettings [ nChn ] . nMixPlugin | | ( chn . pModInstrument ! = nullptr & & chn . pModInstrument - > nMixPlug ) ) ;
if ( samplePlaying | | plugAssigned )
{
int vol = chn . nVolume ;
int insVol = chn . nInsVol ; // This is the "SV * IV" value in ITTECH.TXT
ProcessVolumeSwing ( chn , m_playBehaviour [ kITSwingBehaviour ] ? insVol : vol ) ;
ProcessPanningSwing ( chn ) ;
ProcessTremolo ( chn , vol ) ;
ProcessTremor ( nChn , vol ) ;
// Clip volume and multiply (extend to 14 bits)
Limit ( vol , 0 , 256 ) ;
vol < < = 6 ;
// Process Envelopes
if ( pIns )
{
if ( m_playBehaviour [ kITEnvelopePositionHandling ] )
{
// In IT compatible mode, envelope position indices are shifted by one for proper envelope pausing,
// so we have to update the position before we actually process the envelopes.
// When using MPT behaviour, we get the envelope position for the next tick while we are still calculating the current tick,
// which then results in wrong position information when the envelope is paused on the next row.
// Test cases: s77.it
IncrementEnvelopePositions ( chn ) ;
}
ProcessVolumeEnvelope ( chn , vol ) ;
ProcessInstrumentFade ( chn , vol ) ;
ProcessPanningEnvelope ( chn ) ;
ProcessPitchPanSeparation ( chn ) ;
} else
{
// No Envelope: key off => note cut
if ( chn . dwFlags [ CHN_NOTEFADE ] ) // 1.41-: CHN_KEYOFF|CHN_NOTEFADE
{
chn . nFadeOutVol = 0 ;
vol = 0 ;
}
}
// vol is 14-bits
if ( vol )
{
// IMPORTANT: chn.nRealVolume is 14 bits !!!
// -> Util::muldiv( 14+8, 6+6, 18); => RealVolume: 14-bit result (22+12-20)
if ( chn . dwFlags [ CHN_SYNCMUTE ] )
{
chn . nRealVolume = 0 ;
} else if ( m_PlayConfig . getGlobalVolumeAppliesToMaster ( ) )
{
// Don't let global volume affect level of sample if
// Global volume is going to be applied to master output anyway.
chn . nRealVolume = Util : : muldiv ( vol * MAX_GLOBAL_VOLUME , chn . nGlobalVol * insVol , 1 < < 20 ) ;
} else
{
chn . nRealVolume = Util : : muldiv ( vol * m_PlayState . m_nGlobalVolume , chn . nGlobalVol * insVol , 1 < < 20 ) ;
}
}
chn . nCalcVolume = vol ; // Update calculated volume for MIDI macros
// ST3 only clamps the final output period, but never the channel's internal period.
// Test case: PeriodLimit.s3m
if ( chn . nPeriod < m_nMinPeriod
& & GetType ( ) ! = MOD_TYPE_S3M
& & ! PeriodsAreFrequencies ( ) )
{
chn . nPeriod = m_nMinPeriod ;
}
if ( m_playBehaviour [ kFT2Periods ] ) Clamp ( chn . nPeriod , 1 , 31999 ) ;
period = chn . nPeriod ;
// When glissando mode is set to semitones, clamp to the next halftone.
if ( ( chn . dwFlags & ( CHN_GLISSANDO | CHN_PORTAMENTO ) ) = = ( CHN_GLISSANDO | CHN_PORTAMENTO )
& & ( ! m_SongFlags [ SONG_PT_MODE ] | | ( chn . rowCommand . IsPortamento ( ) & & ! m_SongFlags [ SONG_FIRSTTICK ] ) ) )
{
if ( period ! = chn . cachedPeriod )
{
// Only recompute this whole thing in case the base period has changed.
chn . cachedPeriod = period ;
chn . glissandoPeriod = GetPeriodFromNote ( GetNoteFromPeriod ( period , chn . nFineTune , chn . nC5Speed ) , chn . nFineTune , chn . nC5Speed ) ;
}
period = chn . glissandoPeriod ;
}
ProcessArpeggio ( nChn , period , arpeggioSteps ) ;
// Preserve Amiga freq limits.
// In ST3, the frequency is always clamped to periods 113 to 856, while in ProTracker,
// the limit is variable, depending on the finetune of the sample.
// The int32_max test is for the arpeggio wrap-around in ProcessArpeggio().
// Test case: AmigaLimits.s3m, AmigaLimitsFinetune.mod
if ( m_SongFlags [ SONG_AMIGALIMITS | SONG_PT_MODE ] & & period ! = int32_max )
{
int limitLow = 113 * 4 , limitHigh = 856 * 4 ;
if ( GetType ( ) ! = MOD_TYPE_S3M )
{
const int tableOffset = XM2MODFineTune ( chn . nFineTune ) * 12 ;
limitLow = ProTrackerTunedPeriods [ tableOffset + 11 ] / 2 ;
limitHigh = ProTrackerTunedPeriods [ tableOffset ] * 2 ;
// Amiga cannot actually keep up with lower periods
if ( limitLow < 113 * 4 ) limitLow = 113 * 4 ;
}
Limit ( period , limitLow , limitHigh ) ;
Limit ( chn . nPeriod , limitLow , limitHigh ) ;
}
ProcessPanbrello ( chn ) ;
}
// IT Compatibility: Ensure that there is no pan swing, panbrello, panning envelopes, etc. applied on surround channels.
// Test case: surround-pan.it
if ( chn . dwFlags [ CHN_SURROUND ] & & ! m_SongFlags [ SONG_SURROUNDPAN ] & & m_playBehaviour [ kITNoSurroundPan ] )
{
chn . nRealPan = 128 ;
}
// Now that all relevant envelopes etc. have been processed, we can parse the MIDI macro data.
ProcessMacroOnChannel ( nChn ) ;
// After MIDI macros have been processed, we can also process the pitch / filter envelope and other pitch-related things.
if ( samplePlaying )
{
int cutoff = ProcessPitchFilterEnvelope ( chn , period ) ;
if ( cutoff > = 0 & & chn . dwFlags [ CHN_ADLIB ] & & m_opl )
{
// Cutoff doubles as modulator intensity for FM instruments
m_opl - > Volume ( nChn , static_cast < uint8 > ( cutoff / 4 ) , true ) ;
}
}
if ( chn . rowCommand . volcmd = = VOLCMD_VIBRATODEPTH & &
( chn . rowCommand . command = = CMD_VIBRATO | | chn . rowCommand . command = = CMD_VIBRATOVOL | | chn . rowCommand . command = = CMD_FINEVIBRATO ) )
{
if ( GetType ( ) = = MOD_TYPE_XM )
{
// XM Compatibility: Vibrato should be advanced twice (but not added up) if both volume-column and effect column vibrato is present.
// Effect column vibrato parameter has precedence if non-zero.
// Test case: VibratoDouble.xm
if ( ! m_SongFlags [ SONG_FIRSTTICK ] )
chn . nVibratoPos + = chn . nVibratoSpeed ;
} else if ( GetType ( ) & ( MOD_TYPE_IT | MOD_TYPE_MPT ) )
{
// IT Compatibility: Vibrato should be applied twice if both volume-colum and effect column vibrato is present.
// Volume column vibrato parameter has precedence if non-zero.
// Test case: VibratoDouble.it
Vibrato ( chn , chn . rowCommand . vol ) ;
ProcessVibrato ( nChn , period , vibratoFactor ) ;
}
}
// Plugins may also receive vibrato
ProcessVibrato ( nChn , period , vibratoFactor ) ;
if ( samplePlaying )
{
int nPeriodFrac = 0 ;
ProcessSampleAutoVibrato ( chn , period , vibratoFactor , nPeriodFrac ) ;
// Final Period
// ST3 only clamps the final output period, but never the channel's internal period.
// Test case: PeriodLimit.s3m
if ( period < = m_nMinPeriod )
{
if ( m_playBehaviour [ kST3LimitPeriod ] ) chn . nLength = 0 ; // Pattern 15 in watcha.s3m
period = m_nMinPeriod ;
}
const bool hasTuning = chn . HasCustomTuning ( ) ;
if ( hasTuning )
{
if ( chn . m_CalculateFreq | | ( chn . m_ReCalculateFreqOnFirstTick & & m_PlayState . m_nTickCount = = 0 ) )
{
chn . RecalcTuningFreq ( vibratoFactor , arpeggioSteps , * this ) ;
if ( ! chn . m_CalculateFreq )
chn . m_ReCalculateFreqOnFirstTick = false ;
else
chn . m_CalculateFreq = false ;
}
}
if ( ( chn . dwFlags & ( CHN_ADLIB | CHN_MUTE | CHN_SYNCMUTE ) ) = = CHN_ADLIB & & m_opl )
{
const bool doProcess = m_playBehaviour [ kOPLFlexibleNoteOff ] | | ! chn . dwFlags [ CHN_NOTEFADE ] | | GetType ( ) = = MOD_TYPE_S3M ;
if ( doProcess & & ! ( GetType ( ) = = MOD_TYPE_S3M & & chn . dwFlags [ CHN_KEYOFF ] ) )
{
// In ST3, a sample rate of 8363 Hz is mapped to middle-C, which is 261.625 Hz in a tempered scale at A4 = 440.
// Hence, we have to translate our "sample rate" into pitch.
const auto freq = hasTuning ? chn . nPeriod : GetFreqFromPeriod ( period , chn . nC5Speed , nPeriodFrac ) ;
auto milliHertz = Util : : muldivr_unsigned ( freq , 261625 , 8363 < < FREQ_FRACBITS ) ;
# ifndef MODPLUG_TRACKER
milliHertz = Util : : muldivr_unsigned ( milliHertz , m_nFreqFactor , 65536 ) ;
# endif // !MODPLUG_TRACKER
const bool keyOff = chn . dwFlags [ CHN_KEYOFF ] | | ( chn . dwFlags [ CHN_NOTEFADE ] & & chn . nFadeOutVol = = 0 ) ;
m_opl - > Frequency ( nChn , milliHertz , keyOff , m_playBehaviour [ kOPLBeatingOscillators ] ) ;
}
if ( doProcess )
{
// Scale volume to OPL range (0...63).
m_opl - > Volume ( nChn , static_cast < uint8 > ( Util : : muldivr_unsigned ( chn . nCalcVolume * chn . nGlobalVol * chn . nInsVol , 63 , 1 < < 26 ) ) , false ) ;
chn . nRealPan = m_opl - > Pan ( nChn , chn . nRealPan ) * 128 + 128 ;
}
// Deallocate OPL channels for notes that are most definitely never going to play again.
if ( const auto * ins = chn . pModInstrument ; ins ! = nullptr
& & ( ins - > VolEnv . dwFlags & ( ENV_ENABLED | ENV_LOOP | ENV_SUSTAIN ) ) = = ENV_ENABLED
& & ! ins - > VolEnv . empty ( )
& & chn . GetEnvelope ( ENV_VOLUME ) . nEnvPosition > = ins - > VolEnv . back ( ) . tick
& & ins - > VolEnv . back ( ) . value = = 0 )
{
m_opl - > NoteCut ( nChn ) ;
if ( ! m_playBehaviour [ kOPLNoResetAtEnvelopeEnd ] )
chn . dwFlags . reset ( CHN_ADLIB ) ;
chn . dwFlags . set ( CHN_NOTEFADE ) ;
chn . nFadeOutVol = 0 ;
} else if ( m_playBehaviour [ kOPLFlexibleNoteOff ] & & chn . dwFlags [ CHN_NOTEFADE ] & & chn . nFadeOutVol = = 0 )
{
m_opl - > NoteCut ( nChn ) ;
chn . dwFlags . reset ( CHN_ADLIB ) ;
}
}
SamplePosition ninc = GetChannelIncrement ( chn , period , nPeriodFrac ) ;
# ifndef MODPLUG_TRACKER
ninc . MulDiv ( m_nFreqFactor , 65536 ) ;
# endif // !MODPLUG_TRACKER
if ( ninc . IsZero ( ) )
{
ninc . Set ( 0 , 1 ) ;
}
chn . increment = ninc ;
}
// Increment envelope positions
if ( pIns ! = nullptr & & ! m_playBehaviour [ kITEnvelopePositionHandling ] )
{
// In IT and FT2 compatible mode, envelope positions are updated above.
// Test cases: s77.it, EnvLoops.xm
IncrementEnvelopePositions ( chn ) ;
}
// Volume ramping
chn . dwFlags . set ( CHN_VOLUMERAMP , ( chn . nRealVolume | chn . rightVol | chn . leftVol ) ! = 0 & & ! chn . dwFlags [ CHN_ADLIB ] ) ;
constexpr uint8 VUMETER_DECAY = 4 ;
chn . nLeftVU = ( chn . nLeftVU > VUMETER_DECAY ) ? ( chn . nLeftVU - VUMETER_DECAY ) : 0 ;
chn . nRightVU = ( chn . nRightVU > VUMETER_DECAY ) ? ( chn . nRightVU - VUMETER_DECAY ) : 0 ;
chn . newLeftVol = chn . newRightVol = 0 ;
chn . pCurrentSample = ( chn . pModSample & & chn . pModSample - > HasSampleData ( ) & & chn . nLength & & chn . IsSamplePlaying ( ) ) ? chn . pModSample - > samplev ( ) : nullptr ;
if ( chn . pCurrentSample | | ( chn . HasMIDIOutput ( ) & & ! chn . dwFlags [ CHN_KEYOFF | CHN_NOTEFADE ] ) )
{
// Update VU-Meter (nRealVolume is 14-bit)
uint32 vul = ( chn . nRealVolume * ( 256 - chn . nRealPan ) ) / ( 1 < < 14 ) ;
if ( vul > 127 ) vul = 127 ;
if ( chn . nLeftVU > 127 ) chn . nLeftVU = ( uint8 ) vul ;
vul / = 2 ;
if ( chn . nLeftVU < vul ) chn . nLeftVU = ( uint8 ) vul ;
uint32 vur = ( chn . nRealVolume * chn . nRealPan ) / ( 1 < < 14 ) ;
if ( vur > 127 ) vur = 127 ;
if ( chn . nRightVU > 127 ) chn . nRightVU = ( uint8 ) vur ;
vur / = 2 ;
if ( chn . nRightVU < vur ) chn . nRightVU = ( uint8 ) vur ;
} else
{
// Note change but no sample
if ( chn . nLeftVU > 128 ) chn . nLeftVU = 0 ;
if ( chn . nRightVU > 128 ) chn . nRightVU = 0 ;
}
if ( chn . pCurrentSample )
{
# ifdef MODPLUG_TRACKER
const uint32 kChnMasterVol = chn . dwFlags [ CHN_EXTRALOUD ] ? ( uint32 ) m_PlayConfig . getNormalSamplePreAmp ( ) : nMasterVol ;
# else
const uint32 kChnMasterVol = nMasterVol ;
# endif // MODPLUG_TRACKER
// Adjusting volumes
if ( m_MixerSettings . gnChannels > = 2 )
{
int32 pan = chn . nRealPan ;
Limit ( pan , 0 , 256 ) ;
int32 realvol ;
if ( m_PlayConfig . getUseGlobalPreAmp ( ) )
{
realvol = ( chn . nRealVolume * kChnMasterVol ) / 128 ;
} else
{
// Extra attenuation required here if we're bypassing pre-amp.
realvol = ( chn . nRealVolume * kChnMasterVol ) / 256 ;
}
const ForcePanningMode panningMode = m_PlayConfig . getForcePanningMode ( ) ;
if ( panningMode = = forceSoftPanning | | ( panningMode = = dontForcePanningMode & & ( m_MixerSettings . MixerFlags & SNDMIX_SOFTPANNING ) ) )
{
if ( pan < 128 )
{
chn . newLeftVol = ( realvol * 128 ) / 256 ;
chn . newRightVol = ( realvol * pan ) / 256 ;
} else
{
chn . newLeftVol = ( realvol * ( 256 - pan ) ) / 256 ;
chn . newRightVol = ( realvol * 128 ) / 256 ;
}
} else if ( panningMode = = forceFT2Panning )
{
// FT2 uses square root panning. There is a 257-entry LUT for this,
// but FT2's internal panning ranges from 0 to 255 only, meaning that
// you can never truly achieve 100% right panning in FT2, only 100% left.
// Test case: FT2PanLaw.xm
LimitMax ( pan , 255 ) ;
const int panL = pan > 0 ? XMPanningTable [ 256 - pan ] : 65536 ;
const int panR = XMPanningTable [ pan ] ;
chn . newLeftVol = ( realvol * panL ) / 65536 ;
chn . newRightVol = ( realvol * panR ) / 65536 ;
} else
{
chn . newLeftVol = ( realvol * ( 256 - pan ) ) / 256 ;
chn . newRightVol = ( realvol * pan ) / 256 ;
}
} else
{
chn . newLeftVol = ( chn . nRealVolume * kChnMasterVol ) / 256 ;
chn . newRightVol = chn . newLeftVol ;
}
// Clipping volumes
//if (chn.nNewRightVol > 0xFFFF) chn.nNewRightVol = 0xFFFF;
//if (chn.nNewLeftVol > 0xFFFF) chn.nNewLeftVol = 0xFFFF;
if ( chn . pModInstrument & & Resampling : : IsKnownMode ( chn . pModInstrument - > resampling ) )
{
// For defined resampling modes, use per-instrument resampling mode if set
chn . resamplingMode = chn . pModInstrument - > resampling ;
} else if ( Resampling : : IsKnownMode ( m_nResampling ) )
{
chn . resamplingMode = m_nResampling ;
} else if ( m_SongFlags [ SONG_ISAMIGA ] & & m_Resampler . m_Settings . emulateAmiga ! = Resampling : : AmigaFilter : : Off )
{
// Enforce Amiga resampler for Amiga modules
chn . resamplingMode = SRCMODE_AMIGA ;
} else
{
// Default to global mixer settings
chn . resamplingMode = m_Resampler . m_Settings . SrcMode ;
}
if ( chn . increment . IsUnity ( ) & & ! ( chn . dwFlags [ CHN_VIBRATO ] | | chn . nAutoVibDepth | | chn . resamplingMode = = SRCMODE_AMIGA ) )
{
// Exact sample rate match, do not interpolate at all
// - unless vibrato is applied, because in this case the constant enabling and disabling
// of resampling can introduce clicks (this is easily observable with a sine sample
// played at the mix rate).
chn . resamplingMode = SRCMODE_NEAREST ;
}
const int extraAttenuation = m_PlayConfig . getExtraSampleAttenuation ( ) ;
chn . newLeftVol / = ( 1 < < extraAttenuation ) ;
chn . newRightVol / = ( 1 < < extraAttenuation ) ;
// Dolby Pro-Logic Surround
if ( chn . dwFlags [ CHN_SURROUND ] & & m_MixerSettings . gnChannels = = 2 ) chn . newRightVol = - chn . newRightVol ;
// Checking Ping-Pong Loops
if ( chn . dwFlags [ CHN_PINGPONGFLAG ] ) chn . increment . Negate ( ) ;
// Setting up volume ramp
ProcessRamping ( chn ) ;
// Adding the channel in the channel list
if ( ! chn . dwFlags [ CHN_ADLIB ] )
{
m_PlayState . ChnMix [ m_nMixChannels + + ] = nChn ;
}
} else
{
chn . rightVol = chn . leftVol = 0 ;
chn . nLength = 0 ;
// Put the channel back into the mixer for end-of-sample pop reduction
if ( chn . nLOfs | | chn . nROfs )
m_PlayState . ChnMix [ m_nMixChannels + + ] = nChn ;
}
chn . dwOldFlags = chn . dwFlags ;
}
// If there are more channels being mixed than allowed, order them by volume and discard the most quiet ones
if ( m_nMixChannels > = m_MixerSettings . m_nMaxMixChannels )
{
std : : partial_sort ( std : : begin ( m_PlayState . ChnMix ) , std : : begin ( m_PlayState . ChnMix ) + m_MixerSettings . m_nMaxMixChannels , std : : begin ( m_PlayState . ChnMix ) + m_nMixChannels ,
[ this ] ( CHANNELINDEX i , CHANNELINDEX j ) { return ( m_PlayState . Chn [ i ] . nRealVolume > m_PlayState . Chn [ j ] . nRealVolume ) ; } ) ;
}
return true ;
}
void CSoundFile : : ProcessMacroOnChannel ( CHANNELINDEX nChn )
{
ModChannel & chn = m_PlayState . Chn [ nChn ] ;
if ( nChn < GetNumChannels ( ) )
{
// TODO evaluate per-plugin macros here
//ProcessMIDIMacro(nChn, false, m_MidiCfg.szMidiGlb[MIDIOUT_PAN]);
//ProcessMIDIMacro(nChn, false, m_MidiCfg.szMidiGlb[MIDIOUT_VOLUME]);
if ( ( chn . rowCommand . command = = CMD_MIDI & & m_SongFlags [ SONG_FIRSTTICK ] ) | | chn . rowCommand . command = = CMD_SMOOTHMIDI )
{
if ( chn . rowCommand . param < 0x80 )
ProcessMIDIMacro ( nChn , ( chn . rowCommand . command = = CMD_SMOOTHMIDI ) , m_MidiCfg . szMidiSFXExt [ chn . nActiveMacro ] , chn . rowCommand . param ) ;
else
2022-01-30 23:19:36 +00:00
ProcessMIDIMacro ( nChn , ( chn . rowCommand . command = = CMD_SMOOTHMIDI ) , m_MidiCfg . szMidiZXXExt [ ( chn . rowCommand . param & 0x7F ) ] , chn . rowCommand . param ) ;
2021-12-26 11:29:43 +00:00
}
}
}
# ifndef NO_PLUGINS
void CSoundFile : : ProcessMidiOut ( CHANNELINDEX nChn )
{
ModChannel & chn = m_PlayState . Chn [ nChn ] ;
// Do we need to process MIDI?
// For now there is no difference between mute and sync mute with VSTis.
if ( chn . dwFlags [ CHN_MUTE | CHN_SYNCMUTE ] | | ! chn . HasMIDIOutput ( ) ) return ;
// Get instrument info and plugin reference
const ModInstrument * pIns = chn . pModInstrument ; // Can't be nullptr at this point, as we have valid MIDI output.
// No instrument or muted instrument?
if ( pIns - > dwFlags [ INS_MUTE ] )
{
return ;
}
// Check instrument plugins
const PLUGINDEX nPlugin = GetBestPlugin ( nChn , PrioritiseInstrument , RespectMutes ) ;
IMixPlugin * pPlugin = nullptr ;
if ( nPlugin > 0 & & nPlugin < = MAX_MIXPLUGINS )
{
pPlugin = m_MixPlugins [ nPlugin - 1 ] . pMixPlugin ;
}
// Couldn't find a valid plugin
if ( pPlugin = = nullptr ) return ;
const ModCommand : : NOTE note = chn . rowCommand . note ;
// Check for volume commands
uint8 vol = 0xFF ;
if ( chn . rowCommand . volcmd = = VOLCMD_VOLUME )
{
vol = std : : min ( chn . rowCommand . vol , uint8 ( 64 ) ) ;
} else if ( chn . rowCommand . command = = CMD_VOLUME )
{
vol = std : : min ( chn . rowCommand . param , uint8 ( 64 ) ) ;
}
const bool hasVolCommand = ( vol ! = 0xFF ) ;
if ( m_playBehaviour [ kMIDICCBugEmulation ] )
{
if ( note ! = NOTE_NONE )
{
ModCommand : : NOTE realNote = note ;
if ( ModCommand : : IsNote ( note ) )
realNote = pIns - > NoteMap [ note - NOTE_MIN ] ;
SendMIDINote ( nChn , realNote , static_cast < uint16 > ( chn . nVolume ) ) ;
} else if ( hasVolCommand )
{
pPlugin - > MidiCC ( MIDIEvents : : MIDICC_Volume_Fine , vol , nChn ) ;
}
return ;
}
const uint32 defaultVolume = pIns - > nGlobalVol ;
//If new note, determine notevelocity to use.
if ( note ! = NOTE_NONE )
{
int32 velocity = static_cast < int32 > ( 4 * defaultVolume ) ;
switch ( pIns - > pluginVelocityHandling )
{
case PLUGIN_VELOCITYHANDLING_CHANNEL :
velocity = chn . nVolume ;
break ;
default :
break ;
}
int32 swing = chn . nVolSwing ;
if ( m_playBehaviour [ kITSwingBehaviour ] ) swing * = 4 ;
velocity + = swing ;
Limit ( velocity , 0 , 256 ) ;
ModCommand : : NOTE realNote = note ;
if ( ModCommand : : IsNote ( note ) )
realNote = pIns - > NoteMap [ note - NOTE_MIN ] ;
// Experimental VST panning
//ProcessMIDIMacro(nChn, false, m_MidiCfg.szMidiGlb[MIDIOUT_PAN], 0, nPlugin);
SendMIDINote ( nChn , realNote , static_cast < uint16 > ( velocity ) ) ;
}
const bool processVolumeAlsoOnNote = ( pIns - > pluginVelocityHandling = = PLUGIN_VELOCITYHANDLING_VOLUME ) ;
const bool hasNote = m_playBehaviour [ kMIDIVolumeOnNoteOffBug ] ? ( note ! = NOTE_NONE ) : ModCommand : : IsNote ( note ) ;
if ( ( hasVolCommand & & ! hasNote ) | | ( hasNote & & processVolumeAlsoOnNote ) )
{
switch ( pIns - > pluginVolumeHandling )
{
case PLUGIN_VOLUMEHANDLING_DRYWET :
if ( hasVolCommand ) pPlugin - > SetDryRatio ( 2 * vol ) ;
else pPlugin - > SetDryRatio ( 2 * defaultVolume ) ;
break ;
case PLUGIN_VOLUMEHANDLING_MIDI :
if ( hasVolCommand ) pPlugin - > MidiCC ( MIDIEvents : : MIDICC_Volume_Coarse , std : : min ( uint8 ( 127 ) , static_cast < uint8 > ( 2 * vol ) ) , nChn ) ;
else pPlugin - > MidiCC ( MIDIEvents : : MIDICC_Volume_Coarse , static_cast < uint8 > ( std : : min ( uint32 ( 127 ) , static_cast < uint32 > ( 2 * defaultVolume ) ) ) , nChn ) ;
break ;
default :
break ;
}
}
}
# endif // NO_PLUGINS
template < int channels >
MPT_FORCEINLINE void ApplyGlobalVolumeWithRamping ( int32 * SoundBuffer , int32 * RearBuffer , int32 lCount , int32 m_nGlobalVolume , int32 step , int32 & m_nSamplesToGlobalVolRampDest , int32 & m_lHighResRampingGlobalVolume )
{
const bool isStereo = ( channels > = 2 ) ;
const bool hasRear = ( channels > = 4 ) ;
for ( int pos = 0 ; pos < lCount ; + + pos )
{
if ( m_nSamplesToGlobalVolRampDest > 0 )
{
// Ramping required
m_lHighResRampingGlobalVolume + = step ;
SoundBuffer [ 0 ] = Util : : muldiv ( SoundBuffer [ 0 ] , m_lHighResRampingGlobalVolume , MAX_GLOBAL_VOLUME < < VOLUMERAMPPRECISION ) ;
if constexpr ( isStereo ) SoundBuffer [ 1 ] = Util : : muldiv ( SoundBuffer [ 1 ] , m_lHighResRampingGlobalVolume , MAX_GLOBAL_VOLUME < < VOLUMERAMPPRECISION ) ;
if constexpr ( hasRear ) RearBuffer [ 0 ] = Util : : muldiv ( RearBuffer [ 0 ] , m_lHighResRampingGlobalVolume , MAX_GLOBAL_VOLUME < < VOLUMERAMPPRECISION ) ; else MPT_UNUSED_VARIABLE ( RearBuffer ) ;
if constexpr ( hasRear ) RearBuffer [ 1 ] = Util : : muldiv ( RearBuffer [ 1 ] , m_lHighResRampingGlobalVolume , MAX_GLOBAL_VOLUME < < VOLUMERAMPPRECISION ) ; else MPT_UNUSED_VARIABLE ( RearBuffer ) ;
m_nSamplesToGlobalVolRampDest - - ;
} else
{
SoundBuffer [ 0 ] = Util : : muldiv ( SoundBuffer [ 0 ] , m_nGlobalVolume , MAX_GLOBAL_VOLUME ) ;
if constexpr ( isStereo ) SoundBuffer [ 1 ] = Util : : muldiv ( SoundBuffer [ 1 ] , m_nGlobalVolume , MAX_GLOBAL_VOLUME ) ;
if constexpr ( hasRear ) RearBuffer [ 0 ] = Util : : muldiv ( RearBuffer [ 0 ] , m_nGlobalVolume , MAX_GLOBAL_VOLUME ) ; else MPT_UNUSED_VARIABLE ( RearBuffer ) ;
if constexpr ( hasRear ) RearBuffer [ 1 ] = Util : : muldiv ( RearBuffer [ 1 ] , m_nGlobalVolume , MAX_GLOBAL_VOLUME ) ; else MPT_UNUSED_VARIABLE ( RearBuffer ) ;
m_lHighResRampingGlobalVolume = m_nGlobalVolume < < VOLUMERAMPPRECISION ;
}
SoundBuffer + = isStereo ? 2 : 1 ;
if constexpr ( hasRear ) RearBuffer + = 2 ;
}
}
void CSoundFile : : ProcessGlobalVolume ( long lCount )
{
// should we ramp?
if ( IsGlobalVolumeUnset ( ) )
{
// do not ramp if no global volume was set before (which is the case at song start), to prevent audible glitches when default volume is > 0 and it is set to 0 in the first row
m_PlayState . m_nGlobalVolumeDestination = m_PlayState . m_nGlobalVolume ;
m_PlayState . m_nSamplesToGlobalVolRampDest = 0 ;
m_PlayState . m_nGlobalVolumeRampAmount = 0 ;
} else if ( m_PlayState . m_nGlobalVolumeDestination ! = m_PlayState . m_nGlobalVolume )
{
// User has provided new global volume
// m_nGlobalVolume: the last global volume which got set e.g. by a pattern command
// m_nGlobalVolumeDestination: the current target of the ramping algorithm
const bool rampUp = m_PlayState . m_nGlobalVolume > m_PlayState . m_nGlobalVolumeDestination ;
m_PlayState . m_nGlobalVolumeDestination = m_PlayState . m_nGlobalVolume ;
m_PlayState . m_nSamplesToGlobalVolRampDest = m_PlayState . m_nGlobalVolumeRampAmount = rampUp ? m_MixerSettings . GetVolumeRampUpSamples ( ) : m_MixerSettings . GetVolumeRampDownSamples ( ) ;
}
// calculate ramping step
int32 step = 0 ;
if ( m_PlayState . m_nSamplesToGlobalVolRampDest > 0 )
{
// Still some ramping left to do.
int32 highResGlobalVolumeDestination = static_cast < int32 > ( m_PlayState . m_nGlobalVolumeDestination ) < < VOLUMERAMPPRECISION ;
const long delta = highResGlobalVolumeDestination - m_PlayState . m_lHighResRampingGlobalVolume ;
step = delta / static_cast < long > ( m_PlayState . m_nSamplesToGlobalVolRampDest ) ;
if ( m_nMixLevels = = mixLevels1_17RC2 )
{
// Define max step size as some factor of user defined ramping value: the lower the value, the more likely the click.
// If step is too big (might cause click), extend ramp length.
// Warning: This increases the volume ramp length by EXTREME amounts (factors of 100 are easily reachable)
// compared to the user-defined setting, so this really should not be used!
int32 maxStep = std : : max ( int32 ( 50 ) , static_cast < int32 > ( ( 10000 / ( m_PlayState . m_nGlobalVolumeRampAmount + 1 ) ) ) ) ;
while ( std : : abs ( step ) > maxStep )
{
m_PlayState . m_nSamplesToGlobalVolRampDest + = m_PlayState . m_nGlobalVolumeRampAmount ;
step = delta / static_cast < int32 > ( m_PlayState . m_nSamplesToGlobalVolRampDest ) ;
}
}
}
// apply volume and ramping
if ( m_MixerSettings . gnChannels = = 1 )
{
ApplyGlobalVolumeWithRamping < 1 > ( MixSoundBuffer , MixRearBuffer , lCount , m_PlayState . m_nGlobalVolume , step , m_PlayState . m_nSamplesToGlobalVolRampDest , m_PlayState . m_lHighResRampingGlobalVolume ) ;
} else if ( m_MixerSettings . gnChannels = = 2 )
{
ApplyGlobalVolumeWithRamping < 2 > ( MixSoundBuffer , MixRearBuffer , lCount , m_PlayState . m_nGlobalVolume , step , m_PlayState . m_nSamplesToGlobalVolRampDest , m_PlayState . m_lHighResRampingGlobalVolume ) ;
} else if ( m_MixerSettings . gnChannels = = 4 )
{
ApplyGlobalVolumeWithRamping < 4 > ( MixSoundBuffer , MixRearBuffer , lCount , m_PlayState . m_nGlobalVolume , step , m_PlayState . m_nSamplesToGlobalVolRampDest , m_PlayState . m_lHighResRampingGlobalVolume ) ;
}
}
void CSoundFile : : ProcessStereoSeparation ( long countChunk )
{
ApplyStereoSeparation ( MixSoundBuffer , MixRearBuffer , m_MixerSettings . gnChannels , countChunk , m_MixerSettings . m_nStereoSeparation ) ;
}
OPENMPT_NAMESPACE_END