# espeak-ng 1.52.0 — el sintetizador de voz. Con esto la distro HABLA. # # ══ POR QUÉ ENTRA: ES EL HUECO QUE DEJA GENTE AFUERA ═══════════════════════════════════════════ # `censo-distro.py` marcaba «accesibilidad» **4 de 4 ausente**: sin lector de pantalla ni voz, una # distro es inusable para quien no ve — no incómoda, inusable. Y no hay forma de medir eso con las # otras métricas del repo: el tablero puede estar en 1158/1158 y clausura 10 de 10 mientras la # máquina es muda. # # espeak-ng es el eslabón de abajo y el que vale por sí solo: 100+ idiomas por síntesis formante, # sin modelos ni red, en unos pocos MB. Es lo que usan `orca` y `speech-dispatcher`, y también lo # que alguien invoca a mano (`espeak-ng -v es "hola"`). # # ══ ⚠ LOS DOS GUARDIANES SON EL PUNTO DE ESTA RECETA ═══════════════════════════════════════════ # espeak-ng busca TODAS sus deps con `find_library`/`find_path` **sin REQUIRED** # (`cmake/deps.cmake`): sonic, pcaudio, mbrola. O sea que **construye y sella sin ninguna**, y # entonces: # · sin `pcaudio` → sintetiza a un `.wav` y **no reproduce**. `espeak-ng "hola"` no hace ruido. # · sin los datos de voz compilados → arranca y no tiene con qué pronunciar. # Las dos cosas son «sellado, con contenido, reproducible y sin la función que promete», que es la # figura que este catálogo persigue; en accesibilidad es peor, porque la promesa es que la máquina # habla. ⇒ el `install` comprueba que el binario SÍ enlace pcaudio y que los datos estén. # # ══ LAS PERILLAS ═══════════════════════════════════════════════════════════════════════════════ # BUILD_SHARED_LIBS=ON `speech-dispatcher` y `orca` cargan `libespeak-ng.so` con dlopen; el # default de upstream es OFF, que dejaría sólo la `.a` # USE_MBROLA=OFF voces MBROLA: binario externo, no está en el corpus y son diccionarios # por idioma aparte. La síntesis formante propia no las necesita # USE_SPEECHPLAYER=OFF sintetizador alternativo experimental # # ⚠ **`sonic` ES DEP OBLIGATORIA AUNQUE SU `find_library` NO LO PAREZCA.** `deps.cmake` la busca sin # REQUIRED… y si no la encuentra **la CLONA de git** (`FetchContent_Declare(sonic-git …)`), lo que en # un sandbox sin red mata el build: # CMake Error at sonic-git-subbuild/…/sonic-git-populate-gitclone.cmake:50 # Y `USE_LIBSONIC=OFF` no lo evita: el fetch está antes de que la opción se evalúe, y la rama # `else()` pone `HAVE_LIBSONIC ON` igual. ⇒ `recipes/sonic.toml` existe para que el `find_library` # acierte, pineada al MISMO commit que espeak-ng declara. Lección: **un `find_library` sin REQUIRED # no significa «opcional»; hay que leer el `else()`.** # ESPEAK_COMPAT=ON instala también el nombre viejo `espeak`, que es el que invocan los # scripts y las apps que no se actualizaron a `espeak-ng` # # ⚠ **LOS `-l` DE PULSE Y ALSA VAN A MANO, y es el problema transitivo del corpus estático otra # vez.** Con `sonic` resuelto, el build llegó a enlazar y murió así: # Error relocating /src/build/src/libespeak-ng/libespeak-ng.so.1: pa_strerror: symbol not found # Error relocating …: snd_strerror: symbol not found # `libpcaudio.a` referencia esos símbolos, pero espeak-ng la encuentra con # `find_library(PCAUDIO_LIB pcaudio)` — que devuelve **la ruta del `.a` y nada más**, sin las # transitivas. Y pcaudiolib no publica `.pc` (upstream no trae plantilla), así que no hay de dónde # sacarlas automáticamente. Medido con `nm`: `pa_strerror` está en `libpulse.so` y `snd_strerror` en # `libasound.so`. # # ⇒ Es la MISMA figura que `fontconfig.pc → Requires.private: expat` en fcitx5, con una diferencia # que importa: allá la cura fue `PKG_CONFIG_ARGN=--static` porque el `.pc` existía y sabía sus # privadas; **acá no hay `.pc`, así que las transitivas se escriben a mano o no hay forma.** Y el # fallo sale al EJECUTAR, no al enlazar —la fase de datos corre el propio `espeak-ng` para compilar # los fonemas—, que es por qué el síntoma es «symbol not found» y no «undefined reference». name = "espeak-ng" version = "1.52.0" license = "GPL-3.0-or-later" [source] tarball = "https://github.com/espeak-ng/espeak-ng/archive/refs/tags/1.52.0/espeak-ng-1.52.0.tar.gz" sha256 = "bb4338102ff3b49a81423da8a1a158b420124b055b60fa76cfb4b18677130a23" [build] compiler = "zig-cc" target = "x86_64-linux-musl" link = "dynamic" flags = [] [build.phases] configure = ''' ZW="$PWD/.zwrap"; mkdir -p "$ZW" # El envoltorio reescribe `--target`: patrón canónico del repo (30 recetas) para builds que inventan # su propio triple y hacen que zig corte con «UnknownOperatingSystem». printf '%s\n' '#!/bin/sh' 'for a do' 'case "$a" in --target=*) a=--target=x86_64-linux-musl ;; esac' 'set -- "$@" "$a"' 'shift' 'done' 'exec /opt/zig/zig cc -mcpu=baseline "$@"' > "$ZW/cc" printf '%s\n' '#!/bin/sh' 'for a do' 'case "$a" in --target=*) a=--target=x86_64-linux-musl ;; esac' 'set -- "$@" "$a"' 'shift' 'done' 'exec /opt/zig/zig c++ -mcpu=baseline "$@"' > "$ZW/cxx" chmod +x "$ZW/cc" "$ZW/cxx" rm -rf build cmake -B build -G Ninja -Wno-dev \ -DCMAKE_C_COMPILER="$ZW/cc" -DCMAKE_CXX_COMPILER="$ZW/cxx" \ -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr \ -DBUILD_SHARED_LIBS=ON -DUSE_MBROLA=OFF -DUSE_SPEECHPLAYER=OFF \ -DESPEAK_COMPAT=ON -DBUILD_TESTING=OFF \ -DCMAKE_SHARED_LINKER_FLAGS="-lpulse-simple -lpulse -lasound" \ -DCMAKE_EXE_LINKER_FLAGS="-lpulse-simple -lpulse -lasound" ''' compile = "cmake --build build" install = ''' set -e DESTDIR=/out cmake --install build [ -x /out/usr/bin/espeak-ng ] || { echo "espeak-ng: no quedó el binario — NO sello" >&2; exit 1; } so=$(find /out/usr/lib -name 'libespeak-ng.so*' -type f 2>/dev/null | head -1) [ -n "$so" ] || { echo "espeak-ng: no quedó libespeak-ng.so — orca y speech-dispatcher la cargan con dlopen" >&2; exit 1; } # ⚠ GUARDIÁN 1: que REPRODUZCA, no sólo que sintetice. # # **ESTE CONTROL ESTUVO MAL Y RECHAZÓ UN BUILD CORRECTO.** Mi primera versión miraba # `readelf -d` del BINARIO buscando «pcaudio», y eso no puede funcionar por dos razones que se # podían comprobar en un minuto: # · `libpcaudio` es un `.a`: un estático se enlaza DENTRO y **nunca** produce una entrada NEEDED. # · el binario sólo enlaza `libespeak-ng.so.1`; el audio vive en la `.so`, no en él. # Y su respaldo por `strings` buscaba `pulse_simple`, que no es el nombre de ningún símbolo # (`pa_simple_new` sí). O sea: tres comprobaciones y las tres contra un modelo mío del artefacto en # vez de contra el artefacto. # # ⇒ Lo de abajo son HECHOS medidos sobre un build correcto (cmake dijo # `libpcaudio: ON (/usr/lib/libpcaudio.a)` y `USE_LIBPCAUDIO:BOOL=ON`): # la `.so` trae `libpulse-simple.so.0 libpulse.so.0 libasound.so.2` en NEEDED, y exporta los # símbolos `audio_object_*` que son la API de pcaudiolib. # Se piden las dos cosas porque cubren fallos distintos: los NEEDED prueban que las bibliotecas de # audio están enlazadas; los símbolos, que el código de pcaudio entró de verdad. sim=$(nm -D "$so" 2>/dev/null | grep -c ' T audio_object_' || true) [ "${sim:-0}" -gt 0 ] || { echo "espeak-ng: la .so no exporta ningún audio_object_* — el código de pcaudiolib NO entró, sintetizaría a fichero y no sonaría" >&2; exit 1; } need=$(readelf -d "$so" 2>/dev/null | grep NEEDED | grep -cE 'libpulse|libasound' || true) [ "${need:-0}" -gt 0 ] || { echo "espeak-ng: la .so no enlaza ni libpulse ni libasound — no tiene a dónde sacar el sonido" >&2; readelf -d "$so" 2>/dev/null | grep NEEDED >&2; exit 1; } # ⚠ GUARDIÁN 2: los datos de voz. Se compilan desde la fuente (`cmake/data.cmake`), no se bajan — # pero si la fase de datos se saltara, quedaría un sintetizador sin fonemas que no pronuncia nada. d=$(find /out/usr/share/espeak-ng-data -type f 2>/dev/null | wc -l) [ "$d" -gt 50 ] || { echo "espeak-ng: sólo $d fichero(s) en espeak-ng-data — esperaba >50; sin fonemas ni voces no pronuncia" >&2; exit 1; } echo "espeak-ng: $sim símbolo(s) de audio + $need lib(s) de sonido en NEEDED + $d fichero(s) de datos" ''' [deps] # ⚠ `binutils` está acá por los GUARDIANES, no por el build: aportan `readelf` y `strings`, y sin # ellos el control de audio fallaría por falta de herramienta en vez de por el defecto que busca — # un guardián que se cae solo acusa a la receta de algo que no hizo. # ⚠ **`libsndfile` NO es dep de espeak-ng: es dep de la cadena que pcaudiolib arrastra.** Con # `-lpulse` resuelto, el cargador cayó un nivel más abajo: # Error relocating /usr/lib/pulseaudio/libpulsecommon-17.0.so: sf_command: symbol not found # `libpulse` carga `libpulsecommon`, que declara `NEEDED libsndfile.so.1` (medido con readelf). Como # el sandbox sólo monta lo declarado, había que declararla acá aunque espeak-ng no la nombre nunca. # ⇒ La cadena completa, que es lo que hay que tener en la cabeza para este corpus: # espeak-ng → libpcaudio.a → libpulse.so → libpulsecommon.so → libsndfile.so.1 # Tres saltos, y el `.a` del medio corta la propagación automática porque un `.a` no declara # NEEDED. Se declaran también las deps de runtime de pulseaudio (`glib`, `libffi`, `pcre2`) por el # mismo motivo. build = ["cmake", "samurai", "pkgconf", "pcaudiolib", "sonic", "pulseaudio", "alsa-lib", "libsndfile", "glib", "libffi", "pcre2", "binutils"]