/* ** ** **--------------------------------------------------------------------------- ** 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(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(probe->Name); delete probe; } else { pNext = &probe->Next; } probe = next; } Environments = &Off; } CUSTOM_CVAR(Bool, eaxedit_test, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | 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(v), rev->Min, rev->Max); } else if (rev->Float != nullptr) { v = CurrentEnv->Properties.*(rev->Float) = clamp(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(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> 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(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 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"); }