Skip to content

Commit

Permalink
plugin: add manual undo stack
Browse files Browse the repository at this point in the history
  • Loading branch information
JoepVanlier committed Dec 31, 2024
1 parent 4f61258 commit 098c47a
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 4 deletions.
3 changes: 3 additions & 0 deletions exports/ysfx.txt
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ ysfx_load_state
ysfx_save_state
ysfx_state_free
ysfx_state_dup
ysfx_load_serialized_state
ysfx_preset_free
ysfx_preset_from_state
ysfx_get_bank_path
ysfx_load_bank
ysfx_save_bank
Expand Down
8 changes: 8 additions & 0 deletions include/ysfx.h
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,8 @@ YSFX_API ysfx_state_t *ysfx_save_state(ysfx_t *fx);
YSFX_API void ysfx_state_free(ysfx_state_t *state);
// duplicate a state object
YSFX_API ysfx_state_t *ysfx_state_dup(ysfx_state_t *state);
// load only serialized state
YSFX_API bool ysfx_load_serialized_state(ysfx_t *fx, ysfx_state_t *state);

typedef struct ysfx_preset_s {
// name of the preset
Expand All @@ -387,6 +389,11 @@ typedef struct ysfx_preset_s {
ysfx_state_t *state;
} ysfx_preset_t;

// release a preset object
YSFX_API void ysfx_preset_free(ysfx_preset_t *preset);
// Create a preset from a state blob and string. Note that the state blob is _not_ copied.
YSFX_API ysfx_preset_t *ysfx_preset_from_state(const char* value, ysfx_state_t* state);

typedef struct ysfx_bank_s {
// name of the bank
char *name;
Expand Down Expand Up @@ -612,6 +619,7 @@ YSFX_DEFINE_AUTO_PTR(ysfx_u, ysfx_t, ysfx_free);
YSFX_DEFINE_AUTO_PTR(ysfx_state_u, ysfx_state_t, ysfx_state_free);
YSFX_DEFINE_AUTO_PTR(ysfx_bank_u, ysfx_bank_t, ysfx_bank_free);
YSFX_DEFINE_AUTO_PTR(ysfx_menu_u, ysfx_menu_t, ysfx_menu_free);
YSFX_DEFINE_AUTO_PTR(ysfx_preset_u, ysfx_preset_t, ysfx_preset_free);

#define YSFX_DEFINE_SHARED_PTR(sptr, styp, freefn) \
struct sptr##_deleter { \
Expand Down
15 changes: 15 additions & 0 deletions plugin/editor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ struct YsfxEditor::Impl {
std::unique_ptr<juce::TextButton> m_btnPresetOpts;
std::unique_ptr<juce::TextButton> m_btnSwitchEditor;
std::unique_ptr<juce::TextButton> m_btnReload;
std::unique_ptr<juce::TextButton> m_btnUndo;
std::unique_ptr<juce::TextButton> m_btnRedo;
std::unique_ptr<juce::TextButton> m_btnGfxScaling;

std::unique_ptr<juce::Label> m_lblFilePath;
Expand Down Expand Up @@ -899,6 +901,10 @@ void YsfxEditor::Impl::createUI()
m_self->addAndMakeVisible(*m_btnRecentFiles);
m_btnReload.reset(new juce::TextButton(TRANS("Reload")));
m_self->addAndMakeVisible(*m_btnReload);
m_btnUndo.reset(new juce::TextButton(TRANS("R")));
m_self->addAndMakeVisible(*m_btnUndo);
m_btnRedo.reset(new juce::TextButton(TRANS("U")));
m_self->addAndMakeVisible(*m_btnRedo);
m_btnEditCode.reset(new juce::TextButton(TRANS("Edit")));
m_self->addAndMakeVisible(*m_btnEditCode);
m_btnGfxScaling.reset(new juce::TextButton(TRANS("x1")));
Expand Down Expand Up @@ -972,6 +978,11 @@ void YsfxEditor::Impl::connectUI()
this->m_self
);
};
m_btnUndo->onClick = [this] {
this->m_proc->popUndoState();
};
//m_btnRedo

m_btnGfxScaling->onClick = [this] {
if (m_graphicsView) {
float newScaling = (m_graphicsView->getScaling() + 0.5f);
Expand Down Expand Up @@ -1097,6 +1108,10 @@ void YsfxEditor::Impl::relayoutUI()
m_btnReload->setBounds(temp.removeFromLeft(buttonWidth));
temp.removeFromLeft(spacing);
m_btnReload->setVisible(true);

m_btnUndo->setBounds(temp.removeFromLeft(25));
temp.removeFromLeft(spacing);
m_btnUndo->setVisible(true);
} else {
m_btnReload->setVisible(false);
}
Expand Down
61 changes: 61 additions & 0 deletions plugin/processor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
#include <mutex>
#include <condition_variable>
#include <cmath>
#include <deque>
#include <algorithm>

struct YsfxProcessor::Impl : public juce::AudioProcessorListener {
YsfxProcessor *m_self = nullptr;
Expand Down Expand Up @@ -60,6 +62,9 @@ struct YsfxProcessor::Impl : public juce::AudioProcessorListener {
void loadNewPreset(const ysfx_preset_t &preset);
void resetPresetInfo();

void pushUndoState();
void popUndoState();

//==========================================================================
struct LoadRequest : public std::enable_shared_from_this<LoadRequest> {
juce::String filePath;
Expand All @@ -83,10 +88,14 @@ struct YsfxProcessor::Impl : public juce::AudioProcessorListener {

LoadRequest::Ptr m_loadRequest;
PresetRequest::Ptr m_presetRequest;
bool m_wantPopUndo{false};
bool m_wantUndoPoint{false};
ysfx::sync_bitset64 m_sliderParamsToNotify[ysfx_max_slider_groups];
ysfx::sync_bitset64 m_sliderParamsTouching[ysfx_max_slider_groups];
bool m_updateParamNames{false};

std::deque<ysfx_preset_u> m_undoStack;
int m_undoPosition{-1};

//==========================================================================
class SliderNotificationUpdater : public juce::AsyncUpdater {
Expand Down Expand Up @@ -321,6 +330,12 @@ void YsfxProcessor::loadJsfxPreset(YsfxInfo::Ptr info, ysfx_bank_shared bank, ui
}
}

void YsfxProcessor::popUndoState()
{
m_impl->m_wantPopUndo = true;
m_impl->m_background->wakeUp();
}

bool YsfxProcessor::presetExists(const char* presetName)
{
auto sourceBank = m_impl->m_bank;
Expand Down Expand Up @@ -985,6 +1000,46 @@ void YsfxProcessor::Impl::loadNewPreset(const ysfx_preset_t &preset)
m_background->wakeUp();
}

void YsfxProcessor::Impl::pushUndoState()
{
ysfx_state_t* state;
{
AudioProcessorSuspender sus(*m_self);
sus.lockCallbacks();
ysfx_t *fx = m_fx.get();
state = ysfx_save_state(fx);
}

if (!m_currentPresetInfo) return;
ysfx_preset_u preset;
preset.reset(ysfx_preset_from_state(m_currentPresetInfo->m_lastChosenPreset.toStdString().c_str(), state));

// We add a new undo state -> Invalidate everything after our current position
m_undoStack.erase(m_undoStack.begin() + std::max<int>(0, m_undoPosition + 1), m_undoStack.end());

m_undoStack.emplace_back(std::move(preset));
m_undoPosition = static_cast<int>(m_undoStack.size()) - 1;

if (m_undoStack.size() > 14) {
m_undoStack.pop_front();
m_undoPosition -= 1;
}
}

void YsfxProcessor::Impl::popUndoState()
{
AudioProcessorSuspender sus{*m_self};
sus.lockCallbacks();

m_undoPosition -= 1;
if (m_undoPosition < 0) return; // Nothing to undo

ysfx_t *fx = m_fx.get();
auto& preset = m_undoStack[static_cast<size_t>(m_undoPosition)];
ysfx_load_serialized_state(fx, preset->state);
m_background->wakeUp();
}

void YsfxProcessor::Impl::resetPresetInfo()
{
YsfxCurrentPresetInfo::Ptr presetInfo{new YsfxCurrentPresetInfo()};
Expand Down Expand Up @@ -1085,9 +1140,15 @@ void YsfxProcessor::Impl::Background::run()

if (m_impl->m_wantUndoPoint) {
m_impl->m_wantUndoPoint = false;
m_impl->pushUndoState();
Impl::ManualUndoPointUpdater *undoPointUpdater = m_impl->m_manualUndoPointUpdater.get();
undoPointUpdater->triggerAsyncUpdate();
}

if (m_impl->m_wantPopUndo) {
m_impl->popUndoState();
m_impl->m_wantPopUndo = false;
}
}
}

Expand Down
1 change: 1 addition & 0 deletions plugin/processor.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class YsfxProcessor : public juce::AudioProcessor {
YsfxParameter *getYsfxParameter(int sliderIndex);
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();
bool presetExists(const char *preset_name);
void reloadBank();
void savePreset(const char* preset_name, ysfx_state_t *preset);
Expand Down
21 changes: 21 additions & 0 deletions sources/ysfx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1591,6 +1591,27 @@ bool ysfx_load_state(ysfx_t *fx, ysfx_state_t *state)
return true;
}

bool ysfx_load_serialized_state(ysfx_t *fx, ysfx_state_t *state)
{
if (!fx->code.compiled)
return false;

// restore the serialization
std::string buffer((char *)state->data, state->data_size);

// invoke @serialize
{
std::unique_lock<ysfx::mutex> lock;
ysfx_serializer_t *serializer = static_cast<ysfx_serializer_t *>(ysfx_get_file(fx, 0, lock));
assert(serializer);
serializer->begin(false, buffer);
lock.unlock();
ysfx_serialize(fx);
lock.lock();
serializer->end();
}
}

ysfx_state_t *ysfx_save_state(ysfx_t *fx)
{
if (!fx->code.compiled)
Expand Down
18 changes: 14 additions & 4 deletions sources/ysfx_preset.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@

#include "WDL/lineparse.h"

static void ysfx_preset_clear(ysfx_preset_t *preset);
static ysfx_bank_t *ysfx_load_bank_from_rpl_text(const std::string &text);
static void ysfx_parse_preset_from_rpl_blob(ysfx_preset_t *preset, const char *name, const std::vector<uint8_t> &data);

Expand Down Expand Up @@ -75,15 +74,17 @@ void ysfx_bank_free(ysfx_bank_t *bank)
if (ysfx_preset_t *presets = bank->presets) {
uint32_t count = bank->preset_count;
for (uint32_t i = 0; i < count; ++i)
ysfx_preset_clear(&presets[i]);
ysfx_preset_free(&presets[i]);
delete[] presets;
}

delete bank;
}

static void ysfx_preset_clear(ysfx_preset_t *preset)
void ysfx_preset_free(ysfx_preset_t *preset)
{
if (!preset) return;

delete[] preset->name;
preset->name = nullptr;

Expand All @@ -94,6 +95,15 @@ static void ysfx_preset_clear(ysfx_preset_t *preset)
preset->state = nullptr;
}

ysfx_preset_t *ysfx_preset_from_state(const char* value, ysfx_state_t* state) {
ysfx_preset_t *preset = new ysfx_preset_t{};
preset->name = ysfx::strdup_using_new(value);
preset->blob_name = ysfx::strdup_using_new(value);
preset->state = state;

return preset;
}

static ysfx_bank_t *ysfx_load_bank_from_rpl_text(const std::string &text)
{
LineParser parser;
Expand All @@ -106,7 +116,7 @@ static ysfx_bank_t *ysfx_load_bank_from_rpl_text(const std::string &text)

auto list_cleanup = ysfx::defer([&preset_list]() {
for (ysfx_preset_t &pst : preset_list)
ysfx_preset_clear(&pst);
ysfx_preset_free(&pst);
});

///
Expand Down

0 comments on commit 098c47a

Please sign in to comment.