Skip to content
Open
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
15 changes: 7 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@
[![CurseForge](https://img.shields.io/curseforge/dt/874633?logo=curseforge&label=CurseForge)](https://www.curseforge.com/minecraft/mc-mods/soundsbegone)
[![](https://dcbadge.limes.pink/api/server/dvg3tcQCPW)](https://discord.gg/dvg3tcQCPW)

Inspired by my own misophonia, Sounds Be Gone keeps Minecraft playable when specific noises are overwhelming. The mod passively records every sound you have heard in the past minute and lets you mute any of them permanently with a couple of clicks. Everything is client-side, so your preferences affect only your game.
Inspired by my own misophonia, Sounds Be Gone keeps Minecraft playable when specific noises are overwhelming. The mod passively records every sound you have heard in the past minute and lets you reduce the volume or mute any of them permanently with a couple of clicks. Everything is client-side, so your preferences affect only your game.

### _Fabric is supported on releases below 1.20. NeoForge builds are available for Minecraft 1.20.4 and newer._

## Why players use Sounds Be Gone

- Instantly silence any sound event (vanilla or modded) without memorizing filenames.
- The "recently played" list removes guesswork—scroll, select, and mute.
- Keeps an audit trail so you can easily unmute later when you change your mind.
- Reduce the volume or instantly silence any sound event (vanilla or modded) without memorizing filenames.
- The "recently played" list removes guesswork—scroll, select, and adjust.
- Keeps an audit trail so you can easily readjust volumes later when you change your mind.
- Built for sensory accessibility: predictable UI, reversible actions, and zero server requirements.

## Infrequent Sounds!
Expand All @@ -31,13 +31,13 @@ Use **Infrequent** for sounds where silence would remove useful feedback, but no

1. When you hear an unwanted sound, press `B` (default keybind) to open the Sound Sanctuary.
2. Pick the sound(s) from the "Played in the last 60 seconds" list.
3. Toggle them off to mute permanently; the change saves immediately to your local profile.
3. Use the volume slider to turn it down, or set it to 0% to mute it permanently; the change saves immediately.

### Minecraft 1.19 and below

1. Press `ESC`, choose `MODS`, then select **Sounds Be Gone**.
2. Click the settings cog in the top-right corner to open the configuration screen.
3. Use the list of recent sounds to disable whatever is bothering you.
3. Use the list of recent sounds to turn down or disable whatever is bothering you.

## Installation

Expand All @@ -58,7 +58,6 @@ Use **Infrequent** for sounds where silence would remove useful feedback, but no
## Configuration tips

- Rebind the `B` key if it clashes with another mod under `Options → Controls → Key Binds → Sounds Be Gone`.
- Sounds stay muted until you manually re-enable them, even if you swap worlds or restart the client.
- Mod Menu makes it easy to open the config without jumping into a world, especially on older Minecraft versions.
- Keep Cloth Config up to date—new releases occasionally add UX improvements for navigating large sound lists.

Expand All @@ -83,4 +82,4 @@ Thanks to all the supporters who make this project possible!



<!-- marker:patrons-end -->
<!-- marker:patrons-end -->
Empty file modified gradlew
100644 → 100755
Empty file.
72 changes: 45 additions & 27 deletions src/main/java/gg/meza/soundsbegone/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
import java.lang.reflect.Type;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import static gg.meza.soundsbegone.SoundsBeGoneConfig.LOGGER;
import static gg.meza.soundsbegone.SoundsBeGoneConfig.MOD_ID;
Expand Down Expand Up @@ -41,45 +43,44 @@ public void toggleTelemetry(boolean toValue) {
configData.telemetry = toValue;
}

public void disableSound(String sound) {
resetSound(sound);
configData.sounds.add(sound);
}

public void reduceSound(String sound) {
resetSound(sound);
configData.infrequent.add(sound);
public void setInfrequent(String sound, boolean value) {
if (value) {
configData.infrequent.add(sound);
} else {
configData.infrequent.remove(sound);
}
}

public void resetSound(String sound) {
configData.infrequent.remove(sound);
configData.sounds.remove(sound);
public int getSoundVolume(String sound) {
return configData.soundVolumes.getOrDefault(sound, 100);
}

public boolean isSoundDisabled(String sound) {
return configData.sounds.contains(sound);
public void setSoundVolume(String sound, int volume) {
if (volume == 100) {
configData.soundVolumes.remove(sound);
} else {
configData.soundVolumes.put(sound, volume);
}
}
Comment on lines +54 to 64

public boolean isSoundInfrequent(String sound) {
return configData.infrequent.contains(sound);
}

public Set<String> disabledSounds() {
return configData.sounds;
return configData.soundVolumes.entrySet().stream()
.filter(e -> e.getValue() == 0)
.map(Map.Entry::getKey).collect(Collectors.toUnmodifiableSet());
}

public Set<String> infrequentSounds() {
return configData.infrequent;
public Set<String> reducedSounds() {
return configData.soundVolumes.entrySet().stream()
.filter(e -> e.getValue() > 0 && e.getValue() < 100)
.map(Map.Entry::getKey).collect(Collectors.toUnmodifiableSet());
}

public SoundState getSoundState(String sound) {
if (isSoundDisabled(sound)) {
return SoundState.DISABLED;
} else if (isSoundInfrequent(sound)) {
return SoundState.INFREQUENT;
} else {
return SoundState.ENABLED;
}
public Set<String> infrequentSounds() {
return Set.copyOf(configData.infrequent);
}
Comment on lines +82 to 84
Comment on lines 70 to 84

public double getFrequencyPercentage() {
Expand All @@ -98,14 +99,16 @@ public void initConfig() {
configData = GSON.fromJson(reader, ConfigData.class);
reader.close();
} catch (IOException | JsonParseException e) {
SoundsBeGoneConfig.LOGGER.error("Cause: " + e.getCause().getClass().getSimpleName());
if (e.getCause().getClass() == IllegalStateException.class) {
Throwable cause = e.getCause();
LOGGER.error("Failed to load config", e);
if (cause != null && cause.getClass() == IllegalStateException.class) {
convertConfig(configPath);
return;
}
throw new SerializationException(e);
}
}
migrateLegacySounds();
}

public void saveConfig() {
Expand Down Expand Up @@ -142,7 +145,11 @@ private void convertConfig(Path configPath) {
}.getType();
Set<String> disabledSounds = GSON.fromJson(reader, setType);
reader.close();
this.configData.sounds = disabledSounds;
if (disabledSounds != null) {
for (String sound : disabledSounds) {
this.configData.soundVolumes.putIfAbsent(sound, 0);
}
}

BufferedWriter writer = Files.newBufferedWriter(configPath);
GSON.toJson(configData, writer);
Expand All @@ -152,4 +159,15 @@ private void convertConfig(Path configPath) {
}
}

private void migrateLegacySounds() {
if (configData.sounds != null && !configData.sounds.isEmpty()) {
Comment on lines +162 to +163
SoundsBeGoneConfig.LOGGER.info("Migrating legacy disabled sounds to volume map");
for (String sound : configData.sounds) {
configData.soundVolumes.putIfAbsent(sound, 0);
}
configData.sounds = null;
saveConfig();
}
}
Comment thread
KdGaming0 marked this conversation as resolved.

}
5 changes: 4 additions & 1 deletion src/main/java/gg/meza/soundsbegone/ConfigData.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package gg.meza.soundsbegone;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

public class ConfigData {
public String lastVersionSeen = "";
public boolean telemetry = true;
public double frequencyPercentage = 10;
public Set<String> sounds = new HashSet<>();
public Map<String, Integer> soundVolumes = new HashMap<>();
public Set<String> sounds = new HashSet<>(); // Legacy field, migrated to soundVolumes on load
Comment thread
KdGaming0 marked this conversation as resolved.
public Set<String> infrequent = new HashSet<>();
}
7 changes: 0 additions & 7 deletions src/main/java/gg/meza/soundsbegone/SoundState.java

This file was deleted.

85 changes: 54 additions & 31 deletions src/main/java/gg/meza/soundsbegone/client/ConfigScreen.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package gg.meza.soundsbegone.client;

import gg.meza.soundsbegone.SoundState;
import gg.meza.soundsbegone.SoundsBeGoneConfig;

import gg.meza.supporters.clothconfig.SupportCategory;
Expand All @@ -12,6 +11,9 @@
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.network.chat.Component;

import java.util.HashSet;
import java.util.Set;

public class ConfigScreen {
public static Screen getConfigScreen(Screen parent) {
ConfigBuilder builder = ConfigBuilder.create()
Expand All @@ -21,12 +23,11 @@ public static Screen getConfigScreen(Screen parent) {
SoundsBeGoneClient.config.saveConfig();
});
ConfigCategory general = builder.getOrCreateCategory(Component.translatable("soundsbegone.config.category.latest"));
ConfigCategory disabled = builder.getOrCreateCategory(Component.translatable("soundsbegone.config.category.disabled"));
ConfigCategory infrequent = builder.getOrCreateCategory(Component.translatable("soundsbegone.config.category.infrequent"));
ConfigCategory configured = builder.getOrCreateCategory(Component.translatable("soundsbegone.config.category.configured"));
ConfigCategory settings = builder.getOrCreateCategory(Component.translatable("soundsbegone.config.category.settings"));
ConfigEntryBuilder entryBuilder = builder.entryBuilder();

infrequent.addEntry(
settings.addEntry(
entryBuilder.startDoubleField(Component.translatable("soundsbegone.config.infrequent.frequency.title"), SoundsBeGoneClient.config.getFrequencyPercentage())
.setMin(0.001)
.setMax(99.999)
Expand Down Expand Up @@ -62,42 +63,64 @@ public static Screen getConfigScreen(Screen parent) {
SoundsBeGoneClient.SoundMap
.keySet()
.stream()
.filter(s -> !SoundsBeGoneClient.config.isSoundDisabled(s))
.filter(s -> SoundsBeGoneClient.config.getSoundVolume(s) == 100 && !SoundsBeGoneClient.config.isSoundInfrequent(s))
.sorted()
.forEach((key) -> general.addEntry(constructOption(entryBuilder, key)));
Comment on lines +66 to 68

SoundsBeGoneClient.config.infrequentSounds().stream().sorted().forEach((key) -> infrequent.addEntry(constructOption(entryBuilder, key)));
SoundsBeGoneClient.config.disabledSounds().stream().sorted().forEach((key) -> disabled.addEntry(constructOption(entryBuilder, key)));
Set<String> allModifiedSounds = new HashSet<>();
allModifiedSounds.addAll(SoundsBeGoneClient.config.disabledSounds());
allModifiedSounds.addAll(SoundsBeGoneClient.config.reducedSounds());
Comment on lines +71 to +72
allModifiedSounds.addAll(SoundsBeGoneClient.config.infrequentSounds());

allModifiedSounds.stream()
.sorted()
.forEach((key) -> configured.addEntry(constructOption(entryBuilder, key)));

SupportCategory.add(builder, entryBuilder);
return builder.build();
}

private static AbstractConfigListEntry<?> constructOption(ConfigEntryBuilder builder, String key) {
return builder
.startEnumSelector(Component.translatable(key), SoundState.class, SoundsBeGoneClient.config.getSoundState(key))
.setDefaultValue(SoundsBeGoneClient.config.getSoundState(key))
.setEnumNameProvider((state) -> Component.translatable("soundsbegone.config.sound.state." + state.name().toLowerCase()))
int currentVolume = SoundsBeGoneClient.config.getSoundVolume(key);
boolean isInfrequent = SoundsBeGoneClient.config.isSoundInfrequent(key);

SubCategoryBuilder sub = builder.startSubCategory(Component.translatable(key));
sub.setExpanded(false);

sub.add(builder.startIntSlider(
Component.translatable("soundsbegone.config.sound.volume"),
currentVolume, 0, 100)
.setDefaultValue(100)
Comment on lines +84 to +93
.setSaveConsumer(newVolume -> {
int oldVolume = SoundsBeGoneClient.config.getSoundVolume(key);
if (oldVolume == 0 && newVolume > 0) {
SoundsBeGoneClient.telemetry.unmutedSound(key);
} else if (oldVolume > 0 && newVolume == 0) {
SoundsBeGoneClient.telemetry.mutedSound(key);
}
SoundsBeGoneClient.config.setSoundVolume(key, newVolume);
})
Comment on lines +94 to +102
Comment on lines +94 to +102
.setTextGetter(value -> Component.literal(value + "%"))
.build()
);

sub.add(builder.startBooleanToggle(
Component.translatable("soundsbegone.config.sound.infrequent"),
isInfrequent)
.setDefaultValue(false)
.setSaveConsumer(newValue -> {
boolean hasChanged = SoundsBeGoneClient.config.getSoundState(key) != newValue;
if (!hasChanged) return;
switch (newValue) {
case DISABLED -> {
SoundsBeGoneConfig.LOGGER.info("Disabling sound: " + key);
SoundsBeGoneClient.config.disableSound(key);
SoundsBeGoneClient.telemetry.mutedSound(key);
}
case INFREQUENT -> {
SoundsBeGoneConfig.LOGGER.info("Setting sound to infrequent: " + key);
SoundsBeGoneClient.config.reduceSound(key);
SoundsBeGoneClient.telemetry.setInfrequentSound(key, SoundsBeGoneClient.config.getFrequencyPercentage());
}
case ENABLED -> {
SoundsBeGoneConfig.LOGGER.info("Enabling sound: " + key);
SoundsBeGoneClient.config.resetSound(key);
SoundsBeGoneClient.telemetry.unmutedSound(key);
}
boolean wasInfrequent = SoundsBeGoneClient.config.isSoundInfrequent(key);
SoundsBeGoneClient.config.setInfrequent(key, newValue);
if (!wasInfrequent && newValue) {
SoundsBeGoneConfig.LOGGER.info("Setting sound to infrequent: {}", key);
SoundsBeGoneClient.telemetry.setInfrequentSound(key, SoundsBeGoneClient.config.getFrequencyPercentage());
} else if (wasInfrequent && !newValue) {
SoundsBeGoneConfig.LOGGER.info("Removing sound from infrequent: {}", key);
}
})
.build();
.build()
);

return sub.build();
}
}
}
Loading