Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for setting a volume multiplier per sound category #140

Merged
merged 1 commit into from
Dec 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 6 additions & 27 deletions src/main/java/dynamic_fps/impl/DynamicFPSMod.java
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,8 @@ public static int targetFrameRate() {
return config.frameRateTarget();
}

public static float volumeMultiplier() {
return config.volumeMultiplier();
public static float volumeMultiplier(SoundSource source) {
return config.volumeMultiplier(source);
}

public static boolean shouldShowToasts() {
Expand Down Expand Up @@ -147,8 +147,10 @@ public static void handleStateChange(PowerState previous, PowerState current) {
System.gc();
}

if (before.volumeMultiplier() != config.volumeMultiplier()) {
setVolumeMultiplier(config.volumeMultiplier());
for (var source : SoundSource.values()) {
if (before.volumeMultiplier(source) != config.volumeMultiplier(source)) {
minecraft.getSoundManager().dynamic_fps$updateVolume(source);
}
}

if (before.graphicsState() != config.graphicsState()) {
Expand Down Expand Up @@ -202,29 +204,6 @@ private static void checkForStateChanges0() {
}
}

private static void setVolumeMultiplier(float multiplier) {
// Set the sound engine to a new volume multiplier,
// Or instead pause it when the multiplier is zero.

// We can not set the sound engine to a zero volume
// Because it stops all actively playing sounds and
// Makes for a rather jarring experience when music
// Is stopped. Also fixes now-playing compatibility

var manager = minecraft.getSoundManager();

if (multiplier == 0) {
manager.pause();
} else {
manager.resume();

manager.updateSourceVolume(
SoundSource.MASTER,
minecraft.options.getSoundSourceVolume(SoundSource.MASTER) * multiplier
);
}
}

private static boolean checkForRender(long timeSinceLastRender) {
int frameRateTarget = config.frameRateTarget();

Expand Down
30 changes: 0 additions & 30 deletions src/main/java/dynamic_fps/impl/GraphicsState.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
package dynamic_fps.impl;

import java.util.Locale;

import com.mojang.serialization.Codec;
import com.mojang.serialization.DataResult;
import com.mojang.serialization.DynamicOps;
import com.mojang.serialization.codecs.PrimitiveCodec;

/*
* Graphics settings to apply within a given power state.
*/
Expand All @@ -25,27 +18,4 @@ public enum GraphicsState {
* Reduce graphics settings to minimal values, this will reload the world!
*/
MINIMAL;

public static final Codec<GraphicsState> CODEC = new PrimitiveCodec<GraphicsState>() {
@Override
public <T> T write(DynamicOps<T> ops, GraphicsState value) {
return ops.createString(value.toString());
}

@Override
public <T> DataResult<GraphicsState> read(DynamicOps<T> ops, T input) {
var value = ops.getStringValue(input).get().left();

if (value.isEmpty()) {
return DataResult.error(() -> "Graphics state must not be empty!");
} else {
return DataResult.success(GraphicsState.valueOf(value.get().toUpperCase(Locale.ROOT)));
}
}
};

@Override
public String toString() {
return super.toString().toLowerCase(Locale.ROOT);
}
}
25 changes: 0 additions & 25 deletions src/main/java/dynamic_fps/impl/PowerState.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
package dynamic_fps.impl;

import java.util.Locale;

import com.mojang.serialization.Codec;
import com.mojang.serialization.DataResult;
import com.mojang.serialization.DynamicOps;
import com.mojang.serialization.codecs.PrimitiveCodec;

/**
* An analog for device power states, applied to the Minecraft window.
*
Expand Down Expand Up @@ -35,24 +28,6 @@ public enum PowerState {

public final boolean configurable;

public static final Codec<PowerState> CODEC = new PrimitiveCodec<PowerState>() {
@Override
public <T> T write(DynamicOps<T> ops, PowerState value) {
return ops.createString(value.toString().toLowerCase(Locale.ROOT));
}

@Override
public <T> DataResult<PowerState> read(DynamicOps<T> ops, T input) {
var value = ops.getStringValue(input).get().left();

if (value.isEmpty()) {
return DataResult.error(() -> "Power state must not be empty!");
} else {
return DataResult.success(PowerState.valueOf(value.get().toUpperCase(Locale.ROOT)));
}
}
};

private PowerState(boolean configurable) {
this.configurable = configurable;
}
Expand Down
32 changes: 20 additions & 12 deletions src/main/java/dynamic_fps/impl/compat/ClothConfig.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package dynamic_fps.impl.compat;

import me.shedaniel.clothconfig2.api.ConfigBuilder;
import me.shedaniel.clothconfig2.api.ConfigEntryBuilder;
import net.minecraft.ChatFormatting;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.network.chat.Component;
import net.minecraft.sounds.SoundSource;

import static dynamic_fps.impl.util.Localization.localized;

Expand Down Expand Up @@ -48,17 +48,25 @@ public static Screen genConfigScreen(Screen parent) {
.build()
);

category.addEntry(
entryBuilder.startIntSlider(
localized("config", "volume_multiplier"),
(int) (config.volumeMultiplier() * 100),
0, 100
)
.setDefaultValue((int) (standard.volumeMultiplier() * 100))
.setSaveConsumer(value -> config.setVolumeMultiplier(value / 100f))
.setTextGetter(ClothConfig::volumeMultiplierMessage)
.build()
);
var volumes = entryBuilder.startSubCategory(localized("config", "volume_multiplier"));

for (var source : SoundSource.values()) {
var name = source.getName();

volumes.add(
entryBuilder.startIntSlider(
Component.translatable("soundCategory." + name),
(int) (config.volumeMultiplier(source) * 100),
0, 100
)
.setDefaultValue((int) (standard.volumeMultiplier(source) * 100))
.setSaveConsumer(value -> config.setVolumeMultiplier(source, value / 100f))
.setTextGetter(ClothConfig::volumeMultiplierMessage)
.build()
);
}

category.addEntry(volumes.build());

category.addEntry(
entryBuilder.startEnumSelector(
Expand Down
29 changes: 19 additions & 10 deletions src/main/java/dynamic_fps/impl/config/Config.java
Original file line number Diff line number Diff line change
@@ -1,30 +1,35 @@
package dynamic_fps.impl.config;

import java.util.HashMap;
import java.util.Map;

import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;

import dynamic_fps.impl.GraphicsState;
import dynamic_fps.impl.util.EnumCodec;
import net.minecraft.sounds.SoundSource;

public final class Config {
private int frameRateTarget;
private float volumeMultiplier;
private Map<SoundSource, Float> volumeMultipliers;
private GraphicsState graphicsState;
private boolean showToasts;
private boolean runGarbageCollector;

public static final Codec<Config> CODEC = RecordCodecBuilder.create(instance -> instance.group(
Codec.INT.fieldOf("frame_rate_target").forGetter(Config::frameRateTarget),
Codec.FLOAT.fieldOf("volume_multiplier").forGetter(Config::volumeMultiplier),
GraphicsState.CODEC.fieldOf("graphics_state").forGetter(Config::graphicsState),
Codec.unboundedMap(new EnumCodec<>(SoundSource.values()), Codec.FLOAT).fieldOf("volume_multipliers").forGetter(Config::volumeMultipliers),
new EnumCodec<>(GraphicsState.values()).fieldOf("graphics_state").forGetter(Config::graphicsState),
Codec.BOOL.fieldOf("show_toasts").forGetter(Config::showToasts),
Codec.BOOL.fieldOf("run_garbage_collector").forGetter(Config::runGarbageCollector)
).apply(instance, Config::new));

public static final Config ACTIVE = new Config(-1, 1.0f, GraphicsState.DEFAULT, true, false);
public static final Config ACTIVE = new Config(-1, new HashMap<>(), GraphicsState.DEFAULT, true, false);

public Config(int frameRateTarget, float volumeMultiplier, GraphicsState graphicsState, boolean showToasts, boolean runGarbageCollector) {
public Config(int frameRateTarget, Map<SoundSource, Float> volumeMultipliers, GraphicsState graphicsState, boolean showToasts, boolean runGarbageCollector) {
this.frameRateTarget = frameRateTarget;
this.volumeMultiplier = volumeMultiplier;
this.volumeMultipliers = new HashMap<>(volumeMultipliers); // Ensure the map is mutable
this.graphicsState = graphicsState;
this.showToasts = showToasts;
this.runGarbageCollector = runGarbageCollector;
Expand All @@ -38,12 +43,16 @@ public void setFrameRateTarget(int value) {
this.frameRateTarget = value;
}

public float volumeMultiplier() {
return this.volumeMultiplier;
public Map<SoundSource, Float> volumeMultipliers() {
return this.volumeMultipliers;
}

public float volumeMultiplier(SoundSource category) {
return this.volumeMultipliers.getOrDefault(category, 1.0f);
}

public void setVolumeMultiplier(float value) {
this.volumeMultiplier = value;
public void setVolumeMultiplier(SoundSource category, float value) {
this.volumeMultipliers.put(category, value);
}

public GraphicsState graphicsState() {
Expand Down
63 changes: 59 additions & 4 deletions src/main/java/dynamic_fps/impl/config/DynamicFPSConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,30 @@
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;

import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;
import com.mojang.serialization.Codec;
import com.mojang.serialization.JsonOps;
import com.mojang.serialization.codecs.RecordCodecBuilder;

import dynamic_fps.impl.DynamicFPSMod;
import dynamic_fps.impl.GraphicsState;
import dynamic_fps.impl.PowerState;
import dynamic_fps.impl.util.EnumCodec;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.sounds.SoundSource;

public final class DynamicFPSConfig {
private Map<PowerState, Config> configs;

private static final Path CONFIGS = FabricLoader.getInstance().getConfigDir();
private static final Path CONFIG_FILE = CONFIGS.resolve(DynamicFPSMod.MOD_ID + ".json");

private static final Codec<Map<PowerState, Config>> STATES_CODEC = Codec.unboundedMap(PowerState.CODEC, Config.CODEC);
private static final Codec<Map<PowerState, Config>> STATES_CODEC = Codec.unboundedMap(new EnumCodec<>(PowerState.values()), Config.CODEC);

private static final Codec<DynamicFPSConfig> CODEC = RecordCodecBuilder.create(instance -> instance.group(
STATES_CODEC.fieldOf("states").forGetter(DynamicFPSConfig::configs)
Expand Down Expand Up @@ -67,6 +72,8 @@ public static DynamicFPSConfig load() {
}

var root = JsonParser.parseString(data);

upgradeConfig((JsonObject) root);
var parsed = CODEC.parse(JsonOps.INSTANCE, root);

return parsed.getOrThrow(false, RuntimeException::new);
Expand All @@ -91,17 +98,65 @@ public void save() {
public static Config getDefaultConfig(PowerState state) {
switch (state) {
case HOVERED: {
return new Config(60, 1.0f, GraphicsState.DEFAULT, true, false);
return new Config(60, withMasterVolume(1.0f), GraphicsState.DEFAULT, true, false);
}
case UNFOCUSED: {
return new Config(1, 0.25f, GraphicsState.DEFAULT, false, false);
return new Config(1, withMasterVolume(0.25f), GraphicsState.DEFAULT, false, false);
}
case INVISIBLE: {
return new Config(0, 0.0f, GraphicsState.DEFAULT, false, false);
return new Config(0, withMasterVolume(0.0f), GraphicsState.DEFAULT, false, false);
}
default: {
throw new RuntimeException("Getting default configuration for unhandled power state " + state.toString());
}
}
}

private static Map<SoundSource, Float> withMasterVolume(float value) {
var volumes = new HashMap<SoundSource, Float>();
volumes.put(SoundSource.MASTER, value);
return volumes;
}

private static void upgradeConfig(JsonObject root) {
upgradeVolumeMultiplier(root);
}

private static void upgradeVolumeMultiplier(JsonObject root) {
// Convert each old power state config
// - { "volume_multiplier": 0.0, ... }
// + { "volume_multipliers": { "master": 0.0 }, ... }
if (!root.has("states")) {
return;
}

var states = root.getAsJsonObject("states");

if (!states.isJsonObject()) {
return;
}

for (var key : states.keySet()) {
var element = states.getAsJsonObject(key);

if (!element.isJsonObject()) {
continue;
}

if (!element.has("volume_multiplier")) {
continue;
}

var multiplier = element.get("volume_multiplier");

if (!multiplier.isJsonPrimitive() || !((JsonPrimitive)multiplier).isNumber()) {
continue;
}

var multipliers = new JsonObject();
multipliers.add("master", multiplier);

element.add("volume_multipliers", multipliers);
}
}
}
5 changes: 3 additions & 2 deletions src/main/java/dynamic_fps/impl/mixin/LoadingOverlayMixin.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package dynamic_fps.impl.mixin;

import dynamic_fps.impl.util.DynamicFPSSplashOverlay;
import net.minecraft.client.gui.screens.LoadingOverlay;

import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;

import dynamic_fps.impl.util.duck.DuckSplashOverlay;

@Mixin(LoadingOverlay.class)
public class LoadingOverlayMixin implements DynamicFPSSplashOverlay {
public class LoadingOverlayMixin implements DuckSplashOverlay {
@Shadow
private long fadeOutStart;

Expand Down
4 changes: 2 additions & 2 deletions src/main/java/dynamic_fps/impl/mixin/ScreenMixin.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;

import dynamic_fps.impl.util.DynamicFPSScreen;
import dynamic_fps.impl.util.ModCompatibility;
import dynamic_fps.impl.util.duck.DuckScreen;
import net.minecraft.client.gui.screens.Screen;

@Mixin(Screen.class)
public class ScreenMixin implements DynamicFPSScreen {
public class ScreenMixin implements DuckScreen {
@Unique
private boolean dynamic_fps$canOptimize = false;

Expand Down
Loading