I found out today about whistled languages, and I couldn't resist programming a basic Silbo Gomero reader.
Log in to post a comment.
// 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 https://www.cambridge.org/core/services/aop-cambridge-core/content/view/A2829FE674349AD9A836982A23AA74B7/S0952675705000552a.pdf/phonological_and_phonetic_aspects_of_whistled_languages.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); });