Initial commit: Werewolf game with voice prompts

This commit is contained in:
Terrible Entry 2026-06-16 13:15:22 +02:00
commit 5cdc9fdf82
63 changed files with 1387 additions and 0 deletions

218
werewoolf/.gitignore vendored Normal file
View file

@ -0,0 +1,218 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[codz]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py.cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# UV
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
#uv.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
#poetry.toml
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
#pdm.lock
#pdm.toml
.pdm-python
.pdm-build/
# pixi
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
#pixi.lock
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
# in the .venv directory. It is recommended not to include this directory in version control.
.pixi
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# Redis
*.rdb
*.aof
*.pid
# RabbitMQ
mnesia/
rabbitmq/
rabbitmq-data/
# ActiveMQ
activemq-data/
# SageMath parsed files
*.sage.py
# Environments
.env
.envrc
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
# Abstra
# Abstra is an AI-powered process automation framework.
# Ignore directories containing user credentials, local state, and settings.
# Learn more at https://abstra.io/docs
.abstra/
# Visual Studio Code
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
# and can be added to the global gitignore or merged into this file. However, if you prefer,
# you could uncomment the following to ignore the entire vscode folder
# .vscode/
# Ruff stuff:
.ruff_cache/
# PyPI configuration file
.pypirc
# Marimo
marimo/_static/
marimo/_lsp/
__marimo__/
# Streamlit
.streamlit/secrets.toml
# custom
voicesline/playersname

87
werewoolf/finger_count.py Normal file
View file

@ -0,0 +1,87 @@
# code inspiration from https://www.geekering.com/categories/computer-vision/marcellacavalcanti/hand-tracking-and-finger-counting-in-python-with-mediapipe/ because i dont know how to use mediapipe
import cv2
import mediapipe as mp
import time
mp_drawing = mp.solutions.drawing_utils
mp_drawing_styles = mp.solutions.drawing_styles
mp_hands = mp.solutions.hands
def count_fingers(stable_duration=2):
previous_finger_count = -1 # Initialize to an invalid count
stable_start_time = None # To track when the count became stable
cap = cv2.VideoCapture(0)
with mp_hands.Hands(
model_complexity=0,
min_detection_confidence=0.5,
min_tracking_confidence=0.5) as hands:
while cap.isOpened():
success, image = cap.read()
if not success:
print("Ignoring empty camera frame.")
continue
image.flags.writeable = False
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
results = hands.process(image)
image.flags.writeable = True
image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
fingerCount = 0 # Reset fingerCount for each frame
if results.multi_hand_landmarks:
for hand_landmarks in results.multi_hand_landmarks:
handIndex = results.multi_hand_landmarks.index(hand_landmarks)
handLabel = results.multi_handedness[handIndex].classification[0].label
handLandmarks = []
for landmarks in hand_landmarks.landmark:
handLandmarks.append([landmarks.x, landmarks.y])
# Count fingers
if handLabel == "Left" and handLandmarks[4][0] > handLandmarks[3][0]:
fingerCount += 1
elif handLabel == "Right" and handLandmarks[4][0] < handLandmarks[3][0]:
fingerCount += 1
if handLandmarks[8][1] < handLandmarks[6][1]: # Index finger
fingerCount += 1
if handLandmarks[12][1] < handLandmarks[10][1]: # Middle finger
fingerCount += 1
if handLandmarks[16][1] < handLandmarks[14][1]: # Ring finger
fingerCount += 1
if handLandmarks[20][1] < handLandmarks[18][1]: # Pinky
fingerCount += 1
# Draw hand landmarks
mp_drawing.draw_landmarks(
image,
hand_landmarks,
mp_hands.HAND_CONNECTIONS,
mp_drawing_styles.get_default_hand_landmarks_style(),
mp_drawing_styles.get_default_hand_connections_style())
# Display finger count
cv2.putText(image, str(fingerCount), (50, 450), cv2.FONT_HERSHEY_SIMPLEX, 3, (255, 0, 0), 10)
#Display image
cv2.imshow('Finger Count', image)
# Check if the finger count is stable
if fingerCount == previous_finger_count and fingerCount != 0:
if stable_start_time is None:
stable_start_time = time.time() # Start the timer
elif time.time() - stable_start_time >= stable_duration and fingerCount != 0:
cap.release()
cv2.destroyAllWindows()
return fingerCount # Return the stable finger count
else:
stable_start_time = None # Reset the timer if the count changes
previous_finger_count = fingerCount # Update the previous count
if cv2.waitKey(5) & 0xFF == 27:
break
return None # Return None if the loop ends without a stable count

