Skip to content

Commit

Permalink
[api][feature][file] Add ID & Save preset as remix
Browse files Browse the repository at this point in the history
The idea is to highlight AVS's remix culture by creating metadata to
both machine-readably identify presets globally, as well as keep track
of any presets the current preset was based on. This could, in the
future, enable a database of presets to trace inheritance connections
among other things.

To that end:
  - Add an "ID" parameter to the Root config that gets randomly
    assigned a UUID on first creation of a new preset. The ID does not
    change when the preset is saved, it's more of a "project id".
  - Add a "Based On" list parameter to the Root config that can record
    previous presets with ID, date, name and authors.
  - Add a `remix()` method to the Root effect, which switches some
    metadata and assigns a new ID and initial date.
  - Add an `as_remix` parameter to both `avs_preset_set()` and
    `avs_preset_save()` API functions. Setting this flag invokes the
    `remix()` method on the current Root before saving.

While not strictly related, also in this commit:
  - Split the Root config's "Date" field into initial date and last-
    edited date, to reflect both start of the preset and most recent
    version.
  - When creating a new preset give it a semi-random name of the
    format "Untitled Preset xxxxxxxxxx", where xx..xx is a 10-digit
    random alphanumeric suffix.
  • Loading branch information
grandchild committed Nov 1, 2024
1 parent 47e43fd commit 1455a0d
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 14 deletions.
11 changes: 7 additions & 4 deletions avs/vis_avs/avs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -161,20 +161,23 @@ bool avs_preset_set(AVS_Handle avs, const char* preset) {
return instance->preset_load(preset, false);
}
AVS_API
bool avs_preset_save(AVS_Handle avs, const char* file_path, bool indent) {
bool avs_preset_save(AVS_Handle avs,
const char* file_path,
bool as_remix,
bool indent) {
AVS_Instance* instance = get_instance_from_handle(avs);
if (instance == nullptr) {
return false;
}
return instance->preset_save_file(file_path, indent);
return instance->preset_save_file(file_path, as_remix, indent);
}
AVS_API
const char* avs_preset_get(AVS_Handle avs, bool indent) {
const char* avs_preset_get(AVS_Handle avs, bool as_remix, bool indent) {
AVS_Instance* instance = get_instance_from_handle(avs);
if (instance == nullptr) {
return nullptr;
}
return instance->preset_save(indent);
return instance->preset_save(as_remix, indent);
}
AVS_API
bool avs_preset_set_legacy(AVS_Handle avs,
Expand Down
8 changes: 5 additions & 3 deletions avs/vis_avs/avs.h
Original file line number Diff line number Diff line change
Expand Up @@ -266,15 +266,17 @@ bool avs_preset_load(AVS_Handle avs, const char* file_path);
bool avs_preset_set(AVS_Handle avs, const char* preset);

/**
* Save the currently loaded preset to the given file path. If `indent` is `true`, the
* Save the currently loaded preset to the given file path. If `as_remix` is `true`, the
* preset's metadata will be refreshed, a new ID assigned and some of the old preset's
* metadata appended to the history list for the new preset. If `indent` is `true`, the
* JSON will be indented with 4 spaces in the file. Otherwise it will be a single line.
*
* If `file_path` does not end in ".avs" it will be appended. Setting `file_path` to a
* directory is an error.
*
* Returns `true` on success or `false` if saving fails for some reason.
*/
bool avs_preset_save(AVS_Handle avs, const char* file_path, bool indent);
bool avs_preset_save(AVS_Handle avs, const char* file_path, bool as_remix, bool indent);

/**
* Return the currently loaded preset's JSON string. If `indent` is `true`, the JSON
Expand All @@ -289,7 +291,7 @@ bool avs_preset_save(AVS_Handle avs, const char* file_path, bool indent);
*
* Returns `NULL` on error.
*/
const char* avs_preset_get(AVS_Handle avs, bool indent);
const char* avs_preset_get(AVS_Handle avs, bool as_remix, bool indent);

/**
* Load a preset from a legacy binary buffer.
Expand Down
28 changes: 27 additions & 1 deletion avs/vis_avs/e_root.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,24 @@
(data[pos] | (data[pos + 1] << 8) | (data[pos + 2] << 16) | (data[pos + 3] << 24))

constexpr Parameter Root_Info::author_parameters[];
constexpr Parameter Root_Info::remix_parameters[];
constexpr Parameter Root_Info::parameters[];

E_Root::E_Root(AVS_Instance* avs) : Configurable_Effect(avs), buffers_saved(false) {}
std::string random_preset_name_slug() {
static const char charset[] = "0123456789abcdefghijklmnopqrstuvwxyz";
std::string slug;
for (int i = 0; i < 10; i++) {
slug += charset[rand() % (sizeof(charset) - 1)];
}
return slug;
}

E_Root::E_Root(AVS_Instance* avs) : Configurable_Effect(avs), buffers_saved(false) {
this->config.date_init = current_date_str();
this->config.name = "Untitled Preset " + random_preset_name_slug();
log_info("Name: %s", this->config.name.c_str());
this->config.id = uuid4();
}
E_Root::~E_Root() {
for (int i = 0; i < NBUF; i++) {
free(this->buffers[i]);
Expand All @@ -30,6 +45,17 @@ E_Root::~E_Root() {
}
}

void E_Root::remix() {
Root_RemixParent_Config remix_parent;
remix_parent.id = this->config.id;
remix_parent.name = this->config.name;
remix_parent.date = this->config.date_last;
remix_parent.authors = this->config.authors;
this->config.based_on.push_back(remix_parent);
this->config.id = uuid4();
this->config.date_init = current_date_str();
}

int E_Root::render(char visdata[2][2][576],
int is_beat,
int* framebuffer,
Expand Down
48 changes: 45 additions & 3 deletions avs/vis_avs/e_root.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,21 @@ struct Root_Author_Config : public Effect_Config {
std::string name;
};

struct Root_BasedOn_Config : public Effect_Config {
std::string id;
std::string name;
std::string date;
std::vector<Root_Author_Config> authors;
};

struct Root_Config : public Effect_Config {
bool clear = false;
std::string name;
std::string date;
std::string date_init;
std::string date_last;
std::vector<Root_Author_Config> authors;
std::string id;
std::vector<Root_BasedOn_Config> based_on;
};

struct Root_Info : public Effect_Info {
Expand All @@ -34,20 +44,51 @@ struct Root_Info : public Effect_Info {
"Role",
"'author', 'remixer', 'editor', 'curator' - or anything else")};

static constexpr uint32_t num_parameters = 4;
static constexpr uint32_t num_remix_parameters = 4;
static constexpr Parameter remix_parameters[num_remix_parameters] = {
P_STRING(offsetof(Root_BasedOn_Config, id), "ID", "UUID of the predecessor"),
P_STRING(offsetof(Root_BasedOn_Config, name),
"Name",
"Name of the predecessor"),
P_STRING(offsetof(Root_BasedOn_Config, date),
"Date",
"Last-edited date of the predecessor"),
P_LIST<Root_Author_Config>(offsetof(Root_BasedOn_Config, authors),
"Authors",
author_parameters,
num_author_parameters,
0,
0,
"Authors of the predecessor"),
};

static constexpr uint32_t num_parameters = 7;
static constexpr Parameter parameters[num_parameters] = {
P_BOOL(offsetof(Root_Config, clear),
"Clear",
"Clear the screen for every new frame"),
P_STRING(offsetof(Root_Config, name), "Name", "Name of the preset"),
P_STRING(offsetof(Root_Config, date), "Date", "Date of the preset"),
P_STRING(offsetof(Root_Config, date_init),
"Date Initial",
"Date of preset's first save"),
P_STRING(offsetof(Root_Config, date_last),
"Date Last",
"Date of preset's latest save"),
P_LIST<Root_Author_Config>(offsetof(Root_Config, authors),
"Authors",
author_parameters,
num_author_parameters,
0,
0,
"Authors of the preset"),
P_STRING(offsetof(Root_Config, id), "ID", "UUID of the preset"),
P_LIST<Root_BasedOn_Config>(offsetof(Root_Config, based_on),
"Based On",
remix_parameters,
num_remix_parameters,
0,
0,
"Previous presets used to create this one"),
};

virtual bool can_have_child_components() const { return true; }
Expand All @@ -69,6 +110,7 @@ class E_Root : public Configurable_Effect<Root_Info, Root_Config> {
virtual void load_legacy(unsigned char* data, int len);
virtual int save_legacy(unsigned char* data);
virtual E_Root* clone() { return new E_Root(*this); }
void remix();
int64_t get_num_renders() { return this->children.size(); }
void start_buffer_context();
void end_buffer_context();
Expand Down
10 changes: 7 additions & 3 deletions avs/vis_avs/instance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,8 @@ bool AVS_Instance::preset_load_legacy(const uint8_t* preset,
return false;
}

bool AVS_Instance::preset_save_file(const char* file_path, bool indent) {
auto preset_str = this->preset_save(indent);
bool AVS_Instance::preset_save_file(const char* file_path, bool as_remix, bool indent) {
auto preset_str = this->preset_save(as_remix, indent);
FILE* fp = fopen(file_path, "wb");
if (fp != nullptr) {
fwrite(preset_str, 1, strlen(preset_str), fp);
Expand Down Expand Up @@ -210,10 +210,14 @@ int AVS_Instance::preset_save_file_legacy(const char* file_path) {
return result;
}

const char* AVS_Instance::preset_save(bool indent) {
const char* AVS_Instance::preset_save(bool as_remix, bool indent) {
json j;
j["preset format version"] = "0";
j["avs version"] = "2.81.4";
if (as_remix) {
this->root.remix();
}
this->root.config.date_last = current_date_str();
try {
j.update(this->root.save());
j.erase("effect");
Expand Down

0 comments on commit 1455a0d

Please sign in to comment.