Ejemplo n.º 1
0
bool module_renderer::ReadITProject(const uint8_t * lpStream, const uint32_t dwMemLength)
//-----------------------------------------------------------------------
{
    UINT i,n,nsmp;
    uint32_t id,len,size;
    uint32_t dwMemPos = 0;
    uint32_t version;

    ASSERT_CAN_READ(12);

    // Check file ID

    memcpy(&id,lpStream+dwMemPos,sizeof(uint32_t));
    if(id != ITP_FILE_ID) return false;
    dwMemPos += sizeof(uint32_t);

    memcpy(&id,lpStream+dwMemPos,sizeof(uint32_t));
    version = id;
    dwMemPos += sizeof(uint32_t);

    // bad_max supported version
    if(version > ITP_VERSION)
    {
        return false;
    }

    m_nType = MOD_TYPE_IT;

    // Song name

    // name string length
    memcpy(&id,lpStream+dwMemPos,sizeof(uint32_t));
    len = id;
    dwMemPos += sizeof(uint32_t);

    // name string
    ASSERT_CAN_READ(len);
    if (len <= MAX_SAMPLENAME)
    {
        assign_without_padding(this->song_name, reinterpret_cast<const char *>(lpStream + dwMemPos), len);
        dwMemPos += len;
    }
    else return false;

    // Song comments

    // comment string length
    ASSERT_CAN_READ(4);
    memcpy(&id,lpStream+dwMemPos,sizeof(uint32_t));
    dwMemPos += sizeof(uint32_t);
    if(id > UINT16_MAX) return false;

    // allocate and copy comment string
    ASSERT_CAN_READ(id);
    if(id > 0)
    {
        ReadMessage(lpStream + dwMemPos, id - 1, leCR);
    }
    dwMemPos += id;

    // Song global config
    ASSERT_CAN_READ(5*4);

    // m_dwSongFlags
    memcpy(&id,lpStream+dwMemPos,sizeof(uint32_t));
    m_dwSongFlags = (id & SONG_FILE_FLAGS);
    dwMemPos += sizeof(uint32_t);

    if(!(m_dwSongFlags & SONG_ITPROJECT)) return false;

    // m_nDefaultGlobalVolume
    memcpy(&id,lpStream+dwMemPos,sizeof(uint32_t));
    m_nDefaultGlobalVolume = id;
    dwMemPos += sizeof(uint32_t);

    // m_nSamplePreAmp
    memcpy(&id,lpStream+dwMemPos,sizeof(uint32_t));
    m_nSamplePreAmp = id;
    dwMemPos += sizeof(uint32_t);

    // m_nDefaultSpeed
    memcpy(&id,lpStream+dwMemPos,sizeof(uint32_t));
    m_nDefaultSpeed = id;
    dwMemPos += sizeof(uint32_t);

    // m_nDefaultTempo
    memcpy(&id,lpStream+dwMemPos,sizeof(uint32_t));
    m_nDefaultTempo = id;
    dwMemPos += sizeof(uint32_t);

    // Song channels data
    ASSERT_CAN_READ(2*4);

    // m_nChannels
    memcpy(&id,lpStream+dwMemPos,sizeof(uint32_t));
    m_nChannels = (modplug::tracker::chnindex_t)id;
    dwMemPos += sizeof(uint32_t);
    if(m_nChannels > 127) return false;

    // channel name string length (=MAX_CHANNELNAME)
    memcpy(&id,lpStream+dwMemPos,sizeof(uint32_t));
    len = id;
    dwMemPos += sizeof(uint32_t);
    if(len > MAX_CHANNELNAME) return false;

    // Channels' data
    for(i=0; i<m_nChannels; i++){
        ASSERT_CAN_READ(3*4 + len);

        // ChnSettings[i].nPan
        memcpy(&id,lpStream+dwMemPos,sizeof(uint32_t));
        ChnSettings[i].nPan = id;
        dwMemPos += sizeof(uint32_t);

        // ChnSettings[i].dwFlags
        memcpy(&id,lpStream+dwMemPos,sizeof(uint32_t));
        ChnSettings[i].dwFlags = id;
        dwMemPos += sizeof(uint32_t);

        // ChnSettings[i].nVolume
        memcpy(&id,lpStream+dwMemPos,sizeof(uint32_t));
        ChnSettings[i].nVolume = id;
        dwMemPos += sizeof(uint32_t);

        // ChnSettings[i].szName
        memcpy(&ChnSettings[i].szName[0],lpStream+dwMemPos,len);
        SetNullTerminator(ChnSettings[i].szName);
        dwMemPos += len;
    }

    // Song mix plugins
    // size of mix plugins data
    ASSERT_CAN_READ(4);
    memcpy(&id,lpStream+dwMemPos,sizeof(uint32_t));
    dwMemPos += sizeof(uint32_t);

    // mix plugins
    ASSERT_CAN_READ(id);
    dwMemPos += LoadMixPlugins(lpStream+dwMemPos, id);

    // Song midi config

    // midi cfg data length
    ASSERT_CAN_READ(4);
    memcpy(&id,lpStream+dwMemPos,sizeof(uint32_t));
    dwMemPos += sizeof(uint32_t);

    // midi cfg
    ASSERT_CAN_READ(id);
    if (id <= sizeof(m_MidiCfg))
    {
        memcpy(&m_MidiCfg, lpStream + dwMemPos, id);
        SanitizeMacros();
        dwMemPos += id;
    }

    // Song Instruments

    // m_nInstruments
    ASSERT_CAN_READ(4);
    memcpy(&id,lpStream+dwMemPos,sizeof(uint32_t));
    m_nInstruments = (modplug::tracker::instrumentindex_t)id;
    if(m_nInstruments > MAX_INSTRUMENTS) return false;
    dwMemPos += sizeof(uint32_t);

    // path string length (=_MAX_PATH)
    ASSERT_CAN_READ(4);
    memcpy(&id,lpStream+dwMemPos,sizeof(uint32_t));
    len = id;
    if(len > _MAX_PATH) return false;
    dwMemPos += sizeof(uint32_t);

    // instruments' paths
    for(i=0; i<m_nInstruments; i++){
        ASSERT_CAN_READ(len);
        memcpy(&m_szInstrumentPath[i][0],lpStream+dwMemPos,len);
        SetNullTerminator(m_szInstrumentPath[i]);
        dwMemPos += len;
    }

    // Song Orders

    // size of order array (=MAX_ORDERS)
    ASSERT_CAN_READ(4);
    memcpy(&id,lpStream+dwMemPos,sizeof(uint32_t));
    size = id;
    if(size > MAX_ORDERS) return false;
    dwMemPos += sizeof(uint32_t);

    // order data
    ASSERT_CAN_READ(size);
    Order.ReadAsByte(lpStream+dwMemPos, size, dwMemLength-dwMemPos);
    dwMemPos += size;



    // Song Patterns

    ASSERT_CAN_READ(3*4);
    // number of patterns (=MAX_PATTERNS)
    memcpy(&id,lpStream+dwMemPos,sizeof(uint32_t));
    size = id;
    dwMemPos += sizeof(uint32_t);
    if(size > MAX_PATTERNS) return false;

    // m_nPatternNames
    memcpy(&id,lpStream+dwMemPos,sizeof(uint32_t));
    const modplug::tracker::patternindex_t numNamedPats = id;
    dwMemPos += sizeof(uint32_t);

    // pattern name string length (=MAX_PATTERNNAME)
    memcpy(&id,lpStream+dwMemPos,sizeof(uint32_t));
    const uint32_t patNameLen = id;
    dwMemPos += sizeof(uint32_t);

    // m_lpszPatternNames
    ASSERT_CAN_READ(numNamedPats * patNameLen);
    char *patNames = (char *)(lpStream + dwMemPos);
    dwMemPos += numNamedPats * patNameLen;

    // modcommand data length
    ASSERT_CAN_READ(4);
    memcpy(&id,lpStream+dwMemPos,sizeof(uint32_t));
    n = id;
    if(n != 6) return false;
    dwMemPos += sizeof(uint32_t);

    for(modplug::tracker::patternindex_t npat=0; npat<size; npat++)
    {
        // Patterns[npat].GetNumRows()
        ASSERT_CAN_READ(4);
        memcpy(&id,lpStream+dwMemPos,sizeof(uint32_t));
        if(id > MAX_PATTERN_ROWS) return false;
        const modplug::tracker::rowindex_t nRows = id;
        dwMemPos += sizeof(uint32_t);

        // Try to allocate & read only sized patterns
        if(nRows)
        {

            // Allocate pattern
            if(Patterns.Insert(npat, nRows))
            {
                dwMemPos += m_nChannels * Patterns[npat].GetNumRows() * n;
                continue;
            }
            if(npat < numNamedPats && patNameLen > 0)
            {
                Patterns[npat].SetName(patNames, patNameLen);
                patNames += patNameLen;
            }

            // Pattern data
            long datasize = m_nChannels * Patterns[npat].GetNumRows() * n;
            //if (streamPos+datasize<=dwMemLength) {
            if(Patterns[npat].ReadITPdata(lpStream, dwMemPos, datasize, dwMemLength))
            {
                ErrorBox(IDS_ERR_FILEOPEN, NULL);
                return false;
            }
            //memcpy(Patterns[npat],lpStream+streamPos,datasize);
            //streamPos += datasize;
            //}
        }
    }

    // Load embeded samples

    ITSAMPLESTRUCT pis;

    // Read original number of samples
    ASSERT_CAN_READ(4);
    memcpy(&id,lpStream+dwMemPos,sizeof(uint32_t));
    if(id > MAX_SAMPLES) return false;
    m_nSamples = (modplug::tracker::sampleindex_t)id;
    dwMemPos += sizeof(uint32_t);

    // Read number of embeded samples
    ASSERT_CAN_READ(4);
    memcpy(&id,lpStream+dwMemPos,sizeof(uint32_t));
    if(id > MAX_SAMPLES) return false;
    n = id;
    dwMemPos += sizeof(uint32_t);

    // Read samples
    for(i=0; i<n; i++){

        ASSERT_CAN_READ(4 + sizeof(ITSAMPLESTRUCT) + 4);

        // Sample id number
        memcpy(&id,lpStream+dwMemPos,sizeof(uint32_t));
        nsmp = id;
        dwMemPos += sizeof(uint32_t);

        if(nsmp < 1 || nsmp >= MAX_SAMPLES)
            return false;

        // Sample struct
        memcpy(&pis,lpStream+dwMemPos,sizeof(ITSAMPLESTRUCT));
        dwMemPos += sizeof(ITSAMPLESTRUCT);

        // Sample length
        memcpy(&id,lpStream+dwMemPos,sizeof(uint32_t));
        len = id;
        dwMemPos += sizeof(uint32_t);
        if(dwMemPos >= dwMemLength || len > dwMemLength - dwMemPos) return false;

        // Copy sample struct data (ut-oh... this code looks very familiar!)
        if(pis.id == LittleEndian(IT_IMPS))
        {
            modsample_t *pSmp = &Samples[nsmp];
            memcpy(pSmp->legacy_filename, pis.filename, 12);
            pSmp->flags = 0;
            pSmp->length = 0;
            pSmp->loop_start = pis.loopbegin;
            pSmp->loop_end = pis.loopend;
            pSmp->sustain_start = pis.susloopbegin;
            pSmp->sustain_end = pis.susloopend;
            pSmp->c5_samplerate = pis.C5Speed;
            if(!pSmp->c5_samplerate) pSmp->c5_samplerate = 8363;
            if(pis.C5Speed < 256) pSmp->c5_samplerate = 256;
            pSmp->default_volume = pis.vol << 2;
            if(pSmp->default_volume > 256) pSmp->default_volume = 256;
            pSmp->global_volume = pis.gvl;
            if(pSmp->global_volume > 64) pSmp->global_volume = 64;
            if(pis.flags & 0x10) pSmp->flags |= CHN_LOOP;
            if(pis.flags & 0x20) pSmp->flags |= CHN_SUSTAINLOOP;
            if(pis.flags & 0x40) pSmp->flags |= CHN_PINGPONGLOOP;
            if(pis.flags & 0x80) pSmp->flags |= CHN_PINGPONGSUSTAIN;
            pSmp->default_pan = (pis.dfp & 0x7F) << 2;
            if(pSmp->default_pan > 256) pSmp->default_pan = 256;
            if(pis.dfp & 0x80) pSmp->flags |= CHN_PANNING;
            pSmp->vibrato_type = autovibit2xm[pis.vit & 7];
            pSmp->vibrato_rate = pis.vis;
            pSmp->vibrato_depth = pis.vid & 0x7F;
            pSmp->vibrato_sweep = pis.vir;
            if(pis.length){
                pSmp->length = pis.length;
                if (pSmp->length > MAX_SAMPLE_LENGTH) pSmp->length = MAX_SAMPLE_LENGTH;
                UINT flags = (pis.cvt & 1) ? RS_PCM8S : RS_PCM8U;
                if (pis.flags & 2){
                    flags += 5;
                    if (pis.flags & 4) flags |= RSF_STEREO;
                    pSmp->flags |= CHN_16BIT;
                }
                else{
                    if (pis.flags & 4) flags |= RSF_STEREO;
                }
                // Read sample data
                ReadSample(&Samples[nsmp], flags, (LPSTR)(lpStream+dwMemPos), len);
                dwMemPos += len;
                memcpy(m_szNames[nsmp], pis.name, 26);
            }
        }
    }

    // Load instruments

    CMappedFile f;
    LPBYTE lpFile;

    for(modplug::tracker::instrumentindex_t i = 0; i < m_nInstruments; i++)
    {

        if(m_szInstrumentPath[i][0] == '\0' || !f.Open(m_szInstrumentPath[i])) continue;

        len = f.GetLength();
        lpFile = f.Lock(len);
        if(!lpFile) { f.Close(); continue; }

        ReadInstrumentFromFile(i+1, lpFile, len);
        f.Unlock();
        f.Close();
    }

    // Extra info data

    __int32 fcode = 0;
    const uint8_t * ptr = lpStream + bad_min(dwMemPos, dwMemLength);

    if (dwMemPos <= dwMemLength - 4) {
        fcode = (*((__int32 *)ptr));
    }

    // Embed instruments' header [v1.01]
    if(version >= 0x00000101 && m_dwSongFlags & SONG_ITPEMBEDIH && fcode == 'EBIH')
    {
        // jump embeded instrument header tag
        ptr += sizeof(__int32);

        // set first instrument's header as current
        i = 1;

        // parse file
        while( uintptr_t(ptr - lpStream) <= dwMemLength - 4 && i <= m_nInstruments )
        {

            fcode = (*((__int32 *)ptr));                    // read field code

            switch( fcode )
            {
            case 'MPTS': goto mpts; //:)            // reached end of instrument headers
            case 'SEP@': case 'MPTX':
                ptr += sizeof(__int32);                    // jump code
                i++;                                                    // switch to next instrument
                break;

            default:
                ptr += sizeof(__int32);                    // jump field code
                ReadExtendedInstrumentProperty(Instruments[i], fcode, ptr, lpStream + dwMemLength);
                break;
            }
        }
    }

    //HACK: if we fail on i <= m_nInstruments above, arrive here without having set fcode as appropriate,
    //      hence the code duplication.
    if ( (uintptr_t)(ptr - lpStream) <= dwMemLength - 4 )
    {
        fcode = (*((__int32 *)ptr));
    }

    // Song extensions
mpts:
    if( fcode == 'MPTS' )
        LoadExtendedSongProperties(MOD_TYPE_IT, ptr, lpStream, dwMemLength);

    m_nMaxPeriod = 0xF000;
    m_nMinPeriod = 8;

    if(m_dwLastSavedWithVersion < MAKE_VERSION_NUMERIC(1, 17, 2, 50))
    {
        SetModFlag(MSF_COMPATIBLE_PLAY, false);
        SetModFlag(MSF_MIDICC_BUGEMULATION, true);
        SetModFlag(MSF_OLDVOLSWING, true);
    }

    return true;
}
Ejemplo n.º 2
0
OPENMPT_NAMESPACE_BEGIN

