```

```

### Whistled Speech

I found out today about whistled languages, and I couldn't resist programming a basic Silbo Gomero reader.

```// Very basic "Silbo Gomero" whistled speech generator
/*
This program converts a string of lowercase letters (a-z) and spaces
into an audio interpretation in Silbo Gomero whistle speech.
Silbo Gomero is a whistled register of Spanish used by inhabitants
of La Gomera in the Canary Islands to communicate across long distances.

I've used a very basic mapping from Spanish phonemes into
corresponding whistled ones, and the audio is generated using
a sine wave that is modulated in frequency and amplitude.

Some information on the phonetics of the Silbo language
can be found in:
https://en.wikipedia.org/wiki/Silbo_Gomero
http://silbo-gomero.com/Meyer-English.pdf
http://silbo-gomero.com/wlanguages.pdf
*/

let sentence = "el silbo gomero es un lenguaje silbado practicado por algunos habitantes de canarias para comunicarse a traves de barrancos y valles";
//let sentence = "como estas";
//let sentence = "hola  estas alli    si  estoy aqi   esta jose alli   no  el no esta aqi";

ditty.bpm = 60; // 1 tick = 1 second

function nsin(a) {
// Not quite a sinusoid
let x = a - (~~a);
//return Math.sin(2*Math.PI*x);
return x*(x-1)*(x-0.5)*20.785;
//x -= 0.5; x *= 2 * 3.0786; return x - x**3/6 + x**5/(1*2*3*4*5) - x**7/(1*2*3*4*5*6*7);
}

const chirpSynth = synth.def( class {
constructor(o) {
this.p = 0;
this.f = o.f;
this.g = 0; // gain
}
process(n,e,t,o) {
let a0 = clamp01(ditty.dt / o.dt);
this.f += a0 * (o.f - this.f);
this.g = clamp01(this.g + 1.5*a0 * (2*o.g-1));
this.p += this.f * ditty.dt;
return nsin(this.p) * this.g;
}
}, {f: 1400, g:1, dt:0.12, env:one, amp:0.1});

function rn(a) {
// Random number between -a and +a
return 2*a*(Math.random()-0.5);
}
function rrn(a) {
// Relative random number between exp(-a) and exp(+a)
return Math.exp(rn(a));
}

function getFreq(char) {
// Get frequency corresponding to a given character sound

// Vowels
if(char == "u") {
return 1450 + rn(50);
} else if (char == "o") {
return 1600+rn(120);
} else if (char == "a") {
return 1700+rn(100);
} else if (char == "e") {
return 2200+rn(300);
} else if (char == "i") {
return 2700+rn(120);
// Consonants
} else if (["d","j","l","n","r","s","t","y","z"].includes(char)) {
return 3100+rn(100);
} else if (["b","c","f","g","k","m","p","q","v","w","x"].includes(char)) {
return 800+rn(50);
}
return null;
}

function getGain(char) {
if("abdefgijlmnorsuvwyz".includes(char)) {
return 1;
}
return 0;
}

function getDur(char) {
if(char == " ") {
return 0.5;
} else if("aeiou".includes(char)) {
return 0.4;
} else if("stlckqj".includes(char)) {
return 0.05;
}
return 0.12;
}

loop( () => {
let prevdur = 1000;
let s = chirpSynth.play({f:1400, g:0});
for(let i=0; i<sentence.length; i++) {
let char = sentence.charAt(i);
let freq = getFreq(char);
if(freq) {
s.options.f = freq;
}
let gain = getGain(char);
s.options.g = gain;
let dur = getDur(char);
s.options.dt = Math.min(dur, prevdur);
sleep(dur);
prevdur = dur;
}
s.options.g = 0;
sleep(1000);
});```