From: <sag...@us...> - 2012-06-18 00:43:05
|
Revision: 1303 http://modplug.svn.sourceforge.net/modplug/?rev=1303&view=rev Author: saga-games Date: 2012-06-18 00:42:57 +0000 (Mon, 18 Jun 2012) Log Message: ----------- [Fix] Rewrote AMS (Velvet Studio) loader... Old one was commented out because it basically crashed on almost all VS modules. [Fix] Unbreak creating instruments from sample editor [Mod] OpenMPT: Version is now 1.20.01.08 Modified Paths: -------------- trunk/OpenMPT/mptrack/Modedit.cpp trunk/OpenMPT/mptrack/version.h trunk/OpenMPT/soundlib/Load_ams.cpp trunk/OpenMPT/soundlib/Loaders.h trunk/OpenMPT/soundlib/Snd_defs.h trunk/OpenMPT/soundlib/Sndfile.cpp trunk/OpenMPT/soundlib/Sndfile.h trunk/OpenMPT/soundlib/Tables.cpp trunk/OpenMPT/soundlib/modcommand.cpp Modified: trunk/OpenMPT/mptrack/Modedit.cpp =================================================================== --- trunk/OpenMPT/mptrack/Modedit.cpp 2012-06-17 15:31:45 UTC (rev 1302) +++ trunk/OpenMPT/mptrack/Modedit.cpp 2012-06-18 00:42:57 UTC (rev 1303) @@ -523,7 +523,7 @@ CriticalSection cs; - ModInstrument *pIns = m_SndFile.AllocateInstrument(newsmp); + ModInstrument *pIns = m_SndFile.AllocateInstrument(newins, newsmp); if(pIns == nullptr) { cs.Leave(); @@ -542,8 +542,6 @@ // -! NEW_FEATURE#0023 } - m_SndFile.Instruments[newins] = pIns; - SetModified(); return newins; Modified: trunk/OpenMPT/mptrack/version.h =================================================================== --- trunk/OpenMPT/mptrack/version.h 2012-06-17 15:31:45 UTC (rev 1302) +++ trunk/OpenMPT/mptrack/version.h 2012-06-18 00:42:57 UTC (rev 1303) @@ -19,7 +19,7 @@ #define VER_MAJORMAJOR 1 #define VER_MAJOR 20 #define VER_MINOR 01 -#define VER_MINORMINOR 07 +#define VER_MINORMINOR 08 //Creates version number from version parts that appears in version string. //For example MAKE_VERSION_NUMERIC(1,17,02,28) gives version number of Modified: trunk/OpenMPT/soundlib/Load_ams.cpp =================================================================== --- trunk/OpenMPT/soundlib/Load_ams.cpp 2012-06-17 15:31:45 UTC (rev 1302) +++ trunk/OpenMPT/soundlib/Load_ams.cpp 2012-06-18 00:42:57 UTC (rev 1303) @@ -49,8 +49,8 @@ // Callback function for reading text -void Convert_AMS_Text_Chars(char &c) -//---------------------------------- +void ConvertAMSTextChars(char &c) +//------------------------------- { switch((unsigned char)c) { @@ -83,7 +83,7 @@ || (!pfh->patterns) || (!pfh->orders) || (!pfh->samples) || (pfh->samples >= MAX_SAMPLES) || (pfh->patterns > MAX_PATTERNS) || (pfh->orders > MAX_ORDERS)) { - return ReadAMS2(lpStream, dwMemLength); + return false; } dwMemPos = sizeof(AMSFILEHEADER) + pfh->extra; if (dwMemPos + pfh->samples * sizeof(AMSSAMPLEHEADER) >= dwMemLength) return false; @@ -167,7 +167,7 @@ if (dwMemPos + tmp >= dwMemLength) return true; if (tmp) { - ReadMessage(lpStream + dwMemPos, tmp, leCR, &Convert_AMS_Text_Chars); + ReadMessage(lpStream + dwMemPos, tmp, leCR, &ConvertAMSTextChars); } dwMemPos += tmp; @@ -295,314 +295,712 @@ ///////////////////////////////////////////////////////////////////// -// AMS (Velvet Studio) 2.2 loader +// AMS (Velvet Studio) 2.1 / 2.2 loader -#pragma pack(push, 1) +#pragma pack(1) -typedef struct AMS2FILEHEADER +// AMS2 File Header +struct AMS2FileHeader { - DWORD dwHdr1; // AMShdr - WORD wHdr2; - BYTE b1A; // 0x1A - BYTE titlelen; // 30-bytes max - CHAR szTitle[30]; // [titlelen] -} AMS2FILEHEADER; + enum FileFlags + { + linearSlides = 0x40, + }; -typedef struct AMS2SONGHEADER + uint16 format; // Version of format (Hi = MainVer, Low = SubVer e.g. 0202 = 2.2) + uint8 numIns; // Nr of Instruments (0-255) + uint16 numPats; // Nr of Patterns (1-1024) + uint16 numOrds; // Nr of Positions (1-65535) + // Rest of header differs between format revision 2.01 and 2.02 + + // Convert all multi-byte numeric values to current platform's endianness or vice versa. + void ConvertEndianness() + { + SwapBytesLE(format); + SwapBytesLE(numPats); + SwapBytesLE(numOrds); + }; +}; + + +// AMS2 Instument Envelope +struct AMS2Envelope { - WORD version; - BYTE instruments; - WORD patterns; - WORD orders; - WORD bpm; - BYTE speed; - BYTE channels; - BYTE commands; - BYTE rows; - WORD flags; -} AMS2SONGHEADER; + uint8 speed; // Envelope speed + uint8 sustainPoint; // Envelope sustain point + uint8 loopStart; // Envelope loop Start + uint8 loopEnd; // Envelope loop End + uint8 numPoints; // Envelope length -typedef struct AMS2INSTRUMENT + // Read envelope and do partial conversion. + void ConvertToMPT(InstrumentEnvelope &mptEnv, FileReader &file) + { + file.Read(*this); + + // Read envelope points + struct + { + uint8 data[64][3]; + } env; + file.ReadStructPartial(env, numPoints * 3); + + if(numPoints <= 1) + { + // This is not an envelope. + return; + } + + STATIC_ASSERT(MAX_ENVPOINTS >= CountOf(env.data)); + mptEnv.nNodes = Util::Min(numPoints, uint8(CountOf(env.data))); + mptEnv.nLoopStart = loopStart; + mptEnv.nLoopEnd = loopEnd; + mptEnv.nSustainStart = mptEnv.nSustainEnd = sustainPoint; + + for(size_t i = 0; i < mptEnv.nNodes; i++) + { + if(i != 0) + { + mptEnv.Ticks[i] = mptEnv.Ticks[i - 1] + Util::Max(1, env.data[i][0] | ((env.data[i][1] & 0x01) << 8)); + } + mptEnv.Values[i] = env.data[i][2]; + } + } +}; + + +// AMS2 Instrument Data +struct AMS2Instrument { - BYTE samples; - BYTE notemap[NOTE_MAX]; -} AMS2INSTRUMENT; + enum EnvelopeFlags + { + envLoop = 0x01, + envSustain = 0x02, + envEnabled = 0x04, + + // Flag shift amounts + volEnvShift = 0, + panEnvShift = 1, + vibEnvShift = 2, -typedef struct AMS2ENVELOPE + vibAmpMask = 0x3000, + vibAmpShift = 12, + fadeOutMask = 0xFFF, + }; + + uint8 shadowInstr; // Shadow Instrument. If non-zero, the value=the shadowed inst. + uint16 vibampFadeout; // Vib.Amplify + Volume fadeout in one variable! + uint16 envFlags; // See EnvelopeFlags + + // Convert all multi-byte numeric values to current platform's endianness or vice versa. + void ConvertEndianness() + { + SwapBytesLE(vibampFadeout); + SwapBytesLE(envFlags); + } + + void ApplyFlags(InstrumentEnvelope &mptEnv, EnvelopeFlags shift) const + { + const uint8 flags = envFlags >> (shift * 3); + if(flags & envEnabled) mptEnv.dwFlags |= ENV_ENABLED; + if(flags & envLoop) mptEnv.dwFlags |= ENV_LOOP; + if(flags & envSustain) mptEnv.dwFlags |= ENV_SUSTAIN; + + // "Break envelope" should stop the envelope loop when encountering a note-off... We can only use the sustain loop to emulate this behaviour. + if(!(flags & envSustain) && (flags & envLoop) != 0 && (flags & (1 << (9 - shift * 2))) != 0) + { + mptEnv.nSustainStart = mptEnv.nLoopStart; + mptEnv.nSustainEnd = mptEnv.nLoopEnd; + mptEnv.dwFlags |= ENV_SUSTAIN; + mptEnv.dwFlags &= ~ENV_LOOP; + } + } + +}; + + +// AMS2 Sample Header +struct AMS2SampleHeader { - BYTE speed; - BYTE sustain; - BYTE loopbegin; - BYTE loopend; - BYTE points; - BYTE info[3]; -} AMS2ENVELOPE; + enum SampleFlags + { + smpPacked = 0x03, + smp16Bit = 0x04, + smpLoop = 0x08, + smpBidiLoop = 0x10, + }; -typedef struct AMS2SAMPLE + uint32 length; + uint32 loopStart; + uint32 loopEnd; + uint16 sampledRate; // Whyyyy? + uint8 panFinetune; // High nibble = pan position, low nibble = finetune value + uint16 c4speed; // Why is all of this so redundant? + int8 relativeTone; // q.e.d. + uint8 volume; // 0...127 + uint8 flags; // See SampleFlags + + // Convert all multi-byte numeric values to current platform's endianness or vice versa. + void ConvertEndianness() + { + SwapBytesLE(length); + SwapBytesLE(loopStart); + SwapBytesLE(loopEnd); + SwapBytesLE(sampledRate); + SwapBytesLE(c4speed); + } + + // Convert sample header to OpenMPT's internal format. + void ConvertToMPT(ModSample &mptSmp) const + { + mptSmp.Initialize(); + + mptSmp.nLength = length; + mptSmp.nLoopStart = Util::Min(loopStart, length); + mptSmp.nLoopEnd = Util::Min(loopEnd, length); + + mptSmp.nC5Speed = c4speed * 2; + if(!mptSmp.nC5Speed) + { + mptSmp.nC5Speed = 8363 * 2; + } + // Why, oh why, does this format need a c5speed and transpose/finetune at the same time... + uint32 newC4speed = ModSample::TransposeToFrequency(relativeTone, MOD2XMFineTune(panFinetune & 0x0F)); + mptSmp.nC5Speed = (mptSmp.nC5Speed * newC4speed) / 8363; + + mptSmp.nVolume = (Util::Min(uint8(127), volume) * 256 + 64) / 127; + if(panFinetune & 0xF0) + { + mptSmp.nPan = (panFinetune & 0xF0); + mptSmp.uFlags = CHN_PANNING; + } + + if(flags & smp16Bit) + { + mptSmp.uFlags |= CHN_16BIT; + } + if((flags & smpLoop) && mptSmp.nLoopStart < mptSmp.nLoopEnd) + { + mptSmp.uFlags |= CHN_LOOP; + if(flags & smpBidiLoop) + { + mptSmp.uFlags |= CHN_PINGPONGLOOP; + } + } + } +}; + + +// AMS2 Song Description Header +struct AMS2Description { - DWORD length; - DWORD loopstart; - DWORD loopend; - WORD frequency; - BYTE finetune; - WORD nC5Speed; - CHAR transpose; - BYTE volume; - BYTE flags; -} AMS2SAMPLE; + uint32 packedLen; // Including header + uint32 unpackedLen; + uint8 packRoutine; // 01 + uint8 preProcessing; // None! + uint8 packingMethod; // RLE + // Convert all multi-byte numeric values to current platform's endianness or vice versa. + void ConvertEndianness() + { + SwapBytesLE(packedLen); + SwapBytesLE(unpackedLen); + } +}; -#pragma pack(pop) +#pragma pack() -bool CSoundFile::ReadAMS2(LPCBYTE /*lpStream*/, DWORD /*dwMemLength*/) -//------------------------------------------------------------ + +// Read variable-length AMS string (we ignore the maximum text length specified by the AMS specs and accept any length). +template<size_t destSize> +bool ReadAMSString(char (&destBuffer)[destSize], FileReader &file) +//---------------------------------------------------------------- { - return false; -#if 0 - const AMS2FILEHEADER *pfh = (AMS2FILEHEADER *)lpStream; - AMS2SONGHEADER *psh; - DWORD dwMemPos; - BYTE smpmap[16]; - BYTE packedsamples[MAX_SAMPLES]; + const size_t length = file.ReadUint8(); + return file.ReadString<StringFixer::spacePadded>(destBuffer, length); +} - if ((pfh->dwHdr1 != 0x68534D41) || (pfh->wHdr2 != 0x7264) - || (pfh->b1A != 0x1A) || (pfh->titlelen > 30)) return false; - dwMemPos = pfh->titlelen + 8; - psh = (AMS2SONGHEADER *)(lpStream + dwMemPos); - if (((psh->version & 0xFF00) != 0x0200) || (!psh->instruments) - || (psh->instruments >= MAX_INSTRUMENTS) || (!psh->patterns) || (!psh->orders)) return false; - dwMemPos += sizeof(AMS2SONGHEADER); - if (pfh->titlelen) + +bool CSoundFile::ReadAMS2(FileReader &file) +//----------------------------------------- +{ + file.Rewind(); + + AMS2FileHeader fileHeader; + if(!file.ReadMagic("AMShdr\x1A") + || !ReadAMSString(m_szNames[0], file) + || !file.ReadConvertEndianness(fileHeader)) { - memcpy(m_szNames[0], pfh->szTitle, pfh->titlelen); - StringFixer::SpaceToNullStringFixed(m_szNames[0], pfh->titlelen); + return false; } - m_nType = MOD_TYPE_AMS; + + uint16 headerFlags; + if(fileHeader.format == 0x202) + { + m_nDefaultTempo = Util::Max(uint8(32), static_cast<uint8>(file.ReadUint16LE() >> 8)); // 16.16 Tempo + m_nDefaultSpeed = Util::Max(uint8(1), file.ReadUint8()); + file.Skip(3); // Default values for pattern editor + headerFlags = file.ReadUint16LE(); + } else if(fileHeader.format == 0x201) + { + m_nDefaultTempo = Util::Max(uint8(32), file.ReadUint8()); + m_nDefaultSpeed = Util::Max(uint8(1), file.ReadUint8()); + headerFlags = file.ReadUint8(); + } else + { + return false; + } + + m_nType = MOD_TYPE_AMS2; + m_dwSongFlags = SONG_ITCOMPATGXX | SONG_ITOLDEFFECTS | ((headerFlags & AMS2FileHeader::linearSlides) ? SONG_LINEARSLIDES : 0); + m_nDefaultGlobalVolume = MAX_GLOBAL_VOLUME; + m_nSamplePreAmp = m_nVSTiVolume = 48; + m_nInstruments = fileHeader.numIns; m_nChannels = 32; - m_nDefaultTempo = psh->bpm >> 8; - m_nDefaultSpeed = psh->speed; - m_nInstruments = psh->instruments; - m_nSamples = 0; - if (psh->flags & 0x40) m_dwSongFlags |= SONG_LINEARSLIDES; - for (UINT nIns=1; nIns<=m_nInstruments; nIns++) + + // Instruments + vector<SAMPLEINDEX> firstSample; // First sample of instrument + vector<uint16> sampleSettings; // Shadow sample map... Lo byte = Instrument, Hi byte, lo nibble = Sample index in instrument, Hi byte, hi nibble = Sample pack status + enum { - UINT insnamelen = lpStream[dwMemPos]; - CHAR *pinsname = (CHAR *)(lpStream+dwMemPos+1); - dwMemPos += insnamelen + 1; - AMS2INSTRUMENT *pSmp = (AMS2INSTRUMENT *)(lpStream + dwMemPos); - dwMemPos += sizeof(AMS2INSTRUMENT); - if (dwMemPos + 1024 >= dwMemLength) return TRUE; - AMS2ENVELOPE *volenv, *panenv, *pitchenv; - volenv = (AMS2ENVELOPE *)(lpStream+dwMemPos); - dwMemPos += 5 + volenv->points*3; - panenv = (AMS2ENVELOPE *)(lpStream+dwMemPos); - dwMemPos += 5 + panenv->points*3; - pitchenv = (AMS2ENVELOPE *)(lpStream+dwMemPos); - dwMemPos += 5 + pitchenv->points*3; - ModInstrument *pIns; - try + instrIndexMask = 0xFF, // Shadow instrument + sampleIndexMask = 0x7F00, // Sample index in instrument + sampleIndexShift = 8, + packStatusMask = 0x8000, // If bit is set, sample is packed + }; + + for(INSTRUMENTINDEX ins = 1; ins <= m_nInstruments; ins++) + { + ModInstrument *instrument = AllocateInstrument(ins); + if(instrument == nullptr + || !ReadAMSString(instrument->name, file)) { - pIns = new ModInstrument(); - } catch(MPTMemoryException) - { - return true; + return false; } - MemsetZero(smpmap); + uint8 numSamples = file.ReadUint8(); + uint8 sampleAssignment[120]; - for (UINT ismpmap=0; ismpmap<pSmp->samples; ismpmap++) + if(numSamples == 0 + || !file.ReadArray(sampleAssignment)) { - if ((ismpmap >= 16) || (m_nSamples+1 >= MAX_SAMPLES)) break; - m_nSamples++; - smpmap[ismpmap] = m_nSamples; + continue; } - Instruments[nIns] = pIns; - if (insnamelen) + STATIC_ASSERT(CountOf(instrument->Keyboard) >= CountOf(sampleAssignment)); + for(size_t i = 0; i < 120; i++) { - if (insnamelen > 31) insnamelen = 31; - memcpy(pIns->name, pinsname, insnamelen); - pIns->name[insnamelen] = 0; + instrument->Keyboard[i] = sampleAssignment[i] + GetNumSamples() + 1; } - for (UINT inotemap=0; inotemap<NOTE_MAX; inotemap++) + + AMS2Envelope volEnv, panEnv, vibratoEnv; + volEnv.ConvertToMPT(instrument->VolEnv, file); + panEnv.ConvertToMPT(instrument->PanEnv, file); + vibratoEnv.ConvertToMPT(instrument->PitchEnv, file); + + AMS2Instrument instrHeader; + file.ReadConvertEndianness(instrHeader); + instrument->nFadeOut = (instrHeader.vibampFadeout & AMS2Instrument::fadeOutMask) * 2; + const uint8 vibAmp = 1 + ((instrHeader.vibampFadeout & AMS2Instrument::vibAmpMask) >> AMS2Instrument::vibAmpShift); // "Close enough" + + instrHeader.ApplyFlags(instrument->VolEnv, AMS2Instrument::volEnvShift); + instrHeader.ApplyFlags(instrument->PanEnv, AMS2Instrument::panEnvShift); + instrHeader.ApplyFlags(instrument->PitchEnv, AMS2Instrument::vibEnvShift); + + // Scale envelopes to correct range + for(size_t i = 0; i < MAX_ENVPOINTS; i++) { - pIns->NoteMap[inotemap] = inotemap+1; - pIns->Keyboard[inotemap] = smpmap[pSmp->notemap[inotemap] & 0x0F]; + instrument->VolEnv.Values[i] = Util::Min(uint8(ENVELOPE_MAX), static_cast<uint8>((instrument->VolEnv.Values[i] * ENVELOPE_MAX + 64u) / 127u)); + instrument->PanEnv.Values[i] = Util::Min(uint8(ENVELOPE_MAX), static_cast<uint8>((instrument->PanEnv.Values[i] * ENVELOPE_MAX + 128u) / 255u)); + instrument->PitchEnv.Values[i] = Util::Min(uint8(ENVELOPE_MAX), static_cast<uint8>(32 + (static_cast<int8>(instrument->PitchEnv.Values[i] - 128) * vibAmp) / 255)); } - // Volume Envelope + + // Sample headers - we will have to read them even for shadow samples, and we will have to load them several times, + // as it is possible that shadow samples use different sample settings like base frequency or panning. + const SAMPLEINDEX firstSmp = GetNumSamples() + 1; + for(SAMPLEINDEX smp = 0; smp < numSamples; smp++) { - UINT pos = 0; - pIns->VolEnv.nNodes = (volenv->points > 16) ? 16 : volenv->points; - pIns->VolEnv.nSustainStart = pIns->VolEnv.nSustainEnd = volenv->sustain; - pIns->VolEnv.nLoopStart = volenv->loopbegin; - pIns->VolEnv.nLoopEnd = volenv->loopend; - for (UINT i=0; i<pIns->VolEnv.nNodes; i++) + if(firstSmp + smp >= MAX_SAMPLES) { - pIns->VolEnv.Values[i] = (BYTE)((volenv->info[i*3+2] & 0x7F) >> 1); - pos += volenv->info[i*3] + ((volenv->info[i*3+1] & 1) << 8); - pIns->VolEnv.Ticks[i] = (WORD)pos; + file.Skip(sizeof(AMS2SampleHeader)); + break; } + ReadAMSString(m_szNames[firstSmp + smp], file); + + AMS2SampleHeader sampleHeader; + file.ReadConvertEndianness(sampleHeader); + sampleHeader.ConvertToMPT(Samples[firstSmp + smp]); + + uint16 settings = (instrHeader.shadowInstr & instrIndexMask) | ((smp << sampleIndexShift) & sampleIndexMask) | ((sampleHeader.flags & AMS2SampleHeader::smpPacked) ? packStatusMask : 0); + sampleSettings.push_back(settings); } - pIns->nFadeOut = (((lpStream[dwMemPos+2] & 0x0F) << 8) | (lpStream[dwMemPos+1])) << 3; - UINT envflags = lpStream[dwMemPos+3]; - if (envflags & 0x01) pIns->VolEnv.dwFlags |= ENV_LOOP; - if (envflags & 0x02) pIns->VolEnv.dwFlags |= ENV_SUSTAIN; - if (envflags & 0x04) pIns->VolEnv.dwFlags |= ENV_ENABLED; - dwMemPos += 5; - // Read Samples - for (UINT ismp=0; ismp<pSmp->samples; ismp++) + + firstSample.push_back(firstSmp); + m_nSamples = Util::Min(MAX_SAMPLES - 1, GetNumSamples() + numSamples); + } + + // Text + + // Read composer name + uint8 composerLength = file.ReadUint8(); + if(composerLength) + { + ReadMessage(file, composerLength, leAutodetect, ConvertAMSTextChars); + } + + // Channel names + for(CHANNELINDEX chn = 0; chn < 32; chn++) + { + ReadAMSString(ChnSettings[chn].szName, file); + } + + // RLE-Packed description text + AMS2Description descriptionHeader; + if(!file.ReadConvertEndianness(descriptionHeader)) + { + return true; + } + if(descriptionHeader.packedLen) + { + const size_t textLength = descriptionHeader.packedLen - sizeof(descriptionHeader); + vector<uint8> textIn, textOut(descriptionHeader.unpackedLen); + file.ReadVector(textIn, textLength); + + size_t readLen = 0, writeLen = 0; + while(readLen < textLength && writeLen < descriptionHeader.unpackedLen) { - ModSample *psmp = ((ismp < 16) && (smpmap[ismp])) ? &Samples[smpmap[ismp]] : nullptr; - UINT smpnamelen = lpStream[dwMemPos]; - if ((psmp) && (smpnamelen) && (smpnamelen <= 22)) + uint8 c = textIn[readLen++]; + if(c == 0xFF && textLength - readLen >= 2) { - memcpy(m_szNames[smpmap[ismp]], lpStream+dwMemPos+1, smpnamelen); - StringFixer::SpaceToNullStringFixed(m_szNames[smpmap[ismp]], smpnamelen); - } - dwMemPos += smpnamelen + 1; - if (psmp) + c = textIn[readLen++]; + uint32 count = textIn[readLen++]; + for(size_t i = Util::Min(descriptionHeader.unpackedLen - writeLen, count); i != 0; i--) + { + textOut[writeLen++] = c; + } + } else { - AMS2SAMPLE *pams = (AMS2SAMPLE *)(lpStream+dwMemPos); - psmp->nGlobalVol = 64; - psmp->nPan = 128; - psmp->nLength = pams->length; - psmp->nLoopStart = pams->loopstart; - psmp->nLoopEnd = pams->loopend; - psmp->nC5Speed = pams->nC5Speed; - psmp->RelativeTone = pams->transpose; - psmp->nVolume = pams->volume / 2; - packedsamples[smpmap[ismp]] = pams->flags; - if (pams->flags & 0x04) psmp->uFlags |= CHN_16BIT; - if (pams->flags & 0x08) psmp->uFlags |= CHN_LOOP; - if (pams->flags & 0x10) psmp->uFlags |= CHN_PINGPONGLOOP; + textOut[writeLen++] = c; } - dwMemPos += sizeof(AMS2SAMPLE); } + // Packed text doesn't include any line breaks! + ReadFixedLineLengthMessage(&textOut[0], descriptionHeader.unpackedLen, 74, 0, ConvertAMSTextChars); } - if (dwMemPos + 256 >= dwMemLength) return true; - // Comments + + // Read Order List + vector<uint16> orders; + if(file.ReadVector(orders, fileHeader.numOrds)) { - UINT composernamelen = lpStream[dwMemPos]; - if (composernamelen) + Order.resize(fileHeader.numOrds); + for(size_t i = 0; i < fileHeader.numOrds; i++) { - ReadMessage(lpStream + dwMemPos + 1, composernamelen, leCR); + Order[i] = SwapBytesLE(orders[i]); } - dwMemPos += composernamelen + 1; - // channel names - for (UINT i=0; i<32; i++) - { - UINT chnnamlen = lpStream[dwMemPos]; - if ((chnnamlen) && (chnnamlen < MAX_CHANNELNAME)) - { - memcpy(ChnSettings[i].szName, lpStream+dwMemPos+1, chnnamlen); - StringFixer::SpaceToNullStringFixed(ChnSettings[i].szName, chnnamlen); - } - dwMemPos += chnnamlen + 1; - if (dwMemPos + chnnamlen + 256 >= dwMemLength) return true; - } - // packed comments (ignored) - UINT songtextlen = *((LPDWORD)(lpStream+dwMemPos)); - dwMemPos += songtextlen; - if (dwMemPos + 256 >= dwMemLength) return true; } - // Order List + + // Read Patterns + for(PATTERNINDEX pat = 0; pat < fileHeader.numPats; pat++) { - if ((dwMemPos + 2 * psh->orders) >= dwMemLength) return true; - Order.resize(psh->orders, Order.GetInvalidPatIndex()); - for (UINT iOrd = 0; iOrd < psh->orders; iOrd++) + uint32 patLength = file.ReadUint32LE(); + FileReader patternChunk = file.GetChunk(patLength); + + const ROWINDEX numRows = patternChunk.ReadUint8() + 1; + // We don't need to know the number of channels or commands. + patternChunk.Skip(1); + + if(Patterns.Insert(pat, numRows)) { - Order[iOrd] = (PATTERNINDEX)*((WORD *)(lpStream + dwMemPos)); - dwMemPos += 2; + continue; } - } - // Pattern Data - for (UINT ipat=0; ipat<psh->patterns; ipat++) - { - if (dwMemPos+8 >= dwMemLength) return true; - UINT packedlen = *((LPDWORD)(lpStream+dwMemPos)); - UINT numrows = 1 + (UINT)(lpStream[dwMemPos+4]); - //UINT patchn = 1 + (UINT)(lpStream[dwMemPos+5] & 0x1F); - //UINT patcmds = 1 + (UINT)(lpStream[dwMemPos+5] >> 5); - UINT patnamlen = lpStream[dwMemPos+6]; - dwMemPos += 4; - if ((ipat < MAX_PATTERNS) && (packedlen < dwMemLength-dwMemPos) && (numrows >= 8)) + + char patternName[11]; + ReadAMSString(patternName, patternChunk); + Patterns[pat].SetName(patternName); + + enum { - if ((patnamlen) && (patnamlen < MAX_PATTERNNAME)) + emptyRow = 0xFF, // No commands on row + endOfRowMask = 0x80, // If set, no more commands on this row + noteMask = 0x40, // If set, no note+instr in this command + channelMask = 0x1F, // Mask for extracting channel + + // Note flags + readNextCmd = 0x80, // One more command follows + noteDataMask = 0x7F, // Extract note + + // Command flags + volCommand = 0x40, // Effect is compressed volume command + commandMask = 0x3F, // Command or volume mask + }; + + // Extended (non-Protracker) effects + static const uint8 effTrans[] = + { + CMD_S3MCMDEX, // Forward / Backward + CMD_PORTAMENTOUP, // Extra fine slide up + CMD_PORTAMENTODOWN, // Extra fine slide up + CMD_RETRIG, // Retrigger + CMD_NONE, + CMD_TONEPORTAVOL, // Toneporta with fine volume slide + CMD_VIBRATOVOL, // Vibrato with fine volume slide + CMD_NONE, + CMD_PANNINGSLIDE, + CMD_NONE, + CMD_VOLUMESLIDE, // Two times finder volume slide than Axx + CMD_NONE, + CMD_CHANNELVOLUME, // Channel volume (0...127) + CMD_PATTERNBREAK, // Long pattern break (in hex) + CMD_S3MCMDEX, // Fine slide commands + CMD_NONE, // Fractional BPM + CMD_KEYOFF, // Key off at tick xx + CMD_PORTAMENTOUP, // Porta up, but uses all octaves (?) + CMD_PORTAMENTODOWN, // Porta down, but uses all octaves (?) + CMD_NONE, + CMD_NONE, + CMD_NONE, + CMD_NONE, + CMD_NONE, + CMD_NONE, + CMD_NONE, + CMD_GLOBALVOLSLIDE, // Global volume slide + CMD_NONE, + CMD_GLOBALVOLUME, // Global volume (0... 127) + }; + + for(ROWINDEX row = 0; row < numRows; row++) + { + PatternRow baseRow = Patterns[pat].GetRow(row); + while(patternChunk.BytesLeft()) { - char s[MAX_PATTERNNAME]; - memcpy(s, lpStream+dwMemPos+3, patnamlen); - StringFixer::SpaceToNullStringFixed(s, patnamlen); - SetPatternName(ipat, s); - } - if(Patterns.Insert(ipat, numrows)) return true; - // Unpack Pattern Data - LPCBYTE psrc = lpStream + dwMemPos; - UINT pos = 3 + patnamlen; - UINT row = 0; - while ((pos < packedlen) && (row < numrows)) - { - ModCommand *m = Patterns[ipat] + row * m_nChannels; - UINT byte1 = psrc[pos++]; - UINT ch = byte1 & 0x1F; - // Read Note + Instr - if (!(byte1 & 0x40)) + const uint8 flags = patternChunk.ReadUint8(); + if(flags == emptyRow) { - UINT byte2 = psrc[pos++]; - UINT note = byte2 & 0x7F; - if (note) m[ch].note = (note > 1) ? (note-1) : 0xFF; - m[ch].instr = psrc[pos++]; - // Read Effect - while (byte2 & 0x80) + break; + } + + ModCommand &m = baseRow[flags & channelMask]; + bool moreCommands = true; + if(!(flags & noteMask)) + { + uint8 note = patternChunk.ReadUint8(), instr = patternChunk.ReadUint8(); + moreCommands = (note & readNextCmd) != 0; + note &= noteDataMask; + + if(note == 1) { - byte2 = psrc[pos++]; - if (byte2 & 0x40) + m.note = NOTE_KEYOFF; + } else if(note >= 2 && note <= 121) + { + m.note = note - 2 + NOTE_MIN; + } + m.instr = instr; + } + + while(moreCommands) + { + ModCommand origCmd = m; + const uint8 command = patternChunk.ReadUint8(), effect = (command & commandMask); + moreCommands = (command & readNextCmd) != 0; + + if(command & volCommand) + { + m.volcmd = VOLCMD_VOLUME; + m.vol = effect; + } else + { + m.param = patternChunk.ReadUint8(); + + if(effect < 0x10) { - m[ch].volcmd = VOLCMD_VOLUME; - m[ch].vol = byte2 & 0x3F; + m.command = effect; + ConvertModCommand(m); + + switch(m.command) + { + case CMD_PANNING8: + // 4-Bit panning + m.command = CMD_PANNING8; + m.param = (m.param & 0x0F) * 0x11; + break; + + case CMD_VOLUME: + m.command = CMD_NONE; + m.volcmd = VOLCMD_VOLUME; + m.vol = Util::Min((m.param + 1) / 2, 64); + break; + + case CMD_MODCMDEX: + if(m.param == 0x80) + { + // Break sample loop (cut after loop) + m.command = CMD_NONE; + } else + { + m.ExtendedMODtoS3MEffect(); + } + break; + } } else { - UINT command = byte2 & 0x3F; - UINT param = psrc[pos++]; - if (command == 0x0C) + if(effect - 0x10 < CountOf(effTrans)) { - m[ch].volcmd = VOLCMD_VOLUME; - m[ch].vol = param / 2; - } else - if (command < 0x10) + m.command = effTrans[effect - 0x10]; + + switch(effect) + { + case 0x10: + // Play sample forwards / backwards + if(m.param <= 0x01) + { + m.param |= 0x9E; + } else + { + m.command = CMD_NONE; + } + break; + + case 0x11: + case 0x12: + // Extra fine slides + m.param = Util::Min(uint8(0x0F), m.param) | 0xE0; + break; + + case 0x15: + case 0x16: + // Fine slides + m.param = (Util::Min(0x10, m.param + 1) / 2) | 0xF0; + break; + + case 0x1E: + // More fine slides + switch(m.param >> 4) + { + case 0x1: + // Fine porta up + m.command = CMD_PORTAMENTOUP; + m.param |= 0xF0; + break; + case 0x2: + // Fine porta down + m.command = CMD_PORTAMENTODOWN; + m.param |= 0xF0; + break; + case 0xA: + // Extra fine volume slide up + m.command = CMD_VOLUMESLIDE; + m.param = ((((m.param & 0x0F) + 1) / 2) << 4) | 0x0F; + break; + case 0xB: + // Extra fine volume slide down + m.command = CMD_VOLUMESLIDE; + m.param = (((m.param & 0x0F) + 1) / 2) | 0xF0; + break; + default: + m.command = CMD_NONE; + break; + } + break; + + case 0x1C: + // Adjust channel volume range + m.param = Util::Min((m.param + 1) / 2, 64); + break; + } + } + } + + if(origCmd.command == CMD_VOLUMESLIDE && (m.command == CMD_VIBRATO || m.command == CMD_TONEPORTAVOL) && m.param == 0) + { + // Merge commands + if(m.command == CMD_VIBRATO) { - m[ch].command = command; - m[ch].param = param; - ConvertModCommand(&m[ch]); + m.command = CMD_VIBRATOVOL; } else { - // TODO: AMS effects + m.command = CMD_TONEPORTAVOL; } + m.param = origCmd.param; + origCmd.command = CMD_NONE; } + + if(ModCommand::GetEffectWeight(origCmd.command) > ModCommand::GetEffectWeight(m.command)) + { + if(ModCommand::ConvertVolEffect(m.command, m.param, true)) + { + // Volume column to the rescue! + m.volcmd = m.command; + m.vol = m.param; + } + + m.command = origCmd.command; + m.param = origCmd.param; + } } } - if (byte1 & 0x80) row++; + + if(flags & endOfRowMask) + { + // End of row + break; + } } } - dwMemPos += packedlen; } + // Read Samples - for (UINT iSmp=1; iSmp<=m_nSamples; iSmp++) if (Samples[iSmp].nLength) + for(SAMPLEINDEX smp = 0; smp < GetNumSamples(); smp++) { - if (dwMemPos >= dwMemLength - 9) return true; + if((sampleSettings[smp] & instrIndexMask) == 0) + { + // Only load samples that aren't part of a shadow instrument + SampleIO( + (Samples[smp + 1].uFlags & CHN_16BIT) ? SampleIO::_16bit : SampleIO::_8bit, + SampleIO::mono, + SampleIO::littleEndian, + (sampleSettings[smp] & packStatusMask) ? SampleIO::AMS : SampleIO::signedPCM) + .ReadSample(Samples[smp + 1], file); + } + } - dwMemPos += SampleIO( - (Samples[iSmp].uFlags & CHN_16BIT) ? SampleIO::_16bit : SampleIO::_8bit, - SampleIO::mono, - SampleIO::littleEndian, - (packedsamples[iSmp] & 0x03) ? SampleIO::AMS, SampleIO::signedPCM) - .ReadSample(Samples[iSmp], format, (LPSTR)(lpStream+dwMemPos), dwMemLength-dwMemPos); + // Copy shadow samples + for(SAMPLEINDEX smp = 0; smp < GetNumSamples(); smp++) + { + INSTRUMENTINDEX sourceInstr = (sampleSettings[smp] & instrIndexMask); + if(sourceInstr == 0 + || --sourceInstr >= firstSample.size()) + { + continue; + } + + SAMPLEINDEX sourceSample = ((sampleSettings[smp] & sampleIndexMask) >> sampleIndexShift) + firstSample[sourceInstr]; + if(sourceSample > GetNumSamples()) + { + continue; + } + + // Copy over original sample + ModSample &sample = Samples[smp + 1]; + ModSample &source = Samples[sourceSample]; + if(source.uFlags & CHN_16BIT) + { + sample.uFlags |= CHN_16BIT; + } else + { + sample.uFlags &= ~CHN_16BIT; + } + sample.nLength = source.nLength; + if(sample.AllocateSample()) + { + memcpy(sample.pSample, source.pSample, source.GetSampleSizeInBytes()); + AdjustSampleLoop(sample); + } } + return true; -#endif } + ///////////////////////////////////////////////////////////////////// // AMS Sample unpacking Modified: trunk/OpenMPT/soundlib/Loaders.h =================================================================== --- trunk/OpenMPT/soundlib/Loaders.h 2012-06-17 15:31:45 UTC (rev 1302) +++ trunk/OpenMPT/soundlib/Loaders.h 2012-06-18 00:42:57 UTC (rev 1303) @@ -12,6 +12,7 @@ #include "Endianness.h" #include "../common/misc_util.h" #include "../common/StringFixer.h" +#include <vector> // Execute "action" if "request_bytes" bytes cannot be read from stream at position "position" // DEPRECATED. Use FileReader instead. @@ -177,8 +178,7 @@ T target; if(Read(target)) { - SwapBytesLE(target); - return target; + return SwapBytesLE(target); } else { return 0; @@ -194,8 +194,7 @@ T target; if(Read(target)) { - SwapBytesBE(target); - return target; + return SwapBytesBE(target); } else { return 0; @@ -383,7 +382,6 @@ template<typename T, size_t destSize> bool ReadArray(T (&destArray)[destSize]) { - static_assert(sizeof(destArray) == sizeof(T) * destSize, "Huh?"); if(CanRead(sizeof(destArray))) { memcpy(destArray, streamData + streamPos, sizeof(destArray)); @@ -396,20 +394,40 @@ } } + // Read destSize elements of type T into a vector. + // If successful, the file cursor is advanced by the size of the vector. + // Otherwise, the vector is cleared. + template<typename T> + bool ReadVector(std::vector<T> &destVector, size_t destSize) + { + const size_t readSize = sizeof(T) * destSize; + if(CanRead(readSize)) + { + destVector.resize(destSize); + memcpy(&destVector[0], streamData + streamPos, readSize); + streamPos += readSize; + return true; + } else + { + destVector.clear(); + return false; + } + } + // Compare a magic string with the current stream position. // Returns true if they are identical. // The file cursor is advanced by the the length of the "magic" string. bool ReadMagic(const char *magic) { const size_t magicLength = strlen(magic); - if(!CanRead(magicLength)) + if(CanRead(magicLength)) { - return false; - } else - { bool result = !memcmp(streamData + streamPos, magic, magicLength); streamPos += magicLength; return result; + } else + { + return false; } } Modified: trunk/OpenMPT/soundlib/Snd_defs.h =================================================================== --- trunk/OpenMPT/soundlib/Snd_defs.h 2012-06-17 15:31:45 UTC (rev 1302) +++ trunk/OpenMPT/soundlib/Snd_defs.h 2012-06-18 00:42:57 UTC (rev 1303) @@ -107,6 +107,7 @@ MOD_TYPE_J2B = 0x800000, MOD_TYPE_MPT = 0x1000000, MOD_TYPE_IMF = 0x2000000, + MOD_TYPE_AMS2 = 0x4000000, MOD_TYPE_UMX = 0x80000000, // Fake type }; @@ -145,7 +146,7 @@ #define CHN_KEYOFF 0x200 // exit sustain #define CHN_NOTEFADE 0x400 // fade note (instrument mode) #define CHN_SURROUND 0x800 // use surround channel -#define CHN_NOIDO 0x1000 // Indicates if the channel is near enough to an exact multiple of the base frequency that any interpolation won't be noticeable - or if interpolation was switched off completely. --Storlek +#define CHN_NOIDO 0x1000 // (IDO = Interpolation Do?) Indicates if the channel is near enough to an exact multiple of the base frequency that any interpolation won't be noticeable - or if interpolation was switched off completely. --Storlek #define CHN_HQSRC 0x2000 // High quality sample rate conversion (i.e. apply interpolation) #define CHN_FILTER 0x4000 // filtered output #define CHN_VOLUMERAMP 0x8000 // ramp volume @@ -307,7 +308,7 @@ #define SNDMIX_MUTECHNMODE 0x100000 // Notes are not played on muted channels -#define MAX_GLOBAL_VOLUME 256 +#define MAX_GLOBAL_VOLUME 256u // Resampling modes enum { Modified: trunk/OpenMPT/soundlib/Sndfile.cpp =================================================================== --- trunk/OpenMPT/soundlib/Sndfile.cpp 2012-06-17 15:31:45 UTC (rev 1302) +++ trunk/OpenMPT/soundlib/Sndfile.cpp 2012-06-18 00:42:57 UTC (rev 1303) @@ -606,6 +606,7 @@ && !Read669(file) && !ReadFAR(file) && !ReadAMS(lpStream, dwMemLength) + && !ReadAMS2(file) && !ReadOKT(file) && !ReadPTM(lpStream, dwMemLength) && !ReadUlt(lpStream, dwMemLength) Modified: trunk/OpenMPT/soundlib/Sndfile.h =================================================================== --- trunk/OpenMPT/soundlib/Sndfile.h 2012-06-17 15:31:45 UTC (rev 1302) +++ trunk/OpenMPT/soundlib/Sndfile.h 2012-06-18 00:42:57 UTC (rev 1303) @@ -383,7 +383,7 @@ bool ReadDSM(const LPCBYTE lpStream, const DWORD dwMemLength); bool ReadFAR(FileReader &file); bool ReadAMS(const LPCBYTE lpStream, const DWORD dwMemLength); - bool ReadAMS2(const LPCBYTE lpStream, const DWORD dwMemLength); + bool ReadAMS2(FileReader &file); bool ReadMDL(const LPCBYTE lpStream, const DWORD dwMemLength); bool ReadOKT(FileReader &file); bool ReadDMF(const LPCBYTE lpStream, const DWORD dwMemLength); Modified: trunk/OpenMPT/soundlib/Tables.cpp =================================================================== --- trunk/OpenMPT/soundlib/Tables.cpp 2012-06-17 15:31:45 UTC (rev 1302) +++ trunk/OpenMPT/soundlib/Tables.cpp 2012-06-18 00:42:57 UTC (rev 1303) @@ -41,6 +41,7 @@ { MOD_TYPE_WAV, "Wave", ".wav", 0 }, { MOD_TYPE_MID, "Midi", ".mid", 0 }, { MOD_TYPE_AMS, "Extreme's Tracker", ".ams", 0 }, + { MOD_TYPE_AMS2, "Velvet Studio", ".ams", 0 }, { MOD_TYPE_AMF|MOD_TYPE_AMF0,"Asylum / DSMI", ".amf", 0 }, { MOD_TYPE_DSM, "DSIK Format", ".dsm", 0 }, { MOD_TYPE_DMF, "X-Tracker", ".dmf", 0 }, Modified: trunk/OpenMPT/soundlib/modcommand.cpp =================================================================== --- trunk/OpenMPT/soundlib/modcommand.cpp 2012-06-17 15:31:45 UTC (rev 1302) +++ trunk/OpenMPT/soundlib/modcommand.cpp 2012-06-18 00:42:57 UTC (rev 1303) @@ -852,8 +852,8 @@ return false; case CMD_VIBRATO: if(force) - param = min(param, 9); - else if(param > 9) + param = min(param & 0x0F, 9); + else if((param & 0x0F) > 9) return false; effect = VOLCMD_VIBRATODEPTH; break; This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |