Compare commits
5 Commits
feat/lb-fl
...
feat/tts-m
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2d4831349b | ||
|
|
5beced0041 | ||
| 455633178c | |||
| 9361cfdee6 | |||
| f558c64886 |
@@ -27,6 +27,7 @@
|
||||
<div id="live-clock" class="live-clock">--:--:--</div>
|
||||
<a href="/leaderboard.html" class="leaderboard-btn">🏆</a>
|
||||
<a href="/settings" class="settings-btn">⚙️</a>
|
||||
<script src="/tts.js" defer></script>
|
||||
|
||||
<div class="heartbeat-indicators">
|
||||
<div
|
||||
@@ -976,6 +977,18 @@
|
||||
if (validStatuses.includes(data.status2)) status2 = data.status2;
|
||||
best1 = data.best1;
|
||||
best2 = data.best2;
|
||||
|
||||
// TTS: bei Übergang running -> finished die Endzeit ansagen
|
||||
// ("Neue Zeit: ..."). Bahn-/Status-Phrasen werden bewusst
|
||||
// weggelassen.
|
||||
if (window.tts && tts.isEnabled()) {
|
||||
if (oldStatus1 === 'running' && status1 === 'finished' && data.time1 > 0) {
|
||||
tts.sayTime(data.time1);
|
||||
}
|
||||
if (oldStatus2 === 'running' && status2 === 'finished' && data.time2 > 0) {
|
||||
tts.sayTime(data.time2);
|
||||
}
|
||||
}
|
||||
learningMode = data.learningMode;
|
||||
learningButton = data.learningButton || "";
|
||||
lastSync = Date.now();
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
<!-- Stylesheets -->
|
||||
<link rel="stylesheet" href="leaderboard.css" />
|
||||
<title>Ninjacross Timer - Leaderboard</title>
|
||||
<script src="/tts.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Modern Notification Toast -->
|
||||
@@ -54,6 +55,10 @@
|
||||
<script>
|
||||
let leaderboardData = [];
|
||||
let lastUpdateTime = null;
|
||||
// Identität (name + Zeit) des aktuellen Top-1-Eintrags. Wechselt
|
||||
// diese, sagen wir „neue Bestzeit + Zeit" an. Initial null →
|
||||
// beim allerersten Poll wird nur registriert, nicht angesagt.
|
||||
let lastTopId = null;
|
||||
|
||||
// Seite laden
|
||||
window.onload = function () {
|
||||
@@ -67,15 +72,31 @@
|
||||
try {
|
||||
const response = await fetch("/api/leaderboard-full");
|
||||
const data = await response.json();
|
||||
const wasFirstLoad = leaderboardData.length === 0 && lastTopId === null;
|
||||
leaderboardData = data.leaderboard || [];
|
||||
lastUpdateTime = new Date();
|
||||
updateLeaderboardDisplay();
|
||||
announceTopIfChanged(wasFirstLoad);
|
||||
} catch (error) {
|
||||
console.error("Fehler beim Laden des Leaderboards:", error);
|
||||
showMessage("Fehler beim Laden des Leaderboards", "error");
|
||||
}
|
||||
}
|
||||
|
||||
// Wechselt der Top-1-Eintrag, kommt eine TTS-Ansage ("Neue Zeit
|
||||
// + Zeit"). Beim allerersten Laden nur den Stand merken, sonst
|
||||
// würde jeder Seitenaufruf die aktuelle Bestzeit erneut ansagen.
|
||||
function announceTopIfChanged(isFirstLoad) {
|
||||
const top = leaderboardData[0];
|
||||
const newId = top ? `${top.name}::${top.timeFormatted}` : null;
|
||||
if (lastTopId !== newId) {
|
||||
if (!isFirstLoad && newId && window.tts && tts.isEnabled()) {
|
||||
tts.sayTime(tts.parseFormattedTime(top.timeFormatted));
|
||||
}
|
||||
lastTopId = newId;
|
||||
}
|
||||
}
|
||||
|
||||
// Leaderboard anzeigen
|
||||
function updateLeaderboardDisplay() {
|
||||
const container = document.getElementById("leaderboard-container");
|
||||
|
||||
308
data/tts.js
Normal file
308
data/tts.js
Normal file
@@ -0,0 +1,308 @@
|
||||
// tts.js — Plays pre-rendered MP3 snippets from /tts/ gaplessly.
|
||||
// Uses the Web Audio API: each MP3 is decoded once into an AudioBuffer,
|
||||
// then chained playback is scheduled with start(when) so there is no
|
||||
// JS-callback gap between snippets. Persists enable-state in
|
||||
// localStorage. The toggle button doubles as the user gesture that
|
||||
// unlocks the AudioContext on browsers with autoplay restrictions
|
||||
// (iOS Safari, many SmartTV browsers).
|
||||
(() => {
|
||||
'use strict';
|
||||
|
||||
const BASE = '/tts/';
|
||||
const STORAGE_KEY = 'aqm_tts_enabled';
|
||||
// All snippets shipped in /tts/. Listed explicitly so we can
|
||||
// preload (and decode) them upfront before the first announcement.
|
||||
// Numbers 0-99 are spoken as natural German words ("vierzehn",
|
||||
// "sechsundneunzig"), produced by tools/generate-tts.py.
|
||||
const FILES = [
|
||||
'bereit', 'komma', 'minute', 'minuten',
|
||||
'neue_zeit', 'sekunden', 'und',
|
||||
];
|
||||
for (let i = 0; i < 100; i++) FILES.push(String(i));
|
||||
|
||||
let enabled = localStorage.getItem(STORAGE_KEY) === '1';
|
||||
let audioCtx = null;
|
||||
// For each name we cache { buffer, offset, duration } where offset
|
||||
// and duration mark the non-silent region of the decoded buffer.
|
||||
// Edge-TTS pads each MP3 with ~50-150 ms of leading/trailing
|
||||
// silence, which would otherwise stack up between snippets.
|
||||
const buffers = Object.create(null);
|
||||
let scheduled = [];
|
||||
let preloadPromise = null;
|
||||
let btn = null;
|
||||
|
||||
// Linear amplitude threshold below which a sample counts as silence
|
||||
// (~ -46 dBFS). Slightly higher than typical decoder noise floor.
|
||||
const SILENCE_THRESHOLD = 0.005;
|
||||
// Tiny grace at the start so we don't chop a soft consonant attack.
|
||||
const LEAD_GRACE_S = 0.01;
|
||||
|
||||
// Scans both channels of an AudioBuffer to find the first and last
|
||||
// sample whose absolute value exceeds SILENCE_THRESHOLD, returning
|
||||
// {offset, duration} in seconds for use with start(when, offset,
|
||||
// duration). Falls back to the full buffer if everything looks
|
||||
// silent (shouldn't happen for our snippets, but be safe).
|
||||
function findNonSilentRange(buffer) {
|
||||
const channels = buffer.numberOfChannels;
|
||||
const len = buffer.length;
|
||||
const sampleRate = buffer.sampleRate;
|
||||
let firstHit = len;
|
||||
let lastHit = -1;
|
||||
for (let c = 0; c < channels; c++) {
|
||||
const data = buffer.getChannelData(c);
|
||||
for (let i = 0; i < firstHit; i++) {
|
||||
if (Math.abs(data[i]) >= SILENCE_THRESHOLD) {
|
||||
firstHit = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (let i = len - 1; i > lastHit; i--) {
|
||||
if (Math.abs(data[i]) >= SILENCE_THRESHOLD) {
|
||||
lastHit = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (lastHit < 0 || firstHit >= len) {
|
||||
return { offset: 0, duration: buffer.duration };
|
||||
}
|
||||
const grace = Math.floor(LEAD_GRACE_S * sampleRate);
|
||||
const start = Math.max(0, firstHit - grace);
|
||||
const end = Math.min(len, lastHit + 1);
|
||||
return {
|
||||
offset: start / sampleRate,
|
||||
duration: (end - start) / sampleRate,
|
||||
};
|
||||
}
|
||||
|
||||
// Builds the spoken-snippet sequence for a duration in seconds.
|
||||
// Examples (all sound natural in German):
|
||||
// 14.96 -> ["14","komma","96","sekunden"] "vierzehn Komma sechsundneunzig Sekunden"
|
||||
// 14.05 -> ["14","komma","0","5","sekunden"] "vierzehn Komma null fünf Sekunden"
|
||||
// 14.00 -> ["14","sekunden"] "vierzehn Sekunden"
|
||||
// 65.96 -> ["minute","und","5","komma","96","sekunden"]
|
||||
// "eine Minute und fünf Komma sechsundneunzig Sekunden"
|
||||
// 125.50 -> ["2","minuten","und","5","komma","50","sekunden"]
|
||||
//
|
||||
// Note on hundredths < 10: the leading zero is spoken digit-by-
|
||||
// digit ("null fünf" for .05) so .05 stays distinguishable from
|
||||
// .50 ("fünfzig"). For >= 10 the value is spoken as a single
|
||||
// German word ("sechsundneunzig" for .96).
|
||||
function timeToSeq(seconds) {
|
||||
const total = Math.max(0, Number(seconds) || 0);
|
||||
// Replicate the server's exact float-based formatting so the
|
||||
// announcement always matches what the user sees on screen.
|
||||
// The ESP (databasebackend.h, gamemodes.h) does:
|
||||
// float s = timeMs / 1000.0;
|
||||
// int totalSec = (int)s;
|
||||
// int hundredths = (int)((s - totalSec) * 100);
|
||||
// C++ `float` is single precision (24-bit mantissa); JS Number
|
||||
// is double precision. For times near a hundredths boundary
|
||||
// (e.g. 14.090) the two give different floor results — server
|
||||
// says "14.08" but a naive double calculation announces "14.09".
|
||||
// Math.fround forces single-precision rounding at each step so
|
||||
// the chain matches the server bit-for-bit.
|
||||
const sFloat = Math.fround(total);
|
||||
const totalSec = Math.trunc(sFloat);
|
||||
const minutes = Math.floor(totalSec / 60);
|
||||
const remSec = totalSec % 60;
|
||||
const diffFloat = Math.fround(sFloat - totalSec);
|
||||
const scaledFloat = Math.fround(diffFloat * 100);
|
||||
let hundredths = Math.trunc(scaledFloat);
|
||||
if (hundredths < 0) hundredths = 0;
|
||||
if (hundredths > 99) hundredths = 99;
|
||||
|
||||
const out = [];
|
||||
|
||||
if (minutes > 0) {
|
||||
if (minutes === 1) {
|
||||
out.push('minute'); // "eine Minute"
|
||||
} else {
|
||||
out.push(String(minutes));
|
||||
out.push('minuten');
|
||||
}
|
||||
if (remSec > 0 || hundredths > 0) out.push('und');
|
||||
}
|
||||
|
||||
if (remSec > 0 || hundredths > 0 || minutes === 0) {
|
||||
out.push(String(remSec));
|
||||
if (hundredths > 0) {
|
||||
out.push('komma');
|
||||
if (hundredths < 10) {
|
||||
out.push('0');
|
||||
out.push(String(hundredths));
|
||||
} else {
|
||||
out.push(String(hundredths));
|
||||
}
|
||||
}
|
||||
out.push('sekunden');
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// "12.34" or "01:23.45" -> seconds. timeToSeq() then re-rounds
|
||||
// the value through Math.fround to match the server's float math,
|
||||
// so the parseFloat drift is harmless here.
|
||||
function parseFormattedTime(str) {
|
||||
if (!str) return 0;
|
||||
if (str.includes(':')) {
|
||||
const [mm, rest] = str.split(':');
|
||||
return parseInt(mm, 10) * 60 + parseFloat(rest);
|
||||
}
|
||||
return parseFloat(str) || 0;
|
||||
}
|
||||
|
||||
function ensureContext() {
|
||||
if (audioCtx) return;
|
||||
const Ctx = window.AudioContext || window.webkitAudioContext;
|
||||
if (!Ctx) {
|
||||
console.warn('TTS: Web Audio API not supported');
|
||||
return;
|
||||
}
|
||||
audioCtx = new Ctx();
|
||||
}
|
||||
|
||||
// Limit concurrent fetches: the ESP's async web server only serves
|
||||
// a handful of requests well in parallel, and the browser's 6-per-
|
||||
// host pool would otherwise starve the 1 s /api/data poll while
|
||||
// 107 MP3s come in. Two parallel fetches finishes the preload in
|
||||
// ~2 s without holding up the live timer.
|
||||
const PRELOAD_CONCURRENCY = 2;
|
||||
|
||||
async function fetchAndStore(name) {
|
||||
const res = await fetch(BASE + name + '.mp3');
|
||||
const arr = await res.arrayBuffer();
|
||||
// Older Safari only supports the callback form of decodeAudioData.
|
||||
const buffer = await new Promise((resolve, reject) => {
|
||||
const p = audioCtx.decodeAudioData(arr, resolve, reject);
|
||||
if (p && typeof p.then === 'function') p.then(resolve, reject);
|
||||
});
|
||||
const { offset, duration } = findNonSilentRange(buffer);
|
||||
buffers[name] = { buffer, offset, duration };
|
||||
}
|
||||
|
||||
function preload() {
|
||||
if (preloadPromise) return preloadPromise;
|
||||
ensureContext();
|
||||
if (!audioCtx) return Promise.resolve();
|
||||
let i = 0;
|
||||
const worker = async () => {
|
||||
while (i < FILES.length) {
|
||||
const name = FILES[i++];
|
||||
try {
|
||||
await fetchAndStore(name);
|
||||
} catch (e) {
|
||||
console.warn('TTS preload failed:', name, e);
|
||||
}
|
||||
}
|
||||
};
|
||||
const workers = [];
|
||||
for (let n = 0; n < PRELOAD_CONCURRENCY; n++) workers.push(worker());
|
||||
preloadPromise = Promise.all(workers);
|
||||
return preloadPromise;
|
||||
}
|
||||
|
||||
function stop() {
|
||||
scheduled.forEach(s => { try { s.stop(); } catch (_) {} });
|
||||
scheduled = [];
|
||||
}
|
||||
|
||||
function play(seq) {
|
||||
if (!enabled || !seq || !seq.length) return;
|
||||
ensureContext();
|
||||
if (!audioCtx) return;
|
||||
// Browsers may suspend the context until a user gesture. resume()
|
||||
// is a no-op if already running.
|
||||
if (audioCtx.state === 'suspended') {
|
||||
audioCtx.resume().catch(() => {});
|
||||
}
|
||||
stop();
|
||||
let when = audioCtx.currentTime;
|
||||
seq.forEach((name) => {
|
||||
const entry = buffers[name];
|
||||
if (!entry) {
|
||||
console.warn('TTS buffer missing:', name);
|
||||
return;
|
||||
}
|
||||
const src = audioCtx.createBufferSource();
|
||||
src.buffer = entry.buffer;
|
||||
src.connect(audioCtx.destination);
|
||||
// start(when, offset, duration) plays only the non-silent slice
|
||||
// we identified during preload, so trailing silence in each
|
||||
// snippet doesn't stack up between words.
|
||||
src.start(when, entry.offset, entry.duration);
|
||||
scheduled.push(src);
|
||||
when += entry.duration;
|
||||
});
|
||||
}
|
||||
|
||||
function setEnabled(on) {
|
||||
enabled = !!on;
|
||||
localStorage.setItem(STORAGE_KEY, enabled ? '1' : '0');
|
||||
if (!enabled) stop();
|
||||
updateToggleUI();
|
||||
}
|
||||
|
||||
function updateToggleUI() {
|
||||
if (!btn) return;
|
||||
btn.textContent = enabled ? '🔊' : '🔇';
|
||||
btn.title = enabled ? 'Ansagen deaktivieren' : 'Ansagen aktivieren';
|
||||
btn.setAttribute('aria-pressed', enabled ? 'true' : 'false');
|
||||
}
|
||||
|
||||
function injectToggle() {
|
||||
if (btn || !document.body) return;
|
||||
btn = document.createElement('button');
|
||||
btn.id = 'tts-toggle';
|
||||
btn.style.cssText =
|
||||
'position:fixed;bottom:14px;right:14px;z-index:9998;' +
|
||||
'width:54px;height:54px;border-radius:50%;border:none;' +
|
||||
'background:rgba(0,0,0,0.55);color:#fff;font-size:24px;' +
|
||||
'cursor:pointer;display:flex;align-items:center;justify-content:center;' +
|
||||
'box-shadow:0 2px 10px rgba(0,0,0,0.35);';
|
||||
btn.addEventListener('click', async () => {
|
||||
const next = !enabled;
|
||||
setEnabled(next);
|
||||
if (next) {
|
||||
// The click itself is the user gesture that lets us create
|
||||
// and resume the AudioContext. Preload then play a short ack.
|
||||
await preload();
|
||||
play(['bereit']);
|
||||
}
|
||||
});
|
||||
document.body.appendChild(btn);
|
||||
updateToggleUI();
|
||||
}
|
||||
|
||||
// Decode buffers in the background. On a fresh page load the
|
||||
// AudioContext is created in suspended state (no audio yet, just
|
||||
// decoding — allowed without a gesture), so the first real
|
||||
// announcement after the user clicks anywhere is already gapless.
|
||||
// Defer the start so the initial render and the first /api/data
|
||||
// poll go through unimpeded — otherwise the ESP's web server is
|
||||
// busy serving MP3s and the live timer freezes for several seconds.
|
||||
function eagerPreload() {
|
||||
if (!enabled) return;
|
||||
setTimeout(preload, 2000);
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
injectToggle();
|
||||
eagerPreload();
|
||||
});
|
||||
} else {
|
||||
injectToggle();
|
||||
eagerPreload();
|
||||
}
|
||||
|
||||
window.tts = {
|
||||
isEnabled: () => enabled,
|
||||
setEnabled,
|
||||
play,
|
||||
stop,
|
||||
timeToSeq,
|
||||
parseFormattedTime,
|
||||
sayTime: (sec) => play(['neue_zeit', ...timeToSeq(sec)]),
|
||||
};
|
||||
})();
|
||||
BIN
data/tts/0.mp3
Normal file
BIN
data/tts/0.mp3
Normal file
Binary file not shown.
BIN
data/tts/1.mp3
Normal file
BIN
data/tts/1.mp3
Normal file
Binary file not shown.
BIN
data/tts/10.mp3
Normal file
BIN
data/tts/10.mp3
Normal file
Binary file not shown.
BIN
data/tts/11.mp3
Normal file
BIN
data/tts/11.mp3
Normal file
Binary file not shown.
BIN
data/tts/12.mp3
Normal file
BIN
data/tts/12.mp3
Normal file
Binary file not shown.
BIN
data/tts/13.mp3
Normal file
BIN
data/tts/13.mp3
Normal file
Binary file not shown.
BIN
data/tts/14.mp3
Normal file
BIN
data/tts/14.mp3
Normal file
Binary file not shown.
BIN
data/tts/15.mp3
Normal file
BIN
data/tts/15.mp3
Normal file
Binary file not shown.
BIN
data/tts/16.mp3
Normal file
BIN
data/tts/16.mp3
Normal file
Binary file not shown.
BIN
data/tts/17.mp3
Normal file
BIN
data/tts/17.mp3
Normal file
Binary file not shown.
BIN
data/tts/18.mp3
Normal file
BIN
data/tts/18.mp3
Normal file
Binary file not shown.
BIN
data/tts/19.mp3
Normal file
BIN
data/tts/19.mp3
Normal file
Binary file not shown.
BIN
data/tts/2.mp3
Normal file
BIN
data/tts/2.mp3
Normal file
Binary file not shown.
BIN
data/tts/20.mp3
Normal file
BIN
data/tts/20.mp3
Normal file
Binary file not shown.
BIN
data/tts/21.mp3
Normal file
BIN
data/tts/21.mp3
Normal file
Binary file not shown.
BIN
data/tts/22.mp3
Normal file
BIN
data/tts/22.mp3
Normal file
Binary file not shown.
BIN
data/tts/23.mp3
Normal file
BIN
data/tts/23.mp3
Normal file
Binary file not shown.
BIN
data/tts/24.mp3
Normal file
BIN
data/tts/24.mp3
Normal file
Binary file not shown.
BIN
data/tts/25.mp3
Normal file
BIN
data/tts/25.mp3
Normal file
Binary file not shown.
BIN
data/tts/26.mp3
Normal file
BIN
data/tts/26.mp3
Normal file
Binary file not shown.
BIN
data/tts/27.mp3
Normal file
BIN
data/tts/27.mp3
Normal file
Binary file not shown.
BIN
data/tts/28.mp3
Normal file
BIN
data/tts/28.mp3
Normal file
Binary file not shown.
BIN
data/tts/29.mp3
Normal file
BIN
data/tts/29.mp3
Normal file
Binary file not shown.
BIN
data/tts/3.mp3
Normal file
BIN
data/tts/3.mp3
Normal file
Binary file not shown.
BIN
data/tts/30.mp3
Normal file
BIN
data/tts/30.mp3
Normal file
Binary file not shown.
BIN
data/tts/31.mp3
Normal file
BIN
data/tts/31.mp3
Normal file
Binary file not shown.
BIN
data/tts/32.mp3
Normal file
BIN
data/tts/32.mp3
Normal file
Binary file not shown.
BIN
data/tts/33.mp3
Normal file
BIN
data/tts/33.mp3
Normal file
Binary file not shown.
BIN
data/tts/34.mp3
Normal file
BIN
data/tts/34.mp3
Normal file
Binary file not shown.
BIN
data/tts/35.mp3
Normal file
BIN
data/tts/35.mp3
Normal file
Binary file not shown.
BIN
data/tts/36.mp3
Normal file
BIN
data/tts/36.mp3
Normal file
Binary file not shown.
BIN
data/tts/37.mp3
Normal file
BIN
data/tts/37.mp3
Normal file
Binary file not shown.
BIN
data/tts/38.mp3
Normal file
BIN
data/tts/38.mp3
Normal file
Binary file not shown.
BIN
data/tts/39.mp3
Normal file
BIN
data/tts/39.mp3
Normal file
Binary file not shown.
BIN
data/tts/4.mp3
Normal file
BIN
data/tts/4.mp3
Normal file
Binary file not shown.
BIN
data/tts/40.mp3
Normal file
BIN
data/tts/40.mp3
Normal file
Binary file not shown.
BIN
data/tts/41.mp3
Normal file
BIN
data/tts/41.mp3
Normal file
Binary file not shown.
BIN
data/tts/42.mp3
Normal file
BIN
data/tts/42.mp3
Normal file
Binary file not shown.
BIN
data/tts/43.mp3
Normal file
BIN
data/tts/43.mp3
Normal file
Binary file not shown.
BIN
data/tts/44.mp3
Normal file
BIN
data/tts/44.mp3
Normal file
Binary file not shown.
BIN
data/tts/45.mp3
Normal file
BIN
data/tts/45.mp3
Normal file
Binary file not shown.
BIN
data/tts/46.mp3
Normal file
BIN
data/tts/46.mp3
Normal file
Binary file not shown.
BIN
data/tts/47.mp3
Normal file
BIN
data/tts/47.mp3
Normal file
Binary file not shown.
BIN
data/tts/48.mp3
Normal file
BIN
data/tts/48.mp3
Normal file
Binary file not shown.
BIN
data/tts/49.mp3
Normal file
BIN
data/tts/49.mp3
Normal file
Binary file not shown.
BIN
data/tts/5.mp3
Normal file
BIN
data/tts/5.mp3
Normal file
Binary file not shown.
BIN
data/tts/50.mp3
Normal file
BIN
data/tts/50.mp3
Normal file
Binary file not shown.
BIN
data/tts/51.mp3
Normal file
BIN
data/tts/51.mp3
Normal file
Binary file not shown.
BIN
data/tts/52.mp3
Normal file
BIN
data/tts/52.mp3
Normal file
Binary file not shown.
BIN
data/tts/53.mp3
Normal file
BIN
data/tts/53.mp3
Normal file
Binary file not shown.
BIN
data/tts/54.mp3
Normal file
BIN
data/tts/54.mp3
Normal file
Binary file not shown.
BIN
data/tts/55.mp3
Normal file
BIN
data/tts/55.mp3
Normal file
Binary file not shown.
BIN
data/tts/56.mp3
Normal file
BIN
data/tts/56.mp3
Normal file
Binary file not shown.
BIN
data/tts/57.mp3
Normal file
BIN
data/tts/57.mp3
Normal file
Binary file not shown.
BIN
data/tts/58.mp3
Normal file
BIN
data/tts/58.mp3
Normal file
Binary file not shown.
BIN
data/tts/59.mp3
Normal file
BIN
data/tts/59.mp3
Normal file
Binary file not shown.
BIN
data/tts/6.mp3
Normal file
BIN
data/tts/6.mp3
Normal file
Binary file not shown.
BIN
data/tts/60.mp3
Normal file
BIN
data/tts/60.mp3
Normal file
Binary file not shown.
BIN
data/tts/61.mp3
Normal file
BIN
data/tts/61.mp3
Normal file
Binary file not shown.
BIN
data/tts/62.mp3
Normal file
BIN
data/tts/62.mp3
Normal file
Binary file not shown.
BIN
data/tts/63.mp3
Normal file
BIN
data/tts/63.mp3
Normal file
Binary file not shown.
BIN
data/tts/64.mp3
Normal file
BIN
data/tts/64.mp3
Normal file
Binary file not shown.
BIN
data/tts/65.mp3
Normal file
BIN
data/tts/65.mp3
Normal file
Binary file not shown.
BIN
data/tts/66.mp3
Normal file
BIN
data/tts/66.mp3
Normal file
Binary file not shown.
BIN
data/tts/67.mp3
Normal file
BIN
data/tts/67.mp3
Normal file
Binary file not shown.
BIN
data/tts/68.mp3
Normal file
BIN
data/tts/68.mp3
Normal file
Binary file not shown.
BIN
data/tts/69.mp3
Normal file
BIN
data/tts/69.mp3
Normal file
Binary file not shown.
BIN
data/tts/7.mp3
Normal file
BIN
data/tts/7.mp3
Normal file
Binary file not shown.
BIN
data/tts/70.mp3
Normal file
BIN
data/tts/70.mp3
Normal file
Binary file not shown.
BIN
data/tts/71.mp3
Normal file
BIN
data/tts/71.mp3
Normal file
Binary file not shown.
BIN
data/tts/72.mp3
Normal file
BIN
data/tts/72.mp3
Normal file
Binary file not shown.
BIN
data/tts/73.mp3
Normal file
BIN
data/tts/73.mp3
Normal file
Binary file not shown.
BIN
data/tts/74.mp3
Normal file
BIN
data/tts/74.mp3
Normal file
Binary file not shown.
BIN
data/tts/75.mp3
Normal file
BIN
data/tts/75.mp3
Normal file
Binary file not shown.
BIN
data/tts/76.mp3
Normal file
BIN
data/tts/76.mp3
Normal file
Binary file not shown.
BIN
data/tts/77.mp3
Normal file
BIN
data/tts/77.mp3
Normal file
Binary file not shown.
BIN
data/tts/78.mp3
Normal file
BIN
data/tts/78.mp3
Normal file
Binary file not shown.
BIN
data/tts/79.mp3
Normal file
BIN
data/tts/79.mp3
Normal file
Binary file not shown.
BIN
data/tts/8.mp3
Normal file
BIN
data/tts/8.mp3
Normal file
Binary file not shown.
BIN
data/tts/80.mp3
Normal file
BIN
data/tts/80.mp3
Normal file
Binary file not shown.
BIN
data/tts/81.mp3
Normal file
BIN
data/tts/81.mp3
Normal file
Binary file not shown.
BIN
data/tts/82.mp3
Normal file
BIN
data/tts/82.mp3
Normal file
Binary file not shown.
BIN
data/tts/83.mp3
Normal file
BIN
data/tts/83.mp3
Normal file
Binary file not shown.
BIN
data/tts/84.mp3
Normal file
BIN
data/tts/84.mp3
Normal file
Binary file not shown.
BIN
data/tts/85.mp3
Normal file
BIN
data/tts/85.mp3
Normal file
Binary file not shown.
BIN
data/tts/86.mp3
Normal file
BIN
data/tts/86.mp3
Normal file
Binary file not shown.
BIN
data/tts/87.mp3
Normal file
BIN
data/tts/87.mp3
Normal file
Binary file not shown.
BIN
data/tts/88.mp3
Normal file
BIN
data/tts/88.mp3
Normal file
Binary file not shown.
BIN
data/tts/89.mp3
Normal file
BIN
data/tts/89.mp3
Normal file
Binary file not shown.
BIN
data/tts/9.mp3
Normal file
BIN
data/tts/9.mp3
Normal file
Binary file not shown.
BIN
data/tts/90.mp3
Normal file
BIN
data/tts/90.mp3
Normal file
Binary file not shown.
BIN
data/tts/91.mp3
Normal file
BIN
data/tts/91.mp3
Normal file
Binary file not shown.
BIN
data/tts/92.mp3
Normal file
BIN
data/tts/92.mp3
Normal file
Binary file not shown.
BIN
data/tts/93.mp3
Normal file
BIN
data/tts/93.mp3
Normal file
Binary file not shown.
BIN
data/tts/94.mp3
Normal file
BIN
data/tts/94.mp3
Normal file
Binary file not shown.
BIN
data/tts/95.mp3
Normal file
BIN
data/tts/95.mp3
Normal file
Binary file not shown.
BIN
data/tts/96.mp3
Normal file
BIN
data/tts/96.mp3
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user