31
werewoolf/logger.py Normal file
View file

@ -0,0 +1,31 @@
import logging
log = logging.getLogger(__name__)
log.setLevel(logging.INFO)
formatter = logging.Formatter(
"%(asctime)s \033[1m%(levelname)s\033[0m\033[0m %(message)s"
)
# stdout
stream_handler = logging.StreamHandler()
stream_handler.setLevel(logging.DEBUG)
stream_handler.setFormatter(formatter)
log.addHandler(stream_handler)
# Add custom log level names with colors
logging.addLevelName(
logging.DEBUG, "\033[1;30m%s\033[1;0m" % logging.getLevelName(logging.DEBUG)
) # gray
logging.addLevelName(
logging.INFO, "\033[1;34m%s\033[1;0m" % logging.getLevelName(logging.INFO)
) # blue
logging.addLevelName(
logging.WARNING, "\033[1;33m%s\033[1;0m" % logging.getLevelName(logging.WARNING)
) # yellow
logging.addLevelName(
logging.ERROR, "\033[1;31m%s\033[1;0m" % logging.getLevelName(logging.ERROR)
) # red
logging.addLevelName(
logging.CRITICAL, "\033[1;35m%s\033[1;0m" % logging.getLevelName(logging.CRITICAL)
) #

505
werewoolf/main.py Normal file
View file

