Files
TiedUp-/src/main/java/com/tiedup/remake/util/PhoneticMapper.java
NotEvil f6466360b6 Clean repo for open source release
Remove build artifacts, dev tool configs, unused dependencies,
and third-party source dumps. Add proper README, update .gitignore,
clean up Makefile.
2026-04-12 00:51:22 +02:00

508 lines
20 KiB
Java

package com.tiedup.remake.util;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.Set;
/**
* Phonetic transformation system for gagged speech.
* Maps original phonemes to muffled equivalents based on gag material.
*/
public class PhoneticMapper {
private static final Random RANDOM = new Random();
// Phonetic categories
private static final Set<Character> VOWELS = Set.of(
'a',
'e',
'i',
'o',
'u',
'y'
);
private static final Set<Character> PLOSIVES = Set.of(
'b',
'd',
'g',
'k',
'p',
't'
);
private static final Set<Character> NASALS = Set.of('m', 'n');
private static final Set<Character> FRICATIVES = Set.of(
'f',
'h',
's',
'v',
'z'
);
private static final Set<Character> LIQUIDS = Set.of('l', 'r');
// Material-specific phoneme mappings
private static final Map<
GagMaterial,
Map<Character, String[]>
> CONSONANT_MAPS = new EnumMap<>(GagMaterial.class);
private static final Map<GagMaterial, Map<Character, String[]>> VOWEL_MAPS =
new EnumMap<>(GagMaterial.class);
static {
initializeClothMappings();
initializeBallMappings();
initializeTapeMappings();
initializeStuffedMappings();
initializePanelMappings();
initializeLatexMappings();
initializeRingMappings();
initializeBiteMappings();
initializeSpongeMappings();
initializeBaguetteMappings();
}
private static void initializeClothMappings() {
Map<Character, String[]> consonants = new HashMap<>();
consonants.put('b', new String[] { "m", "mph" });
consonants.put('c', new String[] { "h", "kh" });
consonants.put('d', new String[] { "n", "nd" });
consonants.put('f', new String[] { "f", "ph" });
consonants.put('g', new String[] { "ng", "gh" });
consonants.put('h', new String[] { "h", "hh" });
consonants.put('j', new String[] { "zh", "jh" });
consonants.put('k', new String[] { "kh", "gh" });
consonants.put('l', new String[] { "l", "hl" });
consonants.put('m', new String[] { "m", "mm" });
consonants.put('n', new String[] { "n", "nn" });
consonants.put('p', new String[] { "mph", "m" });
consonants.put('q', new String[] { "kh", "gh" });
consonants.put('r', new String[] { "r", "hr" });
consonants.put('s', new String[] { "s", "sh" });
consonants.put('t', new String[] { "th", "n" });
consonants.put('v', new String[] { "f", "vh" });
consonants.put('w', new String[] { "wh", "u" });
consonants.put('x', new String[] { "ks", "kh" });
consonants.put('z', new String[] { "z", "s" });
CONSONANT_MAPS.put(GagMaterial.CLOTH, consonants);
Map<Character, String[]> vowels = new HashMap<>();
vowels.put('a', new String[] { "ah", "a" });
vowels.put('e', new String[] { "eh", "e" });
vowels.put('i', new String[] { "ih", "e" });
vowels.put('o', new String[] { "oh", "o" });
vowels.put('u', new String[] { "uh", "u" });
vowels.put('y', new String[] { "ih", "e" });
VOWEL_MAPS.put(GagMaterial.CLOTH, vowels);
}
private static void initializeBallMappings() {
Map<Character, String[]> consonants = new HashMap<>();
// Ball gag forces mouth open around ball - tongue and lips blocked
consonants.put('b', new String[] { "m", "mm" });
consonants.put('c', new String[] { "g", "kh" });
consonants.put('d', new String[] { "n", "nn" });
consonants.put('f', new String[] { "h", "hh" });
consonants.put('g', new String[] { "ng", "g" });
consonants.put('h', new String[] { "h", "hh" });
consonants.put('j', new String[] { "g", "ng" });
consonants.put('k', new String[] { "g", "gh" });
consonants.put('l', new String[] { "u", "l" });
consonants.put('m', new String[] { "m", "mm" });
consonants.put('n', new String[] { "n", "nn" });
consonants.put('p', new String[] { "m", "mm" });
consonants.put('q', new String[] { "g", "gh" });
consonants.put('r', new String[] { "u", "r" });
consonants.put('s', new String[] { "h", "s" });
consonants.put('t', new String[] { "n", "nn" });
consonants.put('v', new String[] { "h", "f" });
consonants.put('w', new String[] { "u", "w" });
consonants.put('x', new String[] { "g", "ks" });
consonants.put('z', new String[] { "s", "z" });
CONSONANT_MAPS.put(GagMaterial.BALL, consonants);
Map<Character, String[]> vowels = new HashMap<>();
// Ball forces all vowels toward "oo/uu" (rounded)
vowels.put('a', new String[] { "a", "o" });
vowels.put('e', new String[] { "u", "o" });
vowels.put('i', new String[] { "u", "i" });
vowels.put('o', new String[] { "o", "oo" });
vowels.put('u', new String[] { "u", "uu" });
vowels.put('y', new String[] { "u", "i" });
VOWEL_MAPS.put(GagMaterial.BALL, vowels);
}
private static void initializeTapeMappings() {
Map<Character, String[]> consonants = new HashMap<>();
// Tape seals mouth almost completely - only nasal sounds
consonants.put('b', new String[] { "m", "mm" });
consonants.put('c', new String[] { "n", "m" });
consonants.put('d', new String[] { "n", "nn" });
consonants.put('f', new String[] { "m", "hm" });
consonants.put('g', new String[] { "n", "ng" });
consonants.put('h', new String[] { "m", "hm" });
consonants.put('j', new String[] { "n", "m" });
consonants.put('k', new String[] { "n", "m" });
consonants.put('l', new String[] { "n", "m" });
consonants.put('m', new String[] { "m", "mm" });
consonants.put('n', new String[] { "n", "nn" });
consonants.put('p', new String[] { "m", "mm" });
consonants.put('q', new String[] { "n", "m" });
consonants.put('r', new String[] { "n", "m" });
consonants.put('s', new String[] { "m", "s" });
consonants.put('t', new String[] { "n", "nn" });
consonants.put('v', new String[] { "m", "f" });
consonants.put('w', new String[] { "m", "u" });
consonants.put('x', new String[] { "n", "m" });
consonants.put('z', new String[] { "n", "m" });
CONSONANT_MAPS.put(GagMaterial.TAPE, consonants);
Map<Character, String[]> vowels = new HashMap<>();
// Tape muffles all vowels to near silence
vowels.put('a', new String[] { "m", "mm" });
vowels.put('e', new String[] { "n", "m" });
vowels.put('i', new String[] { "n", "m" });
vowels.put('o', new String[] { "m", "mm" });
vowels.put('u', new String[] { "m", "u" });
vowels.put('y', new String[] { "n", "m" });
VOWEL_MAPS.put(GagMaterial.TAPE, vowels);
}
private static void initializeStuffedMappings() {
Map<Character, String[]> consonants = new HashMap<>();
// Stuffed gag - nearly silent
for (char c = 'a'; c <= 'z'; c++) {
if (!VOWELS.contains(c)) {
consonants.put(c, new String[] { "mm", "m", "" });
}
}
CONSONANT_MAPS.put(GagMaterial.STUFFED, consonants);
Map<Character, String[]> vowels = new HashMap<>();
for (char c : VOWELS) {
vowels.put(c, new String[] { "mm", "m", "" });
}
VOWEL_MAPS.put(GagMaterial.STUFFED, vowels);
}
private static void initializePanelMappings() {
// Panel gag - similar to tape but slightly more sound
Map<Character, String[]> consonants = new HashMap<>();
consonants.put('b', new String[] { "m", "mph" });
consonants.put('c', new String[] { "n", "m" });
consonants.put('d', new String[] { "n", "nd" });
consonants.put('f', new String[] { "m", "f" });
consonants.put('g', new String[] { "n", "ng" });
consonants.put('h', new String[] { "hm", "m" });
consonants.put('j', new String[] { "n", "m" });
consonants.put('k', new String[] { "n", "m" });
consonants.put('l', new String[] { "n", "m" });
consonants.put('m', new String[] { "m", "mm" });
consonants.put('n', new String[] { "n", "nn" });
consonants.put('p', new String[] { "m", "mph" });
consonants.put('q', new String[] { "n", "m" });
consonants.put('r', new String[] { "n", "m" });
consonants.put('s', new String[] { "s", "m" });
consonants.put('t', new String[] { "n", "m" });
consonants.put('v', new String[] { "m", "f" });
consonants.put('w', new String[] { "m", "u" });
consonants.put('x', new String[] { "n", "m" });
consonants.put('z', new String[] { "n", "m" });
CONSONANT_MAPS.put(GagMaterial.PANEL, consonants);
Map<Character, String[]> vowels = new HashMap<>();
vowels.put('a', new String[] { "m", "ah" });
vowels.put('e', new String[] { "n", "m" });
vowels.put('i', new String[] { "n", "m" });
vowels.put('o', new String[] { "m", "oh" });
vowels.put('u', new String[] { "m", "u" });
vowels.put('y', new String[] { "n", "m" });
VOWEL_MAPS.put(GagMaterial.PANEL, vowels);
}
private static void initializeLatexMappings() {
Map<Character, String[]> consonants = new HashMap<>();
// Latex - tight seal, rubber sounds
consonants.put('b', new String[] { "m", "mm" });
consonants.put('c', new String[] { "n", "m" });
consonants.put('d', new String[] { "n", "nn" });
consonants.put('f', new String[] { "h", "f" });
consonants.put('g', new String[] { "ng", "m" });
consonants.put('h', new String[] { "h", "hh" });
consonants.put('j', new String[] { "n", "m" });
consonants.put('k', new String[] { "n", "m" });
consonants.put('l', new String[] { "n", "m" });
consonants.put('m', new String[] { "m", "mm" });
consonants.put('n', new String[] { "n", "nn" });
consonants.put('p', new String[] { "m", "mm" });
consonants.put('q', new String[] { "n", "m" });
consonants.put('r', new String[] { "n", "m" });
consonants.put('s', new String[] { "s", "h" });
consonants.put('t', new String[] { "n", "nn" });
consonants.put('v', new String[] { "f", "m" });
consonants.put('w', new String[] { "m", "u" });
consonants.put('x', new String[] { "n", "m" });
consonants.put('z', new String[] { "s", "m" });
CONSONANT_MAPS.put(GagMaterial.LATEX, consonants);
Map<Character, String[]> vowels = new HashMap<>();
vowels.put('a', new String[] { "u", "a" });
vowels.put('e', new String[] { "u", "e" });
vowels.put('i', new String[] { "u", "i" });
vowels.put('o', new String[] { "u", "o" });
vowels.put('u', new String[] { "u", "uu" });
vowels.put('y', new String[] { "u", "i" });
VOWEL_MAPS.put(GagMaterial.LATEX, vowels);
}
private static void initializeRingMappings() {
Map<Character, String[]> consonants = new HashMap<>();
// Ring gag - mouth forced open, tongue partially free
consonants.put('b', new String[] { "b", "bh" });
consonants.put('c', new String[] { "k", "kh" });
consonants.put('d', new String[] { "d", "dh" });
consonants.put('f', new String[] { "f", "fh" });
consonants.put('g', new String[] { "g", "gh" });
consonants.put('h', new String[] { "h", "ah" });
consonants.put('j', new String[] { "j", "zh" });
consonants.put('k', new String[] { "k", "kh" });
consonants.put('l', new String[] { "l", "lh" });
consonants.put('m', new String[] { "m", "mh" });
consonants.put('n', new String[] { "n", "nh" });
consonants.put('p', new String[] { "p", "ph" });
consonants.put('q', new String[] { "k", "kh" });
consonants.put('r', new String[] { "r", "rh" });
consonants.put('s', new String[] { "s", "sh" });
consonants.put('t', new String[] { "t", "th" });
consonants.put('v', new String[] { "v", "vh" });
consonants.put('w', new String[] { "w", "wh" });
consonants.put('x', new String[] { "ks", "kh" });
consonants.put('z', new String[] { "z", "zh" });
CONSONANT_MAPS.put(GagMaterial.RING, consonants);
Map<Character, String[]> vowels = new HashMap<>();
// Ring forces mouth open - vowels become "a/ah"
vowels.put('a', new String[] { "a", "ah" });
vowels.put('e', new String[] { "eh", "a" });
vowels.put('i', new String[] { "ih", "a" });
vowels.put('o', new String[] { "oh", "a" });
vowels.put('u', new String[] { "uh", "a" });
vowels.put('y', new String[] { "ih", "a" });
VOWEL_MAPS.put(GagMaterial.RING, vowels);
}
private static void initializeBiteMappings() {
Map<Character, String[]> consonants = new HashMap<>();
// Bite gag - teeth clenched on bar
consonants.put('b', new String[] { "bh", "ph" });
consonants.put('c', new String[] { "kh", "gh" });
consonants.put('d', new String[] { "dh", "th" });
consonants.put('f', new String[] { "fh", "f" });
consonants.put('g', new String[] { "gh", "ng" });
consonants.put('h', new String[] { "h", "hh" });
consonants.put('j', new String[] { "jh", "zh" });
consonants.put('k', new String[] { "kh", "gh" });
consonants.put('l', new String[] { "lh", "hl" });
consonants.put('m', new String[] { "m", "mh" });
consonants.put('n', new String[] { "n", "nh" });
consonants.put('p', new String[] { "ph", "bh" });
consonants.put('q', new String[] { "kh", "gh" });
consonants.put('r', new String[] { "rh", "hr" });
consonants.put('s', new String[] { "sh", "s" });
consonants.put('t', new String[] { "th", "dh" });
consonants.put('v', new String[] { "vh", "fh" });
consonants.put('w', new String[] { "wh", "uh" });
consonants.put('x', new String[] { "ksh", "kh" });
consonants.put('z', new String[] { "zh", "sh" });
CONSONANT_MAPS.put(GagMaterial.BITE, consonants);
Map<Character, String[]> vowels = new HashMap<>();
vowels.put('a', new String[] { "eh", "ah" });
vowels.put('e', new String[] { "eh", "e" });
vowels.put('i', new String[] { "ih", "eh" });
vowels.put('o', new String[] { "oh", "eh" });
vowels.put('u', new String[] { "uh", "eh" });
vowels.put('y', new String[] { "ih", "eh" });
VOWEL_MAPS.put(GagMaterial.BITE, vowels);
}
private static void initializeSpongeMappings() {
// Sponge - absorbs almost all sound
Map<Character, String[]> consonants = new HashMap<>();
for (char c = 'a'; c <= 'z'; c++) {
if (!VOWELS.contains(c)) {
consonants.put(c, new String[] { "mm", "" });
}
}
CONSONANT_MAPS.put(GagMaterial.SPONGE, consonants);
Map<Character, String[]> vowels = new HashMap<>();
for (char c : VOWELS) {
vowels.put(c, new String[] { "mm", "" });
}
VOWEL_MAPS.put(GagMaterial.SPONGE, vowels);
}
private static void initializeBaguetteMappings() {
Map<Character, String[]> consonants = new HashMap<>();
// Baguette - comedic, food-blocked
consonants.put('b', new String[] { "bm", "mm" });
consonants.put('c', new String[] { "km", "gm" });
consonants.put('d', new String[] { "dm", "nm" });
consonants.put('f', new String[] { "fm", "hm" });
consonants.put('g', new String[] { "gm", "ngm" });
consonants.put('h', new String[] { "hm", "h" });
consonants.put('j', new String[] { "jm", "zhm" });
consonants.put('k', new String[] { "km", "gm" });
consonants.put('l', new String[] { "lm", "mm" });
consonants.put('m', new String[] { "mm", "m" });
consonants.put('n', new String[] { "nm", "n" });
consonants.put('p', new String[] { "pm", "mm" });
consonants.put('q', new String[] { "km", "gm" });
consonants.put('r', new String[] { "rm", "mm" });
consonants.put('s', new String[] { "sm", "shm" });
consonants.put('t', new String[] { "tm", "nm" });
consonants.put('v', new String[] { "vm", "fm" });
consonants.put('w', new String[] { "wm", "um" });
consonants.put('x', new String[] { "ksm", "km" });
consonants.put('z', new String[] { "zm", "sm" });
CONSONANT_MAPS.put(GagMaterial.BAGUETTE, consonants);
Map<Character, String[]> vowels = new HashMap<>();
vowels.put('a', new String[] { "am", "om" });
vowels.put('e', new String[] { "em", "um" });
vowels.put('i', new String[] { "im", "um" });
vowels.put('o', new String[] { "om", "o" });
vowels.put('u', new String[] { "um", "u" });
vowels.put('y', new String[] { "im", "um" });
VOWEL_MAPS.put(GagMaterial.BAGUETTE, vowels);
}
/**
* Map a single phoneme to its muffled equivalent.
*
* @param c The original character
* @param material The gag material
* @param bleedChance Chance (0-1) that the original sound passes through
* @return The muffled phoneme
*/
public static String mapPhoneme(
char c,
GagMaterial material,
float bleedChance
) {
char lower = Character.toLowerCase(c);
// Non-alphabetic characters pass through
if (!Character.isLetter(c)) {
return String.valueOf(c);
}
// Bleed-through check: original sound passes
if (RANDOM.nextFloat() < bleedChance) {
return String.valueOf(c);
}
// Get appropriate map
Map<Character, String[]> map = isVowel(lower)
? VOWEL_MAPS.get(material)
: CONSONANT_MAPS.get(material);
if (map == null) {
return String.valueOf(c);
}
String[] options = map.get(lower);
if (options == null || options.length == 0) {
// Default fallback
return isVowel(lower) ? "mm" : "nn";
}
// Pick a random option
String result = options[RANDOM.nextInt(options.length)];
// Preserve case for first character
if (Character.isUpperCase(c) && !result.isEmpty()) {
return (
Character.toUpperCase(result.charAt(0)) + result.substring(1)
);
}
return result;
}
/**
* Check if a character is a vowel.
*/
public static boolean isVowel(char c) {
return VOWELS.contains(Character.toLowerCase(c));
}
/**
* Check if a character is a plosive consonant.
*/
public static boolean isPlosive(char c) {
return PLOSIVES.contains(Character.toLowerCase(c));
}
/**
* Check if a character is a nasal consonant.
*/
public static boolean isNasal(char c) {
return NASALS.contains(Character.toLowerCase(c));
}
/**
* Check if a character is a fricative consonant.
*/
public static boolean isFricative(char c) {
return FRICATIVES.contains(Character.toLowerCase(c));
}
/**
* Check if a character is a liquid consonant.
*/
public static boolean isLiquid(char c) {
return LIQUIDS.contains(Character.toLowerCase(c));
}
/**
* Check if a character can potentially bleed through for a given material.
* Nasals have higher bleed-through, plosives have lower.
*/
public static float getBleedModifier(char c, GagMaterial material) {
char lower = Character.toLowerCase(c);
// Nasals almost always pass through
if (isNasal(lower)) {
return 2.0f;
}
// Plosives are harder to pronounce with most gags
if (isPlosive(lower)) {
return material == GagMaterial.RING ? 1.5f : 0.3f;
}
// Fricatives depend on whether air can escape
if (isFricative(lower)) {
return (
material == GagMaterial.TAPE ||
material == GagMaterial.STUFFED
)
? 0.1f
: 0.8f;
}
// Liquids depend on tongue freedom
if (isLiquid(lower)) {
return (
material == GagMaterial.RING || material == GagMaterial.BITE
)
? 1.2f
: 0.2f;
}
return 1.0f;
}
}