Skip to content

Instantly share code, notes, and snippets.

@c3r38r170
Last active April 12, 2023 15:09
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save c3r38r170/753636e471156ce14b0349c3c4cd0cf5 to your computer and use it in GitHub Desktop.
Save c3r38r170/753636e471156ce14b0349c3c4cd0cf5 to your computer and use it in GitHub Desktop.
Convierte texto en español a una aproximación fonética en códigos de CMUdict (derivado de ARPAbet) para poder usar en el sitio web 15.ai .
/*
Sitio del objetivo: https://15.ai
CMUdict: http://www.speech.cs.cmu.edu/cgi-bin/cmudict
Referencia para los equivalentes de CMUdict en español:
https://www.lumenvox.com/knowledgebase/index.php?/article/AA-01086/0/Mexican-Spanish-Phonemes.html
https://www.phon.ucl.ac.uk/home/sampa/spanish.htm
Algoritmo de separación en sílabas: https://github.com/nicofrem/silabajs
Uso:
oracionTo15ai(
'Si el grandote pinta para el fondo, va a haber quilombo.'
,'You better not mess with us.'
);
La oración en inglés es para darle contexto emocional.
Hay que tener en cuenta el límite de caracteres en 15.ai .
*/
function oracionTo15ai(oracion,oracionEnIngles=''){
let resultado='';
oracion=oracion.trim().toLowerCase()
//Quitar espacios innecesarios
.replace(/ *(,|¿|¡) */g,',')
.replace(/ *(\p{P}) */gu,'$1')
.replace(/ {2,}/g,' ');
let previoEraPuntuacion=true
,pedazos=oracion.split(' ')
,puntuaciones=/\p{P}/u
,añadirPalabra=(palabra,puntuacion='')=>resultado+='{'+PalabraUtils.toARPAbet(palabra)+'}'+puntuacion;
for(let pedazo of pedazos){
let palabraActual='';
for(let char of pedazo){
let esteEsPuntuacion=puntuaciones.test(char);
if(esteEsPuntuacion){
if(!previoEraPuntuacion){
añadirPalabra(palabraActual,char);
palabraActual='';
}
}else palabraActual+=char;
previoEraPuntuacion=esteEsPuntuacion;
}
if(palabraActual)
añadirPalabra(palabraActual);
}
oracionEnIngles=oracionEnIngles.trim();
return oracionEnIngles?
resultado+'|'+oracionEnIngles
:resultado;
}
const PalabraUtils={
getSilabas:function(palabra){
palabra=palabra.toLowerCase().trim();
let silabaAux
,encontroTonica=false
,silabas=[];
// Se recorre la palabra buscando las sílabas
for (let actPos = 0; actPos < palabra.length;) {
silabaAux = {esTonica:0};
let hayTilde=false
,inicioPosicion=actPos
,esTonica=false;
// Las sílabas constan de tres partes: ataque, núcleo y coda
// Ataque
let ultimaConsonante = 'a';
// Se considera que todas las consonantes iniciales forman parte del ataque
while (
(actPos < palabra.length)
&& this.esConsonante(palabra[actPos])
&& palabra[actPos] != 'y'
)
ultimaConsonante = palabra[actPos++];
// (q | g) + u (ejemplo: queso, gueto)
if (actPos < palabra.length - 1)
if (palabra[actPos] == 'u') {
if (
ultimaConsonante == 'q'
||(
ultimaConsonante == 'g'
&& 'eéií'.includes(palabra [actPos + 1])
)
)
actPos++;
}else if (palabra[actPos] == 'ü' && ultimaConsonante == 'g') // La u con diéresis se añade a la consonante
actPos++;
// Núcleo
let anterior = 0
,char;
// 0 = fuerte
// 1 = débil acentuada
// 2 = débil
if (!(actPos >= palabra.length)){
// Se salta una 'y' al principio del núcleo, considerándola consonante
if (palabra[actPos] == 'y')
actPos++;
let seguir=true;
// Primera vocal
if (actPos < palabra.length) {
char = palabra[actPos];
if ('áàéèóò'.includes(char)){
// Vocal fuerte o débil acentuada
hayTilde=true;
esTonica = true;
anterior = 0;
actPos++;
}else if ('aeo'.includes(char)){
// Vocal fuerte
anterior = 0;
actPos++;
}else if ('íìúùü'.includes(char)){
// Vocal débil acentuada, rompe cualquier posible diptongo
if(char!='ü')
hayTilde=true;
anterior = 1;
actPos++;
esTonica = true;
seguir=false;
}else if ('iu'.includes(char)){
// Vocal débil
anterior = 2;
actPos++;
}
}
if(seguir){
// 'h' intercalada en el núcleo, no condiciona diptongos o hiatos
let hache = false;
if (actPos < palabra.length && palabra[actPos] == 'h') {
actPos++;
hache = true;
}
// Segunda vocal
if (actPos < palabra.length) {
char = palabra[actPos];
if ('áàéèóò'.includes(char)){
// Vocal fuerte o débil acentuada
hayTilde=true;
if (anterior) {
esTonica = true;
actPos++;
}else{
// Dos vocales fuertes no forman silaba
if (hache)
actPos--;
seguir=false;
}
}else if ('aeo'.includes(char)){
// Vocal fuerte
if(anterior)
actPos++;
else { // Dos vocales fuertes no forman silaba
if (hache)
actPos--;
seguir=false;
}
}else if ('íìúùü'.includes(char)){
// Vocal débil acentuada, rompe cualquier posible diptongo
if(char!='ü')
hayTilde=true;
if (anterior) { // Se forma diptongo
esTonica = true;
actPos++;
}else if (hache)
actPos--;
seguir=false;
}else if ('iu'.includes(char)){
// Vocal débil
if (actPos < palabra.length - 1 && !this.esConsonante(palabra [actPos + 1])) {
// ¿Hay tercera vocal?
if (palabra[actPos - 1] == 'h')
actPos--;
}else if (palabra [actPos] != palabra [actPos - 1])
// dos vocales débiles iguales no forman diptongo
actPos++;
// Es un diptongo plano o descendente
seguir=false;
}
}
// ¿tercera vocal?
if(seguir && actPos < palabra.length) {
char = palabra[actPos];
if (char == 'i' || char == 'u') // Vocal débil
actPos++; // Es un triptongo
}
}
}
// Coda
actPos = ((pos)=>{
if (pos >= palabra.length || !this.esConsonante(palabra[pos]))
return pos; // No hay coda
if (pos == palabra.length - 1) // Final de palabra
return pos+1;
// Si sólo hay una consonante entre vocales, pertenece a la siguiente silaba
if (!this.esConsonante(palabra [pos + 1]))
return pos;
let c1 = palabra[pos]
,c2 = palabra[pos + 1];
// ¿Existe posibilidad de una tercera consonante consecutina?
if (pos < palabra.length - 2) {
let c3 = palabra [pos + 2];
if (!this.esConsonante(c3)){ // No hay tercera consonante
if (
(c1 == 'l' && c2 == 'l')
||(c1 == 'c' && c2 == 'h')
||(c1 == 'r' && c2 == 'r')
)
return pos;
///////// grupos nh, sh, rh, hl son ajenos al español(DPD)
if (
(c1 != 's')
&& (c1 != 'r')
&& (c2 == 'h')
)
return pos;
// Si la y está precedida por s, l, r, n o c (consonantes alveolares), una nueva silaba empieza en la consonante previa, si no, empieza en la y
if (c2 == 'y')
return 'slrnc'.includes(c1)?
pos
:pos+1;
// gkbvpft + l
if (
'gkbvpft'.includes(c1)
&& c2 == 'l'
)
return pos;
// gkdtbvpf + r
if (
'gkdtbvpf'.includes(c1)
&& c2 == 'r'
)
return pos;
return pos+1;
}else{ // Hay tercera consonante
if ((pos + 3) == palabra.length) { // Tres consonantes al final ¿palabras extranjeras?
if (
c2 == 'y' //{ // 'y' funciona como vocal
&& 'slrnc'.includes(c1)
)
return pos;
// 'y' final funciona como vocal con c2
return c3 == 'y'?
pos+1
// Tres consonantes al final ¿palabras extranjeras?
:pos+3;
}
if (c2 == 'y') // 'y' funciona como vocal
return !'slrnc'.includes(c1)?
pos+1
:pos;
// Los grupos pt, ct, cn, ps, mn, gn, ft, pn, cz, tz, ts comienzan silaba (Bezos)
if (['pt', 'ct', 'cn', 'ps', 'mn', 'gn', 'ft', 'pn', 'cz', 'tz', 'ts' ].includes(c2+c3))
return pos+1;
return pos+(
(
c3 == 'l' || c3 == 'r' || // Los grupos consonánticos formados por una consonante seguida de 'l' o 'r' no pueden separarse y siempre inician sílaba
(c2 == 'c' && c3 == 'h') || // 'ch'
(c3 == 'y') // 'y' funciona como vocal
)?
1 // Siguiente sílaba empieza en c2
:2 // c3 inicia la siguiente sílaba
);
}
}else if (c2 != 'y')
return pos + 2; // La palabra acaba con dos consonantes
return pos;
})(actPos);
silabaAux.texto = palabra.substring(inicioPosicion, actPos);
// Marca la silaba tónica
if (esTonica && !encontroTonica)
silabaAux.esTonica = encontroTonica = hayTilde?2:1;
silabas.push(silabaAux);
}
// Si no se ha encontrado la sílaba tónica (no hay tilde), se determina en base a las reglas de acentuación
if (!encontroTonica) {
if (silabas.length ==1)// Monosílabos
silabas[0].esTonica = /[aeiou]{2}/.test(silabas[0].texto)?1:0;
// Para diferenciar monosílabos con tilde diacrítica.
else { // Polisílabos
let letraFinal = palabra[palabra.length - 1];
if (
(!this.esConsonante(letraFinal) || (letraFinal == 'y'))
||(
(letraFinal == 'n')
|| (letraFinal == 's')
&& !this.esConsonante(palabra[palabra.length - 2])
)
)
silabas[silabas.length-2].esTonica=1; // Palabra grave
else silabas[silabas.length-1].esTonica=1; // Palabra aguda
}
}
return silabas;
}
,vocalFuerte:letra=>'aáàeéèíìoóòúù'.includes(letra)
,esConsonante:letra=>!'aáàeéèíìoóòúùiuü'.includes(letra)
,toARPAbet:function(palabra){
let silabas=this.getSilabas(palabra)
,ARPAbet=[]
,vocales={
'a':'AA'
,'e':'EY'
,'i':'IY'
,'si':'Y'
,'o':'OW'
,'u':'UW'
,'su':'W'
}
,reemplazos={
'1':'CH'
,'j':'HH'
// ,'ñ':'GN' //15ai no soporta GN
,'ñ':'N IY0'
,'x':'XH'
,'z':'S'
}
,defaultAdd=letra=>ARPAbet.push(reemplazos[letra]||letra.toUpperCase());
for(let [i,silaba] of Object.entries(silabas)){
//reemplazar letras por sonidos
silaba.texto=silaba.texto.replace(/(ch)|(ll)|(h)|(c([aou]))|(c([ei]))|(qu)|(g([ei]))|(v)|(rr)|(gu([ei]))/g,(str,ch,ll,h,cacocu,aou,ceci,eic,qu,gegi,eig,v,rr,guegui,eigu)=>{
if(ch)
return '1';
if(ll)
return 'y';
if(h)
return '';
if(cacocu)
return 'k'+aou;
if(ceci)
return 's'+eic;
if(qu)
return 'k';
if(gegi)
return 'j'+eig;
if(v)
return 'b';
if(rr)
return 'r';
if(guegui)
return 'g'+eigu;
});
for(let [j,letra] of Object.entries(silaba.texto)){
if('aeiou'.includes(letra)){
switch(silaba.esTonica){
case 2:
ARPAbet.push(vocales[letra]+'1');
break;
case 1:
if(!/[aeiou]{2}/.test(silaba.texto)){
ARPAbet.push(vocales[letra]+'1');
break;
}else{
silaba.esTonica=2;
if(letra=='i' || letra=='u'){
ARPAbet.push(vocales['s'+letra]);
break;
}
}
case 0:
ARPAbet.push(vocales[letra]+'0');
}
continue;
}else if('áéíóúàèìòù'.includes(letra)){
ARPAbet.push(vocales[letra.normalize("NFD")[0]]+'1');
continue;
}else if(letra=='g' || letra=='d'){
if(j+1==silaba.length || !this.esConsonante(silaba[j+1])){
ARPAbet.push(letra+'H');
continue;
}
}else if(letra=='t'){
if(j+1!=silaba.length && 'zs'.includes(silaba[j+1])){
ARPAbet.push('TS');
continue;
}
}else if(letra=='n'){
if(!(i+1==silabas.length && j+1==silaba.length)){
let next=(j+1==silaba.length)?
silabas[i][0]
:silaba[j+i];
if(next=='k' || next=='g'){
ARPAbet.push('NG');
continue;
}
}
}else if(letra=='y'){
if(i+1==silabas.length && j+1==silaba.length){
ARPAbet.push('IY0');
continue;
}
}
defaultAdd(letra);
}
}
return ARPAbet.join(' ');
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment