2021-12-26 11:29:43 +00:00
/*
* Load_ams . cpp
* - - - - - - - - - - - -
* Purpose : AMS ( Extreme ' s Tracker / Velvet Studio ) module loader
* Notes : Extreme was renamed to Velvet Development at some point ,
* and thus they also renamed their tracker from
* " Extreme's Tracker " to " Velvet Studio " .
* While the two programs look rather similiar , the structure of both
* programs ' " AMS " format is significantly different in some places -
* Velvet Studio is a rather advanced tracker in comparison to Extreme ' s Tracker .
* The source code of Velvet Studio has been released into the
* public domain in 2013 : https : //github.com/Patosc/VelvetStudio/commits/master
* Authors : OpenMPT Devs
* The OpenMPT source code is released under the BSD license . Read LICENSE for more details .
*/
# include "stdafx.h"
# include "Loaders.h"
OPENMPT_NAMESPACE_BEGIN
// Read AMS or AMS2 (newVersion = true) pattern. At least this part of the format is more or less identical between the two trackers...
static void ReadAMSPattern ( CPattern & pattern , bool newVersion , FileReader & patternChunk )
{
enum
{
emptyRow = 0xFF , // No commands on row
endOfRowMask = 0x80 , // If set, no more commands on this row
noteMask = 0x40 , // If set, no note+instr in this command
channelMask = 0x1F , // Mask for extracting channel
// Note flags
readNextCmd = 0x80 , // One more command follows
noteDataMask = 0x7F , // Extract note
// Command flags
volCommand = 0x40 , // Effect is compressed volume command
commandMask = 0x3F , // Command or volume mask
} ;
// Effect translation table for extended (non-Protracker) effects
static constexpr ModCommand : : COMMAND effTrans [ ] =
{
CMD_S3MCMDEX , // Forward / Backward
CMD_PORTAMENTOUP , // Extra fine slide up
CMD_PORTAMENTODOWN , // Extra fine slide up
CMD_RETRIG , // Retrigger
CMD_NONE ,
CMD_TONEPORTAVOL , // Toneporta with fine volume slide
CMD_VIBRATOVOL , // Vibrato with fine volume slide
CMD_NONE ,
CMD_PANNINGSLIDE ,
CMD_NONE ,
CMD_VOLUMESLIDE , // Two times finder volume slide than Axx
CMD_NONE ,
CMD_CHANNELVOLUME , // Channel volume (0...127)
CMD_PATTERNBREAK , // Long pattern break (in hex)
CMD_S3MCMDEX , // Fine slide commands
CMD_NONE , // Fractional BPM
CMD_KEYOFF , // Key off at tick xx
CMD_PORTAMENTOUP , // Porta up, but uses all octaves (?)
CMD_PORTAMENTODOWN , // Porta down, but uses all octaves (?)
CMD_NONE ,
CMD_NONE ,
CMD_NONE ,
CMD_NONE ,
CMD_NONE ,
CMD_NONE ,
CMD_NONE ,
CMD_GLOBALVOLSLIDE , // Global volume slide
CMD_NONE ,
CMD_GLOBALVOLUME , // Global volume (0... 127)
} ;
ModCommand dummy ;
for ( ROWINDEX row = 0 ; row < pattern . GetNumRows ( ) ; row + + )
{
PatternRow baseRow = pattern . GetRow ( row ) ;
while ( patternChunk . CanRead ( 1 ) )
{
const uint8 flags = patternChunk . ReadUint8 ( ) ;
if ( flags = = emptyRow )
{
break ;
}
const CHANNELINDEX chn = ( flags & channelMask ) ;
ModCommand & m = chn < pattern . GetNumChannels ( ) ? baseRow [ chn ] : dummy ;
bool moreCommands = true ;
if ( ! ( flags & noteMask ) )
{
// Read note + instr
uint8 note = patternChunk . ReadUint8 ( ) ;
moreCommands = ( note & readNextCmd ) ! = 0 ;
note & = noteDataMask ;
if ( note = = 1 )
{
m . note = NOTE_KEYOFF ;
} else if ( note > = 2 & & note < = 121 & & newVersion )
{
m . note = note - 2 + NOTE_MIN ;
} else if ( note > = 12 & & note < = 108 & & ! newVersion )
{
m . note = note + 12 + NOTE_MIN ;
}
m . instr = patternChunk . ReadUint8 ( ) ;
}
while ( moreCommands )
{
// Read one more effect command
ModCommand origCmd = m ;
const uint8 command = patternChunk . ReadUint8 ( ) , effect = ( command & commandMask ) ;
moreCommands = ( command & readNextCmd ) ! = 0 ;
if ( command & volCommand )
{
m . volcmd = VOLCMD_VOLUME ;
m . vol = effect ;
} else
{
m . param = patternChunk . ReadUint8 ( ) ;
if ( effect < 0x10 )
{
// PT commands
m . command = effect ;
CSoundFile : : ConvertModCommand ( m ) ;
// Post-fix some commands
switch ( m . command )
{
case CMD_PANNING8 :
// 4-Bit panning
m . command = CMD_PANNING8 ;
m . param = ( m . param & 0x0F ) * 0x11 ;
break ;
case CMD_VOLUME :
m . command = CMD_NONE ;
m . volcmd = VOLCMD_VOLUME ;
m . vol = static_cast < ModCommand : : VOL > ( std : : min ( ( m . param + 1 ) / 2 , 64 ) ) ;
break ;
case CMD_MODCMDEX :
if ( m . param = = 0x80 )
{
// Break sample loop (cut after loop)
m . command = CMD_NONE ;
} else
{
m . ExtendedMODtoS3MEffect ( ) ;
}
break ;
}
} else if ( effect < 0x10 + CountOf ( effTrans ) )
{
// Extended commands
m . command = effTrans [ effect - 0x10 ] ;
// Post-fix some commands
switch ( effect )
{
case 0x10 :
// Play sample forwards / backwards
if ( m . param < = 0x01 )
{
m . param | = 0x9E ;
} else
{
m . command = CMD_NONE ;
}
break ;
case 0x11 :
case 0x12 :
// Extra fine slides
m . param = static_cast < ModCommand : : PARAM > ( std : : min ( uint8 ( 0x0F ) , m . param ) | 0xE0 ) ;
break ;
case 0x15 :
case 0x16 :
// Fine slides
m . param = static_cast < ModCommand : : PARAM > ( ( std : : min ( 0x10 , m . param + 1 ) / 2 ) | 0xF0 ) ;
break ;
case 0x1E :
// More fine slides
switch ( m . param > > 4 )
{
case 0x1 :
// Fine porta up
m . command = CMD_PORTAMENTOUP ;
m . param | = 0xF0 ;
break ;
case 0x2 :
// Fine porta down
m . command = CMD_PORTAMENTODOWN ;
m . param | = 0xF0 ;
break ;
case 0xA :
// Extra fine volume slide up
m . command = CMD_VOLUMESLIDE ;
m . param = ( ( ( ( m . param & 0x0F ) + 1 ) / 2 ) < < 4 ) | 0x0F ;
break ;
case 0xB :
// Extra fine volume slide down
m . command = CMD_VOLUMESLIDE ;
m . param = ( ( ( m . param & 0x0F ) + 1 ) / 2 ) | 0xF0 ;
break ;
default :
m . command = CMD_NONE ;
break ;
}
break ;
case 0x1C :
// Adjust channel volume range
m . param = static_cast < ModCommand : : PARAM > ( std : : min ( ( m . param + 1 ) / 2 , 64 ) ) ;
break ;
}
}
// Try merging commands first
ModCommand : : CombineEffects ( m . command , m . param , origCmd . command , origCmd . param ) ;
if ( ModCommand : : GetEffectWeight ( origCmd . command ) > ModCommand : : GetEffectWeight ( m . command ) )
{
if ( m . volcmd = = VOLCMD_NONE & & ModCommand : : ConvertVolEffect ( m . command , m . param , true ) )
{
// Volume column to the rescue!
m . volcmd = m . command ;
m . vol = m . param ;
}
m . command = origCmd . command ;
m . param = origCmd . param ;
}
}
}
if ( flags & endOfRowMask )
{
// End of row
break ;
}
}
}
}
/////////////////////////////////////////////////////////////////////
// AMS (Extreme's Tracker) 1.x loader
// AMS File Header
struct AMSFileHeader
{
uint8le versionLow ;
uint8le versionHigh ;
uint8le channelConfig ;
uint8le numSamps ;
uint16le numPats ;
uint16le numOrds ;
uint8le midiChannels ;
uint16le extraSize ;
} ;
MPT_BINARY_STRUCT ( AMSFileHeader , 11 )
// AMS Sample Header
struct AMSSampleHeader
{
enum SampleFlags
{
smp16BitOld = 0x04 , // AMS 1.0 (at least according to docs, I yet have to find such a file)
smp16Bit = 0x80 , // AMS 1.1+
smpPacked = 0x03 ,
} ;
uint32le length ;
uint32le loopStart ;
uint32le loopEnd ;
uint8le panFinetune ; // High nibble = pan position, low nibble = finetune value
uint16le sampleRate ;
uint8le volume ; // 0...127
uint8le flags ; // See SampleFlags
// Convert sample header to OpenMPT's internal format.
void ConvertToMPT ( ModSample & mptSmp ) const
{
mptSmp . Initialize ( ) ;
mptSmp . nLength = length ;
mptSmp . nLoopStart = std : : min ( loopStart , length ) ;
mptSmp . nLoopEnd = std : : min ( loopEnd , length ) ;
mptSmp . nVolume = ( std : : min ( uint8 ( 127 ) , volume . get ( ) ) * 256 + 64 ) / 127 ;
if ( panFinetune & 0xF0 )
{
mptSmp . nPan = ( panFinetune & 0xF0 ) ;
mptSmp . uFlags = CHN_PANNING ;
}
mptSmp . nC5Speed = 2 * sampleRate ;
if ( sampleRate = = 0 )
{
mptSmp . nC5Speed = 2 * 8363 ;
}
uint32 newC4speed = ModSample : : TransposeToFrequency ( 0 , MOD2XMFineTune ( panFinetune & 0x0F ) ) ;
mptSmp . nC5Speed = ( mptSmp . nC5Speed * newC4speed ) / 8363 ;
if ( mptSmp . nLoopStart < mptSmp . nLoopEnd )
{
mptSmp . uFlags . set ( CHN_LOOP ) ;
}
if ( flags & ( smp16Bit | smp16BitOld ) )
{
mptSmp . uFlags . set ( CHN_16BIT ) ;
}
}
} ;
MPT_BINARY_STRUCT ( AMSSampleHeader , 17 )
static bool ValidateHeader ( const AMSFileHeader & fileHeader )
{
if ( fileHeader . versionHigh ! = 0x01 )
{
return false ;
}
return true ;
}
static uint64 GetHeaderMinimumAdditionalSize ( const AMSFileHeader & fileHeader )
{
return fileHeader . extraSize + 3u + fileHeader . numSamps * ( 1u + sizeof ( AMSSampleHeader ) ) + fileHeader . numOrds * 2u + fileHeader . numPats * 4u ;
}
CSoundFile : : ProbeResult CSoundFile : : ProbeFileHeaderAMS ( MemoryFileReader file , const uint64 * pfilesize )
{
if ( ! file . CanRead ( 7 ) )
{
return ProbeWantMoreData ;
}
if ( ! file . ReadMagic ( " Extreme " ) )
{
return ProbeFailure ;
}
AMSFileHeader fileHeader ;
if ( ! file . ReadStruct ( fileHeader ) )
{
return ProbeWantMoreData ;
}
if ( ! ValidateHeader ( fileHeader ) )
{
return ProbeFailure ;
}
return ProbeAdditionalSize ( file , pfilesize , GetHeaderMinimumAdditionalSize ( fileHeader ) ) ;
}
bool CSoundFile : : ReadAMS ( FileReader & file , ModLoadingFlags loadFlags )
{
file . Rewind ( ) ;
if ( ! file . ReadMagic ( " Extreme " ) )
{
return false ;
}
AMSFileHeader fileHeader ;
if ( ! file . ReadStruct ( fileHeader ) )
{
return false ;
}
if ( ! ValidateHeader ( fileHeader ) )
{
return false ;
}
if ( ! file . CanRead ( mpt : : saturate_cast < FileReader : : off_t > ( GetHeaderMinimumAdditionalSize ( fileHeader ) ) ) )
{
return false ;
}
if ( ! file . Skip ( fileHeader . extraSize ) )
{
return false ;
}
if ( loadFlags = = onlyVerifyHeader )
{
return true ;
}
InitializeGlobals ( MOD_TYPE_AMS ) ;
m_SongFlags = SONG_ITCOMPATGXX | SONG_ITOLDEFFECTS ;
m_nChannels = ( fileHeader . channelConfig & 0x1F ) + 1 ;
m_nSamples = fileHeader . numSamps ;
SetupMODPanning ( true ) ;
m_modFormat . formatName = U_ ( " Extreme's Tracker " ) ;
m_modFormat . type = U_ ( " ams " ) ;
m_modFormat . madeWithTracker = mpt : : format ( U_ ( " Extreme's Tracker %1.%2 " ) ) ( fileHeader . versionHigh , fileHeader . versionLow ) ;
m_modFormat . charset = mpt : : Charset : : CP437 ;
std : : vector < bool > packSample ( fileHeader . numSamps ) ;
static_assert ( MAX_SAMPLES > 255 ) ;
for ( SAMPLEINDEX smp = 1 ; smp < = GetNumSamples ( ) ; smp + + )
{
AMSSampleHeader sampleHeader ;
file . ReadStruct ( sampleHeader ) ;
sampleHeader . ConvertToMPT ( Samples [ smp ] ) ;
packSample [ smp - 1 ] = ( sampleHeader . flags & AMSSampleHeader : : smpPacked ) ! = 0 ;
}
// Texts
file . ReadSizedString < uint8le , mpt : : String : : spacePadded > ( m_songName ) ;
// Read sample names
for ( SAMPLEINDEX smp = 1 ; smp < = GetNumSamples ( ) ; smp + + )
{
file . ReadSizedString < uint8le , mpt : : String : : spacePadded > ( m_szNames [ smp ] ) ;
}
// Read channel names
for ( CHANNELINDEX chn = 0 ; chn < GetNumChannels ( ) ; chn + + )
{
ChnSettings [ chn ] . Reset ( ) ;
file . ReadSizedString < uint8le , mpt : : String : : spacePadded > ( ChnSettings [ chn ] . szName ) ;
}
2022-01-30 23:19:36 +00:00
// Read pattern names and create patterns
2021-12-26 11:29:43 +00:00
Patterns . ResizeArray ( fileHeader . numPats ) ;
for ( PATTERNINDEX pat = 0 ; pat < fileHeader . numPats ; pat + + )
{
char name [ 11 ] ;
2022-01-30 23:19:36 +00:00
const bool ok = file . ReadSizedString < uint8le , mpt : : String : : spacePadded > ( name ) ;
2021-12-26 11:29:43 +00:00
// Create pattern now, so name won't be reset later.
2022-01-30 23:19:36 +00:00
if ( Patterns . Insert ( pat , 64 ) & & ok )
2021-12-26 11:29:43 +00:00
{
Patterns [ pat ] . SetName ( name ) ;
}
}
// Read packed song message
const uint16 packedLength = file . ReadUint16LE ( ) ;
if ( packedLength & & file . CanRead ( packedLength ) )
{
std : : vector < uint8 > textIn ;
file . ReadVector ( textIn , packedLength ) ;
std : : string textOut ;
textOut . reserve ( packedLength ) ;
for ( auto c : textIn )
{
if ( c & 0x80 )
{
textOut . insert ( textOut . end ( ) , ( c & 0x7F ) , ' ' ) ;
} else
{
textOut . push_back ( c ) ;
}
}
textOut = mpt : : ToCharset ( mpt : : Charset : : CP437 , mpt : : Charset : : CP437AMS , textOut ) ;
// Packed text doesn't include any line breaks!
m_songMessage . ReadFixedLineLength ( mpt : : byte_cast < const std : : byte * > ( textOut . c_str ( ) ) , textOut . length ( ) , 76 , 0 ) ;
}
// Read Order List
ReadOrderFromFile < uint16le > ( Order ( ) , file , fileHeader . numOrds ) ;
// Read patterns
for ( PATTERNINDEX pat = 0 ; pat < fileHeader . numPats & & file . CanRead ( 4 ) ; pat + + )
{
uint32 patLength = file . ReadUint32LE ( ) ;
FileReader patternChunk = file . ReadChunk ( patLength ) ;
if ( ( loadFlags & loadPatternData ) & & Patterns . IsValidPat ( pat ) )
{
ReadAMSPattern ( Patterns [ pat ] , false , patternChunk ) ;
}
}
if ( loadFlags & loadSampleData )
{
// Read Samples
for ( SAMPLEINDEX smp = 1 ; smp < = GetNumSamples ( ) ; smp + + )
{
SampleIO (
Samples [ smp ] . uFlags [ CHN_16BIT ] ? SampleIO : : _16bit : SampleIO : : _8bit ,
SampleIO : : mono ,
SampleIO : : littleEndian ,
packSample [ smp - 1 ] ? SampleIO : : AMS : SampleIO : : signedPCM )
. ReadSample ( Samples [ smp ] , file ) ;
}
}
return true ;
}
/////////////////////////////////////////////////////////////////////
// AMS (Velvet Studio) 2.0 - 2.02 loader
// AMS2 File Header
struct AMS2FileHeader
{
enum FileFlags
{
linearSlides = 0x40 ,
} ;
uint8le versionLow ; // Version of format (Hi = MainVer, Low = SubVer e.g. 0202 = 2.02)
uint8le versionHigh ; // ditto
uint8le numIns ; // Nr of Instruments (0-255)
uint16le numPats ; // Nr of Patterns (1-1024)
uint16le numOrds ; // Nr of Positions (1-65535)
// Rest of header differs between format revision 2.01 and 2.02
} ;
MPT_BINARY_STRUCT ( AMS2FileHeader , 7 )
// AMS2 Instument Envelope
struct AMS2Envelope
{
uint8 speed ; // Envelope speed (currently not supported, always the same as current BPM)
uint8 sustainPoint ; // Envelope sustain point
uint8 loopStart ; // Envelope loop Start
uint8 loopEnd ; // Envelope loop End
uint8 numPoints ; // Envelope length
// Read envelope and do partial conversion.
void ConvertToMPT ( InstrumentEnvelope & mptEnv , FileReader & file )
{
file . ReadStruct ( * this ) ;
// Read envelope points
uint8 data [ 64 ] [ 3 ] ;
file . ReadStructPartial ( data , numPoints * 3 ) ;
if ( numPoints < = 1 )
{
// This is not an envelope.
return ;
}
static_assert ( MAX_ENVPOINTS > = CountOf ( data ) ) ;
mptEnv . resize ( std : : min ( numPoints , uint8 ( CountOf ( data ) ) ) ) ;
mptEnv . nLoopStart = loopStart ;
mptEnv . nLoopEnd = loopEnd ;
mptEnv . nSustainStart = mptEnv . nSustainEnd = sustainPoint ;
for ( uint32 i = 0 ; i < mptEnv . size ( ) ; i + + )
{
if ( i ! = 0 )
{
mptEnv [ i ] . tick = mptEnv [ i - 1 ] . tick + static_cast < uint16 > ( std : : max ( 1 , data [ i ] [ 0 ] | ( ( data [ i ] [ 1 ] & 0x01 ) < < 8 ) ) ) ;
}
mptEnv [ i ] . value = data [ i ] [ 2 ] ;
}
}
} ;
MPT_BINARY_STRUCT ( AMS2Envelope , 5 )
// AMS2 Instrument Data
struct AMS2Instrument
{
enum EnvelopeFlags
{
envLoop = 0x01 ,
envSustain = 0x02 ,
envEnabled = 0x04 ,
// Flag shift amounts
volEnvShift = 0 ,
panEnvShift = 1 ,
vibEnvShift = 2 ,
vibAmpMask = 0x3000 ,
vibAmpShift = 12 ,
fadeOutMask = 0xFFF ,
} ;
uint8le shadowInstr ; // Shadow Instrument. If non-zero, the value=the shadowed inst.
uint16le vibampFadeout ; // Vib.Amplify + Volume fadeout in one variable!
uint16le envFlags ; // See EnvelopeFlags
void ApplyFlags ( InstrumentEnvelope & mptEnv , EnvelopeFlags shift ) const
{
const int flags = envFlags > > ( shift * 3 ) ;
mptEnv . dwFlags . set ( ENV_ENABLED , ( flags & envEnabled ) ! = 0 ) ;
mptEnv . dwFlags . set ( ENV_LOOP , ( flags & envLoop ) ! = 0 ) ;
mptEnv . dwFlags . set ( ENV_SUSTAIN , ( flags & envSustain ) ! = 0 ) ;
// "Break envelope" should stop the envelope loop when encountering a note-off... We can only use the sustain loop to emulate this behaviour.
if ( ! ( flags & envSustain ) & & ( flags & envLoop ) ! = 0 & & ( flags & ( 1 < < ( 9 - shift * 2 ) ) ) ! = 0 )
{
mptEnv . nSustainStart = mptEnv . nLoopStart ;
mptEnv . nSustainEnd = mptEnv . nLoopEnd ;
mptEnv . dwFlags . set ( ENV_SUSTAIN ) ;
mptEnv . dwFlags . reset ( ENV_LOOP ) ;
}
}
} ;
MPT_BINARY_STRUCT ( AMS2Instrument , 5 )
// AMS2 Sample Header
struct AMS2SampleHeader
{
enum SampleFlags
{
smpPacked = 0x03 ,
smp16Bit = 0x04 ,
smpLoop = 0x08 ,
smpBidiLoop = 0x10 ,
smpReverse = 0x40 ,
} ;
uint32le length ;
uint32le loopStart ;
uint32le loopEnd ;
uint16le sampledRate ; // Whyyyy?
uint8le panFinetune ; // High nibble = pan position, low nibble = finetune value
uint16le c4speed ; // Why is all of this so redundant?
int8le relativeTone ; // q.e.d.
uint8le volume ; // 0...127
uint8le flags ; // See SampleFlags
// Convert sample header to OpenMPT's internal format.
void ConvertToMPT ( ModSample & mptSmp ) const
{
mptSmp . Initialize ( ) ;
mptSmp . nLength = length ;
mptSmp . nLoopStart = std : : min ( loopStart , length ) ;
mptSmp . nLoopEnd = std : : min ( loopEnd , length ) ;
mptSmp . nC5Speed = c4speed * 2 ;
if ( c4speed = = 0 )
{
mptSmp . nC5Speed = 8363 * 2 ;
}
// Why, oh why, does this format need a c5speed and transpose/finetune at the same time...
uint32 newC4speed = ModSample : : TransposeToFrequency ( relativeTone , MOD2XMFineTune ( panFinetune & 0x0F ) ) ;
mptSmp . nC5Speed = ( mptSmp . nC5Speed * newC4speed ) / 8363 ;
mptSmp . nVolume = ( std : : min ( volume . get ( ) , uint8 ( 127 ) ) * 256 + 64 ) / 127 ;
if ( panFinetune & 0xF0 )
{
mptSmp . nPan = ( panFinetune & 0xF0 ) ;
mptSmp . uFlags = CHN_PANNING ;
}
if ( flags & smp16Bit ) mptSmp . uFlags . set ( CHN_16BIT ) ;
if ( ( flags & smpLoop ) & & mptSmp . nLoopStart < mptSmp . nLoopEnd )
{
mptSmp . uFlags . set ( CHN_LOOP ) ;
if ( flags & smpBidiLoop ) mptSmp . uFlags . set ( CHN_PINGPONGLOOP ) ;
if ( flags & smpReverse ) mptSmp . uFlags . set ( CHN_REVERSE ) ;
}
}
} ;
MPT_BINARY_STRUCT ( AMS2SampleHeader , 20 )
// AMS2 Song Description Header
struct AMS2Description
{
uint32le packedLen ; // Including header
uint32le unpackedLen ;
uint8le packRoutine ; // 01
uint8le preProcessing ; // None!
uint8le packingMethod ; // RLE
} ;
MPT_BINARY_STRUCT ( AMS2Description , 11 )
static bool ValidateHeader ( const AMS2FileHeader & fileHeader )
{
if ( fileHeader . versionHigh ! = 2 | | fileHeader . versionLow > 2 )
{
return false ;
}
return true ;
}
static uint64 GetHeaderMinimumAdditionalSize ( const AMS2FileHeader & fileHeader )
{
return 36u + sizeof ( AMS2Description ) + fileHeader . numIns * 2u + fileHeader . numOrds * 2u + fileHeader . numPats * 4u ;
}
CSoundFile : : ProbeResult CSoundFile : : ProbeFileHeaderAMS2 ( MemoryFileReader file , const uint64 * pfilesize )
{
if ( ! file . CanRead ( 7 ) )
{
return ProbeWantMoreData ;
}
if ( ! file . ReadMagic ( " AMShdr \x1A " ) )
{
return ProbeFailure ;
}
if ( ! file . CanRead ( 1 ) )
{
return ProbeWantMoreData ;
}
const uint8 songNameLength = file . ReadUint8 ( ) ;
if ( ! file . Skip ( songNameLength ) )
{
return ProbeWantMoreData ;
}
AMS2FileHeader fileHeader ;
if ( ! file . ReadStruct ( fileHeader ) )
{
return ProbeWantMoreData ;
}
if ( ! ValidateHeader ( fileHeader ) )
{
return ProbeFailure ;
}
return ProbeAdditionalSize ( file , pfilesize , GetHeaderMinimumAdditionalSize ( fileHeader ) ) ;
}
bool CSoundFile : : ReadAMS2 ( FileReader & file , ModLoadingFlags loadFlags )
{
file . Rewind ( ) ;
if ( ! file . ReadMagic ( " AMShdr \x1A " ) )
{
return false ;
}
std : : string songName ;
if ( ! file . ReadSizedString < uint8le , mpt : : String : : spacePadded > ( songName ) )
{
return false ;
}
AMS2FileHeader fileHeader ;
if ( ! file . ReadStruct ( fileHeader ) )
{
return false ;
}
if ( ! ValidateHeader ( fileHeader ) )
{
return false ;
}
if ( ! file . CanRead ( mpt : : saturate_cast < FileReader : : off_t > ( GetHeaderMinimumAdditionalSize ( fileHeader ) ) ) )
{
return false ;
}
if ( loadFlags = = onlyVerifyHeader )
{
return true ;
}
InitializeGlobals ( MOD_TYPE_AMS ) ;
m_songName = songName ;
m_nInstruments = fileHeader . numIns ;
m_nChannels = 32 ;
SetupMODPanning ( true ) ;
m_modFormat . formatName = U_ ( " Velvet Studio " ) ;
m_modFormat . type = U_ ( " ams " ) ;
m_modFormat . madeWithTracker = mpt : : format ( U_ ( " Velvet Studio %1.%2 " ) ) ( fileHeader . versionHigh . get ( ) , mpt : : ufmt : : dec0 < 2 > ( fileHeader . versionLow . get ( ) ) ) ;
m_modFormat . charset = mpt : : Charset : : CP437 ;
uint16 headerFlags ;
if ( fileHeader . versionLow > = 2 )
{
uint16 tempo = std : : max ( uint16 ( 32 < < 8 ) , file . ReadUint16LE ( ) ) ; // 8.8 tempo
m_nDefaultTempo . SetRaw ( ( tempo * TEMPO : : fractFact ) > > 8 ) ;
m_nDefaultSpeed = std : : max ( uint8 ( 1 ) , file . ReadUint8 ( ) ) ;
file . Skip ( 3 ) ; // Default values for pattern editor
headerFlags = file . ReadUint16LE ( ) ;
} else
{
m_nDefaultTempo . Set ( std : : max ( uint8 ( 32 ) , file . ReadUint8 ( ) ) ) ;
m_nDefaultSpeed = std : : max ( uint8 ( 1 ) , file . ReadUint8 ( ) ) ;
headerFlags = file . ReadUint8 ( ) ;
}
m_SongFlags = SONG_ITCOMPATGXX | SONG_ITOLDEFFECTS | ( ( headerFlags & AMS2FileHeader : : linearSlides ) ? SONG_LINEARSLIDES : SongFlags ( 0 ) ) ;
// Instruments
std : : vector < SAMPLEINDEX > firstSample ; // First sample of instrument
std : : vector < uint16 > sampleSettings ; // Shadow sample map... Lo byte = Instrument, Hi byte, lo nibble = Sample index in instrument, Hi byte, hi nibble = Sample pack status
enum
{
instrIndexMask = 0xFF , // Shadow instrument
sampleIndexMask = 0x7F00 , // Sample index in instrument
sampleIndexShift = 8 ,
packStatusMask = 0x8000 , // If bit is set, sample is packed
} ;
static_assert ( MAX_INSTRUMENTS > 255 ) ;
for ( INSTRUMENTINDEX ins = 1 ; ins < = m_nInstruments ; ins + + )
{
ModInstrument * instrument = AllocateInstrument ( ins ) ;
if ( instrument = = nullptr
| | ! file . ReadSizedString < uint8le , mpt : : String : : spacePadded > ( instrument - > name ) )
{
break ;
}
uint8 numSamples = file . ReadUint8 ( ) ;
uint8 sampleAssignment [ 120 ] ;
MemsetZero ( sampleAssignment ) ; // Only really needed for v2.0, where the lowest and highest octave aren't cleared.
if ( numSamples = = 0
| | ( fileHeader . versionLow > 0 & & ! file . ReadArray ( sampleAssignment ) ) // v2.01+: 120 Notes
| | ( fileHeader . versionLow = = 0 & & ! file . ReadRaw ( sampleAssignment + 12 , 96 ) ) ) // v2.0: 96 Notes
{
continue ;
}
static_assert ( mpt : : array_size < decltype ( instrument - > Keyboard ) > : : size > = std : : size ( sampleAssignment ) ) ;
for ( size_t i = 0 ; i < 120 ; i + + )
{
instrument - > Keyboard [ i ] = sampleAssignment [ i ] + GetNumSamples ( ) + 1 ;
}
AMS2Envelope volEnv , panEnv , vibratoEnv ;
volEnv . ConvertToMPT ( instrument - > VolEnv , file ) ;
panEnv . ConvertToMPT ( instrument - > PanEnv , file ) ;
vibratoEnv . ConvertToMPT ( instrument - > PitchEnv , file ) ;
AMS2Instrument instrHeader ;
file . ReadStruct ( instrHeader ) ;
instrument - > nFadeOut = ( instrHeader . vibampFadeout & AMS2Instrument : : fadeOutMask ) ;
const int16 vibAmp = 1 < < ( ( instrHeader . vibampFadeout & AMS2Instrument : : vibAmpMask ) > > AMS2Instrument : : vibAmpShift ) ;
instrHeader . ApplyFlags ( instrument - > VolEnv , AMS2Instrument : : volEnvShift ) ;
instrHeader . ApplyFlags ( instrument - > PanEnv , AMS2Instrument : : panEnvShift ) ;
instrHeader . ApplyFlags ( instrument - > PitchEnv , AMS2Instrument : : vibEnvShift ) ;
// Scale envelopes to correct range
for ( auto & p : instrument - > VolEnv )
{
p . value = std : : min ( uint8 ( ENVELOPE_MAX ) , static_cast < uint8 > ( ( p . value * ENVELOPE_MAX + 64u ) / 127u ) ) ;
}
for ( auto & p : instrument - > PanEnv )
{
p . value = std : : min ( uint8 ( ENVELOPE_MAX ) , static_cast < uint8 > ( ( p . value * ENVELOPE_MAX + 128u ) / 255u ) ) ;
}
for ( auto & p : instrument - > PitchEnv )
{
# ifdef MODPLUG_TRACKER
p . value = std : : min ( uint8 ( ENVELOPE_MAX ) , static_cast < uint8 > ( 32 + Util : : muldivrfloor ( static_cast < int8 > ( p . value - 128 ) , vibAmp , 255 ) ) ) ;
# else
// Try to keep as much precision as possible... divide by 8 since that's the highest possible vibAmp factor.
p . value = static_cast < uint8 > ( 128 + Util : : muldivrfloor ( static_cast < int8 > ( p . value - 128 ) , vibAmp , 8 ) ) ;
# endif
}
// Sample headers - we will have to read them even for shadow samples, and we will have to load them several times,
// as it is possible that shadow samples use different sample settings like base frequency or panning.
const SAMPLEINDEX firstSmp = GetNumSamples ( ) + 1 ;
for ( SAMPLEINDEX smp = 0 ; smp < numSamples ; smp + + )
{
if ( firstSmp + smp > = MAX_SAMPLES )
{
file . Skip ( sizeof ( AMS2SampleHeader ) ) ;
break ;
}
file . ReadSizedString < uint8le , mpt : : String : : spacePadded > ( m_szNames [ firstSmp + smp ] ) ;
AMS2SampleHeader sampleHeader ;
file . ReadStruct ( sampleHeader ) ;
sampleHeader . ConvertToMPT ( Samples [ firstSmp + smp ] ) ;
uint16 settings = ( instrHeader . shadowInstr & instrIndexMask )
| ( ( smp < < sampleIndexShift ) & sampleIndexMask )
| ( ( sampleHeader . flags & AMS2SampleHeader : : smpPacked ) ? packStatusMask : 0 ) ;
sampleSettings . push_back ( settings ) ;
}
firstSample . push_back ( firstSmp ) ;
m_nSamples = static_cast < SAMPLEINDEX > ( std : : min ( MAX_SAMPLES - 1 , GetNumSamples ( ) + numSamples ) ) ;
}
// Text
// Read composer name
if ( std : : string composer ; file . ReadSizedString < uint8le , mpt : : String : : spacePadded > ( composer ) )
{
m_songArtist = mpt : : ToUnicode ( mpt : : Charset : : CP437AMS2 , composer ) ;
}
// Channel names
for ( CHANNELINDEX chn = 0 ; chn < 32 ; chn + + )
{
ChnSettings [ chn ] . Reset ( ) ;
file . ReadSizedString < uint8le , mpt : : String : : spacePadded > ( ChnSettings [ chn ] . szName ) ;
}
// RLE-Packed description text
AMS2Description descriptionHeader ;
if ( ! file . ReadStruct ( descriptionHeader ) )
{
return true ;
}
if ( descriptionHeader . packedLen > sizeof ( descriptionHeader ) & & file . CanRead ( descriptionHeader . packedLen - sizeof ( descriptionHeader ) ) )
{
const uint32 textLength = descriptionHeader . packedLen - static_cast < uint32 > ( sizeof ( descriptionHeader ) ) ;
std : : vector < uint8 > textIn ;
file . ReadVector ( textIn , textLength ) ;
// In the best case, every byte triplet can decode to 255 bytes, which is a ratio of exactly 1:85
const uint32 maxLength = std : : min ( textLength , Util : : MaxValueOfType ( textLength ) / 85u ) * 85u ;
std : : string textOut ;
textOut . reserve ( std : : min ( maxLength , descriptionHeader . unpackedLen . get ( ) ) ) ;
size_t readLen = 0 ;
while ( readLen < textLength )
{
uint8 c = textIn [ readLen + + ] ;
if ( c = = 0xFF & & textLength - readLen > = 2 )
{
c = textIn [ readLen + + ] ;
uint32 count = textIn [ readLen + + ] ;
textOut . insert ( textOut . end ( ) , count , c ) ;
} else
{
textOut . push_back ( c ) ;
}
}
textOut = mpt : : ToCharset ( mpt : : Charset : : CP437 , mpt : : Charset : : CP437AMS2 , textOut ) ;
// Packed text doesn't include any line breaks!
m_songMessage . ReadFixedLineLength ( mpt : : byte_cast < const std : : byte * > ( textOut . c_str ( ) ) , textOut . length ( ) , 74 , 0 ) ;
}
// Read Order List
ReadOrderFromFile < uint16le > ( Order ( ) , file , fileHeader . numOrds ) ;
// Read Patterns
if ( loadFlags & loadPatternData )
Patterns . ResizeArray ( fileHeader . numPats ) ;
for ( PATTERNINDEX pat = 0 ; pat < fileHeader . numPats & & file . CanRead ( 4 ) ; pat + + )
{
uint32 patLength = file . ReadUint32LE ( ) ;
FileReader patternChunk = file . ReadChunk ( patLength ) ;
if ( loadFlags & loadPatternData )
{
const ROWINDEX numRows = patternChunk . ReadUint8 ( ) + 1 ;
// We don't need to know the number of channels or commands.
patternChunk . Skip ( 1 ) ;
if ( ! Patterns . Insert ( pat , numRows ) )
{
continue ;
}
char patternName [ 11 ] ;
2022-01-30 23:19:36 +00:00
if ( patternChunk . ReadSizedString < uint8le , mpt : : String : : spacePadded > ( patternName ) )
Patterns [ pat ] . SetName ( patternName ) ;
2021-12-26 11:29:43 +00:00
ReadAMSPattern ( Patterns [ pat ] , true , patternChunk ) ;
}
}
if ( ! ( loadFlags & loadSampleData ) )
{
return true ;
}
// Read Samples
for ( SAMPLEINDEX smp = 0 ; smp < GetNumSamples ( ) ; smp + + )
{
if ( ( sampleSettings [ smp ] & instrIndexMask ) = = 0 )
{
// Only load samples that aren't part of a shadow instrument
SampleIO (
( Samples [ smp + 1 ] . uFlags & CHN_16BIT ) ? SampleIO : : _16bit : SampleIO : : _8bit ,
SampleIO : : mono ,
SampleIO : : littleEndian ,
( sampleSettings [ smp ] & packStatusMask ) ? SampleIO : : AMS : SampleIO : : signedPCM )
. ReadSample ( Samples [ smp + 1 ] , file ) ;
}
}
// Copy shadow samples
for ( SAMPLEINDEX smp = 0 ; smp < GetNumSamples ( ) ; smp + + )
{
INSTRUMENTINDEX sourceInstr = ( sampleSettings [ smp ] & instrIndexMask ) ;
if ( sourceInstr = = 0
| | - - sourceInstr > = firstSample . size ( ) )
{
continue ;
}
SAMPLEINDEX sourceSample = ( ( sampleSettings [ smp ] & sampleIndexMask ) > > sampleIndexShift ) + firstSample [ sourceInstr ] ;
if ( sourceSample > GetNumSamples ( ) | | ! Samples [ sourceSample ] . HasSampleData ( ) )
{
continue ;
}
// Copy over original sample
ModSample & sample = Samples [ smp + 1 ] ;
ModSample & source = Samples [ sourceSample ] ;
sample . uFlags . set ( CHN_16BIT , source . uFlags [ CHN_16BIT ] ) ;
sample . nLength = source . nLength ;
if ( sample . AllocateSample ( ) )
{
memcpy ( sample . sampleb ( ) , source . sampleb ( ) , source . GetSampleSizeInBytes ( ) ) ;
}
}
return true ;
}
/////////////////////////////////////////////////////////////////////
// AMS Sample unpacking
void AMSUnpack ( const int8 * const source , size_t sourceSize , void * const dest , const size_t destSize , char packCharacter )
{
std : : vector < int8 > tempBuf ( destSize , 0 ) ;
size_t depackSize = destSize ;
// Unpack Loop
{
const int8 * in = source ;
int8 * out = tempBuf . data ( ) ;
size_t i = sourceSize , j = destSize ;
while ( i ! = 0 & & j ! = 0 )
{
int8 ch = * ( in + + ) ;
if ( - - i ! = 0 & & ch = = packCharacter )
{
uint8 repCount = * ( in + + ) ;
repCount = static_cast < uint8 > ( std : : min ( static_cast < size_t > ( repCount ) , j ) ) ;
if ( - - i ! = 0 & & repCount )
{
ch = * ( in + + ) ;
i - - ;
while ( repCount - - ! = 0 )
{
* ( out + + ) = ch ;
j - - ;
}
} else
{
* ( out + + ) = packCharacter ;
j - - ;
}
} else
{
* ( out + + ) = ch ;
j - - ;
}
}
// j should only be non-zero for truncated samples
depackSize - = j ;
}
// Bit Unpack Loop
{
int8 * out = tempBuf . data ( ) ;
uint16 bitcount = 0x80 ;
size_t k = 0 ;
uint8 * dst = static_cast < uint8 * > ( dest ) ;
for ( size_t i = 0 ; i < depackSize ; i + + )
{
uint8 al = * out + + ;
uint16 dh = 0 ;
for ( uint16 count = 0 ; count < 8 ; count + + )
{
uint16 bl = al & bitcount ;
bl = ( ( bl | ( bl < < 8 ) ) > > ( ( dh + 8 - count ) & 7 ) ) & 0xFF ;
bitcount = ( ( bitcount | ( bitcount < < 8 ) ) > > 1 ) & 0xFF ;
dst [ k + + ] | = bl ;
if ( k > = destSize )
{
k = 0 ;
dh + + ;
}
}
bitcount = ( ( bitcount | ( bitcount < < 8 ) ) > > dh ) & 0xFF ;
}
}
// Delta Unpack
{
int8 old = 0 ;
int8 * out = static_cast < int8 * > ( dest ) ;
for ( size_t i = depackSize ; i ! = 0 ; i - - )
{
int pos = * reinterpret_cast < uint8 * > ( out ) ;
if ( pos ! = 128 & & ( pos & 0x80 ) ! = 0 )
{
pos = - ( pos & 0x7F ) ;
}
old - = static_cast < int8 > ( pos ) ;
* ( out + + ) = old ;
}
}
}
OPENMPT_NAMESPACE_END