raze/source/common/audio/sound/s_reverbedit.cpp
2022-10-22 00:12:17 +02:00

550 lines
12 KiB
C++

/*
**
** reverb editor
**
**---------------------------------------------------------------------------
** Copyright 2005-2016 Randy Heit
** Copyright 2005-2017 Christoph Oelckers
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
** derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
**
*/
#include "s_soundinternal.h"
#include "sc_man.h"
#include "cmdlib.h"
#include "filesystem.h"
#include "i_system.h"
#include "printf.h"
#include "i_specialpaths.h"
#include "c_cvars.h"
#include "c_dispatch.h"
#include "vm.h"
#include "dobject.h"
#include "menu.h"
void S_ReadReverbDef (FScanner &sc);
extern ReverbContainer *ForcedEnvironment;
ReverbContainer *CurrentEnv;
REVERB_PROPERTIES SavedProperties;
extern FReverbField ReverbFields[];
extern const char* ReverbFieldNames[];
extern int NumReverbs;
// These are for internal use only and not supposed to be user-settable
CVAR(String, reverbedit_name, "", CVAR_NOSET);
CVAR(Int, reverbedit_id1, 0, CVAR_NOSET);
CVAR(Int, reverbedit_id2, 0, CVAR_NOSET);
CVAR(String, reverbsavename, "", 0);
CUSTOM_CVAR(Bool, eaxedit_test, false, CVAR_NOINITCALL)
{
if (self)
{
ForcedEnvironment = CurrentEnv;
}
else
{
ForcedEnvironment = nullptr;
}
}
struct EnvFlag
{
const char *Name;
int CheckboxControl;
unsigned int Flag;
};
inline int HIBYTE(int i)
{
return (i >> 8) & 255;
}
inline int LOBYTE(int i)
{
return i & 255;
}
uint16_t FirstFreeID(uint16_t base, bool builtin)
{
int tryCount = 0;
int priID = HIBYTE(base);
// If the original sound is built-in, start searching for a new
// primary ID at 30.
if (builtin)
{
for (priID = 30; priID < 256; ++priID)
{
if (S_FindEnvironment(priID << 8) == nullptr)
{
break;
}
}
if (priID == 256)
{ // Oh well.
priID = 30;
}
}
for (;;)
{
uint16_t lastID = Environments->ID;
const ReverbContainer *env = Environments->Next;
// Find the lowest-numbered free ID with the same primary ID as base
// If none are available, add 100 to base's primary ID and try again.
// If that fails, then the primary ID gets incremented
// by 1 until a match is found. If all the IDs searchable by this
// algorithm are in use, then you're in trouble.
while (env != nullptr)
{
if (HIBYTE(env->ID) > priID)
{
break;
}
if (HIBYTE(env->ID) == priID)
{
if (HIBYTE(lastID) == priID)
{
if (LOBYTE(env->ID) - LOBYTE(lastID) > 1)
{
return lastID + 1;
}
}
lastID = env->ID;
}
env = env->Next;
}
if (LOBYTE(lastID) == 255)
{
if (tryCount == 0)
{
base += 100 * 256;
tryCount = 1;
}
else
{
base += 256;
}
}
else if (builtin && lastID == 0)
{
return priID << 8;
}
else
{
return lastID + 1;
}
}
}
FString SuggestNewName(const ReverbContainer *env)
{
const ReverbContainer *probe = nullptr;
char text[32];
size_t len;
int number, numdigits;
strncpy(text, env->Name, 31);
text[31] = 0;
len = strlen(text);
while (text[len - 1] >= '0' && text[len - 1] <= '9')
{
len--;
}
number = atoi(text + len);
if (number < 1)
{
number = 1;
}
if (text[len - 1] != ' ' && len < 31)
{
text[len++] = ' ';
}
for (; number < 100000; ++number)
{
if (number < 10) numdigits = 1;
else if (number < 100) numdigits = 2;
else if (number < 1000) numdigits = 3;
else if (number < 10000)numdigits = 4;
else numdigits = 5;
if (len + numdigits > 31)
{
len = 31 - numdigits;
}
mysnprintf(text + len, countof(text) - len, "%d", number);
probe = Environments;
while (probe != nullptr)
{
if (stricmp(probe->Name, text) == 0)
break;
probe = probe->Next;
}
if (probe == nullptr)
{
break;
}
}
return text;
}
void ExportEnvironments(const char *filename, uint32_t count, const ReverbContainer **envs)
{
FString dest = M_GetDocumentsPath() + filename;
FileWriter *f = FileWriter::Open(dest);
if (f != nullptr)
{
for (uint32_t i = 0; i < count; ++i)
{
const ReverbContainer *env = envs[i];
const ReverbContainer *base;
if ((unsigned int)env->Properties.Environment < 26)
{
base = DefaultEnvironments[env->Properties.Environment];
}
else
{
base = nullptr;
}
f->Printf("\"%s\" %u %u\n{\n", env->Name, HIBYTE(env->ID), LOBYTE(env->ID));
for (int j = 0; j < NumReverbs; ++j)
{
const FReverbField *ctl = &ReverbFields[j];
const char *ctlName = ReverbFieldNames[j];
if (ctlName)
{
if (j == 0 ||
(ctl->Float && base->Properties.*ctl->Float != env->Properties.*ctl->Float) ||
(ctl->Int && base->Properties.*ctl->Int != env->Properties.*ctl->Int))
{
f->Printf("\t%s ", ctlName);
if (ctl->Float)
{
float v = env->Properties.*ctl->Float * 1000;
int vi = int(v >= 0.0 ? v + 0.5 : v - 0.5);
f->Printf("%d.%03d\n", vi / 1000, abs(vi % 1000));
}
else
{
f->Printf("%d\n", env->Properties.*ctl->Int);
}
}
else
{
if ((1 << ctl->Flag) & (env->Properties.Flags ^ base->Properties.Flags))
{
f->Printf("\t%s %s\n", ctlName, ctl->Flag & env->Properties.Flags ? "true" : "false");
}
}
}
}
f->Printf("}\n\n");
}
delete f;
}
else
{
M_StartMessage("Save failed", 1);
}
}
DEFINE_ACTION_FUNCTION(DReverbEdit, GetValue)
{
PARAM_PROLOGUE;
PARAM_INT(index);
float v = 0;
if (index >= 0 && index < NumReverbs)
{
auto rev = &ReverbFields[index];
if (rev->Int != nullptr)
{
v = float(CurrentEnv->Properties.*(rev->Int));
}
else if (rev->Float != nullptr)
{
v = CurrentEnv->Properties.*(rev->Float);
}
else
{
v = !!(CurrentEnv->Properties.Flags & (1 << int(rev->Flag)));
}
}
ACTION_RETURN_FLOAT(v);
}
DEFINE_ACTION_FUNCTION(DReverbEdit, SetValue)
{
PARAM_PROLOGUE;
PARAM_INT(index);
PARAM_FLOAT(v);
if (index >= 0 && index < NumReverbs)
{
auto rev = &ReverbFields[index];
if (rev->Int != nullptr)
{
v = CurrentEnv->Properties.*(rev->Int) = clamp<int>(int(v), rev->Min, rev->Max);
}
else if (rev->Float != nullptr)
{
v = CurrentEnv->Properties.*(rev->Float) = clamp<float>(float(v), rev->Min / 1000.f, rev->Max / 1000.f);
}
else
{
if (v == 0) CurrentEnv->Properties.Flags &= ~(1 << int(rev->Flag));
else CurrentEnv->Properties.Flags |= (1 << int(rev->Flag));
}
}
ACTION_RETURN_FLOAT(v);
}
DEFINE_ACTION_FUNCTION(DReverbEdit, GrayCheck)
{
PARAM_PROLOGUE;
ACTION_RETURN_BOOL(CurrentEnv->Builtin);
}
DEFINE_ACTION_FUNCTION(DReverbEdit, GetSelectedEnvironment)
{
PARAM_PROLOGUE;
if (numret > 1)
{
numret = 2;
ret[1].SetInt(CurrentEnv ? CurrentEnv->ID : -1);
}
if (numret > 0)
{
ret[0].SetString(CurrentEnv ? CurrentEnv->Name : "");
}
return numret;
}
DEFINE_ACTION_FUNCTION(DReverbEdit, FillSelectMenu)
{
PARAM_PROLOGUE;
PARAM_STRING(ccmd);
PARAM_OBJECT(desc, DOptionMenuDescriptor);
desc->mItems.Clear();
for (auto env = Environments; env != nullptr; env = env->Next)
{
FStringf text("(%d, %d) %s", HIBYTE(env->ID), LOBYTE(env->ID), env->Name);
FStringf cmd("%s \"%s\"", ccmd.GetChars(), env->Name);
PClass *cls = PClass::FindClass("OptionMenuItemCommand");
if (cls != nullptr && cls->IsDescendantOf("OptionMenuItem"))
{
auto func = dyn_cast<PFunction>(cls->FindSymbol("Init", true));
if (func != nullptr)
{
DMenuItemBase *item = (DMenuItemBase*)cls->CreateNew();
VMValue params[] = { item, &text, FName(cmd).GetIndex(), false, true };
VMCall(func->Variants[0].Implementation, params, 5, nullptr, 0);
desc->mItems.Push((DMenuItemBase*)item);
}
}
}
return 0;
}
static TArray<std::pair<ReverbContainer*, bool>> SaveState;
DEFINE_ACTION_FUNCTION(DReverbEdit, FillSaveMenu)
{
PARAM_PROLOGUE;
PARAM_OBJECT(desc, DOptionMenuDescriptor);
desc->mItems.Resize(4);
SaveState.Clear();
for (auto env = Environments; env != nullptr; env = env->Next)
{
if (!env->Builtin)
{
int index = (int)SaveState.Push(std::make_pair(env, false));
FStringf text("(%d, %d) %s", HIBYTE(env->ID), LOBYTE(env->ID), env->Name);
PClass *cls = PClass::FindClass("OptionMenuItemReverbSaveSelect");
if (cls != nullptr && cls->IsDescendantOf("OptionMenuItem"))
{
auto func = dyn_cast<PFunction>(cls->FindSymbol("Init", true));
if (func != nullptr)
{
DMenuItemBase *item = (DMenuItemBase*)cls->CreateNew();
VMValue params[] = { item, &text, index, FName("OnOff").GetIndex() };
VMCall(func->Variants[0].Implementation, params, 4, nullptr, 0);
desc->mItems.Push((DMenuItemBase*)item);
}
}
}
}
return 0;
}
DEFINE_ACTION_FUNCTION(DReverbEdit, GetSaveSelection)
{
PARAM_PROLOGUE;
PARAM_INT(index);
bool res = false;
if ((unsigned)index <= SaveState.Size())
{
res = SaveState[index].second;
}
ACTION_RETURN_BOOL(res);
}
DEFINE_ACTION_FUNCTION(DReverbEdit, ToggleSaveSelection)
{
PARAM_PROLOGUE;
PARAM_INT(index);
if ((unsigned)index <= SaveState.Size())
{
SaveState[index].second = !SaveState[index].second;
}
return 0;
}
CCMD(savereverbs)
{
if (SaveState.Size() == 0) return;
TArray<const ReverbContainer*> toSave;
for (auto &p : SaveState)
{
if (p.second) toSave.Push(p.first);
}
ExportEnvironments(reverbsavename, toSave.Size(), &toSave[0]);
SaveState.Clear();
}
static void SelectEnvironment(const char *envname)
{
for (auto env = Environments; env != nullptr; env = env->Next)
{
if (!strcmp(env->Name, envname))
{
CurrentEnv = env;
SavedProperties = env->Properties;
if (eaxedit_test) ForcedEnvironment = env;
// Set up defaults for a new environment based on this one.
int newid = FirstFreeID(env->ID, env->Builtin);
UCVarValue cv;
cv.Int = HIBYTE(newid);
reverbedit_id1->ForceSet(cv, CVAR_Int);
cv.Int = LOBYTE(newid);
reverbedit_id2->ForceSet(cv, CVAR_Int);
FString selectname = SuggestNewName(env);
cv.String = selectname.GetChars();
reverbedit_name->ForceSet(cv, CVAR_String);
return;
}
}
}
void InitReverbMenu()
{
// Make sure that the editor's variables are properly initialized.
SelectEnvironment("Off");
}
CCMD(selectenvironment)
{
if (argv.argc() > 1)
{
auto str = argv[1];
SelectEnvironment(str);
}
else
InitReverbMenu();
}
CCMD(revertenvironment)
{
if (CurrentEnv != nullptr)
{
CurrentEnv->Properties = SavedProperties;
}
}
CCMD(createenvironment)
{
if (S_FindEnvironment(reverbedit_name))
{
M_StartMessage(FStringf("An environment with the name '%s' already exists", *reverbedit_name), 1);
return;
}
int id = (reverbedit_id1 << 8) + reverbedit_id2;
if (S_FindEnvironment(id))
{
M_StartMessage(FStringf("An environment with the ID (%d, %d) already exists", *reverbedit_id1, *reverbedit_id2), 1);
return;
}
auto newenv = new ReverbContainer;
newenv->Builtin = false;
newenv->ID = id;
newenv->Name = copystring(reverbedit_name);
newenv->Next = nullptr;
newenv->Properties = CurrentEnv->Properties;
S_AddEnvironment(newenv);
SelectEnvironment(newenv->Name);
}
CCMD(reverbedit)
{
C_DoCommand("openmenu reverbedit");
}
// This is here because it depends on Doom's resource management and is not universal.
void S_ParseReverbDef ()
{
int lump, lastlump = 0;
while ((lump = fileSystem.FindLump ("REVERBS", &lastlump)) != -1)
{
FScanner sc;
sc.OpenLumpNum(lump);
S_ReadReverbDef (sc);;
}
InitReverbMenu();
}