// Translate instrument properties between two given formats. void ModInstrument::Convert(MODTYPE fromType, MODTYPE toType) //----------------------------------------------------------- { MPT_UNREFERENCED_PARAMETER(fromType); if(toType & MOD_TYPE_XM) { ResetNoteMap(); PitchEnv.dwFlags.reset(ENV_ENABLED | ENV_FILTER); dwFlags.reset(INS_SETPANNING); SetCutoff(GetCutoff(), false); SetResonance(GetResonance(), false); nFilterMode = FLTMODE_UNCHANGED; nCutSwing = nPanSwing = nResSwing = nVolSwing = 0; nPPC = NOTE_MIDDLEC - 1; nPPS = 0; nNNA = NNA_NOTECUT; nDCT = DCT_NONE; nDNA = DNA_NOTECUT; if(nMidiChannel == MidiMappedChannel) { nMidiChannel = 1; } // FT2 only has signed Pitch Wheel Depth, and it's limited to 0...36 (in the GUI, at least. As you would expect it from FT2, this value is actually not sanitized on load). midiPWD = static_cast<int8>(abs(midiPWD)); Limit(midiPWD, int8(0), int8(36)); nGlobalVol = 64; nPan = 128; LimitMax(nFadeOut, 32767u); } VolEnv.Convert(fromType, toType); PanEnv.Convert(fromType, toType); PitchEnv.Convert(fromType, toType); // Limit fadeout length for IT / MPT if(toType & (MOD_TYPE_IT | MOD_TYPE_MPT)) { LimitMax(nFadeOut, 8192u); } // MPT-specific features - remove instrument tunings, Pitch/Tempo Lock, cutoff / resonance swing and filter mode for other formats if(!(toType & MOD_TYPE_MPT)) { SetTuning(nullptr); wPitchToTempoLock = 0; nCutSwing = nResSwing = 0; nFilterMode = FLTMODE_UNCHANGED; } }
// Remove loop points if they're invalid. void ModSample::SanitizeLoops() //----------------------------- { LimitMax(nSustainEnd, nLength); LimitMax(nLoopEnd, nLength); if(nSustainStart >= nSustainEnd) { nSustainStart = nSustainEnd = 0; uFlags.reset(CHN_SUSTAINLOOP | CHN_PINGPONGSUSTAIN); } if(nLoopStart >= nLoopEnd) { nLoopStart = nLoopEnd = 0; uFlags.reset(CHN_LOOP | CHN_PINGPONGLOOP); } }
//Format: First bit tells whether the size indicator is 1 or 2 bytes. static void WriteAdaptive12String(std::ostream& oStrm, const std::string& str) //---------------------------------------------------------------------------- { uint16 s = static_cast<uint16>(str.size()); LimitMax(s, uint16(uint16_max / 2)); WriteAdaptive12(oStrm, s); oStrm.write(str.c_str(), s); }
void Gargle::RecalculateGargleParams() //------------------------------------ { m_period = m_SndFile.GetSampleRate() / RateInHertz(); if(m_period < 2) m_period = 2; m_periodHalf = m_period / 2; LimitMax(m_counter, m_period); }
void ModInstrument::Sanitize(MODTYPE modType) { LimitMax(nFadeOut, 65536u); LimitMax(nGlobalVol, 64u); LimitMax(nPan, 256u); LimitMax(wMidiBank, uint16(16384)); LimitMax(nMidiProgram, uint8(128)); LimitMax(nMidiChannel, uint8(17)); if(nNNA > NNA_NOTEFADE) nNNA = NNA_NOTECUT; if(nDCT > DCT_PLUGIN) nDCT = DCT_NONE; if(nDNA > DNA_NOTEFADE) nDNA = DNA_NOTECUT; LimitMax(nPanSwing, uint8(64)); LimitMax(nVolSwing, uint8(100)); Limit(nPPS, int8(-32), int8(32)); LimitMax(nCutSwing, uint8(64)); LimitMax(nResSwing, uint8(64)); #ifdef MODPLUG_TRACKER MPT_UNREFERENCED_PARAMETER(modType); const uint8 range = ENVELOPE_MAX; #else const uint8 range = modType == MOD_TYPE_AMS2 ? uint8_max : ENVELOPE_MAX; #endif VolEnv.Sanitize(); PanEnv.Sanitize(); PitchEnv.Sanitize(range); for(size_t i = 0; i < CountOf(NoteMap); i++) { if(NoteMap[i] < NOTE_MIN || NoteMap[i] > NOTE_MAX) NoteMap[i] = static_cast<uint8>(i + NOTE_MIN); } }
// Convert an XMSample to OpenMPT's internal sample representation. void XMSample::ConvertToMPT(ModSample &mptSmp) const //-------------------------------------------------- { mptSmp.Initialize(MOD_TYPE_XM); // Volume mptSmp.nVolume = vol * 4; LimitMax(mptSmp.nVolume, uint16(256)); // Panning mptSmp.nPan = pan; mptSmp.uFlags = CHN_PANNING; // Sample Frequency mptSmp.nFineTune = finetune; mptSmp.RelativeTone = relnote; // Sample Length and Loops mptSmp.nLength = length; mptSmp.nLoopStart = loopStart; mptSmp.nLoopEnd = mptSmp.nLoopStart + loopLength; if((flags & XMSample::sample16Bit)) { mptSmp.nLength /= 2; mptSmp.nLoopStart /= 2; mptSmp.nLoopEnd /= 2; } if((flags & XMSample::sampleStereo)) { mptSmp.nLength /= 2; mptSmp.nLoopStart /= 2; mptSmp.nLoopEnd /= 2; } if((flags & (XMSample::sampleLoop | XMSample::sampleBidiLoop)) && mptSmp.nLoopStart < mptSmp.nLength && mptSmp.nLoopEnd > mptSmp.nLoopStart) { mptSmp.uFlags.set(CHN_LOOP); if((flags & XMSample::sampleBidiLoop)) { mptSmp.uFlags.set(CHN_PINGPONGLOOP); } } mptSmp.SanitizeLoops(); strcpy(mptSmp.filename, ""); }
void ReadModPatterns(std::istream& iStrm, CPatternContainer& patc, const size_t) //-------------------------------------------------------------------------------- { srlztn::Ssb ssb(iStrm); ssb.BeginRead(FileIdPatterns, MptVersion::num); if ((ssb.m_Status & srlztn::SNT_FAILURE) != 0) return; modplug::tracker::patternindex_t nPatterns = patc.Size(); uint16_t nCount = UINT16_MAX; if (ssb.ReadItem(nCount, "num") != srlztn::Ssb::EntryNotFound) nPatterns = nCount; LimitMax(nPatterns, ModSpecs::mptm.patternsMax); if (nPatterns > patc.Size()) patc.ResizeArray(nPatterns); for(uint16_t i = 0; i < nPatterns; i++) { ssb.ReadItem(patc[i], &i, sizeof(i), &ReadModPattern); } }
void ReadModPatterns(std::istream& iStrm, CPatternContainer& patc, const size_t) //-------------------------------------------------------------------------------- { srlztn::SsbRead ssb(iStrm); ssb.BeginRead(FileIdPatterns, MptVersion::num); if ((ssb.m_Status & srlztn::SNT_FAILURE) != 0) return; PATTERNINDEX nPatterns = patc.Size(); uint16 nCount = uint16_max; if (ssb.ReadItem(nCount, "num") != srlztn::SsbRead::EntryNotFound) nPatterns = nCount; LimitMax(nPatterns, ModSpecs::mptm.patternsMax); if (nPatterns > patc.Size()) patc.ResizeArray(nPatterns); for(uint16 i = 0; i < nPatterns; i++) { ssb.ReadItem(patc[i], srlztn::IdLE<uint16>(i).GetChars(), sizeof(i), &ReadModPattern); } }
void InstrumentEnvelope::Sanitize(uint8 maxValue) { if(!empty()) { front().tick = 0; LimitMax(front().value, maxValue); for(iterator it = begin() + 1; it != end(); it++) { it->tick = std::max(it->tick, (it - 1)->tick); LimitMax(it->value, maxValue); } } LimitMax(nLoopEnd, static_cast<decltype(nLoopEnd)>(size() - 1)); LimitMax(nLoopStart, nLoopEnd); LimitMax(nSustainEnd, static_cast<decltype(nSustainEnd)>(size() - 1)); LimitMax(nSustainStart, nSustainEnd); if(nReleaseNode != ENV_RELEASE_NODE_UNSET) LimitMax(nReleaseNode, static_cast<decltype(nReleaseNode)>(size() - 1)); }
// Simple 2-poles resonant filter void CSoundFile::SetupChannelFilter(ModChannel *pChn, bool bReset, int flt_modifier) const { int cutoff = (int)pChn->nCutOff + (int)pChn->nCutSwing; int resonance = (int)(pChn->nResonance & 0x7F) + (int)pChn->nResSwing; Limit(cutoff, 0, 127); Limit(resonance, 0, 127); if(!m_playBehaviour[kMPTOldSwingBehaviour]) { pChn->nCutOff = (uint8)cutoff; pChn->nCutSwing = 0; pChn->nResonance = (uint8)resonance; pChn->nResSwing = 0; } // flt_modifier is in [-256, 256], so cutoff is in [0, 127 * 2] after this calculation. const int computedCutoff = cutoff * (flt_modifier + 256) / 256; // Filtering is only ever done in IT if either cutoff is not full or if resonance is set. if(m_playBehaviour[kITFilterBehaviour] && resonance == 0 && computedCutoff >= 254) { if(pChn->rowCommand.IsNote() && !pChn->rowCommand.IsPortamento() && !pChn->nMasterChn && m_SongFlags[SONG_FIRSTTICK]) { // Z7F next to a note disables the filter, however in other cases this should not happen. // Test cases: filter-reset.it, filter-reset-carry.it, filter-nna.it pChn->dwFlags.reset(CHN_FILTER); } return; } pChn->dwFlags.set(CHN_FILTER); // 2 * damping factor const float dmpfac = std::pow(10.0f, -resonance * ((24.0f / 128.0f) / 20.0f)); const float fc = CutOffToFrequency(cutoff, flt_modifier) * (2.0f * (float)M_PI); float d, e; if(m_playBehaviour[kITFilterBehaviour] && !m_SongFlags[SONG_EXFILTERRANGE]) { const float r = m_MixerSettings.gdwMixingFreq / fc; d = dmpfac * r + dmpfac - 1.0f; e = r * r; } else { const float r = fc / m_MixerSettings.gdwMixingFreq; d = (1.0f - 2.0f * dmpfac) * r; LimitMax(d, 2.0f); d = (2.0f * dmpfac - d) / r; e = 1.0f / (r * r); } float fg = 1.0f / (1.0f + d + e); float fb0 = (d + e + e) / (1 + d + e); float fb1 = -e / (1.0f + d + e); #if defined(MPT_INTMIXER) #define FILTER_CONVERT(x) Util::Round<mixsample_t>((x) * (1 << MIXING_FILTER_PRECISION)) #else #define FILTER_CONVERT(x) (x) #endif switch(pChn->nFilterMode) { case FLTMODE_HIGHPASS: pChn->nFilter_A0 = FILTER_CONVERT(1.0f - fg); pChn->nFilter_B0 = FILTER_CONVERT(fb0); pChn->nFilter_B1 = FILTER_CONVERT(fb1); #ifdef MPT_INTMIXER pChn->nFilter_HP = -1; #else pChn->nFilter_HP = 1.0f; #endif // MPT_INTMIXER break; default: pChn->nFilter_A0 = FILTER_CONVERT(fg); pChn->nFilter_B0 = FILTER_CONVERT(fb0); pChn->nFilter_B1 = FILTER_CONVERT(fb1); #ifdef MPT_INTMIXER if(pChn->nFilter_A0 == 0) pChn->nFilter_A0 = 1; // Prevent silence at low filter cutoff and very high sampling rate pChn->nFilter_HP = 0; #else pChn->nFilter_HP = 0; #endif // MPT_INTMIXER break; } #undef FILTER_CONVERT if (bReset) { pChn->nFilter_Y[0][0] = pChn->nFilter_Y[0][1] = 0; pChn->nFilter_Y[1][0] = pChn->nFilter_Y[1][1] = 0; } }
// Translate sample properties between two given formats. void ModSample::Convert(MODTYPE fromType, MODTYPE toType) //------------------------------------------------------- { // Convert between frequency and transpose values if necessary. if((!(toType & (MOD_TYPE_MOD | MOD_TYPE_XM))) && (fromType & (MOD_TYPE_MOD | MOD_TYPE_XM))) { TransposeToFrequency(); RelativeTone = 0; nFineTune = 0; } else if((toType & (MOD_TYPE_MOD | MOD_TYPE_XM)) && (!(fromType & (MOD_TYPE_MOD | MOD_TYPE_XM)))) { FrequencyToTranspose(); if(toType & MOD_TYPE_MOD) { RelativeTone = 0; } } // No ping-pong loop, panning and auto-vibrato for MOD / S3M samples if(toType & (MOD_TYPE_MOD | MOD_TYPE_S3M)) { uFlags &= ~(CHN_PINGPONGLOOP | CHN_PANNING); nVibDepth = 0; nVibRate = 0; nVibSweep = 0; nVibType = VIB_SINE; } // No global volume sustain loops for MOD/S3M/XM if(toType & (MOD_TYPE_MOD | MOD_TYPE_XM | MOD_TYPE_S3M)) { nGlobalVol = 64; // Sustain loops - convert to normal loops if((uFlags & CHN_SUSTAINLOOP) != 0) { // We probably overwrite a normal loop here, but since sustain loops are evaluated before normal loops, this is just correct. nLoopStart = nSustainStart; nLoopEnd = nSustainEnd; uFlags |= CHN_LOOP; if(uFlags & CHN_PINGPONGSUSTAIN) { uFlags |= CHN_PINGPONGLOOP; } else { uFlags &= ~CHN_PINGPONGLOOP; } } nSustainStart = nSustainEnd = 0; uFlags &= ~(CHN_SUSTAINLOOP|CHN_PINGPONGSUSTAIN); } // All XM samples have default panning, and XM's autovibrato settings are rather limited. if(toType & MOD_TYPE_XM) { if(!(uFlags & CHN_PANNING)) { uFlags |= CHN_PANNING; nPan = 128; } LimitMax(nVibDepth, BYTE(15)); LimitMax(nVibRate, BYTE(63)); } // Autovibrato sweep setting is inverse in XM (0 = "no sweep") and IT (0 = "no vibrato") if(((fromType & MOD_TYPE_XM) && (toType & (MOD_TYPE_IT | MOD_TYPE_MPT))) || ((toType & MOD_TYPE_XM) && (fromType & (MOD_TYPE_IT | MOD_TYPE_MPT)))) { if(nVibRate != 0 && nVibDepth != 0) { nVibSweep = 255 - nVibSweep; } } }
// Translate instrument properties between two given formats. void ModInstrument::Convert(MODTYPE fromType, MODTYPE toType) { MPT_UNREFERENCED_PARAMETER(fromType); if(toType & MOD_TYPE_XM) { ResetNoteMap(); PitchEnv.dwFlags.reset(ENV_ENABLED | ENV_FILTER); dwFlags.reset(INS_SETPANNING); SetCutoff(GetCutoff(), false); SetResonance(GetResonance(), false); nFilterMode = FLTMODE_UNCHANGED; nCutSwing = nPanSwing = nResSwing = nVolSwing = 0; nPPC = NOTE_MIDDLEC - 1; nPPS = 0; nNNA = NNA_NOTECUT; nDCT = DCT_NONE; nDNA = DNA_NOTECUT; if(nMidiChannel == MidiMappedChannel) { nMidiChannel = 1; } // FT2 only has unsigned Pitch Wheel Depth, and it's limited to 0...36 (in the GUI, at least. As you would expect it from FT2, this value is actually not sanitized on load). midiPWD = static_cast<int8>(mpt::abs(midiPWD)); Limit(midiPWD, int8(0), int8(36)); nGlobalVol = 64; nPan = 128; LimitMax(nFadeOut, 32767u); } VolEnv.Convert(fromType, toType); PanEnv.Convert(fromType, toType); PitchEnv.Convert(fromType, toType); if(fromType == MOD_TYPE_XM && (toType & (MOD_TYPE_IT | MOD_TYPE_MPT))) { if(!VolEnv.dwFlags[ENV_ENABLED]) { // Note-Off with no envelope cuts the note immediately in XM VolEnv.resize(2); VolEnv[0].tick = 0; VolEnv[0].value = ENVELOPE_MAX; VolEnv[1].tick = 1; VolEnv[1].value = ENVELOPE_MIN; VolEnv.dwFlags.set(ENV_ENABLED | ENV_SUSTAIN); VolEnv.dwFlags.reset(ENV_LOOP); VolEnv.nSustainStart = VolEnv.nSustainEnd = 0; } } // Limit fadeout length for IT if(toType & MOD_TYPE_IT) { LimitMax(nFadeOut, 8192u); } // MPT-specific features - remove instrument tunings, Pitch/Tempo Lock, cutoff / resonance swing and filter mode for other formats if(!(toType & MOD_TYPE_MPT)) { SetTuning(nullptr); pitchToTempoLock.Set(0); nCutSwing = nResSwing = 0; nFilterMode = FLTMODE_UNCHANGED; nVolRampUp = 0; } }
// Check how many samples can be rendered without encountering loop or sample end, and also update loop position / direction MPT_FORCEINLINE uint32 GetSampleCount(ModChannel &chn, uint32 nSamples, bool ITPingPongMode) const { int32 nLoopStart = chn.dwFlags[CHN_LOOP] ? chn.nLoopStart : 0; SamplePosition nInc = chn.increment; if ((nSamples <= 0) || nInc.IsZero() || (!chn.nLength)) return 0; // Part 1: Making sure the play position is valid, and if necessary, invert the play direction in case we reached a loop boundary of a ping-pong loop. chn.pCurrentSample = samplePointer; // Under zero ? if (chn.position.GetInt() < nLoopStart) { if (nInc.IsNegative()) { // Invert loop for bidi loops chn.position = SamplePosition(nLoopStart + nLoopStart, 0) - chn.position; if ((chn.position.GetInt() < nLoopStart) || (chn.position.GetUInt() >= (nLoopStart + chn.nLength) / 2)) { chn.position.Set(nLoopStart, 0); } nInc.Negate(); chn.increment = nInc; if(chn.dwFlags[CHN_PINGPONGLOOP]) { chn.dwFlags.reset(CHN_PINGPONGFLAG); // go forward } else { chn.dwFlags.set(CHN_PINGPONGFLAG); chn.position.SetInt(chn.nLength - 1); chn.increment.Negate(); } if(!chn.dwFlags[CHN_LOOP] || chn.position.GetUInt() >= chn.nLength) { chn.position.Set(chn.nLength); return 0; } } else { // We probably didn't hit the loop end yet (first loop), so we do nothing if (chn.position.GetInt() < 0) chn.position.SetInt(0); } } else if (chn.position.GetUInt() >= chn.nLength) { // Past the end if(!chn.dwFlags[CHN_LOOP]) return 0; // not looping -> stop this channel if(chn.dwFlags[CHN_PINGPONGLOOP]) { // Invert loop if (nInc.IsPositive()) { nInc.Negate(); chn.increment = nInc; } chn.dwFlags.set(CHN_PINGPONGFLAG); // adjust loop position SamplePosition invFract = chn.position.GetInvertedFract(); chn.position = SamplePosition(chn.nLength - (chn.position.GetInt() - chn.nLength) - invFract.GetInt(), invFract.GetFract()); if ((chn.position.GetUInt() <= chn.nLoopStart) || (chn.position.GetUInt() >= chn.nLength)) { // Impulse Tracker's software mixer would put a -2 (instead of -1) in the following line (doesn't happen on a GUS) chn.position.SetInt(chn.nLength - std::min<SmpLength>(chn.nLength, ITPingPongMode ? 2 : 1)); } } else { if (nInc.IsNegative()) // This is a bug { nInc.Negate(); chn.increment = nInc; } // Restart at loop start chn.position += SamplePosition(nLoopStart - chn.nLength, 0); MPT_ASSERT(chn.position.GetInt() >= nLoopStart); // Interpolate correctly after wrapping around chn.dwFlags.set(CHN_WRAPPED_LOOP); } } // Part 2: Compute how many samples we can render until we reach the end of sample / loop boundary / etc. SamplePosition nPos = chn.position; // too big increment, and/or too small loop length if (nPos.GetInt() < nLoopStart) { if (nPos.IsNegative() || nInc.IsNegative()) return 0; } if (nPos.IsNegative() || nPos.GetUInt() >= chn.nLength) return 0; uint32 nSmpCount = nSamples; SamplePosition nInv = nInc; if (nInc.IsNegative()) { nInv.Negate(); } LimitMax(nSamples, maxSamples); SamplePosition incSamples = nInc * (nSamples - 1); int32 nPosDest = (nPos + incSamples).GetInt(); const SmpLength nPosInt = nPos.GetUInt(); const bool isAtLoopStart = (nPosInt >= chn.nLoopStart && nPosInt < chn.nLoopStart + InterpolationMaxLookahead); if(!isAtLoopStart) { chn.dwFlags.reset(CHN_WRAPPED_LOOP); } // Loop wrap-around magic. bool checkDest = true; if(lookaheadPointer != nullptr) { if(nPos.GetUInt() >= lookaheadStart) { #if 0 const uint32 oldCount = nSmpCount; // When going backwards - we can only go back up to lookaheadStart. // When going forwards - read through the whole pre-computed wrap-around buffer if possible. // TODO: ProTracker sample swapping needs hard cut at sample end. int32 samplesToRead = nInc.IsNegative() ? (nPosInt - lookaheadStart) //: 2 * InterpolationMaxLookahead - (nPosInt - mixLoopState.lookaheadStart); : (chn.nLoopEnd - nPosInt); //LimitMax(samplesToRead, chn.nLoopEnd - chn.nLoopStart); nSmpCount = SamplesToBufferLength(samplesToRead, chn); Limit(nSmpCount, 1u, oldCount); #else if (nInc.IsNegative()) { nSmpCount = DistanceToBufferLength(SamplePosition(lookaheadStart, 0), nPos, nInv); } else { nSmpCount = DistanceToBufferLength(nPos, SamplePosition(chn.nLoopEnd, 0), nInv); } #endif chn.pCurrentSample = lookaheadPointer; checkDest = false; } else if(chn.dwFlags[CHN_WRAPPED_LOOP] && isAtLoopStart) { // We just restarted the loop, so interpolate correctly after wrapping around nSmpCount = DistanceToBufferLength(nPos, SamplePosition(nLoopStart + InterpolationMaxLookahead, 0), nInv); chn.pCurrentSample = lookaheadPointer + (chn.nLoopEnd - nLoopStart) * chn.pModSample->GetBytesPerSample(); checkDest = false; } else if(nInc.IsPositive() && static_cast<SmpLength>(nPosDest) >= lookaheadStart && nSmpCount > 1) { // We shouldn't read that far if we're not using the pre-computed wrap-around buffer. nSmpCount = DistanceToBufferLength(nPos, SamplePosition(lookaheadStart, 0), nInv); checkDest = false; } } if(checkDest) { // Fix up sample count if target position is invalid if (nInc.IsNegative()) { if (nPosDest < nLoopStart) { nSmpCount = DistanceToBufferLength(SamplePosition(chn.nLoopStart, 0), nPos, nInv); } } else { if (nPosDest >= (int32)chn.nLength) { nSmpCount = DistanceToBufferLength(nPos, SamplePosition(chn.nLength, 0), nInv); } } } Limit(nSmpCount, 1u, nSamples); #ifdef _DEBUG { SmpLength posDest = (nPos + nInc * (nSmpCount - 1)).GetUInt(); if (posDest < 0 || posDest > chn.nLength) { // We computed an invalid delta! MPT_ASSERT_NOTREACHED(); return 0; } } #endif return nSmpCount; }
OPENMPT_NAMESPACE_BEGIN #if MPT_COMPILER_GCC #if MPT_GCC_AT_LEAST(4,6,0) #pragma GCC diagnostic push #endif #pragma GCC diagnostic ignored "-Wswitch" #elif MPT_COMPILER_CLANG #pragma clang diagnostic push #if MPT_CLANG_AT_LEAST(3,3,0) #pragma clang diagnostic ignored "-Wswitch" #else #pragma clang diagnostic ignored "-Wswitch-enum" #endif #endif // Read a sample from memory size_t SampleIO::ReadSample(ModSample &sample, FileReader &file) const //-------------------------------------------------------------------- { if(sample.nLength < 1 || !file.IsValid()) { return 0; } LimitMax(sample.nLength, MAX_SAMPLE_LENGTH); const char * const sourceBuf = file.GetRawData(); const FileReader::off_t fileSize = file.BytesLeft(), filePosition = file.GetPosition(); FileReader::off_t bytesRead = 0; // Amount of memory that has been read from file sample.uFlags.set(CHN_16BIT, GetBitDepth() >= 16); sample.uFlags.set(CHN_STEREO, GetChannelFormat() != mono); size_t sampleSize = sample.AllocateSample(); // Target sample size in bytes if(sampleSize == 0) { sample.nLength = 0; return 0; } ASSERT(sampleSize >= sample.GetSampleSizeInBytes()); ////////////////////////////////////////////////////// // 8-Bit / Mono / PCM if(GetBitDepth() == 8 && GetChannelFormat() == mono) { switch(GetEncoding()) { case signedPCM: // 8-Bit / Mono / Signed / PCM bytesRead = CopyMonoSample<SC::DecodeInt8>(sample, sourceBuf, fileSize); break; case unsignedPCM: // 8-Bit / Mono / Unsigned / PCM bytesRead = CopyMonoSample<SC::DecodeUint8>(sample, sourceBuf, fileSize); break; case deltaPCM: // 8-Bit / Mono / Delta / PCM case MT2: bytesRead = CopyMonoSample<SC::DecodeInt8Delta>(sample, sourceBuf, fileSize); break; case PCM7to8: // 7 Bit stored as 8-Bit with highest bit unused / Mono / Signed / PCM bytesRead = CopyMonoSample<SC::DecodeInt7>(sample, sourceBuf, fileSize); break; } } ////////////////////////////////////////////////////// // 8-Bit / Stereo Split / PCM else if(GetBitDepth() == 8 && GetChannelFormat() == stereoSplit) { switch(GetEncoding()) { case signedPCM: // 8-Bit / Stereo Split / Signed / PCM bytesRead = CopyStereoSplitSample<SC::DecodeInt8>(sample, sourceBuf, fileSize); break; case unsignedPCM: // 8-Bit / Stereo Split / Unsigned / PCM bytesRead = CopyStereoSplitSample<SC::DecodeUint8>(sample, sourceBuf, fileSize); break; case deltaPCM: // 8-Bit / Stereo Split / Delta / PCM case MT2: bytesRead = CopyStereoSplitSample<SC::DecodeInt8Delta>(sample, sourceBuf, fileSize); break; } } ////////////////////////////////////////////////////// // 8-Bit / Stereo Interleaved / PCM else if(GetBitDepth() == 8 && GetChannelFormat() == stereoInterleaved) { switch(GetEncoding()) { case signedPCM: // 8-Bit / Stereo Interleaved / Signed / PCM bytesRead = CopyStereoInterleavedSample<SC::DecodeInt8>(sample, sourceBuf, fileSize); break; case unsignedPCM: // 8-Bit / Stereo Interleaved / Unsigned / PCM bytesRead = CopyStereoInterleavedSample<SC::DecodeUint8>(sample, sourceBuf, fileSize); break; case deltaPCM: // 8-Bit / Stereo Interleaved / Delta / PCM bytesRead = CopyStereoInterleavedSample<SC::DecodeInt8Delta>(sample, sourceBuf, fileSize); break; } } ////////////////////////////////////////////////////// // 16-Bit / Mono / Little Endian / PCM else if(GetBitDepth() == 16 && GetChannelFormat() == mono && GetEndianness() == littleEndian) { switch(GetEncoding()) { case signedPCM: // 16-Bit / Stereo Interleaved / Signed / PCM bytesRead = CopyMonoSample<SC::DecodeInt16<0, littleEndian16> >(sample, sourceBuf, fileSize); break; case unsignedPCM: // 16-Bit / Stereo Interleaved / Unsigned / PCM bytesRead = CopyMonoSample<SC::DecodeInt16<0x8000u, littleEndian16> >(sample, sourceBuf, fileSize); break; case deltaPCM: // 16-Bit / Stereo Interleaved / Delta / PCM case MT2: bytesRead = CopyMonoSample<SC::DecodeInt16Delta<littleEndian16> >(sample, sourceBuf, fileSize); break; } } ////////////////////////////////////////////////////// // 16-Bit / Mono / Big Endian / PCM else if(GetBitDepth() == 16 && GetChannelFormat() == mono && GetEndianness() == bigEndian) { switch(GetEncoding()) { case signedPCM: // 16-Bit / Mono / Signed / PCM bytesRead = CopyMonoSample<SC::DecodeInt16<0, bigEndian16> >(sample, sourceBuf, fileSize); break; case unsignedPCM: // 16-Bit / Mono / Unsigned / PCM bytesRead = CopyMonoSample<SC::DecodeInt16<0x8000u, bigEndian16> >(sample, sourceBuf, fileSize); break; case deltaPCM: // 16-Bit / Mono / Delta / PCM bytesRead = CopyMonoSample<SC::DecodeInt16Delta<bigEndian16> >(sample, sourceBuf, fileSize); break; } } ////////////////////////////////////////////////////// // 16-Bit / Stereo Split / Little Endian / PCM else if(GetBitDepth() == 16 && GetChannelFormat() == stereoSplit && GetEndianness() == littleEndian) { switch(GetEncoding()) { case signedPCM: // 16-Bit / Stereo Split / Signed / PCM bytesRead = CopyStereoSplitSample<SC::DecodeInt16<0, littleEndian16> >(sample, sourceBuf, fileSize); break; case unsignedPCM: // 16-Bit / Stereo Split / Unsigned / PCM bytesRead = CopyStereoSplitSample<SC::DecodeInt16<0x8000u, littleEndian16> >(sample, sourceBuf, fileSize); break; case deltaPCM: // 16-Bit / Stereo Split / Delta / PCM case MT2: bytesRead = CopyStereoSplitSample<SC::DecodeInt16Delta<littleEndian16> >(sample, sourceBuf, fileSize); break; } } ////////////////////////////////////////////////////// // 16-Bit / Stereo Split / Big Endian / PCM else if(GetBitDepth() == 16 && GetChannelFormat() == stereoSplit && GetEndianness() == bigEndian) { switch(GetEncoding()) { case signedPCM: // 16-Bit / Stereo Split / Signed / PCM bytesRead = CopyStereoSplitSample<SC::DecodeInt16<0, bigEndian16> >(sample, sourceBuf, fileSize); break; case unsignedPCM: // 16-Bit / Stereo Split / Unsigned / PCM bytesRead = CopyStereoSplitSample<SC::DecodeInt16<0x8000u, bigEndian16> >(sample, sourceBuf, fileSize); break; case deltaPCM: // 16-Bit / Stereo Split / Delta / PCM bytesRead = CopyStereoSplitSample<SC::DecodeInt16Delta<bigEndian16> >(sample, sourceBuf, fileSize); break; } } ////////////////////////////////////////////////////// // 16-Bit / Stereo Interleaved / Little Endian / PCM else if(GetBitDepth() == 16 && GetChannelFormat() == stereoInterleaved && GetEndianness() == littleEndian) { switch(GetEncoding()) { case signedPCM: // 16-Bit / Stereo Interleaved / Signed / PCM bytesRead = CopyStereoInterleavedSample<SC::DecodeInt16<0, littleEndian16> >(sample, sourceBuf, fileSize); break; case unsignedPCM: // 16-Bit / Stereo Interleaved / Unsigned / PCM bytesRead = CopyStereoInterleavedSample<SC::DecodeInt16<0x8000u, littleEndian16> >(sample, sourceBuf, fileSize); break; case deltaPCM: // 16-Bit / Stereo Interleaved / Delta / PCM bytesRead = CopyStereoInterleavedSample<SC::DecodeInt16Delta<littleEndian16> >(sample, sourceBuf, fileSize); break; } } ////////////////////////////////////////////////////// // 16-Bit / Stereo Interleaved / Big Endian / PCM else if(GetBitDepth() == 16 && GetChannelFormat() == stereoInterleaved && GetEndianness() == bigEndian) { switch(GetEncoding()) { case signedPCM: // 16-Bit / Stereo Interleaved / Signed / PCM bytesRead = CopyStereoInterleavedSample<SC::DecodeInt16<0, bigEndian16> >(sample, sourceBuf, fileSize); break; case unsignedPCM: // 16-Bit / Stereo Interleaved / Unsigned / PCM bytesRead = CopyStereoInterleavedSample<SC::DecodeInt16<0x8000u, bigEndian16> >(sample, sourceBuf, fileSize); break; case deltaPCM: // 16-Bit / Stereo Interleaved / Delta / PCM bytesRead = CopyStereoInterleavedSample<SC::DecodeInt16Delta<bigEndian16> >(sample, sourceBuf, fileSize); break; } } ////////////////////////////////////////////////////// // 24-Bit / Signed / Mono / PCM else if(GetBitDepth() == 24 && GetChannelFormat() == mono && GetEncoding() == signedPCM) { if(GetEndianness() == littleEndian) { bytesRead = CopyMonoSample<SC::ConversionChain<SC::Convert<int16, int32>, SC::DecodeInt24<0, littleEndian24> > >(sample, sourceBuf, fileSize); } else { bytesRead = CopyMonoSample<SC::ConversionChain<SC::Convert<int16, int32>, SC::DecodeInt24<0, bigEndian24> > >(sample, sourceBuf, fileSize); } } ////////////////////////////////////////////////////// // 24-Bit / Signed / Stereo Interleaved / PCM else if(GetBitDepth() == 24 && GetChannelFormat() == stereoInterleaved && GetEncoding() == signedPCM) { if(GetEndianness() == littleEndian) { bytesRead = CopyStereoInterleavedSample<SC::ConversionChain<SC::Convert<int16, int32>, SC::DecodeInt24<0, littleEndian24> > >(sample, sourceBuf, fileSize); } else { bytesRead = CopyStereoInterleavedSample<SC::ConversionChain<SC::Convert<int16, int32>, SC::DecodeInt24<0, bigEndian24> > >(sample, sourceBuf, fileSize); } } ////////////////////////////////////////////////////// // 32-Bit / Signed / Mono / PCM else if(GetBitDepth() == 32 && GetChannelFormat() == mono && GetEncoding() == signedPCM) { if(GetEndianness() == littleEndian) { bytesRead = CopyMonoSample<SC::ConversionChain<SC::Convert<int16, int32>, SC::DecodeInt32<0, littleEndian32> > >(sample, sourceBuf, fileSize); } else { bytesRead = CopyMonoSample<SC::ConversionChain<SC::Convert<int16, int32>, SC::DecodeInt32<0, bigEndian32> > >(sample, sourceBuf, fileSize); } } ////////////////////////////////////////////////////// // 32-Bit / Signed / Stereo Interleaved / PCM else if(GetBitDepth() == 32 && GetChannelFormat() == stereoInterleaved && GetEncoding() == signedPCM) { if(GetEndianness() == littleEndian) { bytesRead = CopyStereoInterleavedSample<SC::ConversionChain<SC::Convert<int16, int32>, SC::DecodeInt32<0, littleEndian32> > >(sample, sourceBuf, fileSize); } else { bytesRead = CopyStereoInterleavedSample<SC::ConversionChain<SC::Convert<int16, int32>, SC::DecodeInt32<0, bigEndian32> > >(sample, sourceBuf, fileSize); } } ////////////////////////////////////////////////////// // 32-Bit / Float / Mono / PCM else if(GetBitDepth() == 32 && GetChannelFormat() == mono && GetEncoding() == floatPCM) { if(GetEndianness() == littleEndian) { bytesRead = CopyMonoSample<SC::ConversionChain<SC::Convert<int16, float32>, SC::DecodeFloat32<littleEndian32> > >(sample, sourceBuf, fileSize); } else { bytesRead = CopyMonoSample<SC::ConversionChain<SC::Convert<int16, float32>, SC::DecodeFloat32<bigEndian32> > >(sample, sourceBuf, fileSize); } } ////////////////////////////////////////////////////// // 32-Bit / Float / Stereo Interleaved / PCM else if(GetBitDepth() == 32 && GetChannelFormat() == stereoInterleaved && GetEncoding() == floatPCM) { if(GetEndianness() == littleEndian) { bytesRead = CopyStereoInterleavedSample<SC::ConversionChain<SC::Convert<int16, float32>, SC::DecodeFloat32<littleEndian32> > >(sample, sourceBuf, fileSize); } else { bytesRead = CopyStereoInterleavedSample<SC::ConversionChain<SC::Convert<int16, float32>, SC::DecodeFloat32<bigEndian32> > >(sample, sourceBuf, fileSize); } } ////////////////////////////////////////////////////// // 24-Bit / Signed / Mono, Stereo Interleaved / PCM else if(GetBitDepth() == 24 && (GetChannelFormat() == mono || GetChannelFormat() == stereoInterleaved) && GetEncoding() == signedPCMnormalize) { // Normalize to 16-Bit uint32 srcPeak = uint32(1)<<31; if(GetEndianness() == littleEndian) { bytesRead = CopyAndNormalizeSample<SC::NormalizationChain<SC::Convert<int16, int32>, SC::DecodeInt24<0, littleEndian24> > >(sample, sourceBuf, fileSize, &srcPeak); } else { bytesRead = CopyAndNormalizeSample<SC::NormalizationChain<SC::Convert<int16, int32>, SC::DecodeInt24<0, bigEndian24> > >(sample, sourceBuf, fileSize, &srcPeak); } if(bytesRead) { // Adjust sample volume so we do not affect relative volume of the sample. Normalizing is only done to increase precision. sample.nGlobalVol = static_cast<uint16>(Clamp(Util::muldivr_unsigned(sample.nGlobalVol, srcPeak, uint32(1)<<31), uint32(1), uint32(64))); } } ////////////////////////////////////////////////////// // 32-Bit / Signed / Mono, Stereo Interleaved / PCM else if(GetBitDepth() == 32 && (GetChannelFormat() == mono || GetChannelFormat() == stereoInterleaved) && GetEncoding() == signedPCMnormalize) { // Normalize to 16-Bit uint32 srcPeak = uint32(1)<<31; if(GetEndianness() == littleEndian) { bytesRead = CopyAndNormalizeSample<SC::NormalizationChain<SC::Convert<int16, int32>, SC::DecodeInt32<0, littleEndian32> > >(sample, sourceBuf, fileSize, &srcPeak); } else { bytesRead = CopyAndNormalizeSample<SC::NormalizationChain<SC::Convert<int16, int32>, SC::DecodeInt32<0, bigEndian32> > >(sample, sourceBuf, fileSize, &srcPeak); } if(bytesRead) { // Adjust sample volume so we do not affect relative volume of the sample. Normalizing is only done to increase precision. sample.nGlobalVol = static_cast<uint16>(Clamp(Util::muldivr_unsigned(sample.nGlobalVol, srcPeak, uint32(1)<<31), uint32(1), uint32(64))); } } ////////////////////////////////////////////////////// // 32-Bit / Float / Mono, Stereo Interleaved / PCM else if(GetBitDepth() == 32 && (GetChannelFormat() == mono || GetChannelFormat() == stereoInterleaved) && GetEncoding() == floatPCMnormalize) { // Normalize to 16-Bit float32 srcPeak = 1.0f; if(GetEndianness() == littleEndian) { bytesRead = CopyAndNormalizeSample<SC::NormalizationChain<SC::Convert<int16, float32>, SC::DecodeFloat32<littleEndian32> > >(sample, sourceBuf, fileSize, &srcPeak); } else { bytesRead = CopyAndNormalizeSample<SC::NormalizationChain<SC::Convert<int16, float32>, SC::DecodeFloat32<bigEndian32> > >(sample, sourceBuf, fileSize, &srcPeak); } if(bytesRead) { // Adjust sample volume so we do not affect relative volume of the sample. Normalizing is only done to increase precision. sample.nGlobalVol = Util::Round<uint16>(Clamp(sample.nGlobalVol * srcPeak, 1.0f, 64.0f)); } } ////////////////////////////////////////////////////// // 32-Bit / Float / Mono / PCM / full scale 2^15 else if(GetBitDepth() == 32 && GetChannelFormat() == mono && GetEncoding() == floatPCM15) { if(GetEndianness() == littleEndian) { bytesRead = CopyMonoSample (sample, sourceBuf, fileSize, SC::ConversionChain<SC::Convert<int16, float32>, SC::DecodeScaledFloat32<littleEndian32> > (SC::Convert<int16, float32>(), SC::DecodeScaledFloat32<littleEndian32>(1.0f / static_cast<float>(1<<15))) ); } else { bytesRead = CopyMonoSample (sample, sourceBuf, fileSize, SC::ConversionChain<SC::Convert<int16, float32>, SC::DecodeScaledFloat32<bigEndian32> > (SC::Convert<int16, float32>(), SC::DecodeScaledFloat32<bigEndian32>(1.0f / static_cast<float>(1<<15))) ); } } ////////////////////////////////////////////////////// // 32-Bit / Float / Stereo Interleaved / PCM / full scale 2^15 else if(GetBitDepth() == 32 && GetChannelFormat() == stereoInterleaved && GetEncoding() == floatPCM15) { if(GetEndianness() == littleEndian) { bytesRead = CopyStereoInterleavedSample (sample, sourceBuf, fileSize, SC::ConversionChain<SC::Convert<int16, float32>, SC::DecodeScaledFloat32<littleEndian32> > (SC::Convert<int16, float32>(), SC::DecodeScaledFloat32<littleEndian32>(1.0f / static_cast<float>(1<<15))) ); } else { bytesRead = CopyStereoInterleavedSample (sample, sourceBuf, fileSize, SC::ConversionChain<SC::Convert<int16, float32>, SC::DecodeScaledFloat32<bigEndian32> > (SC::Convert<int16, float32>(), SC::DecodeScaledFloat32<bigEndian32>(1.0f / static_cast<float>(1<<15))) ); } } ////////////////////////////////////////////////////// // 32-Bit / Float / Stereo Interleaved / PCM / full scale 2^23 else if(GetBitDepth() == 32 && GetChannelFormat() == mono && GetEncoding() == floatPCM23) { if(GetEndianness() == littleEndian) { bytesRead = CopyMonoSample (sample, sourceBuf, fileSize, SC::ConversionChain<SC::Convert<int16, float32>, SC::DecodeScaledFloat32<littleEndian32> > (SC::Convert<int16, float32>(), SC::DecodeScaledFloat32<littleEndian32>(1.0f / static_cast<float>(1<<23))) ); } else { bytesRead = CopyMonoSample (sample, sourceBuf, fileSize, SC::ConversionChain<SC::Convert<int16, float32>, SC::DecodeScaledFloat32<bigEndian32> > (SC::Convert<int16, float32>(), SC::DecodeScaledFloat32<bigEndian32>(1.0f / static_cast<float>(1<<23))) ); } } ////////////////////////////////////////////////////// // 32-Bit / Float / Stereo Interleaved / PCM / full scale 2^23 else if(GetBitDepth() == 32 && GetChannelFormat() == stereoInterleaved && GetEncoding() == floatPCM23) { if(GetEndianness() == littleEndian) { bytesRead = CopyStereoInterleavedSample (sample, sourceBuf, fileSize, SC::ConversionChain<SC::Convert<int16, float32>, SC::DecodeScaledFloat32<littleEndian32> > (SC::Convert<int16, float32>(), SC::DecodeScaledFloat32<littleEndian32>(1.0f / static_cast<float>(1<<23))) ); } else { bytesRead = CopyStereoInterleavedSample (sample, sourceBuf, fileSize, SC::ConversionChain<SC::Convert<int16, float32>, SC::DecodeScaledFloat32<bigEndian32> > (SC::Convert<int16, float32>(), SC::DecodeScaledFloat32<bigEndian32>(1.0f / static_cast<float>(1<<23))) ); } } ////////////////////////////////////////////////////// // Compressed samples if(*this == SampleIO(_8bit, mono, littleEndian, ADPCM)) { // 4-Bit ADPCM data int8 compressionTable[16]; // ADPCM Compression LUT if(file.ReadArray(compressionTable)) { size_t readLength = (sample.nLength + 1) / 2; LimitMax(readLength, file.BytesLeft()); const uint8 *inBuf = reinterpret_cast<const uint8*>(sourceBuf) + sizeof(compressionTable); int8 *outBuf = static_cast<int8 *>(sample.pSample); int8 delta = 0; for(size_t i = readLength; i != 0; i--) { delta += compressionTable[*inBuf & 0x0F]; *(outBuf++) = delta; delta += compressionTable[(*inBuf >> 4) & 0x0F]; *(outBuf++) = delta; inBuf++; } bytesRead = sizeof(compressionTable) + readLength; }
// Simple 2-poles resonant filter void CSoundFile::SetupChannelFilter(ModChannel *pChn, bool bReset, int flt_modifier) const //---------------------------------------------------------------------------------------- { int cutoff = (int)pChn->nCutOff + (int)pChn->nCutSwing; int resonance = (int)(pChn->nResonance & 0x7F) + (int)pChn->nResSwing; Limit(cutoff, 0, 127); Limit(resonance, 0, 127); if(!GetModFlag(MSF_OLDVOLSWING)) { pChn->nCutOff = (uint8)cutoff; pChn->nCutSwing = 0; pChn->nResonance = (uint8)resonance; pChn->nResSwing = 0; } float d, e; // flt_modifier is in [-256, 256], so cutoff is in [0, 127 * 2] after this calculation. const int computedCutoff = cutoff * (flt_modifier + 256) / 256; // Filtering is only ever done in IT if either cutoff is not full or if resonance is set. if(IsCompatibleMode(TRK_IMPULSETRACKER) && resonance == 0 && computedCutoff >= 254) { if(pChn->rowCommand.IsNote() && !pChn->dwFlags[CHN_PORTAMENTO] && !pChn->nMasterChn && m_SongFlags[SONG_FIRSTTICK]) { // Z7F next to a note disables the filter, however in other cases this should not happen. // Test cases: filter-reset.it, filter-reset-carry.it, filter-nna.it pChn->dwFlags.reset(CHN_FILTER); } return; } pChn->dwFlags.set(CHN_FILTER); if(UseITFilterMode()) { const float freqParameterMultiplier = 128.0f / (24.0f * 256.0f); // 2 ^ (i / 24 * 256) float frequency = 110.0f * pow(2.0f, 0.25f + (float)computedCutoff * freqParameterMultiplier); LimitMax(frequency, (float)(m_MixerSettings.gdwMixingFreq / 2)); const float r = (float)m_MixerSettings.gdwMixingFreq / (2.0f * (float)M_PI * frequency); d = ITResonanceTable[resonance] * r + ITResonanceTable[resonance] - 1.0f; e = r * r; } else { float fc = (float)CutOffToFrequency(cutoff, flt_modifier); const float dmpfac = pow(10.0f, -((24.0f / 128.0f) * (float)resonance) / 20.0f); fc *= (float)(2.0f * (float)M_PI / (float)m_MixerSettings.gdwMixingFreq); d = (1.0f - 2.0f * dmpfac) * fc; LimitMax(d, 2.0f); d = (2.0f * dmpfac - d) / fc; e = pow(1.0f / fc, 2.0f); } float fg = 1.0f / (1.0f + d + e); float fb0 = (d + e + e) / (1 + d + e); float fb1 = -e / (1.0f + d + e); switch(pChn->nFilterMode) { case FLTMODE_HIGHPASS: pChn->nFilter_A0 = 1.0f - fg; pChn->nFilter_B0 = fb0; pChn->nFilter_B1 = fb1; pChn->nFilter_HP = -1; break; default: pChn->nFilter_A0 = fg; pChn->nFilter_B0 = fb0; pChn->nFilter_B1 = fb1; pChn->nFilter_HP = 0; break; } if (bReset) { pChn->nFilter_Y1 = pChn->nFilter_Y2 = 0; pChn->nFilter_Y3 = pChn->nFilter_Y4 = 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 }
void operator() (ModCommand &m) { const CHANNELINDEX curChn = chn; chn++; if(chn >= sndFile.GetNumChannels()) { chn = 0; } if(m.IsPcNote()) { return; } if(sndFile.GetType() == MOD_TYPE_S3M) { // Out-of-range global volume commands should be ignored in S3M. Fixed in OpenMPT 1.19 (r831). // So for tracks made with older versions of OpenMPT, we limit invalid global volume commands. if(sndFile.m_dwLastSavedWithVersion < MAKE_VERSION_NUMERIC(1, 19, 00, 00) && m.command == CMD_GLOBALVOLUME) { LimitMax(m.param, ModCommand::PARAM(64)); } } else if((sndFile.GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT))) { if(sndFile.m_dwLastSavedWithVersion < MAKE_VERSION_NUMERIC(1, 17, 03, 02) || (!compatPlay && sndFile.m_dwLastSavedWithVersion < MAKE_VERSION_NUMERIC(1, 20, 00, 00))) { if(m.command == CMD_GLOBALVOLUME) { // Out-of-range global volume commands should be ignored in IT. // OpenMPT 1.17.03.02 fixed this in compatible mode, OpenMPT 1.20 fixes it in normal mode as well. // So for tracks made with older versions than OpenMPT 1.17.03.02 or tracks made with 1.17.03.02 <= version < 1.20, we limit invalid global volume commands. LimitMax(m.param, ModCommand::PARAM(128)); } // SC0 and SD0 should be interpreted as SC1 and SD1 in IT files. // OpenMPT 1.17.03.02 fixed this in compatible mode, OpenMPT 1.20 fixes it in normal mode as well. else if(m.command == CMD_S3MCMDEX) { if(m.param == 0xC0) { m.command = CMD_NONE; m.note = NOTE_NOTECUT; } else if(m.param == 0xD0) { m.command = CMD_NONE; } } } // In the IT format, slide commands with both nibbles set should be ignored. // For note volume slides, OpenMPT 1.18 fixes this in compatible mode, OpenMPT 1.20 fixes this in normal mode as well. const bool noteVolSlide = (sndFile.m_dwLastSavedWithVersion < MAKE_VERSION_NUMERIC(1, 18, 00, 00) || (!compatPlay && sndFile.m_dwLastSavedWithVersion < MAKE_VERSION_NUMERIC(1, 20, 00, 00))) && (m.command == CMD_VOLUMESLIDE || m.command == CMD_VIBRATOVOL || m.command == CMD_TONEPORTAVOL || m.command == CMD_PANNINGSLIDE); // OpenMPT 1.20 also fixes this for global volume and channel volume slides. const bool chanVolSlide = (sndFile.m_dwLastSavedWithVersion < MAKE_VERSION_NUMERIC(1, 20, 00, 00)) && (m.command == CMD_GLOBALVOLSLIDE || m.command == CMD_CHANNELVOLSLIDE); if(noteVolSlide || chanVolSlide) { if((m.param & 0x0F) != 0x00 && (m.param & 0x0F) != 0x0F && (m.param & 0xF0) != 0x00 && (m.param & 0xF0) != 0xF0) { m.param &= 0x0F; } } if(sndFile.m_dwLastSavedWithVersion < MAKE_VERSION_NUMERIC(1, 22, 01, 04) && sndFile.m_dwLastSavedWithVersion != MAKE_VERSION_NUMERIC(1, 22, 00, 00)) // Ignore compatibility export { // OpenMPT 1.22.01.04 fixes illegal (out of range) instrument numbers; they should do nothing. In previous versions, they stopped the playing sample. if(sndFile.GetNumInstruments() && m.instr > sndFile.GetNumInstruments() && !compatPlay) { m.volcmd = VOLCMD_VOLUME; m.vol = 0; } } } else if(sndFile.GetType() == MOD_TYPE_XM) { // Something made be believe that out-of-range global volume commands are ignored in XM // just like they are ignored in IT, but apparently they are not. Aaaaaargh! if(((sndFile.m_dwLastSavedWithVersion >= MAKE_VERSION_NUMERIC(1, 17, 03, 02) && compatPlay) || (sndFile.m_dwLastSavedWithVersion >= MAKE_VERSION_NUMERIC(1, 20, 00, 00))) && sndFile.m_dwLastSavedWithVersion < MAKE_VERSION_NUMERIC(1, 24, 02, 02) && m.command == CMD_GLOBALVOLUME && m.param > 64) { m.command = CMD_NONE; } if(sndFile.m_dwLastSavedWithVersion < MAKE_VERSION_NUMERIC(1, 19, 00, 00) || (!compatPlay && sndFile.m_dwLastSavedWithVersion < MAKE_VERSION_NUMERIC(1, 20, 00, 00))) { if(m.command == CMD_OFFSET && m.volcmd == VOLCMD_TONEPORTAMENTO) { // If there are both a portamento and an offset effect, the portamento should be preferred in XM files. // OpenMPT 1.19 fixed this in compatible mode, OpenMPT 1.20 fixes it in normal mode as well. m.command = CMD_NONE; } } if(sndFile.m_dwLastSavedWithVersion < MAKE_VERSION_NUMERIC(1, 20, 01, 10) && m.volcmd == VOLCMD_TONEPORTAMENTO && m.command == CMD_TONEPORTAMENTO && (m.vol != 0 || compatPlay) && m.param != 0) { // Mx and 3xx on the same row does weird things in FT2: 3xx is completely ignored and the Mx parameter is doubled. Fixed in revision 1312 / OpenMPT 1.20.01.10 // Previously the values were just added up, so let's fix this! m.volcmd = VOLCMD_NONE; const uint16 param = static_cast<uint16>(m.param) + static_cast<uint16>(m.vol << 4); m.param = mpt::saturate_cast<ModCommand::PARAM>(param); } if(sndFile.m_dwLastSavedWithVersion < MAKE_VERSION_NUMERIC(1, 22, 07, 09) && m.command == CMD_SPEED && m.param == 0) { // OpenMPT can emulate FT2's F00 behaviour now. m.command = CMD_NONE; } } if(sndFile.m_dwLastSavedWithVersion < MAKE_VERSION_NUMERIC(1, 20, 00, 00)) { // Pattern Delay fixes const bool fixS6x = (m.command == CMD_S3MCMDEX && (m.param & 0xF0) == 0x60); // We also fix X6x commands in hacked XM files, since they are treated identically to the S6x command in IT/S3M files. // We don't treat them in files made with OpenMPT 1.18+ that have compatible play enabled, though, since they are ignored there anyway. const bool fixX6x = (m.command == CMD_XFINEPORTAUPDOWN && (m.param & 0xF0) == 0x60 && (!(compatPlay && sndFile.GetType() == MOD_TYPE_XM) || sndFile.m_dwLastSavedWithVersion < MAKE_VERSION_NUMERIC(1, 18, 00, 00))); if(fixS6x || fixX6x) { // OpenMPT 1.20 fixes multiple fine pattern delays on the same row. Previously, only the last command was considered, // but all commands should be added up. Since Scream Tracker 3 itself doesn't support S6x, we also use Impulse Tracker's behaviour here, // since we can assume that most S3Ms that make use of S6x were composed with Impulse Tracker. for(ModCommand *fixCmd = (&m) - curChn; fixCmd < &m; fixCmd++) { if((fixCmd->command == CMD_S3MCMDEX || fixCmd->command == CMD_XFINEPORTAUPDOWN) && (fixCmd->param & 0xF0) == 0x60) { fixCmd->command = CMD_NONE; } } } if(m.command == CMD_S3MCMDEX && (m.param & 0xF0) == 0xE0) { // OpenMPT 1.20 fixes multiple pattern delays on the same row. Previously, only the *last* command was considered, // but Scream Tracker 3 and Impulse Tracker only consider the *first* command. for(ModCommand *fixCmd = (&m) - curChn; fixCmd < &m; fixCmd++) { if(fixCmd->command == CMD_S3MCMDEX && (fixCmd->param & 0xF0) == 0xE0) { fixCmd->command = CMD_NONE; } } } } // Volume column offset in IT/XM is bad, mkay? if(sndFile.GetType() != MOD_TYPE_MPT && m.volcmd == VOLCMD_OFFSET && m.command == CMD_NONE) { m.command = CMD_OFFSET; m.param = m.vol << 3; m.volcmd = VOLCMD_NONE; } }