diff --git a/plugin/editor.cpp b/plugin/editor.cpp index 2315f24..d8e4ce9 100644 --- a/plugin/editor.cpp +++ b/plugin/editor.cpp @@ -47,6 +47,7 @@ struct YsfxEditor::Impl { std::unique_ptr m_modalAlert; std::unique_ptr m_infoTimer; std::unique_ptr m_relayoutTimer; + std::unique_ptr m_undoTimer; std::unique_ptr m_fileChooser; std::unique_ptr m_recentFilesPopup; std::unique_ptr m_recentFilesOptsPopup; @@ -57,6 +58,7 @@ struct YsfxEditor::Impl { bool m_fileChooserActive = false; bool m_mustResizeToGfx = true; bool m_maintainState = false; + int m_keepUndoState{1}; float m_currentScaling{1.0f}; uint64_t m_sliderVisible[ysfx_max_slider_groups]{0}; bool m_visibleSlidersChanged{false}; @@ -900,6 +902,22 @@ void YsfxEditor::Impl::initializeProperties() #endif m_pluginProperties.reset(new juce::PropertiesFile{options}); + + // Manual undo stack + // 1 - default (off in current release, on in the future) + // 2 - enabled + // 3 - always disabled + { + juce::ScopedLock lock{m_pluginProperties->getLock()}; + + auto key = juce::String("ysfx_maintain_serialization_undo"); + if (m_pluginProperties->containsKey(key)) { + m_keepUndoState = m_pluginProperties->getIntValue(key); + } else { + m_pluginProperties->setValue(key, 1); + m_pluginProperties->setNeedsToBeSaved(true); + } + } } void YsfxEditor::Impl::createUI() @@ -1014,6 +1032,15 @@ void YsfxEditor::Impl::connectUI() m_infoTimer.reset(FunctionalTimer::create([this]() { grabInfoAndUpdate(); })); m_infoTimer->startTimer(100); + + m_undoTimer.reset( + FunctionalTimer::create( + [this]() { + if (m_keepUndoState == 2) m_proc->checkForUndoableChanges(); + } + ) + ); + m_undoTimer->startTimer(500); } void YsfxEditor::Impl::relayoutUI() diff --git a/plugin/processor.cpp b/plugin/processor.cpp index 7f6d7b2..722b584 100644 --- a/plugin/processor.cpp +++ b/plugin/processor.cpp @@ -42,6 +42,7 @@ struct YsfxProcessor::Impl : public juce::AudioProcessorListener { YsfxCurrentPresetInfo::Ptr m_currentPresetInfo{new YsfxCurrentPresetInfo}; ysfx_bank_shared m_bank{nullptr}; + int m_maxUndoStack{64}; double m_sample_rate{44100.0}; uint32_t m_block_size{256}; @@ -334,6 +335,14 @@ void YsfxProcessor::loadJsfxPreset(YsfxInfo::Ptr info, ysfx_bank_shared bank, ui } } +void YsfxProcessor::checkForUndoableChanges() +{ + if (ysfx_fetch_want_undopoint(m_impl->m_fx.get())) { + m_impl->m_wantUndoPoint = true; + m_impl->m_background->wakeUp(); + } +} + void YsfxProcessor::popUndoState() { m_impl->m_undoRequest = UndoRequest::wantUndo; @@ -794,11 +803,6 @@ void YsfxProcessor::Impl::processSliderChanges() notify = automated ? true : notify; }; - m_wantUndoPoint = m_wantUndoPoint | ysfx_fetch_want_undopoint(fx); - if (m_wantUndoPoint) { - notify = true; - }; - // this will sync parameters later (on message thread) if (notify) m_background->wakeUp(); @@ -988,15 +992,17 @@ void YsfxProcessor::Impl::installNewFx(YsfxInfo::Ptr info, ysfx_bank_shared bank m_updateParamNames = true; m_wantUndoPoint = false; + if (m_info->m_name != info->m_name) { + m_undoStack.clear(); + m_hasUndo = false; + m_hasRedo = false; + } + YsfxCurrentPresetInfo::Ptr presetInfo{new YsfxCurrentPresetInfo()}; std::atomic_store(&m_currentPresetInfo, presetInfo); std::atomic_store(&m_bank, bank); std::atomic_store(&m_info, info); - m_undoStack.clear(); - m_hasUndo = false; - m_hasRedo = false; - m_background->wakeUp(); } @@ -1032,6 +1038,8 @@ void YsfxProcessor::Impl::loadNewPreset(const ysfx_preset_t &preset) void YsfxProcessor::Impl::pushUndoState() { + if (!m_currentPresetInfo) return; + ysfx_state_t* state; { AudioProcessorSuspender sus(*m_self); @@ -1040,7 +1048,12 @@ void YsfxProcessor::Impl::pushUndoState() state = ysfx_save_state(fx); } - if (!m_currentPresetInfo) return; + // Verify that we don't already have this exact state + if ((m_undoPosition < m_undoStack.size()) && (m_undoPosition >= 0) && ysfx_is_state_equal(state, m_undoStack[m_undoPosition]->state)) { + ysfx_state_free(state); + return; + } + ysfx_preset_u preset; preset.reset(ysfx_preset_from_state(m_currentPresetInfo->m_lastChosenPreset.toStdString().c_str(), state)); @@ -1051,7 +1064,7 @@ void YsfxProcessor::Impl::pushUndoState() m_undoStack.emplace_back(std::move(preset)); m_undoPosition = static_cast(m_undoStack.size()) - 1; - if (m_undoStack.size() > 14) { + if (m_undoStack.size() > m_maxUndoStack) { m_undoStack.pop_front(); m_undoPosition -= 1; } diff --git a/plugin/processor.h b/plugin/processor.h index 99412f5..d16c8ec 100644 --- a/plugin/processor.h +++ b/plugin/processor.h @@ -36,6 +36,7 @@ class YsfxProcessor : public juce::AudioProcessor { void loadJsfxFile(const juce::String &filePath, ysfx_state_t *initialState, bool async, bool preserveState); void loadJsfxPreset(YsfxInfo::Ptr info, ysfx_bank_shared bank, uint32_t index, PresetLoadMode load, bool async); void popUndoState(); + void checkForUndoableChanges(); void redoState(); bool canUndo(); bool canRedo();