// Version changelog:
// v1.03: - Relative unicode instrument paths instead of absolute ANSI paths
//        - Per-path variable string length
//        - Embedded samples are IT-compressed
//        (rev. 3249)
// v1.02: Explicitely updated format to use new instrument flags representation (rev. 483)
// v1.01: Added option to embed instrument headers


bool CSoundFile::ReadITProject(FileReader &file, ModLoadingFlags loadFlags)
//-------------------------------------------------------------------------
{
#ifndef MPT_EXTERNAL_SAMPLES
	// Doesn't really make sense to support this format when there's no support for external files...
	MPT_UNREFERENCED_PARAMETER(file);
	MPT_UNREFERENCED_PARAMETER(loadFlags);
	return false;
#else // MPT_EXTERNAL_SAMPLES
	
	enum ITPSongFlags
	{
		ITP_EMBEDMIDICFG	= 0x00001,	// Embed macros in file
		ITP_ITOLDEFFECTS	= 0x00004,	// Old Impulse Tracker effect implementations
		ITP_ITCOMPATGXX		= 0x00008,	// IT "Compatible Gxx" (IT's flag to behave more like other trackers w/r/t portamento effects)
		ITP_LINEARSLIDES	= 0x00010,	// Linear slides vs. Amiga slides
		ITP_EXFILTERRANGE	= 0x08000,	// Cutoff Filter has double frequency range (up to ~10Khz)
		ITP_ITPROJECT		= 0x20000,	// Is a project file
		ITP_ITPEMBEDIH		= 0x40000,	// Embed instrument headers in project file
	};

	uint32 version;
	FileReader::off_t size;

	file.Rewind();

	// Check file ID
	if(!file.CanRead(12 + 4 + 24 + 4)
		|| file.ReadUint32LE() != MAGIC4BE('.','i','t','p')	// Magic bytes
		|| (version = file.ReadUint32LE()) > 0x00000103		// Format version
		|| version < 0x00000100)
	{
		return false;
	} else if(loadFlags == onlyVerifyHeader)
	{
		return true;
	}

	InitializeGlobals(MOD_TYPE_IT);
	m_playBehaviour.reset();
	file.ReadString<mpt::String::maybeNullTerminated>(m_songName, file.ReadUint32LE());

	// Song comments
	m_songMessage.Read(file, file.ReadUint32LE(), SongMessage::leCR);

	// Song global config
	const uint32 songFlags = file.ReadUint32LE();
	if(!(songFlags & ITP_ITPROJECT))
	{
		return false;
	}
	if(songFlags & ITP_EMBEDMIDICFG)	m_SongFlags.set(SONG_EMBEDMIDICFG);
	if(songFlags & ITP_ITOLDEFFECTS)	m_SongFlags.set(SONG_ITOLDEFFECTS);
	if(songFlags & ITP_ITCOMPATGXX)		m_SongFlags.set(SONG_ITCOMPATGXX);
	if(songFlags & ITP_LINEARSLIDES)	m_SongFlags.set(SONG_LINEARSLIDES);
	if(songFlags & ITP_EXFILTERRANGE)	m_SongFlags.set(SONG_EXFILTERRANGE);

	m_nDefaultGlobalVolume = file.ReadUint32LE();
	m_nSamplePreAmp = file.ReadUint32LE();
	m_nDefaultSpeed = std::max(uint32(1), file.ReadUint32LE());
	m_nDefaultTempo.Set(std::max(uint32(32), file.ReadUint32LE()));
	m_nChannels = static_cast<CHANNELINDEX>(file.ReadUint32LE());
	if(m_nChannels == 0 || m_nChannels > MAX_BASECHANNELS)
	{
		return false;
	}

	// channel name string length (=MAX_CHANNELNAME)
	size = file.ReadUint32LE();

	// Channels' data
	for(CHANNELINDEX chn = 0; chn < m_nChannels; chn++)
	{
		ChnSettings[chn].nPan = std::min(static_cast<uint16>(file.ReadUint32LE()), uint16(256));
		ChnSettings[chn].dwFlags.reset();
		uint32 flags = file.ReadUint32LE();
		if(flags & 0x100) ChnSettings[chn].dwFlags.set(CHN_MUTE);
		if(flags & 0x800) ChnSettings[chn].dwFlags.set(CHN_SURROUND);
		ChnSettings[chn].nVolume = std::min(static_cast<uint16>(file.ReadUint32LE()), uint16(64));
		file.ReadString<mpt::String::maybeNullTerminated>(ChnSettings[chn].szName, size);
	}

	// Song mix plugins
	{
		FileReader plugChunk = file.ReadChunk(file.ReadUint32LE());
		LoadMixPlugins(plugChunk);
	}

	// MIDI Macro config
	file.ReadStructPartial(m_MidiCfg, file.ReadUint32LE());
	m_MidiCfg.Sanitize();

	// Song Instruments
	m_nInstruments = static_cast<INSTRUMENTINDEX>(file.ReadUint32LE());
	if(m_nInstruments >= MAX_INSTRUMENTS)
	{
		return false;
	}

	// Instruments' paths
	if(version <= 0x00000102)
	{
		size = file.ReadUint32LE();	// path string length
	}

	std::vector<mpt::PathString> instrPaths(GetNumInstruments());
	for(INSTRUMENTINDEX ins = 0; ins < GetNumInstruments(); ins++)
	{
		if(version > 0x00000102)
		{
			size = file.ReadUint32LE();	// path string length
		}
		std::string path;
		file.ReadString<mpt::String::maybeNullTerminated>(path, size);
		if(version <= 0x00000102)
		{
			instrPaths[ins] = mpt::PathString::FromLocaleSilent(path);
		} else
		{
			instrPaths[ins] = mpt::PathString::FromUTF8(path);
		}
	}

	// Song Orders
	size = file.ReadUint32LE();
	Order.ReadAsByte(file, size, size, 0xFF, 0xFE);

	// Song Patterns
	const PATTERNINDEX numPats = static_cast<PATTERNINDEX>(file.ReadUint32LE());
	const PATTERNINDEX numNamedPats = static_cast<PATTERNINDEX>(file.ReadUint32LE());
	size_t patNameLen = file.ReadUint32LE();	// Size of each pattern name
	FileReader pattNames = file.ReadChunk(numNamedPats * patNameLen);

	// modcommand data length
	size = file.ReadUint32LE();
	if(size != 6)
	{
		return false;
	}

	for(PATTERNINDEX pat = 0; pat < numPats; pat++)
	{
		const ROWINDEX numRows = file.ReadUint32LE();
		FileReader patternChunk = file.ReadChunk(numRows * size * GetNumChannels());

		// Allocate pattern
		if(!(loadFlags & loadPatternData) || !Patterns.Insert(pat, numRows))
		{
			pattNames.Skip(patNameLen);
			continue;
		}

		if(pat < numNamedPats)
		{
			char patName[32];
			pattNames.ReadString<mpt::String::maybeNullTerminated>(patName, patNameLen);
			Patterns[pat].SetName(patName);
		}

		// Pattern data
		size_t numCommands = GetNumChannels() * numRows;

		if(patternChunk.CanRead(sizeof(MODCOMMAND_ORIGINAL) * numCommands))
		{
			ModCommand *target = Patterns[pat].GetpModCommand(0, 0);
			while(numCommands-- != 0)
			{
				STATIC_ASSERT(sizeof(MODCOMMAND_ORIGINAL) == 6);
				MODCOMMAND_ORIGINAL data;
				patternChunk.ReadStruct(data);
				if(data.command >= MAX_EFFECTS) data.command = CMD_NONE;
				if(data.volcmd >= MAX_VOLCMDS) data.volcmd = VOLCMD_NONE;
				if(data.note > NOTE_MAX && data.note < NOTE_MIN_SPECIAL) data.note = NOTE_NONE;
				*(target++) = data;
			}
		}
	}

	// Load embedded samples

	// Read original number of samples
	m_nSamples = static_cast<SAMPLEINDEX>(file.ReadUint32LE());
	LimitMax(m_nSamples, SAMPLEINDEX(MAX_SAMPLES - 1));

	// Read number of embedded samples
	uint32 embeddedSamples = file.ReadUint32LE();

	// Read samples
	for(uint32 smp = 0; smp < embeddedSamples; smp++)
	{
		SAMPLEINDEX realSample = static_cast<SAMPLEINDEX>(file.ReadUint32LE());
		ITSample sampleHeader;
		file.ReadConvertEndianness(sampleHeader);
		FileReader sampleData = file.ReadChunk(file.ReadUint32LE());

		if(realSample >= 1 && realSample <= GetNumSamples() && !memcmp(sampleHeader.id, "IMPS", 4) && (loadFlags & loadSampleData))
		{
			sampleHeader.ConvertToMPT(Samples[realSample]);
			mpt::String::Read<mpt::String::nullTerminated>(m_szNames[realSample], sampleHeader.name);

			// Read sample data
			sampleHeader.GetSampleFormat().ReadSample(Samples[realSample], sampleData);
		}
	}

	// Load instruments
	for(INSTRUMENTINDEX ins = 0; ins < GetNumInstruments(); ins++)
	{
		if(instrPaths[ins].empty())
			continue;

		if(!file.GetFileName().empty())
		{
			instrPaths[ins] = instrPaths[ins].RelativePathToAbsolute(file.GetFileName().GetPath());
		}
#ifdef MODPLUG_TRACKER
		else if(GetpModDoc() != nullptr)
		{
			instrPaths[ins] = instrPaths[ins].RelativePathToAbsolute(GetpModDoc()->GetPathNameMpt().GetPath());
		}
#endif // MODPLUG_TRACKER

		InputFile f(instrPaths[ins]);
		FileReader file = GetFileReader(f);
		if(!ReadInstrumentFromFile(ins + 1, file, true))
		{
			AddToLog(LogWarning, MPT_USTRING("Unable to open instrument: ") + instrPaths[ins].ToUnicode());
		}
	}

	// Extra info data
	uint32 code = file.ReadUint32LE();

	// Embed instruments' header [v1.01]
	if(version >= 0x00000101 && (songFlags & ITP_ITPEMBEDIH) && code == MAGIC4BE('E', 'B', 'I', 'H'))
	{
		code = file.ReadUint32LE();

		INSTRUMENTINDEX ins = 1;
		while(ins <= GetNumInstruments() && file.CanRead(4))
		{
			if(code == MAGIC4BE('M', 'P', 'T', 'S'))
			{
				break;
			} else if(code == MAGIC4BE('S', 'E', 'P', '@') || code == MAGIC4BE('M', 'P', 'T', 'X'))
			{
				// jump code - switch to next instrument
				ins++;
			} else
			{
				ReadExtendedInstrumentProperty(Instruments[ins], code, file);
			}

			code = file.ReadUint32LE();
		}
	}

	// Song extensions
	if(code == MAGIC4BE('M', 'P', 'T', 'S'))
	{
		file.SkipBack(4);
		LoadExtendedSongProperties(file);
	}

	m_nMaxPeriod = 0xF000;
	m_nMinPeriod = 8;

	// Before OpenMPT 1.20.01.09, the MIDI macros were always read from the file, even if the "embed" flag was not set.
	if(m_dwLastSavedWithVersion >= MAKE_VERSION_NUMERIC(1,20,01,09) && !m_SongFlags[SONG_EMBEDMIDICFG])
	{
		m_MidiCfg.Reset();
	} else if(!m_MidiCfg.IsMacroDefaultSetupUsed())
	{
		m_SongFlags.set(SONG_EMBEDMIDICFG);
	}

	m_madeWithTracker = "OpenMPT " + MptVersion::ToStr(m_dwLastSavedWithVersion);

	return true;
#endif // MPT_EXTERNAL_SAMPLES
}