0. Introducción
Todo lo que se anuncia tiene sus limitaciones. Y este post las tiene... y muchas.En principio, sin web server, no sé como acceder a guardar ficheros, o accede a una BD con simple html y javascript....
En fin esta simple aplicación es para entrenarte en reconocer intervalos musicales. Se han copiado muchas cosas de otras fuentes. Pero al final ha valido la pena, pues se copian los ficheros en una carpeta y se abre el fichero "index.html" en el navegador y a funcionar. No requiere instalaciones previas. Y se pueden copiar las dependecias de "cdn" a dicha carpeta y no haría falta conexión a internet.
Debo dar las gracias a Philip Shim tal como hice en el anterior post.
He perdido mucho tiempo con el dichoso Tone.js, pues mi conocimiento de javascript es bastante pobre, y el tema de la "asincronicidad", si no caes al principio te vuelves loco.
Queremos hacer un entrenador de reconocimiento de intervalos, para ello se genera un sonido de intervalo al azar que hay que adivinar. Se dispone de un selector de los intervalos a probar, y unas ayudas de repetición del audio del intervalo o refrescar la escucha de intervalos ejemplos y se aporta también el piano de nuestro querido Philip Shim.
Como la cosa se hacia larga, se ha partido el código en varios ficheros:
- "index.html" que es el fichero de partida que hace referencia a los scrips.
- "style.js" para dar estilo a la hoja.
- "script.js" que és fichero que contiene el código principal de la carga del programa y eventos.
- "general-utils" funcionalidades básicas como manejo de arrays y tiempos de espera
- "interval-utils.js" que contiene constantes relativas a los intervalos.
- "component-utils" funcionalidades para crear componentes mediante javascript
- "music-utils-edu" con llamnadas a "tone.js" para tocar notas e intervalos. Hace alguna referencia no usada a Web Audio (sobre la que se base tone.js)
Veamos la pantalla que se quiera implementar
Comencemos con los ficheros:
1. index.html
Solo destacar que :- las líneas de botones y componentes que reaccionan con el usuario se han hecho con la etiqueta de listas no ordenadas "<ul>" (de de Philip Shim) a la que se le añaden los controles mediante javascript en tiempo de carga del formulario. La línea 14 contiene la actuación del evento "onload", al que le decimos que rellene el formulario y seleccionamos las ocvtavas entre 3 y 5.
- Se ha añadido una parte de ayuda que es modal.A partir de la línea 58
- Hay un botón para marcar/desmarcar los intervalos seccionados línea 20
- En la líne 22 se cargarán los intrvalos a seleccionar el test
- en la 24 para ver si son ascdendentes o descendentes y la selección de duración de las notas, volumen, y el rango de octavas.
- Las líneas 37 y 28 contienen botones para oir los intervalos típicos para refrescar la memoria.
- La línea 32 aqarece el piano de Philip Shim.
- De las líneas 34 a 41 aparecen los marcadares de intentos, aciertos y fallos
- La línea 43 tiene la selección de respuesta y botones de aceptar, nuevo intento y repetir intervalo.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 | <!DOCTYPE html> <html lang="en" > <head> <!--<meta charset="UTF-8">--> <meta charset="UTF-8"> <!-- <meta charset="UTF-8" http-equiv="Content-Security-Policy" content="default-src *; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline' 'unsafe-eval' http://www.google.com"> --> <title>Aprende a reconocer intervalos</title> <link rel="stylesheet" href="./style.css"> </head> <body onload="fillAll(3,5);"> <p class="centered"> <!-- Trigger/Open The Modal --> <button class="simple-btn simple-btn-edu" id="myHelp">Ayuda!</button> <label class="titol titol-cabecera" id="tttselec">Selecciona intervalos del test.</label> <button class="simple-btn" onclick="marcarTodo()">(Marca/Desmarca) Todo</button> </p> <ul id ="selection"></ul> <ul id ="selectionasc"></ul> <h2> Escucha los intervalos si no te acuerdas. </h2> <ul id ="buttonsasc"></ul> <ul id ="buttonsdes"></ul> <h2> Un piano.. siempre va bien!</h2> <ul id ="pianoedu"></ul> <p> <label class="titol" id="tselec">Selecciona bien la respuesta!:</label> <label class="titol titol-marcador" id="marcadores">Marcadores:</label> <input type="text" class="marc" id="intentos" disabled="true" value="0" > <input type="text" class="marc marc-aciertos" id="aciertos" disabled="true" value="0" > <input type="text" class="marc marc-fallos" id="fallos" disabled="true" value="0" > </p> <ul id ="respuesta"></ul><br> <input type="text" class="message" id="message" value="¿Empezamos?" > <!--------------------------------------------------------------------------------------------> <!-- The Modal help --> <div id="myModal" class="modal"> <!-- Modal content --> <div class="modal-content"> <div class="modal-header"> <span class="close">×</span> <h2>Ayuda</h2> </div> <div class="modal-body"> <p class="mybold" >1. Selecciona:</p> <p>1.1 Los <em>intervalos</em> que quieras practicar (Desde unísono a Octava justa)</p> <p>1.2 Si los quieres <em>acendentes</em> y/o <em>descendentes</em></p> <p>1.3 La figura que indica la <em>duración</em> de las notas (redonda, blanca,...)</p> <p>1.4 El <em>volumen</em>. Si no lo oyes, dale mas volumen!</p> <p>1.5 El <em>rango de octavas</em>. (Se recomienda valores intermedios 3 a 5)</p> <br> <p class="mybold" >2. Puedes escuchar ejemplos de intervalos tantas veces como quieras:</p> <p>2.1 Haz click sobre cualquier boton por ejemplo <em>"2M"</em> y escucha un intervalo.</p> <br> <p class="mybold" >3. Puede utilizar un piano virtual:</p> <p>3.1 Puedes hacer click sobre cualquier tecla del piano de la pantalla y sonará.</p> <p>3.2 Puedes tocar las notas de las 2 primeras octavas con el <em>teclado del ordenador</em>.</p> <p>3.3 Por ejemplo la tecla <em>"q"</em> activa el Do-3</p> <br> <p class="mybold" >4. Comprueba tus avances con los marcadores de intentos, aciertos y fallos</p> <br> <p class="mybold" >5. Para empezar a practicar:</p> <p>5.1 Haz click sobre el botón <em>"Nuevo Intento"</em></p> <p>5.2 Oirás un intervalo que tienes que adivinar</p> <p>5.3 Selecciona un intervalo i si es ascendente/descendente (abajo a la izquierda)</p> <p>5.4 Si lo tiene claro, dale al botón <em>"OK"</em></p> <p>5.5 Con el botón <em>"Repetir Audio"</em>, puedes volver a oir el intervalo</p> </div> <div class="modal-footer"> <h3>Suerte y disfruta!</h3> </div> </div> </div> <!-- <script src='https://unpkg.com/tone@latest/build/Tone.js'></script> --> <script src="./Tone.js"></script> <!--<script src="./Tone.js.map"></script>--> <script src="./interval-utils.js"></script> <script src="./general-utils.js"></script> <script src="./component-utils.js"></script> <!--<script src="./music-utils.js"></script>--> <script src="./music-utils-edu.js"></script> <script src="./modal-help.js"></script> <script src="./script.js"></script> </body> </html> |
Al final se muestran las referencias a ficheros javascrip (líneas 91 a 101).
Veamos ahora el fichero de esilo:
2. style.css
No voy a explicar mucho, solo indicar que se utilizan muchas clases para reaprovechar las clases básicas y añadir las clases que marcan diferencias. Por eso hay componentes que tienen varias clases CSS.La primera línea comentada es para importar algun tipo de fuente y pero para no tener problemas CORS, nio se pueden importar ficheros del CDN:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 | /* @import url("https://fonts.googleapis.com/css?family=Roboto:400,700&display=swap"); */ h2 { color: DarkBlue; text-align: center } .flex { display: flex; } ul .main { position: relative; width: 70px; height: 30px; border: 1px solid black; border-right: none; background: #fffff0; border-radius: 5px; box-shadow: 0px 3px 5px #666; cursor: pointer; display: flex; justify-content: center; align-items: flex-end; padding-bottom: 10px; font-weight: bold; } ul .main-check { border:none; padding:10px; font-size: 125%; /*box-shadow:none;*/ background:LightCyan; } ul .main-resp { width: auto; padding-left: 30px; height: 60px; font-size: 150%; /*margin-right:30px;*/ background:LightCyan; border:none; } ul .main-duration { /*width:350px;*/ width:50%; padding-left: 30px; } ul .main-volume { width:300px; padding-left: 10px; } ul .main-rango { width:400px; padding-left: 10px; } ul .main-key { width: 60px; height: 120px; /*180px;*/ font-size:100%; } ul .main-key:last-child { border-right: 1px solid black; } ul .main-blackkey { position: absolute; top: -1px; left: 37.5px; width: 45px; height: 80px; /*120px;*/ background: black; z-index: 999; color: white; } ul .bt { font-weight: bold; font-size: 100%; border-radius: 5px; box-shadow: 0px 3px 5px #666; } ul .chk { font-size: 100%; margin-left: 10px; margin-right: 10px; color: blue; } ul .sel { font-weight: bold; font-size: 100%; padding:5px; margin-left:5px; margin-right:5px; color:blue; border-radius: 5px; } ul .sel-resp { color:HotPink; } input[type=checkbox]:hover { box-shadow:0px 0px 10px #1300ff; } select:hover { box-shadow:0px 0px 10px #1300ff; } button:active:hover:not([disabled]) { box-shadow:0px 0px 10px #1300ff; } .simple-btn { border-radius: 5px; font-size: 90%; color: blue; box-shadow: 0px 3px 5px #666; } .simple-btn-edu { font-size: 125%; } .centered{ text-align: center; } /*=====================================================*/ body { font-family: 'Roboto', sans-serif; } ul { list-style: none; display: flex; } ul .active { box-shadow: 0px 1px 3px #666; } .titol { font-weight: bold; font-size:150%; color: DarkBlue; margin-left: 40px; } .titol-marcador { margin-left: 60px; } .titol-cabecera { margin-left: none; } .marc { font-weight: bold; margin-left:15px; font-size:150%; color: black; border: 1px solid black; border-radius: 5px; width: 50px; text-align: right; padding: 5px; box-shadow: 0px 3px 5px #666; } .marc-aciertos { color: blue; } .marc-fallos { color: red; } .btn{ font-weight: bold; font-size:95%; border-radius: 5px; height: 50px; padding: 5px; margin-left:5px; margin-right:5px; justify-content: center; box-shadow: 0px 3px 5px #666; } .btn-rep { color: sandybrown; } .btn-ok { font-size:125%; color: blue; } .bt-next { } button:disabled, button[disabled]{ font-style: italic; color: DarkGray; opacity: 0.3; } .message { font-weight: bold; font-size: 125%; margin-left: 40px; margin-right: 10px; color: blue; width:95%; height:30px; border-radius: 5px; box-shadow: 0px 3px 5px #666; } /************************************************************************************ * HELP MODAL FORM ************************************************************************************/ .modal { display: none; /* Hidden by default */ position: fixed; /* Stay in place */ z-index: 1; /* Sit on top */ padding-top: 100px; /* Location of the box */ left: 0; top: 0; width: 100%; /* Full width */ height: 100%; /* Full height */ overflow: auto; /* Enable scroll if needed */ background-color: rgb(0,0,0); /* Fallback color */ background-color: rgba(0,0,0,0.4); /* Black w/ opacity */ } /* Modal Content */ .modal-content { position: relative; background-color: #fefefe; margin: auto; padding: 0; border: 1px solid #888; width: 80%; height: 70%; box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19); -webkit-animation-name: animatetop; -webkit-animation-duration: 0.4s; animation-name: animatetop; animation-duration: 0.4s; overflow: scroll; } .mybold { font-weight: bold; } /* Add Animation */ @-webkit-keyframes animatetop { from {top:-300px; opacity:0} to {top:0; opacity:1} } @keyframes animatetop { from {top:-300px; opacity:0} to {top:0; opacity:1} } /* The Close Button */ .close { color: white; float: right; font-size: 28px; font-weight: bold; } .close:hover, .close:focus { color: #000; text-decoration: none; cursor: pointer; } .modal-header { padding: 2px 16px; background-color: #5cb85c; color: white; } .modal-body {padding: 2px 16px;} .modal-footer { padding: 2px 16px; background-color: #5cb85c; color: white; } |
Veamos el fichero responsable de organizar todo:
3. script.js
A partir de la línea 190 nos encargamos de manejar el piano de Philip Shim.La línea 166 aparece la función fillAll() que es la que se encarga de crear los controles y asignar eventos en la carga del formulario (onload event). en ella se llaman a funciones que aparecen desde las primeras líneas y construyen el formulario (checkboxes, selectores ...) y normalmente se agregan aun bloque <li> de un <ul>. Es decirt cada <ul> representa una línea de controles gemelos y a dicha linea le añadimos elementos <li> que cargan con los controles.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 | const checkboxes=[ ]; const checkboxesAsc=[ ]; const radios=[ ]; /** * Selectioin of intervals * */ function fillSelection() { var aText=getNthArrayElement(intervals,"-",0); for (var i=0; i<=12; i++) { checkboxes.push(createCheckBox("ch"+i ,"chk", "checked")); addLitoUlSimple("selection", "sel"+i, "main main-check", aText[i], checkboxes[i]); } } /** * Selectioin of intervals ascending/descending * */ function fillSelectionAsc() { for (var i=0; i<=1; i++) { checkboxesAsc.push(createCheckBox("ch"+i ,"chk", "checked")); addLitoUlSimple("selectionasc", "selasc"+i, "main main-check", ascen[i], checkboxesAsc[i]); } } /** * Selection of duration of notes * */ function fillDuration() { var selDuration=createSelect("selDuration",duration,"sel") selDuration.selectedIndex = 2; addLitoUlSimple("selectionasc", "dur", "main main-check main-duration", "Duracion notas:", selDuration); } /** * Selectioin of volume of notes (0,100) */ function fillVolume() { var selVolume=createSelect("selVolume",volume,"sel") addLitoUlSimple("selectionasc", "dur", "main main-check main-volume ", "Volumen:", selVolume); selVolume.addEventListener('change', e=> changeVolume()); selVolume.selectedIndex = 7; } function fillOctaves() { var selOctaveFrom=createSelect("seloctavefrom",octaves,"sel") selOctaveFrom.addEventListener('change', e=> getOctaves()); selOctaveFrom.selectedIndex = 3; var li=addLitoUlSimple("selectionasc", "dur", "main main-check main-rango ", "Octavas rango:", selOctaveFrom); var selOctaveTo=createSelect("seloctaveto",octaves,"sel") selOctaveTo.addEventListener('change', e=> getOctaves()); selOctaveTo.selectedIndex = 5; li.appendChild(selOctaveTo); } /* function fillVolumeTxt() { var txtVolume=createTextBox("txtVolume","sel") txtVolume.value= 0; addLitoUlSimple("selectionasc", "dur", "main main-check main-volume ", "", txtVolume); } */ /** * Fill buttons to hear intervals examples * */ function fillButtons() { var aText=getNthArrayElement(intervals,"-",0); for (var i=0; i<=12; i++) { var button = createButton("btasc-"+i ,aText[i]+ascChar, "bt"); //button.addEventListener("click", e=> playDemoInterval(j, 1)); //No guarda la i button.addEventListener("click", e=> playDemoInterval(e.target.id.split('-')[1], 0)); var li=addLitoUlSimple("buttonsasc", "lbtasc"+i, "main main-check", "", button); } for (var i=0; i<=12; i++) { var button = createButton("btdes-"+i ,aText[i]+desChar, "bt"); button.addEventListener("click", e=> playDemoInterval(+e.target.id.split('-')[1], 1)); var li=addLitoUlSimple("buttonsdes", "lbtdes"+i, "main main-check", "", button); } } /** * Fill Piano from octaveDesde to octaveHasta * */ function fillPianoEdu(octaveDesde,octaveHasta) { for (var j=octaveDesde; j<=octaveHasta; j++) { fillPianoEduOctave(j); } } /** * Add a piano with one octave * */ function fillPianoEduOctave(octave) { var aInt1=getNthArrayElement(intervals,"-",1); var aInt2=getNthArrayElement(intervals,"-",2); var aInt3=getNthArrayElement(intervals,"-",3); var aInt4=getNthArrayElement(intervals,"-",4); for (var i=0; i<=11; i++) { var note=aInt2[i]+octave; var ifSharp=note.includes("#"); var noteE=aInt1[i]+octave; if (!ifSharp) { var nextNote=aInt2[i+1]+octave; var ifSharpNext=nextNote.includes("#"); var tecla=note; if (octave==3) tecla= aInt3[i]; if (octave==4) tecla= aInt4[i]; var div=null; if (ifSharpNext) { var nextNoteE=aInt1[i+1]+octave; var tecla1=nextNote; if (octave==3) tecla1= aInt3[i+1]; if (octave==4) tecla1= aInt4[i+1]; div=createDiv("divPi"+i, tecla1, " main main-blackkey", "data-note",nextNoteE); } var li = addLitoUl("pianoedu", "ed"+i, "main main-key", tecla, div,"data-note",noteE) if (note.includes("Do")) li.style.backgroundColor ="LightYellow"; } } } /** * Get the selectors of intervals and asc/desc */ function fillRespuesta() { var aInt0=getNthArrayElement(intervals,"-",0); var selInt=createSelect("selInt",aInt0,"sel sel-resp"); var selAsc=createSelect("selAsc",ascen,"sel sel-resp"); var li = addLitoUlSimple("respuesta", "resp", "main main-resp", "Intervalo:", selInt); li.appendChild(selAsc); // Fill buttons var btOK=createButton("btok","OK", "btn btn-ok", true); btOK.addEventListener("click", e=> checkAnswer()); li.appendChild(btOK); var btNext=createButton("btnext","Nuevo Intento", "btn btn-next", false); btNext.addEventListener("click", e=> nextPlay()); li.appendChild(btNext); var btRep=createButton("btrep","Repetir Audio", "btn btn-rep", true); btRep.addEventListener("click", e=> repeatAudition()); li.appendChild(btRep); } /** * Fills everything */ function fillAll(octaveDesde, octaveHasta) { fillSelection(); fillSelectionAsc(); fillDuration(); fillVolume(); fillOctaves(); //fillVolumeTxt() fillButtons(); fillPianoEdu(octaveDesde,octaveHasta); fillRespuesta(); //fillRespButtons(); changeVolume(); getOctaves(); } function marcarTodo() { var check= (checkboxes[0].checked) ? false : true; for (var i=0; i<checkboxes.length; i++){ checkboxes[i].checked=check; } } // Piano activate keys const pianoEdu = document.getElementById("pianoedu"); pianoEdu.addEventListener("mousedown", e => { //changeVolume(); synth.triggerAttack(e.target.dataset.note); }); pianoEdu.addEventListener("mouseup", e => { synth.triggerRelease(); }); // Play in the keyboard document.addEventListener("keydown", e => { var k=e.key; //changeVolume(); console.log("addEventListener.keydown.tecla=" + k + " volumejn="+synth.volume.value ); for (var i=0; i<12; i++) { if (intervals[i].split('-')[3].includes(k)) { return synth.triggerAttack(intervals[i].split('-')[1]+"3"); } else if (intervals[i].split('-')[4].includes(k)) { return synth.triggerAttack(intervals[i].split('-')[1]+"4"); } } return }) ; document.addEventListener("keyup", e => { var k=e.key; for (var i=0; i<12; i++) { if (intervals[i].split('-')[3].includes(k) || intervals[i].split('-')[4].includes(k)) { synth.triggerRelease(); } } }); |
4. general-utils.js
Se ha buscado como obtener los símbolos de las flechitas ascendentes o descendentes y se han marcado como constante (ascChar y desChar)En javascript y en concreto en tone.js tenmos un problema (o virtud según como se mire), que si queremos tocar 2 notas, si no esperamos a que la primera nota termine, se pone en marcha a tocar la segunda nota también.
En chrome se puede solucionar implementando la función wait que se dedica a esperar tanto tiempo como la duración de la nota que le damos (línea 44). Pero en Mozilla no funciona. Para ello hemos tenido que recurrir a la función window.setTimeout() que ejecuta asíncronamente una función al cabo de cierto tiempo.
También tenemos una función de números aleatorios para obtener los intervalos aleatorios a preguntar.
Para los arrays de varias dimensiones, se ha practicado una función que devuelve una parte del array.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | //Flechas de subida y bajada para marcar intervalos ascendentes o descendentes const ascChar ='\u2191'; const desChar ='\u2193'; /** * Get the nth array element of * An array with elements separated with a separator * * array = arrString * separator= separator * * examples: * * arrTest=["1-a-A","2-b,B","3-c-C","4-d-D"] * * getNthArrayElement(arrTest,"-",0) = ["1","2","3","4"] * getNthArrayElement(arrTest,"-",1) = ["a","b","c","d"] * */ function getNthArrayElement(arrString,separator,n) { var a=[ ]; for (var i=0; i<arrString.length; i++) { a1=arrString[i]; b=a1.split('-'); c=(b.length>n ? b[n] : ''); a.push(c); } return a; } /** * Get a random number between min and max inclusive */ function getRandomInt(min, max) { min = Math.ceil(min); max = Math.floor(max); return Math.floor(Math.random() * (max - min + 1)) + min; } /** * Espera mseg */ function wait(ms){ var start = new Date().getTime(); var end = start; while(end < start + ms) { end = new Date().getTime(); } } |
5. interval-utils.js
Aquí tenemos constantes de canciones para asociar a intervalos
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | const ascIntervalMusics= [ "0.La Tarara", //0 "2m.Tiburon", //1 "2M.Cumpleaños Feliz,Fray Geroni", //2 "3m.Canción de cuna de Brahms", //3 "3M.Ya vienen los reyes magos, For he's a Jolly Good Fellow,", //4 "4J.We wish you a merry christmas,El padrino, Marcha nupcial (ja s'han casat)", //5 "4A.Maria.LosSimpsons", //6 "5J.Star Wars, Twinkle little star, Así hablo Zaratusta", //7 "6m.Manha, Mocita la del clavel", //8 "6M.La traviata", //9 "7m.The winner takes it all, Mi jaca", //10 "7M.Superman" , //11 "8J.Somewhere over the reainbow, Singing in the rain" //12 ]; const desIntervalMusics= [ "0.La Tarara", //0 "2m.Tiburon. Septiminio(erase una vez)", //1 "2M.Va pensiero Nabuco, Yesterday, Si yo fuera rico", //2 "3m.Puente rio Kwai, Hey Hude don't let me down", //3 "3M.Rellotge, Bethoven 5ª sinfonia", //4 "4J.Himno españa", //5 "4A.", //6 "5J.Flinstones", //7solutions[0].equalsIgnoreCase(""+ "6m.Love story", //8 "6M.Nobody knows the trouble", //9 "7m.Un americano en Paris (tii,ta,ta)", //10 "7M.Cole Porter I love you " , //11 "8J Dartacan(eran uno dos y tres)" //12 ]; |
6. component-utils
Hay utilidades para:
- asignar múltiples clases CSS a un objeto (línea 5)
- crear varios tipos de componentes html (select, checkbox, textbox, button, div, li)
- asignar algunas propiedades (disable, value)
- otras (incrementar contadores en textbox)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 | /** * Add classes separated by space */ function addClassEdu(myComp, myClasses) { arrCls=myClasses.trim().split(/\s+/); for (var i=0; i<arrCls.length; i++) myComp.classList.add(arrCls[i]); } /** * Create a select element with: * id= id_ * css class = cssClass * options = the array of string "arrText" */ function createSelect(id_,arrText,cssClass) { var myComp=document.createElement("select"); myComp.id=id_; //myComp.classList.add(cssClass); addClassEdu(myComp, cssClass); for (var i=0; i<arrText.length; i++) { var opt=document.createElement("option"); opt.text=arrText[i]; myComp.add(opt); } return myComp; } /** * Create a checkbox element with: * id= id_ * css class = cssClass */ function createCheckBox(id_,cssClass,sChecked) { var myComp=document.createElement("input"); myComp.type="checkbox"; myComp.name=id_; myComp.id=id_; myComp.checked=sChecked; //myComp.classList.add(cssClass); addClassEdu(myComp, cssClass); return myComp; } /** * Create a textbox element with: * id= id_ * css class = cssClass */ function createTextBox(id_,cssClass) { var myComp=document.createElement("input"); myComp.type="text"; myComp.name=id_; myComp.id=id_; addClassEdu(myComp, cssClass); return myComp; } /** * Create a button element with: * id= id_ * css class = cssClass */ function createButton(id_,text, cssClass, disabled) { var myComp=document.createElement("button"); myComp.type="button"; myComp.id=id_; myComp.disabled=disabled; myComp.appendChild(document.createTextNode(text)); //myComp.classList.add(cssClass); addClassEdu(myComp, cssClass); return myComp; } /** * Create a div element with: * id= id_ * css class = cssClass * attr: attribute (data-note) * attVal: value of the attribute (C4) */ function createDiv(id_,text, cssClass, attr, attVal ) { var myComp=document.createElement("div"); myComp.id=id_; myComp.appendChild(document.createTextNode(text)); //myComp.classList.add(cssClass); addClassEdu(myComp, cssClass); if (attr.length>0) myComp.setAttribute(attr, attVal); return myComp; } /** * Add a li elelement to an unordered list ul * arguments: * ulId: id of the ul * liId: new id of the li * liCssClass: css class of the li * liText: text for the li * liChild: child component to add to the li */ function addLitoUlSimple(ulId, liId, cssClass, text, liChild) { var ul = document.getElementById(ulId); var li = document.createElement("li"); //li.classList.add(cssClass); addClassEdu(li, cssClass); li.setAttribute("id", liId); if (text.length>0) li.appendChild(document.createTextNode(text)); //li.appendChild(document.createTextNode(text)); if (liChild!=null)li.appendChild(liChild); li.appendChild(liChild); ul.appendChild(li); return li; } /** * Add a li elelement to an unordered list ul * arguments: * ulId: id of the ul * liId: new id of the li * liCssClass: css class of the li * liText: text for the li * liChild: child component to add to the li * attr: attribute (data-note) * attVal: value of the attribute (C4) */ function addLitoUl(ulId, liId, cssClass, text, liChild, attr, attVal) { var ul = document.getElementById(ulId); var li = document.createElement("li"); //li.classList.add(cssClass); addClassEdu(li, cssClass); li.setAttribute("id", liId); if (text.length>0) li.appendChild(document.createTextNode(text)); if (liChild!=null)li.appendChild(liChild); li.setAttribute(attr, attVal); ul.appendChild(li); return li; } /** increment counter * */ function disableComponent(idComponent, disabled){ var myComp= document.getElementById(idComponent); myComp.disabled=disabled; } /** * increment a textBox that stores a counter */ function incrementCounter(idMarcador){ var txtMarcador= document.getElementById(idMarcador); txtMarcador.value++; } /** * Fill textbox with message * */ function fillTextBox(id, value, color) { var myComp= document.getElementById(id); myComp.value=value; myComp.style.color = color; } |
7. music-utils
En general se hace:- Coger las referencias a elementos de otros javascripts en concreto a Tone.Synth() y Tone.Synth.toMaster()
- Definimos los inervalos, notas en nomenclatura inglesa y castellana, y el mapeo del teclado al piano.
- Definimos otras constantes como la duración de notas en base a figuras (blanca, negra, corchea..) volumen, octavas desde hasta--
- Conversión del volumen de sonido a escala logarítmica (simil adecibelios)
- Tocar intervalos.
- Recogida de valores de los controles (duración, volumen, rango de octavas, respuestas..)
- Lógica de negocio del juego (nuevas jugada, verificar resultado, tocar intervalos de ejemplo)
En fin espero que os haya gustado, aunque un poco largo.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 | /******************************************************************************************************************* /** * @see: https://odino.org/emit-a-beeping-sound-with-javascript/ * @see: https://stackoverflow.com/questions/39200994/how-to-play-a-specific-frequency-with-javascript * @see: https://pages.mtu.edu/~suits/notefreqs.html * * @see: https://www.javascripture.com/OscillatorNode * */ //const audioContext=new AudioContext() ;// browsers limit the number of concurrent audio contexts, so you better re-use'em //var audioContext = new (window.AudioContext || window.webkitAudioContext)(); // Tone JS Synthetizer const synth = new Tone.Synth(); // synth.oscillator.type = "sine"; synth.toMaster(); const intervals = [ "Un-C-Do-q-z", //0 "2m-C#-Do#-2-s", //1 "2M-D-Re-w-x", //2 "3m-D#-Re#-3-d", //3 "3M-E-Mi-e-c", //4 "4J-F-Fa-r-v", //5 "4A-F#-Fa#-5-g", //6 "5J-G-Sol-t-b", //7 "6m-G#-Sol#-6-h", //8 "6M-A-La-y-n", //9 "7m-A#-La#-7-j", //10 "7M-B-Si-u-m", //11 "8J-C-Do" //12 ]; const ascen=["Asc","Desc"] const duration=["Redonda","Blanca","Negra","Corchea","Semicorchea"]; //const volume=[100, 70, 40, 10, 7, 4, 1, 0.7, 0.4, 0.1, 0.07, 0.04, 0.01, 0.007, 0.004, 0.001]; const volume=[20, 15, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2 ]; const fqLa4=440; // Frecuencia La4 const octaves = [0,1,2,3,4,5,6,7,8,9,10]; var octaveFrom =2; var octaveTo =5; // Variable that stores the last interval to play var aa={}; /** * Obtenemos la frecuencia de una nota en nomenclatura inglesa * Web audio solo trabaja en frecuencia * nota: "A3", "C4"..... */ function getFrequency(note) { var notes=getNthArrayElement(intervals,"-",1) ; //Get array of music notes var notat=note.trim(); var octave=notat.substr(notat.length-1,1); var nota1=notat.substring(0,notat.length-1); var found=false; var i=-1; while (! found && i<12){ if (notes[++i].localeCompare(nota1)==0) found=true; } var freq=fqLa4*(2**((i-9)/12)); freq=freq*(2**(octave-4)); return freq; } //==========================TONEJS ================================= /** * Plays a note in Tone.js * note1: "A4", "B3", "C#3" ... * note2: "A4", "B3", "C#3" ... * duration (ms) * noecho: stop playing, avoiding residual vibration */ function playInterval(note1, note2, myDuration, noecho) { synth.triggerAttackRelease(note1, myDuration/1000); window.setTimeout(playSecondTone, myDuration, note2, myDuration); if (noecho)window.setTimeout(removeEcho, myDuration*2); } /** Plays the second note of the Interval { * note2 : note to play * duration (ms) */ function playSecondTone(note, myDuration) { synth.triggerAttackRelease(note, myDuration/1000); } /** * Play a note of small duration without any volumen * and removes remaining vibrations */ function removeEcho() { synth.volume.value = "-Infinity"; changeVolume(); } /** * Convert volume in WebAudio to Tonejs * * WebAudio equals to Tone * ======= ======== * 100 100 * 50 25 * 25 0 * 0 "-Infinite" */ function convertVolumeWAudiotoTone(volume) { if (volume==0) return "-Infinity"; var a = 100*Math.log(volume/10)/Math.log(10); //console.log ("convertVolumeWAudiotoTone.volumen.js="+a + " volumen="+ volume); return a; } /** * Get selected duration from form in miliseconds */ function getDurationms() { var select=document.getElementById("selDuration"); //var opc = select.options[select.selectedIndex].value; var opc = select.selectedIndex; return 4/(2**opc)*1000 ; } /** * Get selected volume in % */ function getVolume() { var select=document.getElementById("selVolume"); return select.options[select.selectedIndex].value; } /** * Get Octaves */ function getOctaves() { var selectFrom=document.getElementById("seloctavefrom"); octaveFrom= selectFrom.options[selectFrom.selectedIndex].value; var selectTo=document.getElementById("seloctaveto"); octaveTo= selectTo.options[selectTo.selectedIndex].value; } /** * Change Volum = */ function changeVolume() { synth.volume.value = convertVolumeWAudiotoTone(getVolume()); //console.log("changeVolume.vol="+getVolume()); } /** * Get the selected duration in nth of a whole note (redonda) * 1n , 2n, 3n, 4n, 8n, */ function getDurationNth() { var select=document.getElementById("selDuration"); //var opc = select.options[select.selectedIndex].value; var opc = select.selectedIndex; return ""+(2**opc)+"n" ; } /** * Fill the variable "aa" with the info of the interval to play: * * note1 * note2 * asc: 0/1 for ascending/descending */ function getNotesInterval() { var arrInts=[ ]; var arrAsc= [ ]; for (var i=0; i<13; i++) if (checkboxes[i].checked) arrInts.push(i); for (var i=0; i<2; i++) if (checkboxesAsc[i].checked) arrAsc.push(i); var interval=getRandomInt(0, arrInts.length-1); //Posion relativa interval=arrInts[interval]; //Posicion absoluta var asc=getRandomInt(0, arrAsc.length-1); asc=arrAsc[asc]; var arrNot1=getRandomNote(); var arrNot2=getInterval2ndNote(arrNot1[0],arrNot1[1], interval); aa=getNotesIntervalFromNotePos(arrNot1[0], arrNot1[1],arrNot2[0], arrNot2[1], interval, asc); console.log("getNotesInterval.aa="+aa); } /** * Grts tje second note of an interval * */ function getInterval2ndNote(nota1, octave1, interval){ var a=[ ]; var octave2=octave1; var nota2=+interval+nota1; if (nota2>11) { nota2=+nota2-12; octave2=+octave1+1; } a.push(+nota2); a.push(+octave2); return a; } /** * get notes in English notation like "A4", "B#3" .. * params: * nota1: int position of first note (0..11) * octave1: int octave of first note (0..6) * nota2: int position of second note (0..11) * octave2: int octave of second note (0..6) * interval: Not used but needed to fill aa structure */ function getNotesIntervalFromNotePos(nota1, octave1,nota2, octave2, interval, asc) { var notes=getNthArrayElement(intervals,"-",1) ; //Get array of music notes var bb={}; bb.nota1=notes[nota1]+octave1; bb.nota2=notes[nota2]+octave2; bb.interval=interval; bb.asc=asc; return bb; } /** * Get a random note * return an array of integer: first = note number, second=octave * */ function getRandomNote() { var a=[ ]; a.push(getRandomInt(0, 11)); //Nota); a.push(getRandomInt(octaveFrom, octaveTo)); // octaves return a; } /** * Next play */ function nextPlay() { //Enable/disable Buttons disableComponent("btok" ,false); disableComponent("btnext",true); disableComponent("btrep" ,false); getNotesInterval(); // get new interval to play into variable "aa" repeatAudition(); // Play interval incrementCounter("intentos"); //Increment attempts "intentos" } /** * Play an interval * */ function playIntervalByValue(bb){ if (bb.asc==0) playInterval(bb.nota1, bb.nota2, getDurationms(), true); else playInterval(bb.nota2, bb.nota1, getDurationms(), true); console.log("playIntervalByValue.nota1="+bb.nota1 + " nota2="+bb.nota2 + " interval="+bb.interval +" asc="+bb.asc); } /** * Repeat audition */ function repeatAudition() { playIntervalByValue(aa); } /** * Check Answer */ function checkAnswer() { //Enable/disable Buttons disableComponent("btok" ,true); disableComponent("btnext",false); disableComponent("btrep" ,false); var aText=getNthArrayElement(intervals,"-",0); var selAns=document.getElementById("selInt"); var intUsr=selAns.selectedIndex; var selAnsAsc=document.getElementById("selAsc"); var ascUsr=selAnsAsc.selectedIndex; //Acertamos console.log("checkAnswer.aa.interval="+aa.interval + " intUsr=" + intUsr); if (intUsr==aa.interval && (ascUsr==aa.asc || intUsr == 0) ) { incrementCounter("aciertos"); fillTextBox("message", "¡Correcto! ", "blue"); //Fallamos } else { incrementCounter("fallos"); fillTextBox("message", "¡Noo! La respuesta correcta es: "+ aText[aa.interval] + "-"+ ascen[aa.asc], "red"); } } /** * Function that plays an interval whose first note is C4 * It is triggered by buttons */ function playDemoInterval(interval, asc) { nota1=0; //C4 octave1=4; var arrNot2=getInterval2ndNote(nota1, octave1, interval); var nota2=arrNot2[0]; //Position nota var octave2=arrNot2[1]; //octave console.log("playDemoInterval.1.nota1:" + nota1 + " nota2=" + nota2); var bb=getNotesIntervalFromNotePos(nota1, octave1,nota2, octave2, interval, asc); console.log("playDemoInterval.2.bb="+ bb); var myMessage= (asc==0) ? ascIntervalMusics[interval] : desIntervalMusics[interval]; console.log("playDemoInterval.3.interval:" + interval + " asc="+ asc +" mensaje="+ myMessage + " ascInterv:" + ascIntervalMusics[interval]); fillTextBox("message", myMessage, "deepskyblue"); playIntervalByValue(bb); } |
No hay comentarios :
Publicar un comentario