2018-02-19 01:25:43 -03:00
/*
* SampleFormats . cpp
* - - - - - - - - - - - - - - - - -
* Purpose : Code for loading various more or less common sample and instrument formats .
* Notes : ( currently none )
* Authors : OpenMPT Devs
* The OpenMPT source code is released under the BSD license . Read LICENSE for more details .
*/
# include "stdafx.h"
# include "Sndfile.h"
# include "mod_specifications.h"
# ifdef MODPLUG_TRACKER
# include "../mptrack/Moddoc.h"
# include "Dlsbank.h"
2020-09-22 01:54:24 -03:00
# endif // MODPLUG_TRACKER
2018-02-19 01:25:43 -03:00
# include "../soundlib/AudioCriticalSection.h"
# ifndef MODPLUG_NO_FILESAVE
# include "../common/mptFileIO.h"
2020-09-22 01:54:24 -03:00
# endif // !MODPLUG_NO_FILESAVE
2018-02-19 01:25:43 -03:00
# include "../common/misc_util.h"
2021-03-14 18:55:49 -03:00
# include "../common/Endianness.h"
2018-02-19 01:25:43 -03:00
# include "Tagging.h"
# include "ITTools.h"
# include "XMTools.h"
# include "S3MTools.h"
# include "WAVTools.h"
# include "../common/version.h"
# include "Loaders.h"
# include "ChunkReader.h"
# include "../soundbase/SampleFormatConverters.h"
# include "../soundbase/SampleFormatCopy.h"
# include "../soundlib/ModSampleCopy.h"
2020-09-22 01:54:24 -03:00
# include <functional>
2019-01-23 23:16:37 -03:00
# include <map>
2018-02-19 01:25:43 -03:00
OPENMPT_NAMESPACE_BEGIN
bool CSoundFile : : ReadSampleFromFile ( SAMPLEINDEX nSample , FileReader & file , bool mayNormalize , bool includeInstrumentFormats )
{
if ( ! nSample | | nSample > = MAX_SAMPLES ) return false ;
if ( ! ReadWAVSample ( nSample , file , mayNormalize )
& & ! ( includeInstrumentFormats & & ReadXISample ( nSample , file ) )
& & ! ( includeInstrumentFormats & & ReadITISample ( nSample , file ) )
2019-01-23 23:16:37 -03:00
& & ! ReadW64Sample ( nSample , file )
& & ! ReadCAFSample ( nSample , file )
2018-02-19 01:25:43 -03:00
& & ! ReadAIFFSample ( nSample , file , mayNormalize )
& & ! ReadITSSample ( nSample , file )
& & ! ( includeInstrumentFormats & & ReadPATSample ( nSample , file ) )
& & ! ReadIFFSample ( nSample , file )
& & ! ReadS3ISample ( nSample , file )
2019-01-23 23:16:37 -03:00
& & ! ReadSBISample ( nSample , file )
2018-02-19 01:25:43 -03:00
& & ! ReadAUSample ( nSample , file , mayNormalize )
2020-09-22 01:54:24 -03:00
& & ! ReadBRRSample ( nSample , file )
2018-02-19 01:25:43 -03:00
& & ! ReadFLACSample ( nSample , file )
& & ! ReadOpusSample ( nSample , file )
& & ! ReadVorbisSample ( nSample , file )
2019-01-23 23:16:37 -03:00
& & ! ReadMP3Sample ( nSample , file , false )
2018-02-19 01:25:43 -03:00
& & ! ReadMediaFoundationSample ( nSample , file )
)
{
return false ;
}
if ( nSample > GetNumSamples ( ) )
{
m_nSamples = nSample ;
}
2019-01-23 23:16:37 -03:00
if ( Samples [ nSample ] . uFlags [ CHN_ADLIB ] )
{
InitOPL ( ) ;
}
2018-02-19 01:25:43 -03:00
return true ;
}
bool CSoundFile : : ReadInstrumentFromFile ( INSTRUMENTINDEX nInstr , FileReader & file , bool mayNormalize )
{
if ( ( ! nInstr ) | | ( nInstr > = MAX_INSTRUMENTS ) ) return false ;
if ( ! ReadITIInstrument ( nInstr , file )
& & ! ReadXIInstrument ( nInstr , file )
& & ! ReadPATInstrument ( nInstr , file )
& & ! ReadSFZInstrument ( nInstr , file )
// Generic read
& & ! ReadSampleAsInstrument ( nInstr , file , mayNormalize ) )
{
bool ok = false ;
# ifdef MODPLUG_TRACKER
CDLSBank bank ;
if ( bank . Open ( file ) )
{
ok = bank . ExtractInstrument ( * this , nInstr , 0 , 0 ) ;
}
# endif // MODPLUG_TRACKER
if ( ! ok ) return false ;
}
if ( nInstr > GetNumInstruments ( ) ) m_nInstruments = nInstr ;
return true ;
}
bool CSoundFile : : ReadSampleAsInstrument ( INSTRUMENTINDEX nInstr , FileReader & file , bool mayNormalize )
{
// Scanning free sample
SAMPLEINDEX nSample = GetNextFreeSample ( nInstr ) ; // may also return samples which are only referenced by the current instrument
if ( nSample = = SAMPLEINDEX_INVALID )
{
return false ;
}
// Loading Instrument
ModInstrument * pIns = new ( std : : nothrow ) ModInstrument ( nSample ) ;
if ( pIns = = nullptr )
{
return false ;
}
if ( ! ReadSampleFromFile ( nSample , file , mayNormalize , false ) )
{
delete pIns ;
return false ;
}
// Remove all samples which are only referenced by the old instrument, except for the one we just loaded our new sample into.
RemoveInstrumentSamples ( nInstr , nSample ) ;
// Replace the instrument
DestroyInstrument ( nInstr , doNoDeleteAssociatedSamples ) ;
Instruments [ nInstr ] = pIns ;
# if defined(MPT_ENABLE_FILEIO) && defined(MPT_EXTERNAL_SAMPLES)
SetSamplePath ( nSample , file . GetFileName ( ) ) ;
# endif
return true ;
}
bool CSoundFile : : DestroyInstrument ( INSTRUMENTINDEX nInstr , deleteInstrumentSamples removeSamples )
{
if ( nInstr = = 0 | | nInstr > = MAX_INSTRUMENTS | | ! Instruments [ nInstr ] ) return true ;
if ( removeSamples = = deleteAssociatedSamples )
{
RemoveInstrumentSamples ( nInstr ) ;
}
CriticalSection cs ;
ModInstrument * pIns = Instruments [ nInstr ] ;
Instruments [ nInstr ] = nullptr ;
for ( auto & chn : m_PlayState . Chn )
{
if ( chn . pModInstrument = = pIns )
chn . pModInstrument = nullptr ;
}
delete pIns ;
return true ;
}
// Remove all unused samples from the given nInstr and keep keepSample if provided
bool CSoundFile : : RemoveInstrumentSamples ( INSTRUMENTINDEX nInstr , SAMPLEINDEX keepSample )
{
if ( Instruments [ nInstr ] = = nullptr )
{
return false ;
}
std : : vector < bool > keepSamples ( GetNumSamples ( ) + 1 , true ) ;
// Check which samples are used by the instrument we are going to nuke.
auto referencedSamples = Instruments [ nInstr ] - > GetSamples ( ) ;
for ( auto sample : referencedSamples )
{
if ( sample < = GetNumSamples ( ) )
{
keepSamples [ sample ] = false ;
}
}
// If we want to keep a specific sample, do so.
if ( keepSample ! = SAMPLEINDEX_INVALID )
{
if ( keepSample < = GetNumSamples ( ) )
{
keepSamples [ keepSample ] = true ;
}
}
// Check if any of those samples are referenced by other instruments as well, in which case we want to keep them of course.
for ( INSTRUMENTINDEX nIns = 1 ; nIns < = GetNumInstruments ( ) ; nIns + + ) if ( Instruments [ nIns ] ! = nullptr & & nIns ! = nInstr )
{
Instruments [ nIns ] - > GetSamples ( keepSamples ) ;
}
// Now nuke the selected samples.
RemoveSelectedSamples ( keepSamples ) ;
return true ;
}
////////////////////////////////////////////////////////////////////////////////
//
// I/O From another song
//
bool CSoundFile : : ReadInstrumentFromSong ( INSTRUMENTINDEX targetInstr , const CSoundFile & srcSong , INSTRUMENTINDEX sourceInstr )
{
if ( ( ! sourceInstr ) | | ( sourceInstr > srcSong . GetNumInstruments ( ) )
| | ( targetInstr > = MAX_INSTRUMENTS ) | | ( ! srcSong . Instruments [ sourceInstr ] ) )
{
return false ;
}
if ( m_nInstruments < targetInstr ) m_nInstruments = targetInstr ;
ModInstrument * pIns = new ( std : : nothrow ) ModInstrument ( ) ;
if ( pIns = = nullptr )
{
return false ;
}
DestroyInstrument ( targetInstr , deleteAssociatedSamples ) ;
Instruments [ targetInstr ] = pIns ;
* pIns = * srcSong . Instruments [ sourceInstr ] ;
std : : vector < SAMPLEINDEX > sourceSample ; // Sample index in source song
std : : vector < SAMPLEINDEX > targetSample ; // Sample index in target song
SAMPLEINDEX targetIndex = 0 ; // Next index for inserting sample
for ( auto & sample : pIns - > Keyboard )
{
const SAMPLEINDEX sourceIndex = sample ;
if ( sourceIndex > 0 & & sourceIndex < = srcSong . GetNumSamples ( ) )
{
const auto entry = std : : find ( sourceSample . cbegin ( ) , sourceSample . cend ( ) , sourceIndex ) ;
if ( entry = = sourceSample . end ( ) )
{
// Didn't consider this sample yet, so add it to our map.
targetIndex = GetNextFreeSample ( targetInstr , targetIndex + 1 ) ;
if ( targetIndex < = GetModSpecifications ( ) . samplesMax )
{
sourceSample . push_back ( sourceIndex ) ;
targetSample . push_back ( targetIndex ) ;
sample = targetIndex ;
} else
{
sample = 0 ;
}
} else
{
// Sample reference has already been created, so only need to update the sample map.
sample = * ( entry - sourceSample . begin ( ) + targetSample . begin ( ) ) ;
}
} else
{
// Invalid or no source sample
sample = 0 ;
}
}
# ifdef MODPLUG_TRACKER
2020-09-22 01:54:24 -03:00
if ( pIns - > filename . empty ( ) & & srcSong . GetpModDoc ( ) ! = nullptr & & & srcSong ! = this )
2018-02-19 01:25:43 -03:00
{
2020-09-22 01:54:24 -03:00
pIns - > filename = srcSong . GetpModDoc ( ) - > GetPathNameMpt ( ) . GetFullFileName ( ) . ToLocale ( ) ;
2018-02-19 01:25:43 -03:00
}
# endif
pIns - > Convert ( srcSong . GetType ( ) , GetType ( ) ) ;
// Copy all referenced samples over
for ( size_t i = 0 ; i < targetSample . size ( ) ; i + + )
{
ReadSampleFromSong ( targetSample [ i ] , srcSong , sourceSample [ i ] ) ;
}
return true ;
}
bool CSoundFile : : ReadSampleFromSong ( SAMPLEINDEX targetSample , const CSoundFile & srcSong , SAMPLEINDEX sourceSample )
{
if ( ! sourceSample
| | sourceSample > srcSong . GetNumSamples ( )
| | ( targetSample > = GetModSpecifications ( ) . samplesMax & & targetSample > GetNumSamples ( ) ) )
{
return false ;
}
DestroySampleThreadsafe ( targetSample ) ;
const ModSample & sourceSmp = srcSong . GetSample ( sourceSample ) ;
ModSample & targetSmp = GetSample ( targetSample ) ;
if ( GetNumSamples ( ) < targetSample ) m_nSamples = targetSample ;
targetSmp = sourceSmp ;
2020-09-22 01:54:24 -03:00
m_szNames [ targetSample ] = srcSong . m_szNames [ sourceSample ] ;
2018-02-19 01:25:43 -03:00
2019-01-23 23:16:37 -03:00
if ( sourceSmp . HasSampleData ( ) )
2018-02-19 01:25:43 -03:00
{
2019-01-23 23:16:37 -03:00
targetSmp . pData . pSample = nullptr ; // Don't want to delete the original sample!
2018-02-19 01:25:43 -03:00
if ( targetSmp . AllocateSample ( ) )
{
SmpLength nSize = sourceSmp . GetSampleSizeInBytes ( ) ;
2019-01-23 23:16:37 -03:00
memcpy ( targetSmp . sampleb ( ) , sourceSmp . sampleb ( ) , nSize ) ;
2018-02-19 01:25:43 -03:00
targetSmp . PrecomputeLoops ( * this , false ) ;
}
// Remember on-disk path (for MPTM files), but don't implicitely enable on-disk storage
// (we really don't want this for e.g. duplicating samples or splitting stereo samples)
# ifdef MPT_EXTERNAL_SAMPLES
SetSamplePath ( targetSample , srcSong . GetSamplePath ( sourceSample ) ) ;
# endif
targetSmp . uFlags . reset ( SMP_KEEPONDISK ) ;
}
# ifdef MODPLUG_TRACKER
2020-09-22 01:54:24 -03:00
if ( ( targetSmp . filename . empty ( ) ) & & srcSong . GetpModDoc ( ) ! = nullptr & & & srcSong ! = this )
2018-02-19 01:25:43 -03:00
{
2020-09-22 01:54:24 -03:00
targetSmp . filename = mpt : : ToCharset ( GetCharsetInternal ( ) , srcSong . GetpModDoc ( ) - > GetTitle ( ) ) ;
2018-02-19 01:25:43 -03:00
}
# endif
2019-01-23 23:16:37 -03:00
if ( targetSmp . uFlags [ CHN_ADLIB ] & & ! SupportsOPL ( ) )
{
AddToLog ( " OPL instruments are not supported by this format. " ) ;
}
2018-02-19 01:25:43 -03:00
targetSmp . Convert ( srcSong . GetType ( ) , GetType ( ) ) ;
2019-01-23 23:16:37 -03:00
if ( targetSmp . uFlags [ CHN_ADLIB ] )
{
InitOPL ( ) ;
}
2018-02-19 01:25:43 -03:00
return true ;
}
////////////////////////////////////////////////////////////////////////
// IMA ADPCM Support for WAV files
static bool IMAADPCMUnpack16 ( int16 * target , SmpLength sampleLen , FileReader file , uint16 blockAlign , uint32 numChannels )
{
2020-09-22 01:54:24 -03:00
static constexpr int8 IMAIndexTab [ 8 ] = { - 1 , - 1 , - 1 , - 1 , 2 , 4 , 6 , 8 } ;
static constexpr int16 IMAUnpackTable [ 90 ] =
2018-02-19 01:25:43 -03:00
{
7 , 8 , 9 , 10 , 11 , 12 , 13 , 14 ,
16 , 17 , 19 , 21 , 23 , 25 , 28 , 31 ,
34 , 37 , 41 , 45 , 50 , 55 , 60 , 66 ,
73 , 80 , 88 , 97 , 107 , 118 , 130 , 143 ,
157 , 173 , 190 , 209 , 230 , 253 , 279 , 307 ,
337 , 371 , 408 , 449 , 494 , 544 , 598 , 658 ,
724 , 796 , 876 , 963 , 1060 , 1166 , 1282 , 1411 ,
1552 , 1707 , 1878 , 2066 , 2272 , 2499 , 2749 , 3024 ,
3327 , 3660 , 4026 , 4428 , 4871 , 5358 , 5894 , 6484 ,
7132 , 7845 , 8630 , 9493 , 10442 , 11487 , 12635 , 13899 ,
15289 , 16818 , 18500 , 20350 , 22385 , 24623 , 27086 , 29794 ,
32767 , 0
} ;
if ( target = = nullptr | | blockAlign < 4u * numChannels )
return false ;
SmpLength samplePos = 0 ;
sampleLen * = numChannels ;
while ( file . CanRead ( 4u * numChannels ) & & samplePos < sampleLen )
{
FileReader block = file . ReadChunk ( blockAlign ) ;
FileReader : : PinnedRawDataView blockView = block . GetPinnedRawDataView ( ) ;
2020-09-22 01:54:24 -03:00
const std : : byte * data = blockView . data ( ) ;
2018-02-19 01:25:43 -03:00
const uint32 blockSize = static_cast < uint32 > ( blockView . size ( ) ) ;
for ( uint32 chn = 0 ; chn < numChannels ; chn + + )
{
// Block header
int32 value = block . ReadInt16LE ( ) ;
int32 nIndex = block . ReadUint8 ( ) ;
Limit ( nIndex , 0 , 89 ) ;
block . Skip ( 1 ) ;
SmpLength smpPos = samplePos + chn ;
uint32 dataPos = ( numChannels + chn ) * 4 ;
// Block data
while ( smpPos < = ( sampleLen - 8 ) & & dataPos < = ( blockSize - 4 ) )
{
for ( uint32 i = 0 ; i < 8 ; i + + )
{
2019-01-23 23:16:37 -03:00
uint8 delta = mpt : : byte_cast < uint8 > ( data [ dataPos ] ) ;
2018-02-19 01:25:43 -03:00
if ( i & 1 )
{
delta > > = 4 ;
dataPos + + ;
} else
{
delta & = 0x0F ;
}
int32 v = IMAUnpackTable [ nIndex ] > > 3 ;
if ( delta & 1 ) v + = IMAUnpackTable [ nIndex ] > > 2 ;
if ( delta & 2 ) v + = IMAUnpackTable [ nIndex ] > > 1 ;
if ( delta & 4 ) v + = IMAUnpackTable [ nIndex ] ;
if ( delta & 8 ) value - = v ; else value + = v ;
nIndex + = IMAIndexTab [ delta & 7 ] ;
Limit ( nIndex , 0 , 88 ) ;
Limit ( value , - 32768 , 32767 ) ;
target [ smpPos ] = static_cast < int16 > ( value ) ;
smpPos + = numChannels ;
}
dataPos + = ( numChannels - 1 ) * 4u ;
}
}
samplePos + = ( ( blockSize - ( numChannels * 4u ) ) * 2u ) ;
}
return true ;
}
////////////////////////////////////////////////////////////////////////////////
// WAV Open
bool CSoundFile : : ReadWAVSample ( SAMPLEINDEX nSample , FileReader & file , bool mayNormalize , FileReader * wsmpChunk )
{
WAVReader wavFile ( file ) ;
if ( ! wavFile . IsValid ( )
2021-03-14 18:55:49 -03:00
| | wavFile . GetNumChannels ( ) = = 0
| | wavFile . GetNumChannels ( ) > 2
| | ( wavFile . GetBitsPerSample ( ) = = 0 & & wavFile . GetSampleFormat ( ) ! = WAVFormatChunk : : fmtMP3 )
| | ( wavFile . GetBitsPerSample ( ) < 32 & & wavFile . GetSampleFormat ( ) = = WAVFormatChunk : : fmtFloat )
| | ( wavFile . GetBitsPerSample ( ) > 64 )
| | ( wavFile . GetSampleFormat ( ) ! = WAVFormatChunk : : fmtPCM & & wavFile . GetSampleFormat ( ) ! = WAVFormatChunk : : fmtFloat & & wavFile . GetSampleFormat ( ) ! = WAVFormatChunk : : fmtIMA_ADPCM & & wavFile . GetSampleFormat ( ) ! = WAVFormatChunk : : fmtMP3 & & wavFile . GetSampleFormat ( ) ! = WAVFormatChunk : : fmtALaw & & wavFile . GetSampleFormat ( ) ! = WAVFormatChunk : : fmtULaw ) )
2018-02-19 01:25:43 -03:00
{
return false ;
}
DestroySampleThreadsafe ( nSample ) ;
2020-09-22 01:54:24 -03:00
m_szNames [ nSample ] = " " ;
2018-02-19 01:25:43 -03:00
ModSample & sample = Samples [ nSample ] ;
sample . Initialize ( ) ;
sample . nLength = wavFile . GetSampleLength ( ) ;
sample . nC5Speed = wavFile . GetSampleRate ( ) ;
2019-01-23 23:16:37 -03:00
wavFile . ApplySampleSettings ( sample , GetCharsetInternal ( ) , m_szNames [ nSample ] ) ;
2018-02-19 01:25:43 -03:00
FileReader sampleChunk = wavFile . GetSampleData ( ) ;
SampleIO sampleIO (
SampleIO : : _8bit ,
( wavFile . GetNumChannels ( ) > 1 ) ? SampleIO : : stereoInterleaved : SampleIO : : mono ,
SampleIO : : littleEndian ,
SampleIO : : signedPCM ) ;
if ( wavFile . GetSampleFormat ( ) = = WAVFormatChunk : : fmtIMA_ADPCM & & wavFile . GetNumChannels ( ) < = 2 )
{
// IMA ADPCM 4:1
LimitMax ( sample . nLength , MAX_SAMPLE_LENGTH ) ;
sample . uFlags . set ( CHN_16BIT ) ;
sample . uFlags . set ( CHN_STEREO , wavFile . GetNumChannels ( ) = = 2 ) ;
if ( ! sample . AllocateSample ( ) )
{
return false ;
}
2019-01-23 23:16:37 -03:00
IMAADPCMUnpack16 ( sample . sample16 ( ) , sample . nLength , sampleChunk , wavFile . GetBlockAlign ( ) , wavFile . GetNumChannels ( ) ) ;
2018-02-19 01:25:43 -03:00
sample . PrecomputeLoops ( * this , false ) ;
} else if ( wavFile . GetSampleFormat ( ) = = WAVFormatChunk : : fmtMP3 )
{
// MP3 in WAV
2019-01-23 23:16:37 -03:00
bool loadedMP3 = ReadMP3Sample ( nSample , sampleChunk , false , true ) | | ReadMediaFoundationSample ( nSample , sampleChunk , true ) ;
2018-06-18 21:08:02 -04:00
if ( ! loadedMP3 )
{
return false ;
}
2018-02-19 01:25:43 -03:00
} else if ( ! wavFile . IsExtensibleFormat ( ) & & wavFile . MayBeCoolEdit16_8 ( ) & & wavFile . GetSampleFormat ( ) = = WAVFormatChunk : : fmtPCM & & wavFile . GetBitsPerSample ( ) = = 32 & & wavFile . GetBlockAlign ( ) = = wavFile . GetNumChannels ( ) * 4 )
{
// Syntrillium Cool Edit hack to store IEEE 32bit floating point
// Format is described as 32bit integer PCM contained in 32bit blocks and an WAVEFORMATEX extension size of 2 which contains a single 16 bit little endian value of 1.
// (This is parsed in WAVTools.cpp and returned via MayBeCoolEdit16_8()).
// The data actually stored in this case is little endian 32bit floating point PCM with 2**15 full scale.
// Cool Edit calls this format "16.8 float".
sampleIO | = SampleIO : : _32bit ;
sampleIO | = SampleIO : : floatPCM15 ;
sampleIO . ReadSample ( sample , sampleChunk ) ;
} else if ( ! wavFile . IsExtensibleFormat ( ) & & wavFile . GetSampleFormat ( ) = = WAVFormatChunk : : fmtPCM & & wavFile . GetBitsPerSample ( ) = = 24 & & wavFile . GetBlockAlign ( ) = = wavFile . GetNumChannels ( ) * 4 )
{
// Syntrillium Cool Edit hack to store IEEE 32bit floating point
// Format is described as 24bit integer PCM contained in 32bit blocks.
// The data actually stored in this case is little endian 32bit floating point PCM with 2**23 full scale.
// Cool Edit calls this format "24.0 float".
sampleIO | = SampleIO : : _32bit ;
sampleIO | = SampleIO : : floatPCM23 ;
sampleIO . ReadSample ( sample , sampleChunk ) ;
} else if ( wavFile . GetSampleFormat ( ) = = WAVFormatChunk : : fmtALaw | | wavFile . GetSampleFormat ( ) = = WAVFormatChunk : : fmtULaw )
{
// a-law / u-law
sampleIO | = SampleIO : : _16bit ;
sampleIO | = wavFile . GetSampleFormat ( ) = = WAVFormatChunk : : fmtALaw ? SampleIO : : aLaw : SampleIO : : uLaw ;
sampleIO . ReadSample ( sample , sampleChunk ) ;
} else
{
// PCM / Float
SampleIO : : Bitdepth bitDepth ;
switch ( ( wavFile . GetBitsPerSample ( ) - 1 ) / 8u )
{
default :
case 0 : bitDepth = SampleIO : : _8bit ; break ;
case 1 : bitDepth = SampleIO : : _16bit ; break ;
case 2 : bitDepth = SampleIO : : _24bit ; break ;
case 3 : bitDepth = SampleIO : : _32bit ; break ;
case 7 : bitDepth = SampleIO : : _64bit ; break ;
}
sampleIO | = bitDepth ;
if ( wavFile . GetBitsPerSample ( ) < = 8 )
sampleIO | = SampleIO : : unsignedPCM ;
if ( wavFile . GetSampleFormat ( ) = = WAVFormatChunk : : fmtFloat )
sampleIO | = SampleIO : : floatPCM ;
if ( mayNormalize )
sampleIO . MayNormalize ( ) ;
sampleIO . ReadSample ( sample , sampleChunk ) ;
}
if ( wsmpChunk ! = nullptr )
{
// DLS WSMP chunk
* wsmpChunk = wavFile . GetWsmpChunk ( ) ;
}
sample . Convert ( MOD_TYPE_IT , GetType ( ) ) ;
sample . PrecomputeLoops ( * this , false ) ;
return true ;
}
///////////////////////////////////////////////////////////////
// Save WAV
# ifndef MODPLUG_NO_FILESAVE
2019-01-23 23:16:37 -03:00
bool CSoundFile : : SaveWAVSample ( SAMPLEINDEX nSample , std : : ostream & f ) const
2018-02-19 01:25:43 -03:00
{
2020-09-22 01:54:24 -03:00
const ModSample & sample = Samples [ nSample ] ;
if ( sample . uFlags [ CHN_ADLIB ] )
return false ;
2018-02-19 01:25:43 -03:00
WAVWriter file ( & f ) ;
if ( ! file . IsValid ( ) )
return false ;
file . WriteFormat ( sample . GetSampleRate ( GetType ( ) ) , sample . GetElementarySampleSize ( ) * 8 , sample . GetNumChannels ( ) , WAVFormatChunk : : fmtPCM ) ;
// Write sample data
file . StartChunk ( RIFFChunk : : iddata ) ;
file . Skip ( SampleIO (
sample . uFlags [ CHN_16BIT ] ? SampleIO : : _16bit : SampleIO : : _8bit ,
sample . uFlags [ CHN_STEREO ] ? SampleIO : : stereoInterleaved : SampleIO : : mono ,
SampleIO : : littleEndian ,
sample . uFlags [ CHN_16BIT ] ? SampleIO : : signedPCM : SampleIO : : unsignedPCM )
. WriteSample ( f , sample ) ) ;
file . WriteLoopInformation ( sample ) ;
file . WriteExtraInformation ( sample , GetType ( ) ) ;
if ( sample . HasCustomCuePoints ( ) )
{
file . WriteCueInformation ( sample ) ;
}
FileTags tags ;
2019-01-23 23:16:37 -03:00
tags . SetEncoder ( ) ;
2018-02-19 01:25:43 -03:00
tags . title = mpt : : ToUnicode ( GetCharsetInternal ( ) , m_szNames [ nSample ] ) ;
file . WriteMetatags ( tags ) ;
return true ;
}
# endif // MODPLUG_NO_FILESAVE
2019-01-23 23:16:37 -03:00
/////////////////
// Sony Wave64 //
2018-02-19 01:25:43 -03:00
2019-01-23 23:16:37 -03:00
struct Wave64FileHeader
2018-02-19 01:25:43 -03:00
{
2019-01-23 23:16:37 -03:00
GUIDms GuidRIFF ;
uint64le FileSize ;
GUIDms GuidWAVE ;
} ;
MPT_BINARY_STRUCT ( Wave64FileHeader , 40 )
struct Wave64ChunkHeader
{
GUIDms GuidChunk ;
uint64le Size ;
} ;
MPT_BINARY_STRUCT ( Wave64ChunkHeader , 24 )
struct Wave64Chunk
{
Wave64ChunkHeader header ;
FileReader : : off_t GetLength ( ) const
{
uint64 length = header . Size ;
if ( length < sizeof ( Wave64ChunkHeader ) )
{
length = 0 ;
} else
{
length - = sizeof ( Wave64ChunkHeader ) ;
}
return mpt : : saturate_cast < FileReader : : off_t > ( length ) ;
}
mpt : : UUID GetID ( ) const
{
return mpt : : UUID ( header . GuidChunk ) ;
}
} ;
MPT_BINARY_STRUCT ( Wave64Chunk , 24 )
static void Wave64TagFromLISTINFO ( mpt : : ustring & dst , uint16 codePage , const ChunkReader : : ChunkList < RIFFChunk > & infoChunk , RIFFChunk : : ChunkIdentifiers id )
{
if ( ! infoChunk . ChunkExists ( id ) )
{
return ;
}
FileReader textChunk = infoChunk . GetChunk ( id ) ;
if ( ! textChunk . IsValid ( ) )
{
return ;
}
std : : string str ;
textChunk . ReadString < mpt : : String : : maybeNullTerminated > ( str , textChunk . GetLength ( ) ) ;
str = mpt : : String : : Replace ( str , std : : string ( " \r \n " ) , std : : string ( " \n " ) ) ;
str = mpt : : String : : Replace ( str , std : : string ( " \r " ) , std : : string ( " \n " ) ) ;
2020-09-22 01:54:24 -03:00
dst = mpt : : ToUnicode ( codePage , mpt : : Charset : : Windows1252 , str ) ;
2019-01-23 23:16:37 -03:00
}
bool CSoundFile : : ReadW64Sample ( SAMPLEINDEX nSample , FileReader & file , bool mayNormalize )
{
file . Rewind ( ) ;
constexpr mpt : : UUID guidRIFF = " 66666972-912E-11CF-A5D6-28DB04C10000 " _uuid ;
constexpr mpt : : UUID guidWAVE = " 65766177-ACF3-11D3-8CD1-00C04F8EDB8A " _uuid ;
constexpr mpt : : UUID guidLIST = " 7473696C-912F-11CF-A5D6-28DB04C10000 " _uuid ;
constexpr mpt : : UUID guidFMT = " 20746D66-ACF3-11D3-8CD1-00C04F8EDB8A " _uuid ;
//constexpr mpt::UUID guidFACT = "74636166-ACF3-11D3-8CD1-00C04F8EDB8A"_uuid;
constexpr mpt : : UUID guidDATA = " 61746164-ACF3-11D3-8CD1-00C04F8EDB8A " _uuid ;
//constexpr mpt::UUID guidLEVL = "6C76656C-ACF3-11D3-8CD1-00C04F8EDB8A"_uuid;
//constexpr mpt::UUID guidJUNK = "6b6E756A-ACF3-11D3-8CD1-00C04f8EDB8A"_uuid;
//constexpr mpt::UUID guidBEXT = "74786562-ACF3-11D3-8CD1-00C04F8EDB8A"_uuid;
//constexpr mpt::UUID guiMARKER = "ABF76256-392D-11D2-86C7-00C04F8EDB8A"_uuid;
//constexpr mpt::UUID guiSUMMARYLIST = "925F94BC-525A-11D2-86DC-00C04F8EDB8A"_uuid;
constexpr mpt : : UUID guidCSET = " 54455343-ACF3-11D3-8CD1-00C04F8EDB8A " _uuid ;
Wave64FileHeader fileHeader ;
if ( ! file . ReadStruct ( fileHeader ) )
{
return false ;
}
if ( mpt : : UUID ( fileHeader . GuidRIFF ) ! = guidRIFF )
{
return false ;
}
if ( mpt : : UUID ( fileHeader . GuidWAVE ) ! = guidWAVE )
2018-02-19 01:25:43 -03:00
{
return false ;
}
2019-01-23 23:16:37 -03:00
if ( fileHeader . FileSize ! = file . GetLength ( ) )
{
return false ;
}
ChunkReader chunkFile = file ;
auto chunkList = chunkFile . ReadChunks < Wave64Chunk > ( 8 ) ;
2018-02-19 01:25:43 -03:00
2019-01-23 23:16:37 -03:00
if ( ! chunkList . ChunkExists ( guidFMT ) )
{
return false ;
}
FileReader formatChunk = chunkList . GetChunk ( guidFMT ) ;
WAVFormatChunk format ;
if ( ! formatChunk . ReadStruct ( format ) )
{
return false ;
}
uint16 sampleFormat = format . format ;
if ( format . format = = WAVFormatChunk : : fmtExtensible )
{
WAVFormatChunkExtension formatExt ;
if ( ! formatChunk . ReadStruct ( formatExt ) )
{
return false ;
}
sampleFormat = static_cast < uint16 > ( mpt : : UUID ( formatExt . subFormat ) . GetData1 ( ) ) ;
}
if ( format . sampleRate = = 0 )
{
return false ;
}
if ( format . numChannels = = 0 )
{
return false ;
}
if ( format . numChannels > 2 )
{
return false ;
}
if ( sampleFormat ! = WAVFormatChunk : : fmtPCM & & sampleFormat ! = WAVFormatChunk : : fmtFloat )
{
return false ;
}
if ( sampleFormat = = WAVFormatChunk : : fmtFloat & & format . bitsPerSample ! = 32 & & format . bitsPerSample ! = 64 )
{
return false ;
}
if ( sampleFormat = = WAVFormatChunk : : fmtPCM & & format . bitsPerSample > 64 )
{
return false ;
}
SampleIO : : Bitdepth bitDepth ;
switch ( ( format . bitsPerSample - 1 ) / 8u )
{
default :
case 0 : bitDepth = SampleIO : : _8bit ; break ;
case 1 : bitDepth = SampleIO : : _16bit ; break ;
case 2 : bitDepth = SampleIO : : _24bit ; break ;
case 3 : bitDepth = SampleIO : : _32bit ; break ;
case 7 : bitDepth = SampleIO : : _64bit ; break ;
}
SampleIO sampleIO (
bitDepth ,
( format . numChannels > 1 ) ? SampleIO : : stereoInterleaved : SampleIO : : mono ,
SampleIO : : littleEndian ,
( sampleFormat = = WAVFormatChunk : : fmtFloat ) ? SampleIO : : floatPCM : SampleIO : : signedPCM ) ;
if ( format . bitsPerSample < = 8 )
{
sampleIO | = SampleIO : : unsignedPCM ;
}
if ( mayNormalize )
{
sampleIO . MayNormalize ( ) ;
}
FileTags tags ;
2020-09-22 01:54:24 -03:00
uint16 codePage = 28591 ; // mpt::Charset::ISO8859_1
2019-01-23 23:16:37 -03:00
FileReader csetChunk = chunkList . GetChunk ( guidCSET ) ;
if ( csetChunk . IsValid ( ) )
{
if ( csetChunk . CanRead ( 2 ) )
{
codePage = csetChunk . ReadUint16LE ( ) ;
}
}
if ( chunkList . ChunkExists ( guidLIST ) )
{
ChunkReader listChunk = chunkList . GetChunk ( guidLIST ) ;
if ( listChunk . ReadMagic ( " INFO " ) )
{
auto infoChunk = listChunk . ReadChunks < RIFFChunk > ( 2 ) ;
Wave64TagFromLISTINFO ( tags . title , codePage , infoChunk , RIFFChunk : : idINAM ) ;
Wave64TagFromLISTINFO ( tags . encoder , codePage , infoChunk , RIFFChunk : : idISFT ) ;
//Wave64TagFromLISTINFO(void, codePage, infoChunk, RIFFChunk::idICOP);
Wave64TagFromLISTINFO ( tags . artist , codePage , infoChunk , RIFFChunk : : idIART ) ;
Wave64TagFromLISTINFO ( tags . album , codePage , infoChunk , RIFFChunk : : idIPRD ) ;
Wave64TagFromLISTINFO ( tags . comments , codePage , infoChunk , RIFFChunk : : idICMT ) ;
//Wave64TagFromLISTINFO(void, codePage, infoChunk, RIFFChunk::idIENG);
//Wave64TagFromLISTINFO(void, codePage, infoChunk, RIFFChunk::idISBJ);
Wave64TagFromLISTINFO ( tags . genre , codePage , infoChunk , RIFFChunk : : idIGNR ) ;
//Wave64TagFromLISTINFO(void, codePage, infoChunk, RIFFChunk::idICRD);
Wave64TagFromLISTINFO ( tags . year , codePage , infoChunk , RIFFChunk : : idYEAR ) ;
Wave64TagFromLISTINFO ( tags . trackno , codePage , infoChunk , RIFFChunk : : idTRCK ) ;
Wave64TagFromLISTINFO ( tags . url , codePage , infoChunk , RIFFChunk : : idTURL ) ;
//Wave64TagFromLISTINFO(tags.bpm, codePage, infoChunk, void);
}
}
if ( ! chunkList . ChunkExists ( guidDATA ) )
{
return false ;
}
FileReader audioData = chunkList . GetChunk ( guidDATA ) ;
SmpLength length = mpt : : saturate_cast < SmpLength > ( audioData . GetLength ( ) / ( sampleIO . GetEncodedBitsPerSample ( ) / 8 ) ) ;
ModSample & mptSample = Samples [ nSample ] ;
DestroySampleThreadsafe ( nSample ) ;
mptSample . Initialize ( ) ;
mptSample . nLength = length ;
mptSample . nC5Speed = format . sampleRate ;
sampleIO . ReadSample ( mptSample , audioData ) ;
2020-09-22 01:54:24 -03:00
m_szNames [ nSample ] = mpt : : ToCharset ( GetCharsetInternal ( ) , GetSampleNameFromTags ( tags ) ) ;
2019-01-23 23:16:37 -03:00
mptSample . Convert ( MOD_TYPE_IT , GetType ( ) ) ;
mptSample . PrecomputeLoops ( * this , false ) ;
return true ;
}
# ifndef MODPLUG_NO_FILESAVE
///////////////////////////////////////////////////////////////
// Save RAW
bool CSoundFile : : SaveRAWSample ( SAMPLEINDEX nSample , std : : ostream & f ) const
{
2018-02-19 01:25:43 -03:00
const ModSample & sample = Samples [ nSample ] ;
SampleIO (
sample . uFlags [ CHN_16BIT ] ? SampleIO : : _16bit : SampleIO : : _8bit ,
sample . uFlags [ CHN_STEREO ] ? SampleIO : : stereoInterleaved : SampleIO : : mono ,
SampleIO : : littleEndian ,
SampleIO : : signedPCM )
. WriteSample ( f , sample ) ;
return true ;
}
# endif // MODPLUG_NO_FILESAVE
/////////////////////////////////////////////////////////////
// GUS Patches
struct GF1PatchFileHeader
{
char magic [ 8 ] ; // "GF1PATCH"
char version [ 4 ] ; // "100", or "110"
char id [ 10 ] ; // "ID#000002"
char copyright [ 60 ] ; // Copyright
uint8le numInstr ; // Number of instruments in patch
uint8le voices ; // Number of voices, usually 14
uint8le channels ; // Number of wav channels that can be played concurently to the patch
uint16le numSamples ; // Total number of waveforms for all the .PAT
uint16le volume ; // Master volume
uint32le dataSize ;
char reserved2 [ 36 ] ;
} ;
MPT_BINARY_STRUCT ( GF1PatchFileHeader , 129 )
struct GF1Instrument
{
uint16le id ; // Instrument id: 0-65535
char name [ 16 ] ; // Name of instrument. Gravis doesn't seem to use it
uint32le size ; // Number of bytes for the instrument with header. (To skip to next instrument)
uint8 layers ; // Number of layers in instrument: 1-4
char reserved [ 40 ] ;
} ;
MPT_BINARY_STRUCT ( GF1Instrument , 63 )
struct GF1SampleHeader
{
char name [ 7 ] ; // null terminated string. name of the wave.
uint8le fractions ; // Start loop point fraction in 4 bits + End loop point fraction in the 4 other bits.
uint32le length ; // total size of wavesample. limited to 65535 now by the drivers, not the card.
uint32le loopstart ; // start loop position in the wavesample
uint32le loopend ; // end loop position in the wavesample
uint16le freq ; // Rate at which the wavesample has been sampled
uint32le low_freq , high_freq , root_freq ; // check note.h for the correspondance.
int16le finetune ; // fine tune. -512 to +512, EXCLUDING 0 cause it is a multiplier. 512 is one octave off, and 1 is a neutral value
uint8le balance ; // Balance: 0-15. 0=full left, 15 = full right
uint8le env_rate [ 6 ] ; // attack rates
uint8le env_volume [ 6 ] ; // attack volumes
uint8le tremolo_sweep , tremolo_rate , tremolo_depth ;
uint8le vibrato_sweep , vibrato_rate , vibrato_depth ;
uint8le flags ;
int16le scale_frequency ; // Note
uint16le scale_factor ; // 0...2048 (1024 is normal) or 0...2
char reserved [ 36 ] ;
} ;
MPT_BINARY_STRUCT ( GF1SampleHeader , 96 )
// -- GF1 Envelopes --
//
// It can be represented like this (the envelope is totally bogus, it is
// just to show the concept):
//
// |
// | /----` | |
// | /------/ `\ | | | | |
// | / \ | | | | |
// | / \ | | | | |
// |/ \ | | | | |
// ---------------------------- | | | | | |
// <---> attack rate 0 0 1 2 3 4 5 amplitudes
// <----> attack rate 1
// <> attack rate 2
// <--> attack rate 3
// <> attack rate 4
// <-----> attack rate 5
//
// -- GF1 Flags --
//
// bit 0: 8/16 bit
// bit 1: Signed/Unsigned
// bit 2: off/on looping
// bit 3: off/on bidirectionnal looping
// bit 4: off/on backward looping
// bit 5: off/on sustaining (3rd point in env.)
// bit 6: off/on envelopes
// bit 7: off/on clamped release (6th point, env)
struct GF1Layer
{
uint8le previous ; // If !=0 the wavesample to use is from the previous layer. The waveheader is still needed
uint8le id ; // Layer id: 0-3
uint32le size ; // data size in bytes in the layer, without the header. to skip to next layer for example:
uint8le samples ; // number of wavesamples
char reserved [ 40 ] ;
} ;
MPT_BINARY_STRUCT ( GF1Layer , 47 )
static double PatchFreqToNote ( uint32 nFreq )
{
return std : : log ( nFreq / 2044.0 ) * ( 12.0 * 1.44269504088896340736 ) ; // 1.0/std::log(2.0)
}
static int32 PatchFreqToNoteInt ( uint32 nFreq )
{
2019-01-23 23:16:37 -03:00
return mpt : : saturate_round < int32 > ( PatchFreqToNote ( nFreq ) ) ;
2018-02-19 01:25:43 -03:00
}
static void PatchToSample ( CSoundFile * that , SAMPLEINDEX nSample , GF1SampleHeader & sampleHeader , FileReader & file )
{
ModSample & sample = that - > GetSample ( nSample ) ;
file . ReadStruct ( sampleHeader ) ;
sample . Initialize ( ) ;
if ( sampleHeader . flags & 4 ) sample . uFlags . set ( CHN_LOOP ) ;
if ( sampleHeader . flags & 8 ) sample . uFlags . set ( CHN_PINGPONGLOOP ) ;
if ( sampleHeader . flags & 16 ) sample . uFlags . set ( CHN_REVERSE ) ;
sample . nLength = sampleHeader . length ;
sample . nLoopStart = sampleHeader . loopstart ;
sample . nLoopEnd = sampleHeader . loopend ;
sample . nC5Speed = sampleHeader . freq ;
sample . nPan = ( sampleHeader . balance * 256 + 8 ) / 15 ;
if ( sample . nPan > 256 ) sample . nPan = 128 ;
else sample . uFlags . set ( CHN_PANNING ) ;
sample . nVibType = VIB_SINE ;
sample . nVibSweep = sampleHeader . vibrato_sweep ;
sample . nVibDepth = sampleHeader . vibrato_depth ;
sample . nVibRate = sampleHeader . vibrato_rate / 4 ;
if ( sampleHeader . scale_factor )
{
sample . Transpose ( ( 84.0 - PatchFreqToNote ( sampleHeader . root_freq ) ) / 12.0 ) ;
}
SampleIO sampleIO (
SampleIO : : _8bit ,
SampleIO : : mono ,
SampleIO : : littleEndian ,
( sampleHeader . flags & 2 ) ? SampleIO : : unsignedPCM : SampleIO : : signedPCM ) ;
if ( sampleHeader . flags & 1 )
{
sampleIO | = SampleIO : : _16bit ;
sample . nLength / = 2 ;
sample . nLoopStart / = 2 ;
sample . nLoopEnd / = 2 ;
}
sampleIO . ReadSample ( sample , file ) ;
sample . Convert ( MOD_TYPE_IT , that - > GetType ( ) ) ;
sample . PrecomputeLoops ( * that , false ) ;
2020-09-22 01:54:24 -03:00
that - > m_szNames [ nSample ] = mpt : : String : : ReadBuf ( mpt : : String : : maybeNullTerminated , sampleHeader . name ) ;
2018-02-19 01:25:43 -03:00
}
bool CSoundFile : : ReadPATSample ( SAMPLEINDEX nSample , FileReader & file )
{
file . Rewind ( ) ;
GF1PatchFileHeader fileHeader ;
GF1Instrument instrHeader ; // We only support one instrument
GF1Layer layerHeader ;
if ( ! file . ReadStruct ( fileHeader )
| | memcmp ( fileHeader . magic , " GF1PATCH " , 8 )
| | ( memcmp ( fileHeader . version , " 110 \0 " , 4 ) & & memcmp ( fileHeader . version , " 100 \0 " , 4 ) )
| | memcmp ( fileHeader . id , " ID#000002 \0 " , 10 )
| | ! fileHeader . numInstr | | ! fileHeader . numSamples
| | ! file . ReadStruct ( instrHeader )
//|| !instrHeader.layers // DOO.PAT has 0 layers
| | ! file . ReadStruct ( layerHeader )
| | ! layerHeader . samples )
{
return false ;
}
DestroySampleThreadsafe ( nSample ) ;
GF1SampleHeader sampleHeader ;
PatchToSample ( this , nSample , sampleHeader , file ) ;
if ( instrHeader . name [ 0 ] > ' ' )
{
2020-09-22 01:54:24 -03:00
m_szNames [ nSample ] = mpt : : String : : ReadBuf ( mpt : : String : : maybeNullTerminated , instrHeader . name ) ;
2018-02-19 01:25:43 -03:00
}
return true ;
}
// PAT Instrument
bool CSoundFile : : ReadPATInstrument ( INSTRUMENTINDEX nInstr , FileReader & file )
{
file . Rewind ( ) ;
GF1PatchFileHeader fileHeader ;
GF1Instrument instrHeader ; // We only support one instrument
GF1Layer layerHeader ;
if ( ! file . ReadStruct ( fileHeader )
| | memcmp ( fileHeader . magic , " GF1PATCH " , 8 )
| | ( memcmp ( fileHeader . version , " 110 \0 " , 4 ) & & memcmp ( fileHeader . version , " 100 \0 " , 4 ) )
| | memcmp ( fileHeader . id , " ID#000002 \0 " , 10 )
| | ! fileHeader . numInstr | | ! fileHeader . numSamples
| | ! file . ReadStruct ( instrHeader )
//|| !instrHeader.layers // DOO.PAT has 0 layers
| | ! file . ReadStruct ( layerHeader )
| | ! layerHeader . samples )
{
return false ;
}
ModInstrument * pIns = new ( std : : nothrow ) ModInstrument ( ) ;
if ( pIns = = nullptr )
{
return false ;
}
DestroyInstrument ( nInstr , deleteAssociatedSamples ) ;
if ( nInstr > m_nInstruments ) m_nInstruments = nInstr ;
Instruments [ nInstr ] = pIns ;
2020-09-22 01:54:24 -03:00
pIns - > name = mpt : : String : : ReadBuf ( mpt : : String : : maybeNullTerminated , instrHeader . name ) ;
2018-02-19 01:25:43 -03:00
pIns - > nFadeOut = 2048 ;
if ( GetType ( ) & ( MOD_TYPE_IT | MOD_TYPE_MPT ) )
{
pIns - > nNNA = NNA_NOTEOFF ;
pIns - > nDNA = DNA_NOTEFADE ;
}
SAMPLEINDEX nextSample = 0 ;
int32 nMinSmpNote = 0xFF ;
SAMPLEINDEX nMinSmp = 0 ;
for ( uint8 smp = 0 ; smp < layerHeader . samples ; smp + + )
{
// Find a free sample
nextSample = GetNextFreeSample ( nInstr , nextSample + 1 ) ;
if ( nextSample = = SAMPLEINDEX_INVALID ) break ;
if ( m_nSamples < nextSample ) m_nSamples = nextSample ;
if ( ! nMinSmp ) nMinSmp = nextSample ;
// Load it
GF1SampleHeader sampleHeader ;
PatchToSample ( this , nextSample , sampleHeader , file ) ;
int32 nMinNote = ( sampleHeader . low_freq > 100 ) ? PatchFreqToNoteInt ( sampleHeader . low_freq ) : 0 ;
2020-09-22 01:54:24 -03:00
int32 nMaxNote = ( sampleHeader . high_freq > 100 ) ? PatchFreqToNoteInt ( sampleHeader . high_freq ) : static_cast < uint8 > ( NOTE_MAX ) ;
2018-02-19 01:25:43 -03:00
int32 nBaseNote = ( sampleHeader . root_freq > 100 ) ? PatchFreqToNoteInt ( sampleHeader . root_freq ) : - 1 ;
if ( ! sampleHeader . scale_factor & & layerHeader . samples = = 1 ) { nMinNote = 0 ; nMaxNote = NOTE_MAX ; }
// Fill Note Map
for ( int32 k = 0 ; k < NOTE_MAX ; k + + )
{
if ( k = = nBaseNote | | ( ! pIns - > Keyboard [ k ] & & k > = nMinNote & & k < = nMaxNote ) )
{
if ( ! sampleHeader . scale_factor )
pIns - > NoteMap [ k ] = NOTE_MIDDLEC ;
pIns - > Keyboard [ k ] = nextSample ;
if ( k < nMinSmpNote )
{
nMinSmpNote = k ;
nMinSmp = nextSample ;
}
}
}
}
if ( nMinSmp )
{
// Fill note map and missing samples
for ( uint8 k = 0 ; k < NOTE_MAX ; k + + )
{
if ( ! pIns - > NoteMap [ k ] ) pIns - > NoteMap [ k ] = k + 1 ;
if ( ! pIns - > Keyboard [ k ] )
{
pIns - > Keyboard [ k ] = nMinSmp ;
} else
{
nMinSmp = pIns - > Keyboard [ k ] ;
}
}
}
pIns - > Sanitize ( MOD_TYPE_IT ) ;
pIns - > Convert ( MOD_TYPE_IT , GetType ( ) ) ;
return true ;
}
/////////////////////////////////////////////////////////////
// S3I Samples
bool CSoundFile : : ReadS3ISample ( SAMPLEINDEX nSample , FileReader & file )
{
file . Rewind ( ) ;
S3MSampleHeader sampleHeader ;
if ( ! file . ReadStruct ( sampleHeader )
2019-01-23 23:16:37 -03:00
| | ( sampleHeader . sampleType ! = S3MSampleHeader : : typePCM & & sampleHeader . sampleType ! = S3MSampleHeader : : typeAdMel )
| | ( memcmp ( sampleHeader . magic , " SCRS " , 4 ) & & memcmp ( sampleHeader . magic , " SCRI " , 4 ) )
2018-02-19 01:25:43 -03:00
| | ! file . Seek ( ( sampleHeader . dataPointer [ 1 ] < < 4 ) | ( sampleHeader . dataPointer [ 2 ] < < 12 ) | ( sampleHeader . dataPointer [ 0 ] < < 20 ) ) )
{
return false ;
}
DestroySampleThreadsafe ( nSample ) ;
ModSample & sample = Samples [ nSample ] ;
sampleHeader . ConvertToMPT ( sample ) ;
2020-09-22 01:54:24 -03:00
m_szNames [ nSample ] = mpt : : String : : ReadBuf ( mpt : : String : : nullTerminated , sampleHeader . name ) ;
2019-01-23 23:16:37 -03:00
if ( sampleHeader . sampleType < S3MSampleHeader : : typeAdMel )
sampleHeader . GetSampleFormat ( false ) . ReadSample ( sample , file ) ;
else if ( SupportsOPL ( ) )
InitOPL ( ) ;
else
AddToLog ( " OPL instruments are not supported by this format. " ) ;
2018-02-19 01:25:43 -03:00
sample . Convert ( MOD_TYPE_S3M , GetType ( ) ) ;
sample . PrecomputeLoops ( * this , false ) ;
return true ;
}
2019-01-23 23:16:37 -03:00
# ifndef MODPLUG_NO_FILESAVE
bool CSoundFile : : SaveS3ISample ( SAMPLEINDEX smp , std : : ostream & f ) const
{
const ModSample & sample = Samples [ smp ] ;
S3MSampleHeader sampleHeader ;
MemsetZero ( sampleHeader ) ;
SmpLength length = sampleHeader . ConvertToS3M ( sample ) ;
2020-09-22 01:54:24 -03:00
mpt : : String : : WriteBuf ( mpt : : String : : nullTerminated , sampleHeader . name ) = m_szNames [ smp ] ;
mpt : : String : : WriteBuf ( mpt : : String : : maybeNullTerminated , sampleHeader . reserved2 ) = mpt : : ToCharset ( mpt : : Charset : : UTF8 , Version : : Current ( ) . GetOpenMPTVersionString ( ) ) ;
2019-01-23 23:16:37 -03:00
if ( length )
sampleHeader . dataPointer [ 1 ] = sizeof ( S3MSampleHeader ) > > 4 ;
mpt : : IO : : Write ( f , sampleHeader ) ;
if ( length )
sampleHeader . GetSampleFormat ( false ) . WriteSample ( f , sample , length ) ;
return true ;
}
# endif // MODPLUG_NO_FILESAVE
/////////////////////////////////////////////////////////////
// SBI OPL patch files
bool CSoundFile : : ReadSBISample ( SAMPLEINDEX sample , FileReader & file )
{
file . Rewind ( ) ;
if ( ! file . ReadMagic ( " SBI \x1A " )
| | ! file . CanRead ( 32 + sizeof ( OPLPatch ) )
| | file . CanRead ( 64 ) ) // Arbitrary threshold to reject files that are unlikely to be SBI files
return false ;
if ( ! SupportsOPL ( ) )
{
AddToLog ( " OPL instruments are not supported by this format. " ) ;
return true ;
}
DestroySampleThreadsafe ( sample ) ;
InitOPL ( ) ;
ModSample & mptSmp = Samples [ sample ] ;
mptSmp . Initialize ( MOD_TYPE_S3M ) ;
file . ReadString < mpt : : String : : nullTerminated > ( m_szNames [ sample ] , 32 ) ;
OPLPatch patch ;
file . ReadArray ( patch ) ;
mptSmp . SetAdlib ( true , patch ) ;
mptSmp . Convert ( MOD_TYPE_S3M , GetType ( ) ) ;
return true ;
}
2018-02-19 01:25:43 -03:00
/////////////////////////////////////////////////////////////
// XI Instruments
bool CSoundFile : : ReadXIInstrument ( INSTRUMENTINDEX nInstr , FileReader & file )
{
file . Rewind ( ) ;
XIInstrumentHeader fileHeader ;
if ( ! file . ReadStruct ( fileHeader )
| | memcmp ( fileHeader . signature , " Extended Instrument: " , 21 )
| | fileHeader . version ! = XIInstrumentHeader : : fileVersion
| | fileHeader . eof ! = 0x1A )
{
return false ;
}
ModInstrument * pIns = new ( std : : nothrow ) ModInstrument ( ) ;
if ( pIns = = nullptr )
{
return false ;
}
DestroyInstrument ( nInstr , deleteAssociatedSamples ) ;
if ( nInstr > m_nInstruments )
{
m_nInstruments = nInstr ;
}
Instruments [ nInstr ] = pIns ;
fileHeader . ConvertToMPT ( * pIns ) ;
// Translate sample map and find available sample slots
std : : vector < SAMPLEINDEX > sampleMap ( fileHeader . numSamples ) ;
SAMPLEINDEX maxSmp = 0 ;
for ( size_t i = 0 + 12 ; i < 96 + 12 ; i + + )
{
if ( pIns - > Keyboard [ i ] > = fileHeader . numSamples )
{
continue ;
}
if ( sampleMap [ pIns - > Keyboard [ i ] ] = = 0 )
{
// Find slot for this sample
maxSmp = GetNextFreeSample ( nInstr , maxSmp + 1 ) ;
if ( maxSmp ! = SAMPLEINDEX_INVALID )
{
sampleMap [ pIns - > Keyboard [ i ] ] = maxSmp ;
}
}
pIns - > Keyboard [ i ] = sampleMap [ pIns - > Keyboard [ i ] ] ;
}
if ( m_nSamples < maxSmp )
{
m_nSamples = maxSmp ;
}
std : : vector < SampleIO > sampleFlags ( fileHeader . numSamples ) ;
// Read sample headers
for ( SAMPLEINDEX i = 0 ; i < fileHeader . numSamples ; i + + )
{
XMSample sampleHeader ;
if ( ! file . ReadStruct ( sampleHeader )
| | ! sampleMap [ i ] )
{
continue ;
}
ModSample & mptSample = Samples [ sampleMap [ i ] ] ;
sampleHeader . ConvertToMPT ( mptSample ) ;
fileHeader . instrument . ApplyAutoVibratoToMPT ( mptSample ) ;
mptSample . Convert ( MOD_TYPE_XM , GetType ( ) ) ;
if ( GetType ( ) ! = MOD_TYPE_XM & & fileHeader . numSamples = = 1 )
{
// No need to pan that single sample, thank you...
mptSample . uFlags & = ~ CHN_PANNING ;
}
2020-09-22 01:54:24 -03:00
mptSample . filename = mpt : : String : : ReadBuf ( mpt : : String : : spacePadded , sampleHeader . name ) ;
m_szNames [ sampleMap [ i ] ] = mpt : : String : : ReadBuf ( mpt : : String : : spacePadded , sampleHeader . name ) ;
2018-02-19 01:25:43 -03:00
sampleFlags [ i ] = sampleHeader . GetSampleFormat ( ) ;
}
// Read sample data
for ( SAMPLEINDEX i = 0 ; i < fileHeader . numSamples ; i + + )
{
if ( sampleMap [ i ] )
{
sampleFlags [ i ] . ReadSample ( Samples [ sampleMap [ i ] ] , file ) ;
Samples [ sampleMap [ i ] ] . PrecomputeLoops ( * this , false ) ;
}
}
// Read MPT crap
ReadExtendedInstrumentProperties ( pIns , file ) ;
2019-01-23 23:16:37 -03:00
pIns - > Convert ( MOD_TYPE_XM , GetType ( ) ) ;
2018-02-19 01:25:43 -03:00
pIns - > Sanitize ( GetType ( ) ) ;
return true ;
}
# ifndef MODPLUG_NO_FILESAVE
2019-01-23 23:16:37 -03:00
bool CSoundFile : : SaveXIInstrument ( INSTRUMENTINDEX nInstr , std : : ostream & f ) const
2018-02-19 01:25:43 -03:00
{
ModInstrument * pIns = Instruments [ nInstr ] ;
2019-01-23 23:16:37 -03:00
if ( pIns = = nullptr )
2018-02-19 01:25:43 -03:00
{
return false ;
}
// Create file header
XIInstrumentHeader header ;
header . ConvertToXM ( * pIns , false ) ;
const std : : vector < SAMPLEINDEX > samples = header . instrument . GetSampleList ( * pIns , false ) ;
if ( samples . size ( ) > 0 & & samples [ 0 ] < = GetNumSamples ( ) )
{
// Copy over auto-vibrato settings of first sample
header . instrument . ApplyAutoVibratoToXM ( Samples [ samples [ 0 ] ] , GetType ( ) ) ;
}
2019-01-23 23:16:37 -03:00
mpt : : IO : : Write ( f , header ) ;
2018-02-19 01:25:43 -03:00
std : : vector < SampleIO > sampleFlags ( samples . size ( ) ) ;
// XI Sample Headers
for ( SAMPLEINDEX i = 0 ; i < samples . size ( ) ; i + + )
{
XMSample xmSample ;
if ( samples [ i ] < = GetNumSamples ( ) )
{
xmSample . ConvertToXM ( Samples [ samples [ i ] ] , GetType ( ) , false ) ;
} else
{
MemsetZero ( xmSample ) ;
}
sampleFlags [ i ] = xmSample . GetSampleFormat ( ) ;
2020-09-22 01:54:24 -03:00
mpt : : String : : WriteBuf ( mpt : : String : : spacePadded , xmSample . name ) = m_szNames [ samples [ i ] ] ;
2018-02-19 01:25:43 -03:00
2019-01-23 23:16:37 -03:00
mpt : : IO : : Write ( f , xmSample ) ;
2018-02-19 01:25:43 -03:00
}
// XI Sample Data
for ( SAMPLEINDEX i = 0 ; i < samples . size ( ) ; i + + )
{
if ( samples [ i ] < = GetNumSamples ( ) )
{
sampleFlags [ i ] . WriteSample ( f , Samples [ samples [ i ] ] ) ;
}
}
// Write 'MPTX' extension tag
2019-01-23 23:16:37 -03:00
mpt : : IO : : WriteText ( f , " XTPM " ) ;
2018-02-19 01:25:43 -03:00
WriteInstrumentHeaderStructOrField ( pIns , f ) ; // Write full extended header.
return true ;
}
# endif // MODPLUG_NO_FILESAVE
// Read first sample from XI file into a sample slot
bool CSoundFile : : ReadXISample ( SAMPLEINDEX nSample , FileReader & file )
{
file . Rewind ( ) ;
XIInstrumentHeader fileHeader ;
if ( ! file . ReadStruct ( fileHeader )
| | ! file . CanRead ( sizeof ( XMSample ) )
| | memcmp ( fileHeader . signature , " Extended Instrument: " , 21 )
| | fileHeader . version ! = XIInstrumentHeader : : fileVersion
| | fileHeader . eof ! = 0x1A
| | fileHeader . numSamples = = 0 )
{
return false ;
}
if ( m_nSamples < nSample )
{
m_nSamples = nSample ;
}
uint16 numSamples = fileHeader . numSamples ;
FileReader : : off_t samplePos = sizeof ( XIInstrumentHeader ) + numSamples * sizeof ( XMSample ) ;
// Preferrably read the middle-C sample
auto sample = fileHeader . instrument . sampleMap [ 48 ] ;
if ( sample > = fileHeader . numSamples )
sample = 0 ;
XMSample sampleHeader ;
while ( sample - - )
{
file . ReadStruct ( sampleHeader ) ;
samplePos + = sampleHeader . length ;
}
file . ReadStruct ( sampleHeader ) ;
// Gotta skip 'em all!
file . Seek ( samplePos ) ;
DestroySampleThreadsafe ( nSample ) ;
ModSample & mptSample = Samples [ nSample ] ;
sampleHeader . ConvertToMPT ( mptSample ) ;
if ( GetType ( ) ! = MOD_TYPE_XM )
{
// No need to pan that single sample, thank you...
mptSample . uFlags . reset ( CHN_PANNING ) ;
}
fileHeader . instrument . ApplyAutoVibratoToMPT ( mptSample ) ;
mptSample . Convert ( MOD_TYPE_XM , GetType ( ) ) ;
2020-09-22 01:54:24 -03:00
mptSample . filename = mpt : : String : : ReadBuf ( mpt : : String : : spacePadded , sampleHeader . name ) ;
m_szNames [ nSample ] = mpt : : String : : ReadBuf ( mpt : : String : : spacePadded , sampleHeader . name ) ;
2018-02-19 01:25:43 -03:00
// Read sample data
sampleHeader . GetSampleFormat ( ) . ReadSample ( Samples [ nSample ] , file ) ;
Samples [ nSample ] . PrecomputeLoops ( * this , false ) ;
return true ;
}
2019-01-23 23:16:37 -03:00
///////////////
// Apple CAF //
struct CAFFileHeader
{
uint32be mFileType ;
uint16be mFileVersion ;
uint16be mFileFlags ;
} ;
MPT_BINARY_STRUCT ( CAFFileHeader , 8 )
struct CAFChunkHeader
{
uint32be mChunkType ;
int64be mChunkSize ;
} ;
MPT_BINARY_STRUCT ( CAFChunkHeader , 12 )
struct CAFChunk
{
enum ChunkIdentifiers
{
iddesc = MagicBE ( " desc " ) ,
iddata = MagicBE ( " data " ) ,
idstrg = MagicBE ( " strg " ) ,
idinfo = MagicBE ( " info " )
} ;
CAFChunkHeader header ;
FileReader : : off_t GetLength ( ) const
{
int64 length = header . mChunkSize ;
if ( length = = - 1 )
{
length = std : : numeric_limits < int64 > : : max ( ) ; // spec
}
if ( length < 0 )
{
length = std : : numeric_limits < int64 > : : max ( ) ; // heuristic
}
return mpt : : saturate_cast < FileReader : : off_t > ( length ) ;
}
ChunkIdentifiers GetID ( ) const
{
return static_cast < ChunkIdentifiers > ( header . mChunkType . get ( ) ) ;
}
} ;
MPT_BINARY_STRUCT ( CAFChunk , 12 )
enum {
CAFkAudioFormatLinearPCM = MagicBE ( " lpcm " ) ,
CAFkAudioFormatAppleIMA4 = MagicBE ( " ima4 " ) ,
CAFkAudioFormatMPEG4AAC = MagicBE ( " aac " ) ,
CAFkAudioFormatMACE3 = MagicBE ( " MAC3 " ) ,
CAFkAudioFormatMACE6 = MagicBE ( " MAC6 " ) ,
CAFkAudioFormatULaw = MagicBE ( " ulaw " ) ,
CAFkAudioFormatALaw = MagicBE ( " alaw " ) ,
CAFkAudioFormatMPEGLayer1 = MagicBE ( " .mp1 " ) ,
CAFkAudioFormatMPEGLayer2 = MagicBE ( " .mp2 " ) ,
CAFkAudioFormatMPEGLayer3 = MagicBE ( " .mp3 " ) ,
CAFkAudioFormatAppleLossless = MagicBE ( " alac " )
} ;
enum {
CAFkCAFLinearPCMFormatFlagIsFloat = ( 1L < < 0 ) ,
CAFkCAFLinearPCMFormatFlagIsLittleEndian = ( 1L < < 1 )
} ;
struct CAFAudioFormat
{
float64be mSampleRate ;
uint32be mFormatID ;
uint32be mFormatFlags ;
uint32be mBytesPerPacket ;
uint32be mFramesPerPacket ;
uint32be mChannelsPerFrame ;
uint32be mBitsPerChannel ;
} ;
MPT_BINARY_STRUCT ( CAFAudioFormat , 32 )
static void CAFSetTagFromInfoKey ( mpt : : ustring & dst , const std : : map < std : : string , std : : string > & infoMap , const std : : string & key )
{
auto item = infoMap . find ( key ) ;
if ( item = = infoMap . end ( ) )
{
return ;
}
if ( item - > second . empty ( ) )
{
return ;
}
2020-09-22 01:54:24 -03:00
dst = mpt : : ToUnicode ( mpt : : Charset : : UTF8 , item - > second ) ;
2019-01-23 23:16:37 -03:00
}
bool CSoundFile : : ReadCAFSample ( SAMPLEINDEX nSample , FileReader & file , bool mayNormalize )
{
file . Rewind ( ) ;
CAFFileHeader fileHeader ;
if ( ! file . ReadStruct ( fileHeader ) )
{
return false ;
}
if ( fileHeader . mFileType ! = MagicBE ( " caff " ) )
{
return false ;
}
if ( fileHeader . mFileVersion ! = 1 )
{
return false ;
}
ChunkReader chunkFile = file ;
auto chunkList = chunkFile . ReadChunks < CAFChunk > ( 0 ) ;
CAFAudioFormat audioFormat ;
if ( ! chunkList . GetChunk ( CAFChunk : : iddesc ) . ReadStruct ( audioFormat ) )
{
return false ;
}
if ( audioFormat . mSampleRate < = 0.0 )
{
return false ;
}
if ( audioFormat . mChannelsPerFrame = = 0 )
{
return false ;
}
if ( audioFormat . mChannelsPerFrame > 2 )
{
return false ;
}
if ( ! Util : : TypeCanHoldValue < uint32 > ( mpt : : saturate_round < int64 > ( audioFormat . mSampleRate ) ) )
{
return false ;
}
uint32 sampleRate = static_cast < uint32 > ( mpt : : saturate_round < int64 > ( audioFormat . mSampleRate ) ) ;
if ( sampleRate < = 0 )
{
return false ;
}
SampleIO sampleIO ;
if ( audioFormat . mFormatID = = CAFkAudioFormatLinearPCM )
{
if ( audioFormat . mFramesPerPacket ! = 1 )
{
return false ;
}
if ( audioFormat . mBytesPerPacket = = 0 )
{
return false ;
}
if ( audioFormat . mBitsPerChannel = = 0 )
{
return false ;
}
if ( audioFormat . mFormatFlags & CAFkCAFLinearPCMFormatFlagIsFloat )
{
if ( audioFormat . mBitsPerChannel ! = 32 & & audioFormat . mBitsPerChannel ! = 64 )
{
return false ;
}
if ( audioFormat . mBytesPerPacket ! = audioFormat . mChannelsPerFrame * audioFormat . mBitsPerChannel / 8 )
{
return false ;
}
}
if ( audioFormat . mBytesPerPacket % audioFormat . mChannelsPerFrame ! = 0 )
{
return false ;
}
if ( audioFormat . mBytesPerPacket / audioFormat . mChannelsPerFrame ! = 1
& & audioFormat . mBytesPerPacket / audioFormat . mChannelsPerFrame ! = 2
& & audioFormat . mBytesPerPacket / audioFormat . mChannelsPerFrame ! = 3
& & audioFormat . mBytesPerPacket / audioFormat . mChannelsPerFrame ! = 4
& & audioFormat . mBytesPerPacket / audioFormat . mChannelsPerFrame ! = 8
)
{
return false ;
}
SampleIO : : Channels channels = ( audioFormat . mChannelsPerFrame = = 2 ) ? SampleIO : : stereoInterleaved : SampleIO : : mono ;
SampleIO : : Endianness endianness = ( audioFormat . mFormatFlags & CAFkCAFLinearPCMFormatFlagIsLittleEndian ) ? SampleIO : : littleEndian : SampleIO : : bigEndian ;
SampleIO : : Encoding encoding = ( audioFormat . mFormatFlags & CAFkCAFLinearPCMFormatFlagIsFloat ) ? SampleIO : : floatPCM : SampleIO : : signedPCM ;
SampleIO : : Bitdepth bitdepth = static_cast < SampleIO : : Bitdepth > ( ( audioFormat . mBytesPerPacket / audioFormat . mChannelsPerFrame ) * 8 ) ;
sampleIO = SampleIO ( bitdepth , channels , endianness , encoding ) ;
} else
{
return false ;
}
if ( mayNormalize )
{
sampleIO . MayNormalize ( ) ;
}
/*
std : : map < uint32 , std : : string > stringMap ; // UTF-8
if ( chunkList . ChunkExists ( CAFChunk : : idstrg ) )
{
FileReader stringsChunk = chunkList . GetChunk ( CAFChunk : : idstrg ) ;
uint32 numEntries = stringsChunk . ReadUint32BE ( ) ;
if ( stringsChunk . Skip ( 12 * numEntries ) )
{
FileReader stringData = stringsChunk . ReadChunk ( stringsChunk . BytesLeft ( ) ) ;
stringsChunk . Seek ( 4 ) ;
for ( uint32 entry = 0 ; entry < numEntries & & stringsChunk . CanRead ( 12 ) ; entry + + )
{
uint32 stringID = stringsChunk . ReadUint32BE ( ) ;
int64 offset = stringsChunk . ReadIntBE < int64 > ( ) ;
if ( offset > = 0 & & Util : : TypeCanHoldValue < FileReader : : off_t > ( offset ) )
{
stringData . Seek ( mpt : : saturate_cast < FileReader : : off_t > ( offset ) ) ;
std : : string str ;
if ( stringData . ReadNullString ( str ) )
{
stringMap [ stringID ] = str ;
}
}
}
}
}
*/
std : : map < std : : string , std : : string > infoMap ; // UTF-8
if ( chunkList . ChunkExists ( CAFChunk : : idinfo ) )
{
FileReader informationChunk = chunkList . GetChunk ( CAFChunk : : idinfo ) ;
uint32 numEntries = informationChunk . ReadUint32BE ( ) ;
for ( uint32 entry = 0 ; entry < numEntries & & informationChunk . CanRead ( 2 ) ; entry + + )
{
std : : string key ;
std : : string value ;
if ( ! informationChunk . ReadNullString ( key ) )
{
break ;
}
if ( ! informationChunk . ReadNullString ( value ) )
{
break ;
}
if ( ! key . empty ( ) & & ! value . empty ( ) )
{
infoMap [ key ] = value ;
}
}
}
FileTags tags ;
CAFSetTagFromInfoKey ( tags . bpm , infoMap , " tempo " ) ;
//CAFSetTagFromInfoKey(void, infoMap, "key signature");
//CAFSetTagFromInfoKey(void, infoMap, "time signature");
CAFSetTagFromInfoKey ( tags . artist , infoMap , " artist " ) ;
CAFSetTagFromInfoKey ( tags . album , infoMap , " album " ) ;
CAFSetTagFromInfoKey ( tags . trackno , infoMap , " track number " ) ;
CAFSetTagFromInfoKey ( tags . year , infoMap , " year " ) ;
//CAFSetTagFromInfoKey(void, infoMap, "composer");
//CAFSetTagFromInfoKey(void, infoMap, "lyricist");
CAFSetTagFromInfoKey ( tags . genre , infoMap , " genre " ) ;
CAFSetTagFromInfoKey ( tags . title , infoMap , " title " ) ;
//CAFSetTagFromInfoKey(void, infoMap, "recorded date");
CAFSetTagFromInfoKey ( tags . comments , infoMap , " comments " ) ;
//CAFSetTagFromInfoKey(void, infoMap, "copyright");
//CAFSetTagFromInfoKey(void, infoMap, "source encoder");
CAFSetTagFromInfoKey ( tags . encoder , infoMap , " encoding application " ) ;
//CAFSetTagFromInfoKey(void, infoMap, "nominal bit rate");
//CAFSetTagFromInfoKey(void, infoMap, "channel layout");
//CAFSetTagFromInfoKey(tags.url, infoMap, void);
if ( ! chunkList . ChunkExists ( CAFChunk : : iddata ) )
{
return false ;
}
FileReader dataChunk = chunkList . GetChunk ( CAFChunk : : iddata ) ;
dataChunk . Skip ( 4 ) ; // edit count
FileReader audioData = dataChunk . ReadChunk ( dataChunk . BytesLeft ( ) ) ;
SmpLength length = mpt : : saturate_cast < SmpLength > ( ( audioData . GetLength ( ) / audioFormat . mBytesPerPacket ) * audioFormat . mFramesPerPacket ) ;
ModSample & mptSample = Samples [ nSample ] ;
DestroySampleThreadsafe ( nSample ) ;
mptSample . Initialize ( ) ;
mptSample . nLength = length ;
mptSample . nC5Speed = sampleRate ;
sampleIO . ReadSample ( mptSample , audioData ) ;
2020-09-22 01:54:24 -03:00
m_szNames [ nSample ] = mpt : : ToCharset ( GetCharsetInternal ( ) , GetSampleNameFromTags ( tags ) ) ;
2019-01-23 23:16:37 -03:00
mptSample . Convert ( MOD_TYPE_IT , GetType ( ) ) ;
mptSample . PrecomputeLoops ( * this , false ) ;
return true ;
}
2018-02-19 01:25:43 -03:00
/////////////////////////////////////////////////////////////////////////////////////////
// AIFF File I/O
// AIFF header
struct AIFFHeader
{
char magic [ 4 ] ; // FORM
uint32be length ; // Size of the file, not including magic and length
char type [ 4 ] ; // AIFF or AIFC
} ;
MPT_BINARY_STRUCT ( AIFFHeader , 12 )
// General IFF Chunk header
struct AIFFChunk
{
// 32-Bit chunk identifiers
enum ChunkIdentifiers
{
2019-01-23 23:16:37 -03:00
idCOMM = MagicBE ( " COMM " ) ,
idSSND = MagicBE ( " SSND " ) ,
idINST = MagicBE ( " INST " ) ,
idMARK = MagicBE ( " MARK " ) ,
idNAME = MagicBE ( " NAME " ) ,
2018-02-19 01:25:43 -03:00
} ;
uint32be id ; // See ChunkIdentifiers
uint32be length ; // Chunk size without header
size_t GetLength ( ) const
{
return length ;
}
ChunkIdentifiers GetID ( ) const
{
return static_cast < ChunkIdentifiers > ( id . get ( ) ) ;
}
} ;
MPT_BINARY_STRUCT ( AIFFChunk , 8 )
// "Common" chunk (in AIFC, a compression ID and compression name follows this header, but apart from that it's identical)
struct AIFFCommonChunk
{
uint16be numChannels ;
uint32be numSampleFrames ;
uint16be sampleSize ;
uint8be sampleRate [ 10 ] ; // Sample rate in 80-Bit floating point
// Convert sample rate to integer
uint32 GetSampleRate ( ) const
{
uint32 mantissa = ( sampleRate [ 2 ] < < 24 ) | ( sampleRate [ 3 ] < < 16 ) | ( sampleRate [ 4 ] < < 8 ) | ( sampleRate [ 5 ] < < 0 ) ;
uint32 last = 0 ;
uint8 exp = 30 - sampleRate [ 1 ] ;
while ( exp - - )
{
last = mantissa ;
mantissa > > = 1 ;
}
if ( last & 1 ) mantissa + + ;
return mantissa ;
}
} ;
MPT_BINARY_STRUCT ( AIFFCommonChunk , 18 )
// Sound chunk
struct AIFFSoundChunk
{
uint32be offset ;
uint32be blockSize ;
} ;
MPT_BINARY_STRUCT ( AIFFSoundChunk , 8 )
// Marker
struct AIFFMarker
{
uint16be id ;
uint32be position ; // Position in sample
uint8be nameLength ; // Not counting eventually existing padding byte in name string
} ;
MPT_BINARY_STRUCT ( AIFFMarker , 7 )
// Instrument loop
struct AIFFInstrumentLoop
{
enum PlayModes
{
noLoop = 0 ,
loopNormal = 1 ,
loopBidi = 2 ,
} ;
uint16be playMode ;
uint16be beginLoop ; // Marker index
uint16be endLoop ; // Marker index
} ;
MPT_BINARY_STRUCT ( AIFFInstrumentLoop , 6 )
struct AIFFInstrumentChunk
{
uint8be baseNote ;
uint8be detune ;
uint8be lowNote ;
uint8be highNote ;
uint8be lowVelocity ;
uint8be highVelocity ;
uint16be gain ;
AIFFInstrumentLoop sustainLoop ;
AIFFInstrumentLoop releaseLoop ;
} ;
MPT_BINARY_STRUCT ( AIFFInstrumentChunk , 20 )
bool CSoundFile : : ReadAIFFSample ( SAMPLEINDEX nSample , FileReader & file , bool mayNormalize )
{
file . Rewind ( ) ;
ChunkReader chunkFile ( file ) ;
// Verify header
AIFFHeader fileHeader ;
if ( ! chunkFile . ReadStruct ( fileHeader )
| | memcmp ( fileHeader . magic , " FORM " , 4 )
| | ( memcmp ( fileHeader . type , " AIFF " , 4 ) & & memcmp ( fileHeader . type , " AIFC " , 4 ) ) )
{
return false ;
}
auto chunks = chunkFile . ReadChunks < AIFFChunk > ( 2 ) ;
// Read COMM chunk
FileReader commChunk ( chunks . GetChunk ( AIFFChunk : : idCOMM ) ) ;
AIFFCommonChunk sampleInfo ;
if ( ! commChunk . ReadStruct ( sampleInfo ) )
{
return false ;
}
// Is this a proper sample?
if ( sampleInfo . numSampleFrames = = 0
| | sampleInfo . numChannels < 1 | | sampleInfo . numChannels > 2
| | sampleInfo . sampleSize < 1 | | sampleInfo . sampleSize > 64 )
{
return false ;
}
// Read compression type in AIFF-C files.
uint8 compression [ 4 ] = { ' N ' , ' O ' , ' N ' , ' E ' } ;
SampleIO : : Endianness endian = SampleIO : : bigEndian ;
if ( ! memcmp ( fileHeader . type , " AIFC " , 4 ) )
{
if ( ! commChunk . ReadArray ( compression ) )
{
return false ;
}
if ( ! memcmp ( compression , " twos " , 4 ) )
{
endian = SampleIO : : littleEndian ;
}
}
// Read SSND chunk
FileReader soundChunk ( chunks . GetChunk ( AIFFChunk : : idSSND ) ) ;
AIFFSoundChunk sampleHeader ;
2021-07-07 17:35:06 -04:00
if ( ! soundChunk . ReadStruct ( sampleHeader ) )
2018-02-19 01:25:43 -03:00
{
return false ;
}
SampleIO : : Bitdepth bitDepth ;
switch ( ( sampleInfo . sampleSize - 1 ) / 8 )
{
default :
case 0 : bitDepth = SampleIO : : _8bit ; break ;
case 1 : bitDepth = SampleIO : : _16bit ; break ;
case 2 : bitDepth = SampleIO : : _24bit ; break ;
case 3 : bitDepth = SampleIO : : _32bit ; break ;
case 7 : bitDepth = SampleIO : : _64bit ; break ;
}
SampleIO sampleIO ( bitDepth ,
( sampleInfo . numChannels = = 2 ) ? SampleIO : : stereoInterleaved : SampleIO : : mono ,
endian ,
SampleIO : : signedPCM ) ;
2021-07-07 17:35:06 -04:00
if ( ! memcmp ( compression , " fl32 " , 4 ) | | ! memcmp ( compression , " FL32 " , 4 ) | | ! memcmp ( compression , " fl64 " , 4 ) | | ! memcmp ( compression , " FL64 " , 4 ) )
2018-02-19 01:25:43 -03:00
{
sampleIO | = SampleIO : : floatPCM ;
} else if ( ! memcmp ( compression , " alaw " , 4 ) | | ! memcmp ( compression , " ALAW " , 4 ) )
{
sampleIO | = SampleIO : : aLaw ;
sampleIO | = SampleIO : : _16bit ;
} else if ( ! memcmp ( compression , " ulaw " , 4 ) | | ! memcmp ( compression , " ULAW " , 4 ) )
{
sampleIO | = SampleIO : : uLaw ;
sampleIO | = SampleIO : : _16bit ;
2021-07-07 17:35:06 -04:00
} else if ( ! memcmp ( compression , " raw " , 4 ) )
{
sampleIO | = SampleIO : : unsignedPCM ;
2018-02-19 01:25:43 -03:00
}
if ( mayNormalize )
{
sampleIO . MayNormalize ( ) ;
}
2021-07-07 17:35:06 -04:00
if ( soundChunk . CanRead ( sampleHeader . offset ) )
{
soundChunk . Skip ( sampleHeader . offset ) ;
}
2018-02-19 01:25:43 -03:00
ModSample & mptSample = Samples [ nSample ] ;
DestroySampleThreadsafe ( nSample ) ;
mptSample . Initialize ( ) ;
mptSample . nLength = sampleInfo . numSampleFrames ;
mptSample . nC5Speed = sampleInfo . GetSampleRate ( ) ;
sampleIO . ReadSample ( mptSample , soundChunk ) ;
// Read MARK and INST chunk to extract sample loops
FileReader markerChunk ( chunks . GetChunk ( AIFFChunk : : idMARK ) ) ;
AIFFInstrumentChunk instrHeader ;
if ( markerChunk . IsValid ( ) & & chunks . GetChunk ( AIFFChunk : : idINST ) . ReadStruct ( instrHeader ) )
{
uint16 numMarkers = markerChunk . ReadUint16BE ( ) ;
std : : vector < AIFFMarker > markers ;
markers . reserve ( numMarkers ) ;
for ( size_t i = 0 ; i < numMarkers ; i + + )
{
AIFFMarker marker ;
if ( ! markerChunk . ReadStruct ( marker ) )
{
break ;
}
markers . push_back ( marker ) ;
markerChunk . Skip ( marker . nameLength + ( ( marker . nameLength % 2u ) = = 0 ? 1 : 0 ) ) ;
}
if ( instrHeader . sustainLoop . playMode ! = AIFFInstrumentLoop : : noLoop )
{
mptSample . uFlags . set ( CHN_SUSTAINLOOP ) ;
mptSample . uFlags . set ( CHN_PINGPONGSUSTAIN , instrHeader . sustainLoop . playMode = = AIFFInstrumentLoop : : loopBidi ) ;
}
if ( instrHeader . releaseLoop . playMode ! = AIFFInstrumentLoop : : noLoop )
{
mptSample . uFlags . set ( CHN_LOOP ) ;
mptSample . uFlags . set ( CHN_PINGPONGLOOP , instrHeader . releaseLoop . playMode = = AIFFInstrumentLoop : : loopBidi ) ;
}
// Read markers
for ( const auto & m : markers )
{
if ( m . id = = instrHeader . sustainLoop . beginLoop )
mptSample . nSustainStart = m . position ;
if ( m . id = = instrHeader . sustainLoop . endLoop )
mptSample . nSustainEnd = m . position ;
if ( m . id = = instrHeader . releaseLoop . beginLoop )
mptSample . nLoopStart = m . position ;
if ( m . id = = instrHeader . releaseLoop . endLoop )
mptSample . nLoopEnd = m . position ;
}
mptSample . SanitizeLoops ( ) ;
}
// Extract sample name
FileReader nameChunk ( chunks . GetChunk ( AIFFChunk : : idNAME ) ) ;
if ( nameChunk . IsValid ( ) )
{
nameChunk . ReadString < mpt : : String : : spacePadded > ( m_szNames [ nSample ] , nameChunk . GetLength ( ) ) ;
} else
{
2020-09-22 01:54:24 -03:00
m_szNames [ nSample ] = " " ;
2018-02-19 01:25:43 -03:00
}
mptSample . Convert ( MOD_TYPE_IT , GetType ( ) ) ;
mptSample . PrecomputeLoops ( * this , false ) ;
return true ;
}
2020-09-22 01:54:24 -03:00
static bool AUIsAnnotationLineWithField ( const std : : string & line )
2019-01-23 23:16:37 -03:00
{
std : : size_t pos = line . find ( ' = ' ) ;
if ( pos = = std : : string : : npos )
{
return false ;
}
if ( pos = = 0 )
{
return false ;
}
2020-09-22 01:54:24 -03:00
const auto field = std : : string_view ( line ) . substr ( 0 , pos ) ;
// Scan for invalid chars
2019-01-23 23:16:37 -03:00
for ( auto c : field )
{
if ( ! IsInRange ( c , ' a ' , ' z ' ) & & ! IsInRange ( c , ' A ' , ' Z ' ) & & ! IsInRange ( c , ' 0 ' , ' 9 ' ) & & c ! = ' - ' & & c ! = ' _ ' )
{
2020-09-22 01:54:24 -03:00
return false ;
2019-01-23 23:16:37 -03:00
}
}
return true ;
}
2020-09-22 01:54:24 -03:00
static std : : string AUTrimFieldFromAnnotationLine ( const std : : string & line )
2019-01-23 23:16:37 -03:00
{
if ( ! AUIsAnnotationLineWithField ( line ) )
{
return line ;
}
std : : size_t pos = line . find ( ' = ' ) ;
return line . substr ( pos + 1 ) ;
}
2020-09-22 01:54:24 -03:00
static std : : string AUGetAnnotationFieldFromLine ( const std : : string & line )
2019-01-23 23:16:37 -03:00
{
if ( ! AUIsAnnotationLineWithField ( line ) )
{
return std : : string ( ) ;
}
std : : size_t pos = line . find ( ' = ' ) ;
return line . substr ( 0 , pos ) ;
}
2018-02-19 01:25:43 -03:00
bool CSoundFile : : ReadAUSample ( SAMPLEINDEX nSample , FileReader & file , bool mayNormalize )
{
file . Rewind ( ) ;
// Verify header
2020-09-22 01:54:24 -03:00
const auto magic = file . ReadArray < char , 4 > ( ) ;
const bool bigEndian = ! std : : memcmp ( magic . data ( ) , " .snd " , 4 ) ;
const bool littleEndian = ! std : : memcmp ( magic . data ( ) , " dns. " , 4 ) ;
if ( ! bigEndian & & ! littleEndian )
2018-02-19 01:25:43 -03:00
return false ;
2020-09-22 01:54:24 -03:00
auto readUint32 = std : : bind ( bigEndian ? & FileReader : : ReadUint32BE : & FileReader : : ReadUint32LE , file ) ;
uint32 dataOffset = readUint32 ( ) ; // must be divisible by 8 according to spec, however, there are files that ignore this requirement
uint32 dataSize = readUint32 ( ) ;
uint32 encoding = readUint32 ( ) ;
uint32 sampleRate = readUint32 ( ) ;
uint32 channels = readUint32 ( ) ;
2018-02-19 01:25:43 -03:00
2019-01-23 23:16:37 -03:00
// According to spec, a minimum 8 byte annotation field after the header fields is required,
// however, there are files in the wild that violate this requirement.
// Thus, check for 24 instead of 32 here.
if ( dataOffset < 24 ) // data offset points inside header
{
return false ;
}
2018-02-19 01:25:43 -03:00
if ( channels < 1 | | channels > 2 )
return false ;
2020-09-22 01:54:24 -03:00
SampleIO sampleIO ( SampleIO : : _8bit , channels = = 1 ? SampleIO : : mono : SampleIO : : stereoInterleaved , bigEndian ? SampleIO : : bigEndian : SampleIO : : littleEndian , SampleIO : : signedPCM ) ;
2018-02-19 01:25:43 -03:00
switch ( encoding )
{
2020-09-22 01:54:24 -03:00
case 1 : sampleIO | = SampleIO : : _16bit ; // u-law
2018-02-19 01:25:43 -03:00
sampleIO | = SampleIO : : uLaw ; break ;
2020-09-22 01:54:24 -03:00
case 2 : break ; // 8-bit linear PCM
case 3 : sampleIO | = SampleIO : : _16bit ; break ; // 16-bit linear PCM
case 4 : sampleIO | = SampleIO : : _24bit ; break ; // 24-bit linear PCM
case 5 : sampleIO | = SampleIO : : _32bit ; break ; // 32-bit linear PCM
case 6 : sampleIO | = SampleIO : : _32bit ; // 32-bit IEEE floating point
2018-02-19 01:25:43 -03:00
sampleIO | = SampleIO : : floatPCM ;
break ;
2020-09-22 01:54:24 -03:00
case 7 : sampleIO | = SampleIO : : _64bit ; // 64-bit IEEE floating point
2018-02-19 01:25:43 -03:00
sampleIO | = SampleIO : : floatPCM ;
break ;
2020-09-22 01:54:24 -03:00
case 27 : sampleIO | = SampleIO : : _16bit ; // a-law
2018-02-19 01:25:43 -03:00
sampleIO | = SampleIO : : aLaw ; break ;
default : return false ;
}
2019-01-23 23:16:37 -03:00
if ( ! file . LengthIsAtLeast ( dataOffset ) )
{
2018-02-19 01:25:43 -03:00
return false ;
2019-01-23 23:16:37 -03:00
}
FileTags tags ;
// This reads annotation metadata as written by OpenMPT, sox, ffmpeg.
// Additionally, we fall back to just reading the whole field as a single comment.
2020-09-22 01:54:24 -03:00
// We only read up to the first \0 byte.
2019-01-23 23:16:37 -03:00
file . Seek ( 24 ) ;
2020-09-22 01:54:24 -03:00
std : : string annotation ;
file . ReadString < mpt : : String : : maybeNullTerminated > ( annotation , dataOffset - 24 ) ;
2019-01-23 23:16:37 -03:00
annotation = mpt : : String : : Replace ( annotation , " \r \n " , " \n " ) ;
annotation = mpt : : String : : Replace ( annotation , " \r " , " \n " ) ;
2020-09-22 01:54:24 -03:00
mpt : : Charset charset = mpt : : IsUTF8 ( annotation ) ? mpt : : Charset : : UTF8 : mpt : : Charset : : ISO8859_1 ;
const auto lines = mpt : : String : : Split < std : : string > ( annotation , " \n " ) ;
bool hasFields = false ;
for ( const auto & line : lines )
2019-01-23 23:16:37 -03:00
{
if ( AUIsAnnotationLineWithField ( line ) )
{
2020-09-22 01:54:24 -03:00
hasFields = true ;
break ;
2019-01-23 23:16:37 -03:00
}
}
2020-09-22 01:54:24 -03:00
if ( hasFields )
2019-01-23 23:16:37 -03:00
{
2020-09-22 01:54:24 -03:00
std : : map < std : : string , std : : vector < std : : string > > linesPerField ;
std : : string lastField = " comment " ;
for ( const auto & line : lines )
2019-01-23 23:16:37 -03:00
{
if ( AUIsAnnotationLineWithField ( line ) )
{
2020-09-22 01:54:24 -03:00
lastField = mpt : : ToLowerCaseAscii ( mpt : : String : : Trim ( AUGetAnnotationFieldFromLine ( line ) ) ) ;
2019-01-23 23:16:37 -03:00
}
2020-09-22 01:54:24 -03:00
linesPerField [ lastField ] . push_back ( AUTrimFieldFromAnnotationLine ( line ) ) ;
}
tags . title = mpt : : ToUnicode ( charset , mpt : : String : : Combine ( linesPerField [ " title " ] , std : : string ( " \n " ) ) ) ;
tags . artist = mpt : : ToUnicode ( charset , mpt : : String : : Combine ( linesPerField [ " artist " ] , std : : string ( " \n " ) ) ) ;
tags . album = mpt : : ToUnicode ( charset , mpt : : String : : Combine ( linesPerField [ " album " ] , std : : string ( " \n " ) ) ) ;
tags . trackno = mpt : : ToUnicode ( charset , mpt : : String : : Combine ( linesPerField [ " track " ] , std : : string ( " \n " ) ) ) ;
tags . genre = mpt : : ToUnicode ( charset , mpt : : String : : Combine ( linesPerField [ " genre " ] , std : : string ( " \n " ) ) ) ;
tags . comments = mpt : : ToUnicode ( charset , mpt : : String : : Combine ( linesPerField [ " comment " ] , std : : string ( " \n " ) ) ) ;
2019-01-23 23:16:37 -03:00
} else
{
// Most applications tend to write their own name here,
// thus there is little use in interpreting the string as a title.
annotation = mpt : : String : : RTrim ( annotation , std : : string ( " \r \n " ) ) ;
tags . comments = mpt : : ToUnicode ( charset , annotation ) ;
}
file . Seek ( dataOffset ) ;
2018-02-19 01:25:43 -03:00
ModSample & mptSample = Samples [ nSample ] ;
DestroySampleThreadsafe ( nSample ) ;
mptSample . Initialize ( ) ;
SmpLength length = mpt : : saturate_cast < SmpLength > ( file . BytesLeft ( ) ) ;
if ( dataSize ! = 0xFFFFFFFF )
LimitMax ( length , dataSize ) ;
mptSample . nLength = ( length * 8u ) / ( sampleIO . GetEncodedBitsPerSample ( ) * channels ) ;
mptSample . nC5Speed = sampleRate ;
2020-09-22 01:54:24 -03:00
m_szNames [ nSample ] = mpt : : ToCharset ( GetCharsetInternal ( ) , GetSampleNameFromTags ( tags ) ) ;
2018-02-19 01:25:43 -03:00
if ( mayNormalize )
{
sampleIO . MayNormalize ( ) ;
}
sampleIO . ReadSample ( mptSample , file ) ;
mptSample . Convert ( MOD_TYPE_IT , GetType ( ) ) ;
mptSample . PrecomputeLoops ( * this , false ) ;
return true ;
}
/////////////////////////////////////////////////////////////////////////////////////////
// ITS Samples
bool CSoundFile : : ReadITSSample ( SAMPLEINDEX nSample , FileReader & file , bool rewind )
{
if ( rewind )
{
file . Rewind ( ) ;
}
ITSample sampleHeader ;
if ( ! file . ReadStruct ( sampleHeader )
| | memcmp ( sampleHeader . id , " IMPS " , 4 ) )
{
return false ;
}
DestroySampleThreadsafe ( nSample ) ;
ModSample & sample = Samples [ nSample ] ;
file . Seek ( sampleHeader . ConvertToMPT ( sample ) ) ;
2020-09-22 01:54:24 -03:00
m_szNames [ nSample ] = mpt : : String : : ReadBuf ( mpt : : String : : spacePaddedNull , sampleHeader . name ) ;
2018-02-19 01:25:43 -03:00
2019-01-23 23:16:37 -03:00
if ( sample . uFlags [ CHN_ADLIB ] )
2018-02-19 01:25:43 -03:00
{
2019-01-23 23:16:37 -03:00
OPLPatch patch ;
file . ReadArray ( patch ) ;
sample . SetAdlib ( true , patch ) ;
InitOPL ( ) ;
if ( ! SupportsOPL ( ) )
{
AddToLog ( " OPL instruments are not supported by this format. " ) ;
}
} else if ( ! sample . uFlags [ SMP_KEEPONDISK ] )
{
sampleHeader . GetSampleFormat ( ) . ReadSample ( sample , file ) ;
2018-02-19 01:25:43 -03:00
} else
{
// External sample
size_t strLen ;
file . ReadVarInt ( strLen ) ;
# ifdef MPT_EXTERNAL_SAMPLES
std : : string filenameU8 ;
file . ReadString < mpt : : String : : maybeNullTerminated > ( filenameU8 , strLen ) ;
mpt : : PathString filename = mpt : : PathString : : FromUTF8 ( filenameU8 ) ;
if ( ! filename . empty ( ) )
{
if ( ! file . GetFileName ( ) . empty ( ) )
{
filename = filename . RelativePathToAbsolute ( file . GetFileName ( ) . GetPath ( ) ) ;
}
if ( ! LoadExternalSample ( nSample , filename ) )
{
2019-01-23 23:16:37 -03:00
AddToLog ( LogWarning , U_ ( " Unable to load sample: " ) + filename . ToUnicode ( ) ) ;
2018-02-19 01:25:43 -03:00
}
} else
{
sample . uFlags . reset ( SMP_KEEPONDISK ) ;
}
# else
file . Skip ( strLen ) ;
# endif // MPT_EXTERNAL_SAMPLES
}
sample . Convert ( MOD_TYPE_IT , GetType ( ) ) ;
sample . PrecomputeLoops ( * this , false ) ;
return true ;
}
bool CSoundFile : : ReadITISample ( SAMPLEINDEX nSample , FileReader & file )
{
ITInstrument instrumentHeader ;
file . Rewind ( ) ;
if ( ! file . ReadStruct ( instrumentHeader )
| | memcmp ( instrumentHeader . id , " IMPI " , 4 ) )
{
return false ;
}
file . Rewind ( ) ;
ModInstrument dummy ;
ITInstrToMPT ( file , dummy , instrumentHeader . trkvers ) ;
// Old SchismTracker versions set nos=0
const SAMPLEINDEX nsamples = std : : max ( static_cast < SAMPLEINDEX > ( instrumentHeader . nos ) , * std : : max_element ( std : : begin ( dummy . Keyboard ) , std : : end ( dummy . Keyboard ) ) ) ;
if ( ! nsamples )
return false ;
// Preferrably read the middle-C sample
auto sample = dummy . Keyboard [ NOTE_MIDDLEC - NOTE_MIN ] ;
if ( sample > 0 )
sample - - ;
else
sample = 0 ;
file . Seek ( file . GetPosition ( ) + sample * sizeof ( ITSample ) ) ;
return ReadITSSample ( nSample , file , false ) ;
}
bool CSoundFile : : ReadITIInstrument ( INSTRUMENTINDEX nInstr , FileReader & file )
{
ITInstrument instrumentHeader ;
SAMPLEINDEX smp = 0 ;
file . Rewind ( ) ;
if ( ! file . ReadStruct ( instrumentHeader )
| | memcmp ( instrumentHeader . id , " IMPI " , 4 ) )
{
return false ;
}
if ( nInstr > GetNumInstruments ( ) ) m_nInstruments = nInstr ;
ModInstrument * pIns = new ( std : : nothrow ) ModInstrument ( ) ;
if ( pIns = = nullptr )
{
return false ;
}
DestroyInstrument ( nInstr , deleteAssociatedSamples ) ;
Instruments [ nInstr ] = pIns ;
file . Rewind ( ) ;
ITInstrToMPT ( file , * pIns , instrumentHeader . trkvers ) ;
// Old SchismTracker versions set nos=0
const SAMPLEINDEX nsamples = std : : max ( static_cast < SAMPLEINDEX > ( instrumentHeader . nos ) , * std : : max_element ( std : : begin ( pIns - > Keyboard ) , std : : end ( pIns - > Keyboard ) ) ) ;
// In order to properly compute the position, in file, of eventual extended settings
// such as "attack" we need to keep the "real" size of the last sample as those extra
// setting will follow this sample in the file
FileReader : : off_t extraOffset = file . GetPosition ( ) ;
// Reading Samples
std : : vector < SAMPLEINDEX > samplemap ( nsamples , 0 ) ;
for ( SAMPLEINDEX i = 0 ; i < nsamples ; i + + )
{
smp = GetNextFreeSample ( nInstr , smp + 1 ) ;
if ( smp = = SAMPLEINDEX_INVALID ) break ;
samplemap [ i ] = smp ;
const FileReader : : off_t offset = file . GetPosition ( ) ;
if ( ! ReadITSSample ( smp , file , false ) )
smp - - ;
extraOffset = std : : max ( extraOffset , file . GetPosition ( ) ) ;
file . Seek ( offset + sizeof ( ITSample ) ) ;
}
if ( GetNumSamples ( ) < smp ) m_nSamples = smp ;
// Adjust sample assignment
for ( auto & sample : pIns - > Keyboard )
{
if ( sample > 0 & & sample < = nsamples )
{
sample = samplemap [ sample - 1 ] ;
}
}
if ( file . Seek ( extraOffset ) )
{
// Read MPT crap
ReadExtendedInstrumentProperties ( pIns , file ) ;
}
2019-01-23 23:16:37 -03:00
pIns - > Convert ( MOD_TYPE_IT , GetType ( ) ) ;
2018-02-19 01:25:43 -03:00
pIns - > Sanitize ( GetType ( ) ) ;
return true ;
}
# ifndef MODPLUG_NO_FILESAVE
2019-01-23 23:16:37 -03:00
bool CSoundFile : : SaveITIInstrument ( INSTRUMENTINDEX nInstr , std : : ostream & f , const mpt : : PathString & filename , bool compress , bool allowExternal ) const
2018-02-19 01:25:43 -03:00
{
ITInstrument iti ;
ModInstrument * pIns = Instruments [ nInstr ] ;
2019-01-23 23:16:37 -03:00
if ( ( ! pIns ) | | ( filename . empty ( ) & & allowExternal ) ) return false ;
2018-02-19 01:25:43 -03:00
auto instSize = iti . ConvertToIT ( * pIns , false , * this ) ;
// Create sample assignment table
std : : vector < SAMPLEINDEX > smptable ;
std : : vector < uint8 > smpmap ( GetNumSamples ( ) , 0 ) ;
for ( size_t i = 0 ; i < NOTE_MAX ; i + + )
{
const SAMPLEINDEX smp = pIns - > Keyboard [ i ] ;
if ( smp & & smp < = GetNumSamples ( ) )
{
if ( ! smpmap [ smp - 1 ] )
{
// We haven't considered this sample yet.
smptable . push_back ( smp ) ;
smpmap [ smp - 1 ] = static_cast < uint8 > ( smptable . size ( ) ) ;
}
iti . keyboard [ i * 2 + 1 ] = smpmap [ smp - 1 ] ;
} else
{
iti . keyboard [ i * 2 + 1 ] = 0 ;
}
}
iti . nos = static_cast < uint8 > ( smptable . size ( ) ) ;
smpmap . clear ( ) ;
uint32 filePos = instSize ;
mpt : : IO : : WritePartial ( f , iti , instSize ) ;
filePos + = mpt : : saturate_cast < uint32 > ( smptable . size ( ) * sizeof ( ITSample ) ) ;
// Writing sample headers + data
std : : vector < SampleIO > sampleFlags ;
for ( auto smp : smptable )
{
ITSample itss ;
itss . ConvertToIT ( Samples [ smp ] , GetType ( ) , compress , compress , allowExternal ) ;
const bool isExternal = itss . cvt = = ITSample : : cvtExternalSample ;
2020-09-22 01:54:24 -03:00
mpt : : String : : WriteBuf ( mpt : : String : : nullTerminated , itss . name ) = m_szNames [ smp ] ;
2018-02-19 01:25:43 -03:00
itss . samplepointer = filePos ;
mpt : : IO : : Write ( f , itss ) ;
// Write sample
auto curPos = mpt : : IO : : TellWrite ( f ) ;
mpt : : IO : : SeekAbsolute ( f , filePos ) ;
if ( ! isExternal )
{
filePos + = mpt : : saturate_cast < uint32 > ( itss . GetSampleFormat ( 0x0214 ) . WriteSample ( f , Samples [ smp ] ) ) ;
} else
{
# ifdef MPT_EXTERNAL_SAMPLES
const std : : string filenameU8 = GetSamplePath ( smp ) . AbsolutePathToRelative ( filename . GetPath ( ) ) . ToUTF8 ( ) ;
2019-01-23 23:16:37 -03:00
const size_t strSize = filenameU8 . size ( ) ;
2018-02-19 01:25:43 -03:00
size_t intBytes = 0 ;
if ( mpt : : IO : : WriteVarInt ( f , strSize , & intBytes ) )
{
2019-01-23 23:16:37 -03:00
filePos + = mpt : : saturate_cast < uint32 > ( intBytes + strSize ) ;
2018-02-19 01:25:43 -03:00
mpt : : IO : : WriteRaw ( f , filenameU8 . data ( ) , strSize ) ;
}
# endif // MPT_EXTERNAL_SAMPLES
}
mpt : : IO : : SeekAbsolute ( f , curPos ) ;
}
mpt : : IO : : SeekEnd ( f ) ;
// Write 'MPTX' extension tag
mpt : : IO : : WriteRaw ( f , " XTPM " , 4 ) ;
WriteInstrumentHeaderStructOrField ( pIns , f ) ; // Write full extended header.
return true ;
}
# endif // MODPLUG_NO_FILESAVE
///////////////////////////////////////////////////////////////////////////////////////////////////
// 8SVX / 16SVX Samples
// IFF File Header
struct IFFHeader
{
char form [ 4 ] ; // "FORM"
uint32be size ;
char magic [ 4 ] ; // "8SVX" or "16SV"
} ;
MPT_BINARY_STRUCT ( IFFHeader , 12 )
// General IFF Chunk header
struct IFFChunk
{
// 32-Bit chunk identifiers
enum ChunkIdentifiers
{
2019-01-23 23:16:37 -03:00
idVHDR = MagicBE ( " VHDR " ) ,
idBODY = MagicBE ( " BODY " ) ,
idNAME = MagicBE ( " NAME " ) ,
2021-03-14 18:55:49 -03:00
idCHAN = MagicBE ( " CHAN " ) ,
2018-02-19 01:25:43 -03:00
} ;
uint32be id ; // See ChunkIdentifiers
uint32be length ; // Chunk size without header
size_t GetLength ( ) const
{
if ( length = = 0 ) // Broken files
return std : : numeric_limits < size_t > : : max ( ) ;
return length ;
}
ChunkIdentifiers GetID ( ) const
{
return static_cast < ChunkIdentifiers > ( id . get ( ) ) ;
}
} ;
MPT_BINARY_STRUCT ( IFFChunk , 8 )
struct IFFSampleHeader
{
uint32be oneShotHiSamples ; // Samples in the high octave 1-shot part
uint32be repeatHiSamples ; // Samples in the high octave repeat part
uint32be samplesPerHiCycle ; // Samples/cycle in high octave, else 0
uint16be samplesPerSec ; // Data sampling rate
uint8be octave ; // Octaves of waveforms
uint8be compression ; // Data compression technique used
uint32be volume ;
} ;
MPT_BINARY_STRUCT ( IFFSampleHeader , 20 )
bool CSoundFile : : ReadIFFSample ( SAMPLEINDEX nSample , FileReader & file )
{
file . Rewind ( ) ;
IFFHeader fileHeader ;
if ( ! file . ReadStruct ( fileHeader )
| | memcmp ( fileHeader . form , " FORM " , 4 )
| | ( memcmp ( fileHeader . magic , " 8SVX " , 4 ) & & memcmp ( fileHeader . magic , " 16SV " , 4 ) ) )
{
return false ;
}
ChunkReader chunkFile ( file ) ;
ChunkReader : : ChunkList < IFFChunk > chunks = chunkFile . ReadChunks < IFFChunk > ( 2 ) ;
FileReader vhdrChunk = chunks . GetChunk ( IFFChunk : : idVHDR ) ;
FileReader bodyChunk = chunks . GetChunk ( IFFChunk : : idBODY ) ;
2021-03-14 18:55:49 -03:00
FileReader chanChunk = chunks . GetChunk ( IFFChunk : : idCHAN ) ;
2018-02-19 01:25:43 -03:00
IFFSampleHeader sampleHeader ;
if ( ! bodyChunk . IsValid ( )
| | ! vhdrChunk . IsValid ( )
| | ! vhdrChunk . ReadStruct ( sampleHeader ) )
{
return false ;
}
DestroySampleThreadsafe ( nSample ) ;
// Default values
const uint8 bytesPerSample = memcmp ( fileHeader . magic , " 8SVX " , 4 ) ? 2 : 1 ;
2021-03-14 18:55:49 -03:00
const uint8 channels = chanChunk . ReadUint32BE ( ) = = 6 ? 2 : 1 ;
2018-02-19 01:25:43 -03:00
ModSample & sample = Samples [ nSample ] ;
sample . Initialize ( ) ;
sample . nLoopStart = sampleHeader . oneShotHiSamples / bytesPerSample ;
sample . nLoopEnd = sample . nLoopStart + sampleHeader . repeatHiSamples / bytesPerSample ;
sample . nC5Speed = sampleHeader . samplesPerSec ;
sample . nVolume = static_cast < uint16 > ( sampleHeader . volume > > 8 ) ;
if ( ! sample . nVolume | | sample . nVolume > 256 ) sample . nVolume = 256 ;
if ( ! sample . nC5Speed ) sample . nC5Speed = 22050 ;
sample . Convert ( MOD_TYPE_IT , GetType ( ) ) ;
FileReader nameChunk = chunks . GetChunk ( IFFChunk : : idNAME ) ;
if ( nameChunk . IsValid ( ) )
{
nameChunk . ReadString < mpt : : String : : maybeNullTerminated > ( m_szNames [ nSample ] , nameChunk . GetLength ( ) ) ;
} else
{
2020-09-22 01:54:24 -03:00
m_szNames [ nSample ] = " " ;
2018-02-19 01:25:43 -03:00
}
2021-03-14 18:55:49 -03:00
sample . nLength = mpt : : saturate_cast < SmpLength > ( bodyChunk . GetLength ( ) / ( bytesPerSample * channels ) ) ;
2018-02-19 01:25:43 -03:00
if ( ( sample . nLoopStart + 4 < sample . nLoopEnd ) & & ( sample . nLoopEnd < = sample . nLength ) ) sample . uFlags . set ( CHN_LOOP ) ;
// While this is an Amiga format, the 16SV version appears to be only used on PC, and only with little-endian sample data.
SampleIO (
( bytesPerSample = = 2 ) ? SampleIO : : _16bit : SampleIO : : _8bit ,
2021-03-14 18:55:49 -03:00
( channels = = 2 ) ? SampleIO : : stereoSplit : SampleIO : : mono ,
2018-02-19 01:25:43 -03:00
SampleIO : : littleEndian ,
SampleIO : : signedPCM )
. ReadSample ( sample , bodyChunk ) ;
sample . PrecomputeLoops ( * this , false ) ;
return true ;
}
OPENMPT_NAMESPACE_END