bool CopyWavChannel(ModSample &sample, const FileReader &file, size_t channelIndex, size_t numChannels, SampleConversion conv = SampleConversion()) { MPT_ASSERT(sample.GetNumChannels() == 1); MPT_ASSERT(sample.GetElementarySampleSize() == sizeof(typename SampleConversion::output_t)); const size_t offset = channelIndex * sizeof(typename SampleConversion::input_t) * SampleConversion::input_inc; if(sample.AllocateSample() == 0 || !file.CanRead(offset)) { return false; } const mpt::byte *inBuf = file.GetRawData<mpt::byte>(); CopySample<SampleConversion>(reinterpret_cast<typename SampleConversion::output_t*>(sample.pSample), sample.nLength, 1, inBuf + offset, file.BytesLeft() - offset, numChannels, conv); return true; }
uint32 CSoundFile::CutOffToFrequency(uint32 nCutOff, int flt_modifier) const { MPT_ASSERT(nCutOff < 128); float computedCutoff = static_cast<float>(nCutOff * (flt_modifier + 256)); // 0...127*512 float Fc; if(GetType() != MOD_TYPE_IMF) { Fc = 110.0f * std::pow(2.0f, 0.25f + computedCutoff / (m_SongFlags[SONG_EXFILTERRANGE] ? 20.0f * 512.0f : 24.0f * 512.0f)); } else { // EMU8000: Documentation says the cutoff is in quarter semitones, with 0x00 being 125 Hz and 0xFF being 8 kHz // The first half of the sentence contradicts the second, though. Fc = 125.0f * std::pow(2.0f, computedCutoff * 6.0f / (127.0f * 512.0f)); } int freq = Util::Round<int>(Fc); Limit(freq, 120, 20000); if(freq * 2 > (int)m_MixerSettings.gdwMixingFreq) freq = m_MixerSettings.gdwMixingFreq / 2; return static_cast<uint32>(freq); }
OPENMPT_NAMESPACE_BEGIN // AWE32: cutoff = reg[0-255] * 31.25 + 100 -> [100Hz-8060Hz] // EMU10K1 docs: cutoff = reg[0-127]*62+100 DWORD CSoundFile::CutOffToFrequency(UINT nCutOff, int flt_modifier) const //----------------------------------------------------------------------- { float Fc; MPT_ASSERT(nCutOff < 128); if(m_SongFlags[SONG_EXFILTERRANGE]) Fc = 110.0f * pow(2.0f, 0.25f + ((float)(nCutOff * (flt_modifier + 256))) / (20.0f * 512.0f)); else Fc = 110.0f * pow(2.0f, 0.25f + ((float)(nCutOff * (flt_modifier + 256))) / (24.0f * 512.0f)); LONG freq = (LONG)Fc; Limit(freq, 120, 20000); if (freq * 2 > (LONG)m_MixerSettings.gdwMixingFreq) freq = m_MixerSettings.gdwMixingFreq >> 1; return (DWORD)freq; }
BuildVariant BuildVariants::GetModernWin64BuildVariant() { std::vector<BuildVariant> builds = GetBuildVariants(); MPT_ASSERT(builds[0].Bitness == 64); return builds[0]; }
BuildVariant BuildVariants::GetModernWin32BuildVariant() { std::vector<BuildVariant> builds = GetBuildVariants(); MPT_ASSERT(builds[1].Bitness == 32); return builds[1]; }
// 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; }
// Render count * number of channels samples void CSoundFile::CreateStereoMix(int count) { mixsample_t *pOfsL, *pOfsR; if (!count) return; // Resetting sound buffer StereoFill(MixSoundBuffer, count, gnDryROfsVol, gnDryLOfsVol); if(m_MixerSettings.gnChannels > 2) InitMixBuffer(MixRearBuffer, count*2); CHANNELINDEX nchmixed = 0; const bool ITPingPongMode = m_playBehaviour[kITPingPongMode]; for(uint32 nChn = 0; nChn < m_nMixChannels; nChn++) { ModChannel &chn = m_PlayState.Chn[m_PlayState.ChnMix[nChn]]; if(!chn.pCurrentSample) continue; pOfsR = &gnDryROfsVol; pOfsL = &gnDryLOfsVol; uint32 functionNdx = MixFuncTable::ResamplingModeToMixFlags(static_cast<ResamplingMode>(chn.resamplingMode)); if(chn.dwFlags[CHN_16BIT]) functionNdx |= MixFuncTable::ndx16Bit; if(chn.dwFlags[CHN_STEREO]) functionNdx |= MixFuncTable::ndxStereo; #ifndef NO_FILTER if(chn.dwFlags[CHN_FILTER]) functionNdx |= MixFuncTable::ndxFilter; #endif mixsample_t *pbuffer = MixSoundBuffer; #ifndef NO_REVERB if(((m_MixerSettings.DSPMask & SNDDSP_REVERB) && !chn.dwFlags[CHN_NOREVERB]) || chn.dwFlags[CHN_REVERB]) { pbuffer = m_Reverb.GetReverbSendBuffer(count); pOfsR = &m_Reverb.gnRvbROfsVol; pOfsL = &m_Reverb.gnRvbLOfsVol; } #endif if(chn.dwFlags[CHN_SURROUND] && m_MixerSettings.gnChannels > 2) pbuffer = MixRearBuffer; //Look for plugins associated with this implicit tracker channel. #ifndef NO_PLUGINS PLUGINDEX nMixPlugin = GetBestPlugin(m_PlayState.ChnMix[nChn], PrioritiseInstrument, RespectMutes); if ((nMixPlugin > 0) && (nMixPlugin <= MAX_MIXPLUGINS) && m_MixPlugins[nMixPlugin - 1].pMixPlugin != nullptr) { // Render into plugin buffer instead of global buffer SNDMIXPLUGINSTATE &mixState = m_MixPlugins[nMixPlugin - 1].pMixPlugin->m_MixState; if (mixState.pMixBuffer) { pbuffer = mixState.pMixBuffer; pOfsR = &mixState.nVolDecayR; pOfsL = &mixState.nVolDecayL; if (!(mixState.dwFlags & SNDMIXPLUGINSTATE::psfMixReady)) { StereoFill(pbuffer, count, *pOfsR, *pOfsL); mixState.dwFlags |= SNDMIXPLUGINSTATE::psfMixReady; } } } #endif // NO_PLUGINS MixLoopState mixLoopState(chn); //////////////////////////////////////////////////// CHANNELINDEX naddmix = 0; int nsamples = count; // Keep mixing this sample until the buffer is filled. do { uint32 nrampsamples = nsamples; int32 nSmpCount; if(chn.nRampLength > 0) { if (nrampsamples > chn.nRampLength) nrampsamples = chn.nRampLength; } if((nSmpCount = mixLoopState.GetSampleCount(chn, nrampsamples, ITPingPongMode)) <= 0) { // Stopping the channel chn.pCurrentSample = nullptr; chn.nLength = 0; chn.position.Set(0); chn.nRampLength = 0; EndChannelOfs(chn, pbuffer, nsamples); *pOfsR += chn.nROfs; *pOfsL += chn.nLOfs; chn.nROfs = chn.nLOfs = 0; chn.dwFlags.reset(CHN_PINGPONGFLAG); break; } // Should we mix this channel ? if((nchmixed >= m_MixerSettings.m_nMaxMixChannels) // Too many channels || (!chn.nRampLength && !(chn.leftVol | chn.rightVol))) // Channel is completely silent { chn.position += chn.increment * nSmpCount; chn.nROfs = chn.nLOfs = 0; pbuffer += nSmpCount * 2; naddmix = 0; } else { // Do mixing mixsample_t *pbufmax = pbuffer + (nSmpCount * 2); chn.nROfs = - *(pbufmax-2); chn.nLOfs = - *(pbufmax-1); #ifdef _DEBUG SamplePosition targetpos = chn.position + chn.increment * nSmpCount; #endif MixFuncTable::Functions[functionNdx | (chn.nRampLength ? MixFuncTable::ndxRamp : 0)](chn, m_Resampler, pbuffer, nSmpCount); #ifdef _DEBUG MPT_ASSERT(chn.position.GetUInt() == targetpos.GetUInt()); #endif chn.nROfs += *(pbufmax-2); chn.nLOfs += *(pbufmax-1); pbuffer = pbufmax; naddmix = 1; } nsamples -= nSmpCount; if (chn.nRampLength) { if (chn.nRampLength <= static_cast<uint32>(nSmpCount)) { // Ramping is done chn.nRampLength = 0; chn.leftVol = chn.newLeftVol; chn.rightVol = chn.newRightVol; chn.rightRamp = chn.leftRamp = 0; if(chn.dwFlags[CHN_NOTEFADE] && !chn.nFadeOutVol) { chn.nLength = 0; chn.pCurrentSample = nullptr; } } else { chn.nRampLength -= nSmpCount; } } if(chn.position.GetUInt() >= chn.nLoopEnd && chn.dwFlags[CHN_LOOP]) { if(m_playBehaviour[kMODSampleSwap] && chn.nNewIns && chn.nNewIns <= GetNumSamples() && chn.pModSample != &Samples[chn.nNewIns]) { // ProTracker compatibility: Instrument changes without a note do not happen instantly, but rather when the sample loop has finished playing. // Test case: PTInstrSwap.mod const ModSample &smp = Samples[chn.nNewIns]; chn.pModSample = &smp; chn.pCurrentSample = smp.pSample; chn.dwFlags = (chn.dwFlags & CHN_CHANNELFLAGS) | smp.uFlags; chn.nLength = smp.uFlags[CHN_LOOP] ? smp.nLoopEnd : smp.nLength; chn.nLoopStart = smp.nLoopStart; chn.nLoopEnd = smp.nLoopEnd; chn.position.SetInt(chn.nLoopStart); mixLoopState.UpdateLookaheadPointers(chn); if(!chn.pCurrentSample) { break; } } else if(m_playBehaviour[kMODOneShotLoops] && chn.nLoopStart == 0) { // ProTracker "oneshot" loops (if loop start is 0, play the whole sample once and then repeat until loop end) chn.position.SetInt(0); chn.nLoopEnd = chn.nLength = chn.pModSample->nLoopEnd; } } } while(nsamples > 0); // Restore sample pointer in case it got changed through loop wrap-around chn.pCurrentSample = mixLoopState.samplePointer; nchmixed += naddmix; #ifndef NO_PLUGINS if(naddmix && nMixPlugin > 0 && nMixPlugin <= MAX_MIXPLUGINS && m_MixPlugins[nMixPlugin - 1].pMixPlugin) { m_MixPlugins[nMixPlugin - 1].pMixPlugin->ResetSilence(); } #endif // NO_PLUGINS } m_nMixStat = std::max<CHANNELINDEX>(m_nMixStat, nchmixed); }