From: <sv...@op...> - 2024-10-20 16:13:03
|
Author: sagamusix Date: Sun Oct 20 18:12:51 2024 New Revision: 21901 URL: https://source.openmpt.org/browse/openmpt/?op=revision&rev=21901 Log: [Imp] VST: Improve correctness of PPQ position reporting. The PPQ is now also correct when the tempo changes, and when breaking to the next pattern, the position is rounded to the next start of measure. Partially fixes https://bugs.openmpt.org/view.php?id=1731 [Imp] MIDI Input/Output plugin: PPQ position is now sent when "send timing information" is enabled. [Mod] OpenMPT: Version is now 1.32.00.30 Modified: trunk/OpenMPT/common/versionNumber.h trunk/OpenMPT/mptrack/Vstplug.cpp trunk/OpenMPT/mptrack/dlg_misc.cpp trunk/OpenMPT/mptrack/plugins/MidiInOut.cpp trunk/OpenMPT/mptrack/plugins/MidiInOut.h trunk/OpenMPT/soundlib/MIDIEvents.cpp trunk/OpenMPT/soundlib/MIDIEvents.h trunk/OpenMPT/soundlib/PlayState.cpp trunk/OpenMPT/soundlib/PlayState.h trunk/OpenMPT/soundlib/Snd_defs.h trunk/OpenMPT/soundlib/Snd_fx.cpp trunk/OpenMPT/soundlib/Sndfile.cpp trunk/OpenMPT/soundlib/Sndmix.cpp trunk/OpenMPT/soundlib/UpgradeModule.cpp Modified: trunk/OpenMPT/common/versionNumber.h ============================================================================== --- trunk/OpenMPT/common/versionNumber.h Sun Oct 20 17:24:00 2024 (r21900) +++ trunk/OpenMPT/common/versionNumber.h Sun Oct 20 18:12:51 2024 (r21901) @@ -16,4 +16,4 @@ #define VER_MAJORMAJOR 1 #define VER_MAJOR 32 #define VER_MINOR 00 -#define VER_MINORMINOR 29 +#define VER_MINORMINOR 30 Modified: trunk/OpenMPT/mptrack/Vstplug.cpp ============================================================================== --- trunk/OpenMPT/mptrack/Vstplug.cpp Sun Oct 20 17:24:00 2024 (r21900) +++ trunk/OpenMPT/mptrack/Vstplug.cpp Sun Oct 20 18:12:51 2024 (r21901) @@ -352,9 +352,12 @@ }; CVstPlugin *pVstPlugin = nullptr; + CSoundFile *sndFile = nullptr; if(effect != nullptr) { pVstPlugin = static_cast<CVstPlugin *>(effect->reservedForHost1); + if(pVstPlugin) + sndFile = &pVstPlugin->GetSoundFile(); } switch(opcode) @@ -412,12 +415,13 @@ MemsetZero(timeInfo); timeInfo.sampleRate = pVstPlugin->m_nSampleRate; - CSoundFile &sndFile = pVstPlugin->GetSoundFile(); if(pVstPlugin->IsSongPlaying()) { - timeInfo.flags |= kVstTransportPlaying; - if(pVstPlugin->GetSoundFile().m_PlayState.m_flags[SONG_PATTERNLOOP]) timeInfo.flags |= kVstTransportCycleActive; - timeInfo.samplePos = sndFile.GetTotalSampleCount(); + if(!sndFile->m_PlayState.m_flags[SONG_PAUSED]) + timeInfo.flags |= kVstTransportPlaying; + if(sndFile->m_PlayState.m_flags[SONG_PATTERNLOOP]) + timeInfo.flags |= kVstTransportCycleActive; + timeInfo.samplePos = sndFile->GetTotalSampleCount(); if(pVstPlugin->m_positionChanged) { timeInfo.flags |= kVstTransportChanged; @@ -437,20 +441,20 @@ if((value & kVstPpqPosValid)) { timeInfo.flags |= kVstPpqPosValid; - if (timeInfo.flags & kVstTransportPlaying) - { - timeInfo.ppqPos = (timeInfo.samplePos / timeInfo.sampleRate) * (sndFile.GetCurrentBPM() / 60.0); - } else - { - timeInfo.ppqPos = 0; - } + if(sndFile->m_playBehaviour[kLegacyPPQpos]) + timeInfo.ppqPos = (timeInfo.samplePos / timeInfo.sampleRate) * (sndFile->GetCurrentBPM() / 60.0); + else + timeInfo.ppqPos = sndFile->m_PlayState.m_ppqPosBeat + sndFile->m_PlayState.m_ppqPosFract; ROWINDEX rpm = pVstPlugin->GetSoundFile().m_PlayState.m_nCurrentRowsPerMeasure; if(!rpm) rpm = 4; if((pVstPlugin->GetSoundFile().m_PlayState.m_nRow % rpm) == 0) { - pVstPlugin->lastBarStartPos = std::floor(timeInfo.ppqPos); + if(sndFile->m_playBehaviour[kLegacyPPQpos]) + pVstPlugin->lastBarStartPos = std::floor(timeInfo.ppqPos); + else + pVstPlugin->lastBarStartPos = sndFile->m_PlayState.m_ppqPosBeat; // Only updated at start of measure } if(pVstPlugin->lastBarStartPos >= 0) { @@ -460,7 +464,7 @@ } if((value & kVstTempoValid)) { - timeInfo.tempo = sndFile.GetCurrentBPM(); + timeInfo.tempo = sndFile->GetCurrentBPM(); if (timeInfo.tempo) { timeInfo.flags |= kVstTempoValid; @@ -472,8 +476,8 @@ // Time signature. numerator = rows per beats / rows pear measure (should sound somewhat logical to you). // the denominator is a bit more tricky, since it cannot be set explicitely. so we just assume quarters for now. - ROWINDEX rpb = std::max(sndFile.m_PlayState.m_nCurrentRowsPerBeat, ROWINDEX(1)); - timeInfo.timeSigNumerator = std::max(sndFile.m_PlayState.m_nCurrentRowsPerMeasure, rpb) / rpb; + ROWINDEX rpb = std::max(sndFile->m_PlayState.m_nCurrentRowsPerBeat, ROWINDEX(1)); + timeInfo.timeSigNumerator = std::max(sndFile->m_PlayState.m_nCurrentRowsPerMeasure, rpb) / rpb; timeInfo.timeSigDenominator = 4; //std::gcd(pSndFile->m_nCurrentRowsPerMeasure, pSndFile->m_nCurrentRowsPerBeat); } return ToIntPtr(&timeInfo); @@ -500,9 +504,9 @@ // returns tempo (in bpm * 10000) at sample frame location passed in <value> - DEPRECATED in VST 2.4 case audioMasterTempoAt: // Screw it! Let's just return the tempo at this point in time (might be a bit wrong). - if (pVstPlugin != nullptr) + if(sndFile != nullptr) { - return mpt::saturate_round<int32>(pVstPlugin->GetSoundFile().GetCurrentBPM() * 10000); + return mpt::saturate_round<int32>(sndFile->GetCurrentBPM() * 10000); } return (125 * 10000); @@ -571,7 +575,7 @@ case audioMasterGetOutputLatency: if(pVstPlugin) { - return mpt::saturate_round<intptr_t>(pVstPlugin->GetOutputLatency() * pVstPlugin->GetSoundFile().GetSampleRate()); + return mpt::saturate_round<intptr_t>(pVstPlugin->GetOutputLatency() * sndFile->GetSampleRate()); } break; @@ -615,7 +619,7 @@ return 1; //we replace. case audioMasterGetCurrentProcessLevel: - if(pVstPlugin != nullptr && pVstPlugin->GetSoundFile().IsRenderingToDisc()) + if(sndFile != nullptr && sndFile->IsRenderingToDisc()) return kVstProcessLevelOffline; else return kVstProcessLevelRealtime; Modified: trunk/OpenMPT/mptrack/dlg_misc.cpp ============================================================================== --- trunk/OpenMPT/mptrack/dlg_misc.cpp Sun Oct 20 17:24:00 2024 (r21900) +++ trunk/OpenMPT/mptrack/dlg_misc.cpp Sun Oct 20 18:12:51 2024 (r21901) @@ -733,6 +733,7 @@ case kITDoublePortamentoSlides: desc = _T("Parameters of conflicting volume and effect column portamento commands may overwrite each other"); break; case kS3MIgnoreCombinedFineSlides: desc =_T("Ignore combined fine slides (Kxy / Lxy)"); break; case kFT2AutoVibratoAbortSweep: desc = _T("Key-off before auto-vibrato sweep-in is complete resets auto-vibrato depth"); break; + case kLegacyPPQpos: desc = _T("Report inaccurate PPQ position to VST plugins (like OpenMPT 1.31 and older)"); break; default: MPT_ASSERT_NOTREACHED(); } Modified: trunk/OpenMPT/mptrack/plugins/MidiInOut.cpp ============================================================================== --- trunk/OpenMPT/mptrack/plugins/MidiInOut.cpp Sun Oct 20 17:24:00 2024 (r21900) +++ trunk/OpenMPT/mptrack/plugins/MidiInOut.cpp Sun Oct 20 18:12:51 2024 (r21901) @@ -294,6 +294,17 @@ } m_nextClock -= numFrames; + if(m_sendTimingInfo && !m_positionChanged && m_SndFile.m_PlayState.m_ppqPosFract == 0.0) + { + // Send Song Position on every pattern change or start of new measure + uint16 ppq = mpt::saturate_cast<uint16>((m_SndFile.m_PlayState.m_ppqPosBeat + m_SndFile.m_PlayState.m_ppqPosFract) * 4.0); + if(ppq < 16384) + { + uint32 midiCode = MIDIEvents::SongPosition(ppq); + m_outQueue.push_back(Message(GetOutputTimestamp(), &midiCode, MIDIEvents::GetEventLength(static_cast<uint8>(midiCode)))); + } + } + double now = m_clock.Now() * (1.0 / 1000.0); auto message = m_outQueue.begin(); while(message != m_outQueue.end() && message->m_time <= now) @@ -308,6 +319,7 @@ } m_outQueue.erase(m_outQueue.begin(), message); } + m_positionChanged = false; } @@ -406,8 +418,12 @@ if(m_sendTimingInfo && !m_SndFile.IsPaused()) { MidiSend(0xFC); // Stop + uint16 ppq = mpt::saturate_cast<uint16>((m_SndFile.m_PlayState.m_ppqPosBeat + m_SndFile.m_PlayState.m_ppqPosFract) * 4.0); + if(ppq < 16384) + MidiSend(MIDIEvents::SongPosition(ppq)); MidiSend(0xFA); // Start } + m_positionChanged = true; } Modified: trunk/OpenMPT/mptrack/plugins/MidiInOut.h ============================================================================== --- trunk/OpenMPT/mptrack/plugins/MidiInOut.h Sun Oct 20 17:24:00 2024 (r21900) +++ trunk/OpenMPT/mptrack/plugins/MidiInOut.h Sun Oct 20 18:12:51 2024 (r21901) @@ -133,6 +133,7 @@ MidiDevice m_inputDevice; MidiDevice m_outputDevice; bool m_sendTimingInfo = true; + bool m_positionChanged = false; #ifdef MODPLUG_TRACKER CString m_programName; Modified: trunk/OpenMPT/soundlib/MIDIEvents.cpp ============================================================================== --- trunk/OpenMPT/soundlib/MIDIEvents.cpp Sun Oct 20 17:24:00 2024 (r21900) +++ trunk/OpenMPT/soundlib/MIDIEvents.cpp Sun Oct 20 18:12:51 2024 (r21901) @@ -65,6 +65,13 @@ } +// Build a MIDI Song Position Event +uint32 SongPosition(uint16 quarterNotes) +{ + return Event(evSystem, sysPositionPointer, static_cast<uint8>(quarterNotes & 0x7F), static_cast<uint8>((quarterNotes >> 7) & 0x7F)); +} + + // Get MIDI channel from a MIDI event uint8 GetChannelFromEvent(uint32 midiMsg) { Modified: trunk/OpenMPT/soundlib/MIDIEvents.h ============================================================================== --- trunk/OpenMPT/soundlib/MIDIEvents.h Sun Oct 20 17:24:00 2024 (r21900) +++ trunk/OpenMPT/soundlib/MIDIEvents.h Sun Oct 20 18:12:51 2024 (r21901) @@ -151,6 +151,8 @@ uint32 NoteOn(uint8 midiChannel, uint8 note, uint8 velocity); // Build a MIDI System Event uint8 System(SystemEvent eventType); + // Build a MIDI Song Position Event + uint32 SongPosition(uint16 quarterNotes); // Get MIDI channel from a MIDI event uint8 GetChannelFromEvent(uint32 midiMsg); Modified: trunk/OpenMPT/soundlib/PlayState.cpp ============================================================================== --- trunk/OpenMPT/soundlib/PlayState.cpp Sun Oct 20 17:24:00 2024 (r21900) +++ trunk/OpenMPT/soundlib/PlayState.cpp Sun Oct 20 18:12:51 2024 (r21901) @@ -48,6 +48,17 @@ } +void PlayState::UpdatePPQ(bool patternTransition) noexcept +{ + if(m_lTotalSampleCount > 0 && (patternTransition || !(m_nRow % m_nCurrentRowsPerMeasure))) + { + // Pattern end = end of measure, so round up PPQ to the next full measure + m_ppqPosBeat += (m_nCurrentRowsPerMeasure + (m_nCurrentRowsPerBeat - 1)) / m_nCurrentRowsPerBeat; + m_ppqPosFract = 0; + } +} + + mpt::span<ModChannel> PlayState::PatternChannels(const CSoundFile &sndFile) noexcept { return mpt::as_span(Chn).subspan(0, std::min(Chn.size(), static_cast<size_t>(sndFile.GetNumChannels()))); Modified: trunk/OpenMPT/soundlib/PlayState.h ============================================================================== --- trunk/OpenMPT/soundlib/PlayState.h Sun Oct 20 17:24:00 2024 (r21900) +++ trunk/OpenMPT/soundlib/PlayState.h Sun Oct 20 18:12:51 2024 (r21901) @@ -32,7 +32,9 @@ double m_dBufferDiff = 0.0; // Modern tempo rounding error compensation public: - uint32 m_nTickCount = 0; // Current tick being processed + double m_ppqPosFract = 0.0; // Fractional PPQ position within current measure + uint32 m_ppqPosBeat = 0; // PPQ position of the last start of measure + uint32 m_nTickCount = 0; // Current tick being processed protected: uint32 m_nPatternDelay = 0; // Pattern delay (rows) uint32 m_nFrameDelay = 0; // Fine pattern delay (ticks) @@ -89,6 +91,7 @@ void ResetGlobalVolumeRamping() noexcept; void UpdateTimeSignature(const CSoundFile &sndFile) noexcept; + void UpdatePPQ(bool patternTransition) noexcept; constexpr uint32 TicksOnRow() const noexcept { Modified: trunk/OpenMPT/soundlib/Snd_defs.h ============================================================================== --- trunk/OpenMPT/soundlib/Snd_defs.h Sun Oct 20 17:24:00 2024 (r21900) +++ trunk/OpenMPT/soundlib/Snd_defs.h Sun Oct 20 18:12:51 2024 (r21901) @@ -602,6 +602,7 @@ kITDoublePortamentoSlides, // IT only reads parameters once per row, so if two commands sharing effect parameters are found in the two effect columns, they influence each other kS3MIgnoreCombinedFineSlides, // S3M commands Kxy and Lxy ignore fine slides kFT2AutoVibratoAbortSweep, // Key-off before auto-vibrato sweep-in is complete resets auto-vibrato depth + kLegacyPPQpos, // Report fake PPQ position to VST plugins // Add new play behaviours here. Modified: trunk/OpenMPT/soundlib/Snd_fx.cpp ============================================================================== --- trunk/OpenMPT/soundlib/Snd_fx.cpp Sun Oct 20 17:24:00 2024 (r21900) +++ trunk/OpenMPT/soundlib/Snd_fx.cpp Sun Oct 20 18:12:51 2024 (r21901) @@ -91,6 +91,8 @@ state->m_lTotalSampleCount = 0; state->m_nMusicSpeed = sndFile.Order(m_sequence).GetDefaultSpeed(); state->m_nMusicTempo = sndFile.Order(m_sequence).GetDefaultTempo(); + state->m_ppqPosFract = 0.0; + state->m_ppqPosBeat = 0; state->m_nGlobalVolume = sndFile.m_nDefaultGlobalVolume; state->m_globalScriptState.Initialize(sndFile); chnSettings.assign(sndFile.GetNumChannels(), {}); @@ -555,6 +557,7 @@ playState.m_nRow = 0; } + playState.UpdatePPQ(breakToRow); playState.UpdateTimeSignature(*this); if(ignoreRow) @@ -1066,6 +1069,7 @@ const uint32 rowDuration = tickDuration * numTicks; memory.elapsedTime += static_cast<double>(rowDuration) / static_cast<double>(m_MixerSettings.gdwMixingFreq); playState.m_lTotalSampleCount += rowDuration; + playState.m_ppqPosFract += 1.0 / playState.m_nCurrentRowsPerBeat; if(adjustSamplePos) { Modified: trunk/OpenMPT/soundlib/Sndfile.cpp ============================================================================== --- trunk/OpenMPT/soundlib/Sndfile.cpp Sun Oct 20 17:24:00 2024 (r21900) +++ trunk/OpenMPT/soundlib/Sndfile.cpp Sun Oct 20 18:12:51 2024 (r21901) @@ -926,6 +926,8 @@ m_PlayState.m_nFrameDelay = 0; m_PlayState.m_nextPatStartRow = 0; m_PlayState.m_lTotalSampleCount = 0; + m_PlayState.m_ppqPosFract = 0.0; + m_PlayState.m_ppqPosBeat = 0; m_PlayState.m_globalScriptState.Initialize(*this); } Modified: trunk/OpenMPT/soundlib/Sndmix.cpp ============================================================================== --- trunk/OpenMPT/soundlib/Sndmix.cpp Sun Oct 20 17:24:00 2024 (r21900) +++ trunk/OpenMPT/soundlib/Sndmix.cpp Sun Oct 20 18:12:51 2024 (r21901) @@ -362,6 +362,8 @@ countToRender -= countChunk; m_PlayState.m_nBufferCount -= countChunk; m_PlayState.m_lTotalSampleCount += countChunk; + if(!m_PlayState.m_nBufferCount && !m_PlayState.m_flags[SONG_PAUSED]) + m_PlayState.m_ppqPosFract += 1.0 / (m_PlayState.m_nCurrentRowsPerBeat * m_PlayState.TicksOnRow()); #ifdef MODPLUG_TRACKER if(IsRenderingToDisc()) @@ -455,6 +457,8 @@ MPT_UNUSED_VARIABLE(patternTransition); #endif // MODPLUG_TRACKER + m_PlayState.UpdatePPQ(patternTransition); + // Check if pattern is valid if(!m_PlayState.m_flags[SONG_PATTERNLOOP]) { Modified: trunk/OpenMPT/soundlib/UpgradeModule.cpp ============================================================================== --- trunk/OpenMPT/soundlib/UpgradeModule.cpp Sun Oct 20 17:24:00 2024 (r21900) +++ trunk/OpenMPT/soundlib/UpgradeModule.cpp Sun Oct 20 18:12:51 2024 (r21901) @@ -744,6 +744,7 @@ } } +#ifndef NO_PLUGINS if(m_dwLastSavedWithVersion >= MPT_V("1.27.00.42") && m_dwLastSavedWithVersion < MPT_V("1.30.00.46") && hasAnyPlugins) { // The Flanger DMO plugin is almost identical to the Chorus... but only almost. @@ -792,6 +793,19 @@ } } } + + if(m_dwLastSavedWithVersion >= MPT_V("1.17") && m_dwLastSavedWithVersion < MPT_V("1.32.00.30") && hasAnyPlugins) + { + for(const auto &plugin : m_MixPlugins) + { + if(plugin.Info.dwPluginId1 == PLUGMAGIC('V', 's', 't', 'P')) + { + m_playBehaviour.set(kLegacyPPQpos); + break; + } + } + } +#endif } OPENMPT_NAMESPACE_END |