mirror of
https://github.com/ZDoom/gzdoom.git
synced 2024-11-29 23:33:00 +00:00
1152 lines
28 KiB
C++
1152 lines
28 KiB
C++
/*
|
|
**
|
|
**
|
|
**---------------------------------------------------------------------------
|
|
** 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 "doomtype.h"
|
|
#include "tarray.h"
|
|
#include "s_sound.h"
|
|
#include "sc_man.h"
|
|
#include "cmdlib.h"
|
|
#include "templates.h"
|
|
#include "w_wad.h"
|
|
#include "i_system.h"
|
|
#include "m_misc.h"
|
|
|
|
#include "c_cvars.h"
|
|
#include "c_dispatch.h"
|
|
#include "files.h"
|
|
#include "vm.h"
|
|
#include "dobject.h"
|
|
#include "menu/menu.h"
|
|
|
|
|
|
|
|
void InitReverbMenu();
|
|
REVERB_PROPERTIES SavedProperties;
|
|
ReverbContainer *CurrentEnv;
|
|
extern ReverbContainer *ForcedEnvironment;
|
|
|
|
// 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);
|
|
|
|
struct FReverbField
|
|
{
|
|
int Min, Max;
|
|
float REVERB_PROPERTIES::*Float;
|
|
int REVERB_PROPERTIES::*Int;
|
|
unsigned int Flag;
|
|
};
|
|
|
|
|
|
static const FReverbField ReverbFields[] =
|
|
{
|
|
{ 0, 25, 0, &REVERB_PROPERTIES::Environment, 0 },
|
|
{ 1000, 100000, &REVERB_PROPERTIES::EnvSize, 0, 0 },
|
|
{ 0, 1000, &REVERB_PROPERTIES::EnvDiffusion, 0, 0 },
|
|
{ -10000, 0, 0, &REVERB_PROPERTIES::Room, 0 },
|
|
{ -10000, 0, 0, &REVERB_PROPERTIES::RoomHF, 0 },
|
|
{ -10000, 0, 0, &REVERB_PROPERTIES::RoomLF, 0 },
|
|
{ 100, 20000, &REVERB_PROPERTIES::DecayTime, 0, 0 },
|
|
{ 100, 2000, &REVERB_PROPERTIES::DecayHFRatio, 0, 0 },
|
|
{ 100, 2000, &REVERB_PROPERTIES::DecayLFRatio, 0, 0 },
|
|
{ -10000, 1000, 0, &REVERB_PROPERTIES::Reflections, 0 },
|
|
{ 0, 300, &REVERB_PROPERTIES::ReflectionsDelay, 0, 0 },
|
|
{ -2000000, 2000000, &REVERB_PROPERTIES::ReflectionsPan0, 0, 0 },
|
|
{ -2000000, 2000000, &REVERB_PROPERTIES::ReflectionsPan1, 0, 0 },
|
|
{ -2000000, 2000000, &REVERB_PROPERTIES::ReflectionsPan2, 0, 0 },
|
|
{ -10000, 2000, 0, &REVERB_PROPERTIES::Reverb, 0 },
|
|
{ 0, 100, &REVERB_PROPERTIES::ReverbDelay, 0, 0 },
|
|
{ -2000000, 2000000, &REVERB_PROPERTIES::ReverbPan0, 0, 0 },
|
|
{ -2000000, 2000000, &REVERB_PROPERTIES::ReverbPan1, 0, 0 },
|
|
{ -2000000, 2000000, &REVERB_PROPERTIES::ReverbPan2, 0, 0 },
|
|
{ 75, 250, &REVERB_PROPERTIES::EchoTime, 0, 0 },
|
|
{ 0, 1000, &REVERB_PROPERTIES::EchoDepth, 0, 0 },
|
|
{ 40, 4000, &REVERB_PROPERTIES::ModulationTime, 0, 0 },
|
|
{ 0, 1000, &REVERB_PROPERTIES::ModulationDepth, 0, 0 },
|
|
{ -100000, 0, &REVERB_PROPERTIES::AirAbsorptionHF, 0, 0 },
|
|
{ 1000000, 20000000, &REVERB_PROPERTIES::HFReference, 0, 0 },
|
|
{ 20000, 1000000, &REVERB_PROPERTIES::LFReference, 0, 0 },
|
|
{ 0, 10000, &REVERB_PROPERTIES::RoomRolloffFactor, 0, 0 },
|
|
{ 0, 100000, &REVERB_PROPERTIES::Diffusion, 0, 0 },
|
|
{ 0, 100000, &REVERB_PROPERTIES::Density, 0, 0 },
|
|
{ 0, 0, 0, 0, 1 },
|
|
{ 0, 0, 0, 0, 2 },
|
|
{ 0, 0, 0, 0, 0 },
|
|
{ 0, 0, 0, 0, 5 },
|
|
{ 0, 0, 0, 0, 3 },
|
|
{ 0, 0, 0, 0, 4 },
|
|
{ 0, 0, 0, 0, 6 },
|
|
{ 0, 0, 0, 0, 7 }
|
|
};
|
|
#define NUM_REVERB_FIELDS (int(countof(ReverbFields)))
|
|
|
|
static const char *ReverbFieldNames[NUM_REVERB_FIELDS+2] =
|
|
{
|
|
"Environment",
|
|
"EnvironmentSize",
|
|
"EnvironmentDiffusion",
|
|
"Room",
|
|
"RoomHF",
|
|
"RoomLF",
|
|
"DecayTime",
|
|
"DecayHFRatio",
|
|
"DecayLFRatio",
|
|
"Reflections",
|
|
"ReflectionsDelay",
|
|
"ReflectionsPanX",
|
|
"ReflectionsPanY",
|
|
"ReflectionsPanZ",
|
|
"Reverb",
|
|
"ReverbDelay",
|
|
"ReverbPanX",
|
|
"ReverbPanY",
|
|
"ReverbPanZ",
|
|
"EchoTime",
|
|
"EchoDepth",
|
|
"ModulationTime",
|
|
"ModulationDepth",
|
|
"AirAbsorptionHF",
|
|
"HFReference",
|
|
"LFReference",
|
|
"RoomRolloffFactor",
|
|
"Diffusion",
|
|
"Density",
|
|
"bReflectionsScale",
|
|
"bReflectionsDelayScale",
|
|
"bDecayTimeScale",
|
|
"bDecayHFLimit",
|
|
"bReverbScale",
|
|
"bReverbDelayScale",
|
|
"bEchoTimeScale",
|
|
"bModulationTimeScale",
|
|
"}",
|
|
NULL
|
|
};
|
|
|
|
static const char *BoolNames[3] = { "False", "True", NULL };
|
|
|
|
static ReverbContainer DSPWater =
|
|
{
|
|
// Based on the "off" reverb, this one uses the software water effect,
|
|
// which is completely independant from EAX-like reverb.
|
|
NULL,
|
|
"DSP Water",
|
|
0xffff,
|
|
true,
|
|
false,
|
|
{0, 0, 7.5f, 1.00f, -10000, -10000, 0, 1.00f, 1.00f, 1.0f, -2602, 0.007f, 0.0f,0.0f,0.0f, 200, 0.011f, 0.0f,0.0f,0.0f, 0.250f, 0.00f, 0.25f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.0f, 0.0f, 0.0f, 0x33f },
|
|
true
|
|
};
|
|
|
|
static ReverbContainer Psychotic =
|
|
{
|
|
&DSPWater,
|
|
"Psychotic",
|
|
0x1900,
|
|
true,
|
|
false,
|
|
{0,25, 1.0f, 0.50f, -1000, -151, 0, 7.56f, 0.91f, 1.0f, -626, 0.020f, 0.0f,0.0f,0.0f, 774, 0.030f, 0.0f,0.0f,0.0f, 0.250f, 0.00f, 4.00f, 1.000f, -5.0f, 5000.0f, 250.0f, 0.0f, 100.0f, 100.0f, 0x1f },
|
|
false
|
|
};
|
|
|
|
static ReverbContainer Dizzy =
|
|
{
|
|
&Psychotic,
|
|
"Dizzy",
|
|
0x1800,
|
|
true,
|
|
false,
|
|
{0,24, 1.8f, 0.60f, -1000, -400, 0, 17.23f, 0.56f, 1.0f, -1713, 0.020f, 0.0f,0.0f,0.0f, -613, 0.030f, 0.0f,0.0f,0.0f, 0.250f, 1.00f, 0.81f, 0.310f, -5.0f, 5000.0f, 250.0f, 0.0f, 100.0f, 100.0f, 0x1f },
|
|
false
|
|
};
|
|
|
|
static ReverbContainer Drugged =
|
|
{
|
|
&Dizzy,
|
|
"Drugged",
|
|
0x1700,
|
|
true,
|
|
false,
|
|
{0,23, 1.9f, 0.50f, -1000, 0, 0, 8.39f, 1.39f, 1.0f, -115, 0.002f, 0.0f,0.0f,0.0f, 985, 0.030f, 0.0f,0.0f,0.0f, 0.250f, 0.00f, 0.25f, 1.000f, -5.0f, 5000.0f, 250.0f, 0.0f, 100.0f, 100.0f, 0x1f },
|
|
false
|
|
};
|
|
|
|
static ReverbContainer Underwater =
|
|
{
|
|
&Drugged,
|
|
"Underwater",
|
|
0x1600,
|
|
true,
|
|
false,
|
|
{0,22, 1.8f, 1.00f, -1000, -4000, 0, 1.49f, 0.10f, 1.0f, -449, 0.007f, 0.0f,0.0f,0.0f, 1700, 0.011f, 0.0f,0.0f,0.0f, 0.250f, 0.00f, 1.18f, 0.348f, -5.0f, 5000.0f, 250.0f, 0.0f, 100.0f, 100.0f, 0x3f },
|
|
false
|
|
};
|
|
|
|
static ReverbContainer SewerPipe =
|
|
{
|
|
&Underwater,
|
|
"Sewer Pipe",
|
|
0x1500,
|
|
true,
|
|
false,
|
|
{0,21, 1.7f, 0.80f, -1000, -1000, 0, 2.81f, 0.14f, 1.0f, 429, 0.014f, 0.0f,0.0f,0.0f, 1023, 0.021f, 0.0f,0.0f,0.0f, 0.250f, 0.00f, 0.25f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.0f, 80.0f, 60.0f, 0x3f },
|
|
false
|
|
};
|
|
|
|
static ReverbContainer ParkingLot =
|
|
{
|
|
&SewerPipe,
|
|
"Parking Lot",
|
|
0x1400,
|
|
true,
|
|
false,
|
|
{0,20, 8.3f, 1.00f, -1000, 0, 0, 1.65f, 1.50f, 1.0f, -1363, 0.008f, 0.0f,0.0f,0.0f, -1153, 0.012f, 0.0f,0.0f,0.0f, 0.250f, 0.00f, 0.25f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.0f, 100.0f, 100.0f, 0x1f },
|
|
false
|
|
};
|
|
|
|
static ReverbContainer Plain =
|
|
{
|
|
&ParkingLot,
|
|
"Plain",
|
|
0x1300,
|
|
true,
|
|
false,
|
|
{0,19, 42.5f, 0.21f, -1000, -2000, 0, 1.49f, 0.50f, 1.0f, -2466, 0.179f, 0.0f,0.0f,0.0f, -1926, 0.100f, 0.0f,0.0f,0.0f, 0.250f, 1.00f, 0.25f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.0f, 21.0f, 100.0f, 0x3f },
|
|
false
|
|
};
|
|
|
|
static ReverbContainer Quarry =
|
|
{
|
|
&Plain,
|
|
"Quarry",
|
|
0x1200,
|
|
true,
|
|
false,
|
|
{0,18, 17.5f, 1.00f, -1000, -1000, 0, 1.49f, 0.83f, 1.0f, -10000, 0.061f, 0.0f,0.0f,0.0f, 500, 0.025f, 0.0f,0.0f,0.0f, 0.125f, 0.70f, 0.25f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.0f, 100.0f, 100.0f, 0x3f },
|
|
false
|
|
};
|
|
|
|
static ReverbContainer Mountains =
|
|
{
|
|
&Quarry,
|
|
"Mountains",
|
|
0x1100,
|
|
true,
|
|
false,
|
|
{0,17, 100.0f, 0.27f, -1000, -2500, 0, 1.49f, 0.21f, 1.0f, -2780, 0.300f, 0.0f,0.0f,0.0f, -1434, 0.100f, 0.0f,0.0f,0.0f, 0.250f, 1.00f, 0.25f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.0f, 27.0f, 100.0f, 0x1f },
|
|
false
|
|
};
|
|
|
|
static ReverbContainer City =
|
|
{
|
|
&Mountains,
|
|
"City",
|
|
0x1000,
|
|
true,
|
|
false,
|
|
{0,16, 7.5f, 0.50f, -1000, -800, 0, 1.49f, 0.67f, 1.0f, -2273, 0.007f, 0.0f,0.0f,0.0f, -1691, 0.011f, 0.0f,0.0f,0.0f, 0.250f, 0.00f, 0.25f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.0f, 50.0f, 100.0f, 0x3f },
|
|
false
|
|
};
|
|
|
|
static ReverbContainer Forest =
|
|
{
|
|
&City,
|
|
"Forest",
|
|
0x0F00,
|
|
true,
|
|
false,
|
|
{0,15, 38.0f, 0.30f, -1000, -3300, 0, 1.49f, 0.54f, 1.0f, -2560, 0.162f, 0.0f,0.0f,0.0f, -229, 0.088f, 0.0f,0.0f,0.0f, 0.125f, 1.00f, 0.25f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.0f, 79.0f, 100.0f, 0x3f },
|
|
false
|
|
};
|
|
|
|
static ReverbContainer Alley =
|
|
{
|
|
&Forest,
|
|
"Alley",
|
|
0x0E00,
|
|
true,
|
|
false,
|
|
{0,14, 7.5f, 0.30f, -1000, -270, 0, 1.49f, 0.86f, 1.0f, -1204, 0.007f, 0.0f,0.0f,0.0f, -4, 0.011f, 0.0f,0.0f,0.0f, 0.125f, 0.95f, 0.25f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.0f, 100.0f, 100.0f, 0x3f },
|
|
false
|
|
};
|
|
|
|
static ReverbContainer StoneCorridor =
|
|
{
|
|
&Alley,
|
|
"Stone Corridor",
|
|
0x0D00,
|
|
true,
|
|
false,
|
|
{0,13, 13.5f, 1.00f, -1000, -237, 0, 2.70f, 0.79f, 1.0f, -1214, 0.013f, 0.0f,0.0f,0.0f, 395, 0.020f, 0.0f,0.0f,0.0f, 0.250f, 0.00f, 0.25f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.0f, 100.0f, 100.0f, 0x3f },
|
|
false
|
|
};
|
|
|
|
static ReverbContainer Hallway =
|
|
{
|
|
&StoneCorridor,
|
|
"Hallway",
|
|
0x0C00,
|
|
true,
|
|
false,
|
|
{0,12, 1.8f, 1.00f, -1000, -300, 0, 1.49f, 0.59f, 1.0f, -1219, 0.007f, 0.0f,0.0f,0.0f, 441, 0.011f, 0.0f,0.0f,0.0f, 0.250f, 0.00f, 0.25f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.0f, 100.0f, 100.0f, 0x3f },
|
|
false
|
|
};
|
|
|
|
static ReverbContainer CarpettedHallway =
|
|
{
|
|
&Hallway,
|
|
"Carpetted Hallway",
|
|
0x0B00,
|
|
true,
|
|
false,
|
|
{0,11, 1.9f, 1.00f, -1000, -4000, 0, 0.30f, 0.10f, 1.0f, -1831, 0.002f, 0.0f,0.0f,0.0f, -1630, 0.030f, 0.0f,0.0f,0.0f, 0.250f, 0.00f, 0.25f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.0f, 100.0f, 100.0f, 0x3f },
|
|
false
|
|
};
|
|
|
|
static ReverbContainer Hangar =
|
|
{
|
|
&CarpettedHallway,
|
|
"Hangar",
|
|
0x0A00,
|
|
true,
|
|
false,
|
|
{0,10, 50.3f, 1.00f, -1000, -1000, 0, 10.05f, 0.23f, 1.0f, -602, 0.020f, 0.0f,0.0f,0.0f, 198, 0.030f, 0.0f,0.0f,0.0f, 0.250f, 0.00f, 0.25f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.0f, 100.0f, 100.0f, 0x3f },
|
|
false
|
|
};
|
|
|
|
static ReverbContainer Arena =
|
|
{
|
|
&Hangar,
|
|
"Arena",
|
|
0x0900,
|
|
true,
|
|
false,
|
|
{0, 9, 36.2f, 1.00f, -1000, -698, 0, 7.24f, 0.33f, 1.0f, -1166, 0.020f, 0.0f,0.0f,0.0f, 16, 0.030f, 0.0f,0.0f,0.0f, 0.250f, 0.00f, 0.25f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.0f, 100.0f, 100.0f, 0x3f },
|
|
false
|
|
};
|
|
|
|
static ReverbContainer Cave =
|
|
{
|
|
&Arena,
|
|
"Cave",
|
|
0x0800,
|
|
true,
|
|
false,
|
|
{0, 8, 14.6f, 1.00f, -1000, 0, 0, 2.91f, 1.30f, 1.0f, -602, 0.015f, 0.0f,0.0f,0.0f, -302, 0.022f, 0.0f,0.0f,0.0f, 0.250f, 0.00f, 0.25f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.0f, 100.0f, 100.0f, 0x1f },
|
|
false
|
|
};
|
|
|
|
static ReverbContainer ConcertHall =
|
|
{
|
|
&Cave,
|
|
"Concert Hall",
|
|
0x0700,
|
|
true,
|
|
false,
|
|
{0, 7, 19.6f, 1.00f, -1000, -500, 0, 3.92f, 0.70f, 1.0f, -1230, 0.020f, 0.0f,0.0f,0.0f, -2, 0.029f, 0.0f,0.0f,0.0f, 0.250f, 0.00f, 0.25f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.0f, 100.0f, 100.0f, 0x3f },
|
|
false
|
|
};
|
|
|
|
static ReverbContainer Auditorium =
|
|
{
|
|
&ConcertHall,
|
|
"Auditorium",
|
|
0x0600,
|
|
true,
|
|
false,
|
|
{0, 6, 21.6f, 1.00f, -1000, -476, 0, 4.32f, 0.59f, 1.0f, -789, 0.020f, 0.0f,0.0f,0.0f, -289, 0.030f, 0.0f,0.0f,0.0f, 0.250f, 0.00f, 0.25f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.0f, 100.0f, 100.0f, 0x3f },
|
|
false
|
|
};
|
|
|
|
static ReverbContainer StoneRoom =
|
|
{
|
|
&Auditorium,
|
|
"Stone Room",
|
|
0x0500,
|
|
true,
|
|
false,
|
|
{0, 5, 11.6f, 1.00f, -1000, -300, 0, 2.31f, 0.64f, 1.0f, -711, 0.012f, 0.0f,0.0f,0.0f, 83, 0.017f, 0.0f,0.0f,0.0f, 0.250f, 0.00f, 0.25f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.0f, 100.0f, 100.0f, 0x3f },
|
|
false
|
|
};
|
|
|
|
static ReverbContainer LivingRoom =
|
|
{
|
|
&StoneRoom,
|
|
"Living Room",
|
|
0x0400,
|
|
true,
|
|
false,
|
|
{0, 4, 2.5f, 1.00f, -1000, -6000, 0, 0.50f, 0.10f, 1.0f, -1376, 0.003f, 0.0f,0.0f,0.0f, -1104, 0.004f, 0.0f,0.0f,0.0f, 0.250f, 0.00f, 0.25f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.0f, 100.0f, 100.0f, 0x3f },
|
|
false
|
|
};
|
|
|
|
static ReverbContainer Bathroom =
|
|
{
|
|
&LivingRoom,
|
|
"Bathroom",
|
|
0x0300,
|
|
true,
|
|
false,
|
|
{0, 3, 1.4f, 1.00f, -1000, -1200, 0, 1.49f, 0.54f, 1.0f, -370, 0.007f, 0.0f,0.0f,0.0f, 1030, 0.011f, 0.0f,0.0f,0.0f, 0.250f, 0.00f, 0.25f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.0f, 100.0f, 60.0f, 0x3f },
|
|
false
|
|
};
|
|
|
|
static ReverbContainer Room =
|
|
{
|
|
&Bathroom,
|
|
"Room",
|
|
0x0200,
|
|
true,
|
|
false,
|
|
{0, 2, 1.9f, 1.00f, -1000, -454, 0, 0.40f, 0.83f, 1.0f, -1646, 0.002f, 0.0f,0.0f,0.0f, 53, 0.003f, 0.0f,0.0f,0.0f, 0.250f, 0.00f, 0.25f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.0f, 100.0f, 100.0f, 0x3f },
|
|
false
|
|
};
|
|
|
|
static ReverbContainer PaddedCell =
|
|
{
|
|
&Room,
|
|
"Padded Cell",
|
|
0x0100,
|
|
true,
|
|
false,
|
|
{0, 1, 1.4f, 1.00f, -1000, -6000, 0, 0.17f, 0.10f, 1.0f, -1204, 0.001f, 0.0f,0.0f,0.0f, 207, 0.002f, 0.0f,0.0f,0.0f, 0.250f, 0.00f, 0.25f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.0f, 100.0f, 100.0f, 0x3f },
|
|
false
|
|
};
|
|
|
|
static ReverbContainer Generic =
|
|
{
|
|
&PaddedCell,
|
|
"Generic",
|
|
0x0001,
|
|
true,
|
|
false,
|
|
{0, 0, 7.5f, 1.00f, -1000, -100, 0, 1.49f, 0.83f, 1.0f, -2602, 0.007f, 0.0f,0.0f,0.0f, 200, 0.011f, 0.0f,0.0f,0.0f, 0.250f, 0.00f, 0.25f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.0f, 100.0f, 100.0f, 0x3f },
|
|
false
|
|
};
|
|
|
|
static ReverbContainer Off =
|
|
{
|
|
&Generic,
|
|
"Off",
|
|
0x0000,
|
|
true,
|
|
false,
|
|
{0, 0, 7.5f, 1.00f, -10000, -10000, 0, 1.00f, 1.00f, 1.0f, -2602, 0.007f, 0.0f,0.0f,0.0f, 200, 0.011f, 0.0f,0.0f,0.0f, 0.250f, 0.00f, 0.25f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.0f, 0.0f, 0.0f, 0x33f },
|
|
false
|
|
};
|
|
|
|
ReverbContainer *DefaultEnvironments[26] =
|
|
{
|
|
&Off, &PaddedCell, &Room, &Bathroom, &LivingRoom, &StoneRoom, &Auditorium,
|
|
&ConcertHall, &Cave, &Arena, &Hangar, &CarpettedHallway, &Hallway, &StoneCorridor,
|
|
&Alley, &Forest, &City, &Mountains, &Quarry, &Plain, &ParkingLot, &SewerPipe,
|
|
&Underwater, &Drugged, &Dizzy, &Psychotic
|
|
};
|
|
|
|
ReverbContainer *Environments = &Off;
|
|
|
|
ReverbContainer *S_FindEnvironment (const char *name)
|
|
{
|
|
ReverbContainer *probe = Environments;
|
|
|
|
if (name == NULL)
|
|
return NULL;
|
|
|
|
while (probe != NULL)
|
|
{
|
|
if (stricmp (probe->Name, name) == 0)
|
|
{
|
|
return probe;
|
|
}
|
|
probe = probe->Next;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
ReverbContainer *S_FindEnvironment (int id)
|
|
{
|
|
ReverbContainer *probe = Environments;
|
|
|
|
while (probe != NULL && probe->ID < id)
|
|
{
|
|
probe = probe->Next;
|
|
}
|
|
return (probe && probe->ID == id ? probe : NULL);
|
|
}
|
|
|
|
void S_AddEnvironment (ReverbContainer *settings)
|
|
{
|
|
ReverbContainer *probe = Environments;
|
|
ReverbContainer **ptr = &Environments;
|
|
|
|
while (probe != NULL && probe->ID < settings->ID)
|
|
{
|
|
ptr = &probe->Next;
|
|
probe = probe->Next;
|
|
}
|
|
|
|
if (probe != NULL && probe->ID == settings->ID)
|
|
{
|
|
// Built-in environments cannot be changed
|
|
if (!probe->Builtin)
|
|
{
|
|
settings->Next = probe->Next;
|
|
*ptr = settings;
|
|
delete[] const_cast<char *>(probe->Name);
|
|
delete probe;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
settings->Next = probe;
|
|
*ptr = settings;
|
|
}
|
|
}
|
|
|
|
static void ReadReverbDef (int lump)
|
|
{
|
|
FScanner sc;
|
|
const ReverbContainer *def;
|
|
ReverbContainer *newenv;
|
|
REVERB_PROPERTIES props;
|
|
char *name;
|
|
int id1, id2, i, j;
|
|
bool inited[NUM_REVERB_FIELDS];
|
|
uint8_t bools[32];
|
|
|
|
sc.OpenLumpNum(lump);
|
|
while (sc.GetString ())
|
|
{
|
|
name = copystring (sc.String);
|
|
sc.MustGetNumber ();
|
|
id1 = sc.Number;
|
|
sc.MustGetNumber ();
|
|
id2 = sc.Number;
|
|
sc.MustGetStringName ("{");
|
|
memset (inited, 0, sizeof(inited));
|
|
props.Instance = 0;
|
|
props.Flags = 0;
|
|
while (sc.MustGetString (), NUM_REVERB_FIELDS > (i = sc.MustMatchString (ReverbFieldNames)))
|
|
{
|
|
if (ReverbFields[i].Float)
|
|
{
|
|
sc.MustGetFloat ();
|
|
props.*ReverbFields[i].Float = (float)clamp (sc.Float,
|
|
double(ReverbFields[i].Min)/1000,
|
|
double(ReverbFields[i].Max)/1000);
|
|
}
|
|
else if (ReverbFields[i].Int)
|
|
{
|
|
sc.MustGetNumber ();
|
|
props.*ReverbFields[i].Int = (j = clamp (sc.Number,
|
|
ReverbFields[i].Min, ReverbFields[i].Max));
|
|
if (i == 0 && j != sc.Number)
|
|
{
|
|
sc.ScriptError ("The Environment field is out of range.");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
sc.MustGetString ();
|
|
bools[ReverbFields[i].Flag] = sc.MustMatchString (BoolNames);
|
|
}
|
|
inited[i] = true;
|
|
}
|
|
if (!inited[0])
|
|
{
|
|
sc.ScriptError ("Sound %s is missing an Environment field.", name);
|
|
}
|
|
|
|
// Add the new environment to the list, filling in uninitialized fields
|
|
// with values from the standard environment specified.
|
|
def = DefaultEnvironments[props.Environment];
|
|
for (i = 0; i < NUM_REVERB_FIELDS; ++i)
|
|
{
|
|
if (ReverbFields[i].Float)
|
|
{
|
|
if (!inited[i])
|
|
{
|
|
props.*ReverbFields[i].Float = def->Properties.*ReverbFields[i].Float;
|
|
}
|
|
}
|
|
else if (ReverbFields[i].Int)
|
|
{
|
|
if (!inited[i])
|
|
{
|
|
props.*ReverbFields[i].Int = def->Properties.*ReverbFields[i].Int;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!inited[i])
|
|
{
|
|
int mask = 1 << ReverbFields[i].Flag;
|
|
if (def->Properties.Flags & mask)
|
|
{
|
|
props.Flags |= mask;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (bools[ReverbFields[i].Flag])
|
|
{
|
|
props.Flags |= 1 << ReverbFields[i].Flag;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
newenv = new ReverbContainer;
|
|
newenv->Next = NULL;
|
|
newenv->Name = name;
|
|
newenv->ID = (id1 << 8) | id2;
|
|
newenv->Builtin = false;
|
|
newenv->Properties = props;
|
|
newenv->SoftwareWater = false;
|
|
S_AddEnvironment (newenv);
|
|
}
|
|
}
|
|
|
|
void S_ParseReverbDef ()
|
|
{
|
|
int lump, lastlump = 0;
|
|
|
|
atterm (S_UnloadReverbDef);
|
|
S_UnloadReverbDef ();
|
|
|
|
while ((lump = Wads.FindLump ("REVERBS", &lastlump)) != -1)
|
|
{
|
|
ReadReverbDef (lump);
|
|
}
|
|
InitReverbMenu();
|
|
}
|
|
|
|
void S_UnloadReverbDef ()
|
|
{
|
|
ReverbContainer *probe = Environments;
|
|
ReverbContainer **pNext = NULL;
|
|
|
|
while (probe != NULL)
|
|
{
|
|
ReverbContainer *next = probe->Next;
|
|
if (!probe->Builtin)
|
|
{
|
|
if (pNext != NULL) *pNext = probe->Next;
|
|
delete[] const_cast<char *>(probe->Name);
|
|
delete probe;
|
|
}
|
|
else
|
|
{
|
|
pNext = &probe->Next;
|
|
}
|
|
probe = next;
|
|
}
|
|
Environments = &Off;
|
|
}
|
|
|
|
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;
|
|
size_t j;
|
|
|
|
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 (j = 0; j < countof(ReverbFields); ++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 < (int)countof(ReverbFields))
|
|
{
|
|
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);
|
|
return 1;
|
|
}
|
|
|
|
DEFINE_ACTION_FUNCTION(DReverbEdit, SetValue)
|
|
{
|
|
PARAM_PROLOGUE;
|
|
PARAM_INT(index);
|
|
PARAM_FLOAT(v);
|
|
|
|
if (index >= 0 && index < (int)countof(ReverbFields))
|
|
{
|
|
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);
|
|
return 1;
|
|
}
|
|
|
|
DEFINE_ACTION_FUNCTION(DReverbEdit, GrayCheck)
|
|
{
|
|
PARAM_PROLOGUE;
|
|
ACTION_RETURN_BOOL(CurrentEnv->Builtin);
|
|
return 1;
|
|
}
|
|
|
|
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");
|
|
}
|
|
|