@ -0,0 +1,505 @@
import random
from typing import Dict, List, Optional
from dataclasses import dataclass
import sys
from logger import log
from finger_count import *
from playsound import playsound
import wave
from piper import PiperVoice
import os
from art import *
from translation import *
@dataclass
class Player:
name: str
alias: str
role: str
alive: bool = True
protected: bool = False
class Game:
def __init__(self):
# single source of truth: name -> Player
self.players: Dict[str, Player] = {}
# lovers
self.lovers: Optional[Tuple[str, str]] = None
# Dead this night
self.dead_this_night: [List[str]] = []
# Used potions
self.used_potions: [list[str]] = []
# Language
self.language: str = "fr"
# -------------------------
# setup / I/O
# -------------------------
def load_lists(self) -> Optional[Dict[str, List[str]]]:
"""
Load players and roles from files and return them as lists.
Returns dict with keys 'players' and 'roles'.
"""
players_file = "players.txt"
roles_file = "roles.txt"
words_file = "randoms_words.txt"
log.info("Loading lists...")
try:
with open(players_file, "r", encoding="utf-8") as pf:
player_names = [line.strip() for line in pf if line.strip()]
except FileNotFoundError:
log.critical("Players file not found: %s", players_file)
return None
try:
with open(roles_file, "r", encoding="utf-8") as rf:
roles_list = [line.strip() for line in rf if line.strip()]
except FileNotFoundError:
log.critical("Roles file not found: %s", roles_file)
return None
try:
with open(words_file, "r", encoding="utf-8") as rf:
alias_list = [line.strip() for line in rf if line.strip()]
except FileNotFoundError:
log.critical("Roles file not found: %s", words_file)
return None
log.info("Load complete!")
return {"players": player_names, "roles": roles_list, "alias": alias_list}
# -------------------------
# player / role assignment
# -------------------------
def give_roles_to_players(self, player_names: Optional[List[str]] = None,
roles_list: Optional[List[str]] = None,
alias_list: Optional[List[str]] = None) -> None:
"""
Assign a random role to each player. Accepts optional lists (returned by load_lists).
If lists are not supplied, it will attempt to read files itself.
"""
number = 0
if not player_names:
log.info("Players are not initialized")
return
if not roles_list:
log.info("Roles are not initialized")
return
if len(roles_list) < len(player_names):
log.error("Not enough roles for players (roles: %d, players: %d)",
len(roles_list), len(player_names))
return
if len(alias_list) < len(player_names):
log.error("Not enough randoms words for players (words: %d, players: %d)",
len(alias_list), len(player_names))
return
available_roles = list(roles_list)
random.shuffle(available_roles)
available_alias = list(alias_list)
random.shuffle(alias_list)
# clear any existing players (safe to reassign)
self.players.clear()
# clear any player name audio
if not os.path.exists("./voicesline/playersname"):
os.makedirs("./voicesline/playersname")
for filename in os.listdir("./voicesline/playersname"):
file_path = os.path.join("./voicesline/playersname", filename)
# Check if it is a file (not a subdirectory)
if os.path.isfile(file_path):
os.remove(file_path) # Remove the file
for filename in os.listdir("./voicesline/alias"):
file_path = os.path.join("./voicesline/alias", filename)
# Check if it is a file (not a subdirectory)
if os.path.isfile(file_path):
os.remove(file_path) # Remove the file
if self.language == "fr":
voice = PiperVoice.load("./voices/fr/fr_FR-tom-medium.onnx")
elif self.language == "en":
voice = PiperVoice.load("./voices/en/en_GB-alan-medium.onnx")
else:
log.info("Unknow language")
for name in player_names:
chosen_role = available_roles.pop()
chosen_alias = available_alias.pop()
with wave.open(f"./voicesline/playersname/{name}.wav", "wb") as wav_file:
voice.synthesize_wav(name, wav_file)
with wave.open(f"./voicesline/alias/{chosen_alias}.wav", "wb") as wav_file:
voice.synthesize_wav(chosen_alias, wav_file)
number += 1
self.players[number] = Player(name=name, role=chosen_role, alias=chosen_alias)
self.play_sound("players", name.capitalize())
self.play_sound("prompts", "first_wakes")
if name == "Dan":
self.play_sound("prompts", "cheater")
self.show_text(chosen_role)
self.show_text(chosen_alias)
time.sleep(3)
os.system('clear')
self.play_sound("players", name.capitalize())
self.play_sound("prompts", "sleep_again")
log.debug("Assigned roles:")
for player in self.players.values():
log.debug("%s: %s", player.name, player.role)
# -------------------------
# utilities
# -------------------------
def clear_data(self) -> None:
for player in self.players:
self.players[player].protected = False
def couple(self) -> None:
os.system('clear')
self.play_sound("prompts", "lovers")
log.debug(self.lovers)
for player in self.lovers:
log.info(self.players[player].alias)
self.play_sound("alias", self.players[player].alias)
self.show_text(self.players[player].role)
time.sleep(10)
def show_text(self, text: str) -> None:
if self.language == "fr":
if text in FR:
Art=text2art(FR.get(text))
print(Art)
else:
Art=text2art(text)
print(Art)
if self.language == "en":
Art=text2art(text)
print(Art)
def get_remaining_roles(self) -> List[str]:
remaining_roles = []
for player in self.players:
if self.players[player].alive:
remaining_roles.append(self.players[player].role)
log.DEBUG(remaining_roles)
return remaining_roles
def number_2_name(self, player_number: int):
player_name = self.players[player_number].name
return player_name
def play_sound(self, type, soundinfo) -> None:
if type == "prompts":
f = f"./voicesline/prompts/{self.language}/{soundinfo}.wav"
elif type == "roles":
f = f"./voicesline/roles/{self.language}/{soundinfo}.wav"
elif type == "players":
f = f"./voicesline/playersname/{soundinfo}.wav"
elif type == "alias":
f = f"./voicesline/alias/{soundinfo}.wav"
else:
log.debug("An error occurred")
return
time.sleep(0.2)
playsound(f)
def select_someone(self, prompt: Optional[str] = None) -> Optional[str]:
"""
Prompt the user to enter a player name until a valid one is entered.
Returns None on EOF/KeyboardInterrupt.
"""
prompt = prompt or "show_number"
#self.play_sound("prompts", prompt)
while True:
selected = count_fingers()
# exact match
if selected in self.players:
return selected
def choose_between(self, options):
#Prompt the user to choose between options until a valid one is entered
numbered_options = " ".join([f"{i+1}: {option}" for i, option in enumerate(options)])
log.info(f"Choose between {numbered_options}:")
while True:
selected = count_fingers()
try:
index = int(selected) - 1
if 0 <= index < len(options):
chosen_option = options[index]
log.debug("You chose %s!", chosen_option)
return chosen_option
else:
log.warning("Invalid number entered.")
except ValueError:
log.warning("Invalid input. Please enter an option or number.")
# -------------------------
# game actions
# -------------------------
def put_in_love(self, player1: str, player2: str) -> None:
"""Link two players such that if one dies the other dies too."""
if player1 not in self.players or player2 not in self.players:
log.error("One or both players do not exist: %s, %s", player1, player2)
return
self.lovers = (player1, player2)
log.debug("Players now put in love: %s <-> %s", self.players[player1], self.players[player2])
log.info("They are now bounded by love.")
log.info("They wake up and reveal their role to each other.")
log.info("Those two go back to sleep.")
def kill(self, player: str) -> None:
"""Kill a player."""
if player not in self.players:
log.error("Couldn't kill unknown player: %s", player)
return
target = self.players[player]
# already dead?
if not target.alive:
log.error("%s is already dead!", player)
return
# protected?
if target.protected:
log.debug("%s was protected and survives.", player)
return
# in love?
if player in self.lovers:
log.debug("%s is in love! Killing them and their lover.", player)
for p in self.lovers:
# kill them and their lover
log.debug("Killed %s", p)
self.dead_this_night.append(p)
self.players[p].alive = False
return
# else just kill them
log.info("Killed %s" % player)
self.dead_this_night.append(player)
target.alive = False
def revive(self, player: str) -> None:
"""Revive a player."""
log.info("Players that will die this night are: %s", self.dead_this_night)
if player not in self.players:
log.error("Tried to revive unknown player: %s", player)
return
p = self.players[player]
if not p.alive:
p.alive = True
log.info("%s has been revived.", player)
else:
log.info("%s is already alive.", player)
# -------------------------
# helpers
# -------------------------
def status(self) -> None:
"""Log current players' statuses for debugging."""
# player values
for p in self.players.values():
log.debug("%s -> role: %s, alive: %s, Protected: %s",
p.name, p.role, p.alive, p.protected)
# lovers
log.debug(f"Lovers: {self.lovers}")
def role(func):
"""Decorator for roles"""
def wrapper(self, *args, **kwargs):
# the role is the name of the function thats decorated
role = func.__name__.capitalize()
os.system('clear')
if role in self.get_remaining_roles():
self.play_sound("roles", role)
self.play_sound("prompts", "wakes")
func(self, *args, **kwargs)
self.play_sound("roles", role)
self.play_sound("prompts", "sleep_again")
log.INFO("%s goes back to sleep." % role)
self.status()
return wrapper
# -------------------------
# roles
# -------------------------
@role
def cupidon(self) -> None:
"""Interactively pick two players to link by love."""
self.play_sound("prompts", "linked_death")
self.play_sound("prompts", "first_bound")
p1 = self.select_someone()
if p1 is None:
return
self.play_sound("prompts", "second_bound")
p2 = self.select_someone()
if p2 is None:
return
if p1 == p2:
log.info("Cannot link a player to themselves.")
return
self.put_in_love(p1, p2)
@role
def savior(self) -> None:
"""Interactively choose someone to protect."""
self.play_sound("prompts", "choose_protect")
protected = self.select_someone()
if protected is None:
return
self.players[protected].protected = True
log.debug("Protected: %s", protected)
@role
def witch(self) -> None:
"""Interactively choose to kill or revive someone"""
self.play_sound("prompts", "potions_choice")
self.play_sound("prompts", "might_die")
for player in self.dead_this_night:
self.show_text(self.players[player].name)
if len(self.used_potions) >= 2:
self.show_text("Vous avez utilisé toutes vos potions. ")
time.sleep(5)
else:
while True:
options = ("Death", "Revive", "Nothing")
potionchoice = self.choose_between(options).capitalize()
if potionchoice == "Revive" and "Revive" not in self.used_potions:
player = self.select_someone()
if player in self.dead_this_night:
self.used_potions.append("Revive")
self.revive(player)
return
elif player not in self.dead_this_night and not self.players[player].alive:
self.show_text("Tu ne peux pas faire revivre cette personne.")
elif self.players[player].alive:
self.show_text("Cette personne n'est pas morte.")
else:
log.info("Unknown error: Invalid player choice")
return
elif potionchoice == "Death" and "Death" not in self.used_potions:
player = self.select_someone()
if self.players[player].alive:
self.used_potions.append("Death")
self.kill(player)
time.sleep(1)
return
elif not self.players[player].alive:
self.show_text("Ce joueur est déjà mort.")
else:
log.info("Unknown error: Invalid player choice")
return
elif potionchoice == "Nothing":
self.show_text("Vous ne faites rien cette nuit.")
return
elif potionchoice in self.used_potions:
log.info("You already used this potion.")
else:
log.critical("Unknown error: Invalid potion choice.")
return
@role
def werewolf(self) -> None:
self.play_sound("prompts", "werewolf_choice")
player = self.select_someone()
self.kill(player)
@role
def seer(self) -> None:
log.info("Choose a player to discover their role.")
while True:
player = self.select_someone()
if "Seer" == self.players[player].role:
self.show_text("You can't see your own role.")
else:
self.show_text(self.players[player].role)
time.sleep(3)
return
@role
def hunter(self) -> None:
self.play_sound("prompts", "hunter")
player = self.select_someone()
self.kill(player)
# -------------------------
# game flow
# -------------------------
def first_night_cycle(self) -> None:
self.play_sound("prompts", "fall_asleep_all")
self.cupidon()
self.couple()
self.savior()
self.werewolf()
self.witch()
self.seer()
self.day()
def night(self) -> None:
self.dead_this_night = []
self.play_sound("prompts", "fall_asleep_all")
self.savior()
self.werewolf()
self.witch()
self.seer()
self.day()
def day(self) -> None:
self.play_sound("prompts", "wake_up_all")
self.play_sound("prompts", "night_deaths")
for player in self.dead_this_night:
self.play_sound("players", self.number_2_name(player))
for player in self.dead_this_night:
if self.players[player].role == "Hunter":
self.hunter()
self.play_sound("prompts", "village_vote")
while True:
if count_fingers(stable_duration=4) == 6:
break
self.play_sound("prompts", "now_show_number")
player = self.select_someone()
self.kill(player)
self.night()
if __name__ == "__main__":
print("---\nWerewolfGame\n---")
# instantiate the game
game = Game()
# I/O: read files and assign roles
loaded = game.load_lists()
try:
if loaded:
# start the game!
game.give_roles_to_players(loaded["players"], loaded["roles"], loaded["alias"])
game.first_night_cycle()
# CTRL+C
except KeyboardInterrupt:
print() # new line
log.info("Bye bye!")
sys.exit(0)
# Any unhandled exception
except Exception as e:
log.exception("Unhandled exception: %s" % e)
sys.exit(1)

6
werewoolf/players.txt Normal file
View file

@ -0,0 +1,6 @@
Anavoi
Jean
Paul
Christophe
Jeanne
Julien

View file

@ -0,0 +1,11 @@
Abricot
Pomme
Fraise
Melon
Orange
Framboise
Cerise
Citron
Poire
Raisin
Prune

View file

@ -0,0 +1,7 @@
fuzzywuzzy==0.18.0
opencv_python
mediapipe
piper-tts
playsound
art
pygobject

10
werewoolf/roles.txt Normal file
View file

@ -0,0 +1,10 @@
Werewolf
Seer
Witch
Grandchild
Hunter
Werewolf
Werewolf
Villager
Cupidon
Villager

10
werewoolf/translation.py Normal file
View file

@ -0,0 +1,10 @@
FR = {
"Seer": "Voyante",
"Werewolf": "Loups-Garous",
"Witch": "Sorciere",
"Cupidon": "Cupidon",
"Savior": "Salvateur",
"Villager": "Villageois",
"Grandchild": "Petite-Fille",
"Hunter": "Chasseur"
}

Binary file not shown.

View file

@ -0,0 +1,502 @@
{
"dataset": "tom",
"audio": {
"sample_rate": 44100,
"quality": "medium"
},
"espeak": {
"voice": "fr"
},
"language": {
"code": "fr_FR",
"family": "fr",
"region": "FR",
"name_native": "Français",
"name_english": "French",
"country_english": "France"
},
"inference": {
"noise_scale": 0.667,
"length_scale": 1,
"noise_w": 0.8
},
"phoneme_type": "espeak",
"phoneme_map": {},
"phoneme_id_map": {
" ": [
3
],
"!": [
4
],
"\"": [
150
],
"#": [
149
],
"$": [
2
],
"'": [
5
],
"(": [
6
],
")": [
7
],
",": [
8
],
"-": [
9
],
".": [
10
],
"0": [
130
],
"1": [
131
],
"2": [
132
],
"3": [
133
],
"4": [
134
],
"5": [
135
],
"6": [
136
],
"7": [
137
],
"8": [
138
],
"9": [
139
],
":": [
11
],
";": [
12
],
"?": [
13
],
"X": [
156
],
"^": [
1
],
"_": [
0
],
"a": [
14
],
"b": [
15
],
"c": [
16
],
"d": [
17
],
"e": [
18
],
"f": [
19
],
"g": [
154
],
"h": [
20
],
"i": [
21
],
"j": [
22
],
"k": [
23
],
"l": [
24
],
"m": [
25
],
"n": [
26
],
"o": [
27
],
"p": [
28
],
"q": [
29
],
"r": [
30
],
"s": [
31
],
"t": [
32
],
"u": [
33
],
"v": [
34
],
"w": [
35
],
"x": [
36
],
"y": [
37
],
"z": [
38
],
"æ": [
39
],
"ç": [
40
],
"ð": [
41
],
"ø": [
42
],
"ħ": [
43
],
"ŋ": [
44
],
"œ": [
45
],
"ǀ": [
46
],
"ǁ": [
47
],
"ǂ": [
48
],
"ǃ": [
49
],
"ɐ": [
50
],
"ɑ": [
51
],
"ɒ": [
52
],
"ɓ": [
53
],
"ɔ": [
54
],
"ɕ": [
55
],
"ɖ": [
56
],
"ɗ": [
57
],
"ɘ": [
58
],
"ə": [
59
],
"ɚ": [
60
],
"ɛ": [
61
],
"ɜ": [
62
],
"ɞ": [
63
],
"ɟ": [
64
],
"ɠ": [
65
],
"ɡ": [
66
],
"ɢ": [
67
],
"ɣ": [
68
],
"ɤ": [
69
],
"ɥ": [
70
],
"ɦ": [
71
],
"ɧ": [
72
],
"ɨ": [
73
],
"ɪ": [
74
],
"ɫ": [
75
],
"ɬ": [
76
],
"ɭ": [
77
],
"ɮ": [
78
],
"ɯ": [
79
],
"ɰ": [
80
],
"ɱ": [
81
],
"ɲ": [
82
],
"ɳ": [
83
],
"ɴ": [
84
],
"ɵ": [
85
],
"ɶ": [
86
],
"ɸ": [
87
],
"ɹ": [
88
],
"ɺ": [
89
],
"ɻ": [
90
],
"ɽ": [
91
],
"ɾ": [
92
],
"ʀ": [
93
],
"ʁ": [
94
],
"ʂ": [
95
],
"ʃ": [
96
],
"ʄ": [
97
],
"ʈ": [
98
],
"ʉ": [
99
],
"ʊ": [
100
],
"ʋ": [
101
],
"ʌ": [
102
],
"ʍ": [
103
],
"ʎ": [
104
],
"ʏ": [
105
],
"ʐ": [
106
],
"ʑ": [
107
],
"ʒ": [
108
],
"ʔ": [
109
],
"ʕ": [
110
],
"ʘ": [
111
],
"ʙ": [
112
],
"ʛ": [
113
],
"ʜ": [
114
],
"ʝ": [
115
],
"ʟ": [
116
],
"ʡ": [
117
],
"ʢ": [
118
],
"ʦ": [
155
],
"ʰ": [
145
],
"ʲ": [
119
],
"ˈ": [
120
],
"ˌ": [
121
],
"ː": [
122
],
"ˑ": [
123
],
"˞": [
124
],
"ˤ": [
146
],
"̃": [
141
],
"̧": [
140
],
"̩": [
144
],
"̪": [
142
],
"̯": [
143
],
"̺": [
152
],
"̻": [
153
],
"β": [
125
],
"ε": [
147
],
"θ": [
126
],
"χ": [
127
],
"ᵻ": [
128
],
"↑": [
151
],
"↓": [
148
],
"ⱱ": [
129
]
},
"num_symbols": 256,
"num_speakers": 1,
"speaker_id_map": {},
"piper_version": "1.0.0"
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.