lunes, 27 de abril de 2020

PDF con IText (4). Enviar los correos

0. Introducción

Ahora toca enviar los correos a cada persona. Para ello recorreremos la carpeta donde se han guardado los "zips" y los enviaremos.

1. Crear una sesión de correo


Tenemos que darle el password, que en este caso como es un proceso automatizado lo guardamos en un fichero de propiedades


Session session = Session.getDefaultInstance(
 mailProps,
 new javax.mail.Authenticator() {
  protected PasswordAuthentication getPasswordAuthentication() {
  return new PasswordAuthentication(fromAddress, mailProps.getProperty("mail.smtp.password"));
 }
});


2. Función de enviar un correo

Para enviar un correo no hace falta:

  1. Sesión
  2. Direccion origen del envio
  3. Direcion/es de destino
  4. Mensaje
  5. Adjuntos



 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
public static void sendSSLMessage(Session session, String mensaje, String destinos[], String fileURL, String fromAddress) throws MessagingException {
  
 //Mensaje
 Message msg = new MimeMessage(session);
  
 //Direccion procedencia
 InternetAddress addressFrom = new InternetAddress(fromAddress);
 msg.setFrom(addressFrom);
  
 //Direcciones destino
 InternetAddress[] addressTo = new InternetAddress[destinos.length];
 for (int i = 0; i < destinos.length; i++) {
  addressTo[i] = new InternetAddress(destinos[i]);
 }
 msg.setRecipients(Message.RecipientType.TO, addressTo);

 // Setting the Subject and Content Type
 msg.setSubject(mensaje);
 msg.setContent(msg, "text/html");
  
 // Adding text to the message
 BodyPart messageBodyPart = new MimeBodyPart();
 messageBodyPart.setText(mensaje);
  
 //Adjuntos
 Multipart multipart = new MimeMultipart();
 multipart.addBodyPart(messageBodyPart);
 messageBodyPart = new MimeBodyPart();  
    
 DataSource source = new FileDataSource(fileURL);
 messageBodyPart.setDataHandler(new DataHandler(source));
 String fName="ADJUNTO"+fileURL.substring(0,fileURL.lastIndexOf("."));
 messageBodyPart.setFileName(fName);
 multipart.addBodyPart(messageBodyPart);
 msg.setContent(multipart);
  
 // Damos una fecha para evitar el mensaje 552 Mail with no Date header not accepted here
 msg.setSentDate(new java.util.Date());
  
 // Sending the message
 Transport.send(msg);
  
}


3. Función que recorre una carpeta y envia los ficheros zip


Solo inidcaremos como recorremos la carpeta en busca de ficheros "zip"


for (File fileEntry:folder.listFiles( (dir, name) -> name.endsWith(".zip")) ) {

Happy coding!

PDF con IText (3). Comprimir ficheros con ZIP y protegidos con contraseña

0. Introducción
Aunque esto no tiene nada que ver con IText o PDF, se ha metido en esta entrada ya que se quiere agrupar en un fichero ZIP protegido los ficheros de la misma persona.

En este caso, se recorren todos los ficheros de una carpeta y los que sean de la misma persona (que tengan la misma parte inicial en el nombre del fichero, serán comprimidos en el mismo ZIP.

La contraseña del ZIP está incuida en el nombre del fichero a modo de ejemplo, cosa que no es recomendable.

Se ha utilizado la libreria Zip4j

El proceso consiste en crear un Zip con contraseña y luego se le añade una lista de ficheros.

Los imports son:


import net.lingala.zip4j.ZipFile;
import net.lingala.zip4j.exception.ZipException;
import net.lingala.zip4j.model.ZipParameters;
import net.lingala.zip4j.model.enums.EncryptionMethod;

Este es el código que los comprime. para ello busca los ficheros de la misma persona y los mete en el zip


protected static void zipSimilarFiles() throws ZipException {
 ZipParameters zipParameters = new ZipParameters();
 zipParameters.setEncryptFiles(true);
 zipParameters.setEncryptionMethod(EncryptionMethod.AES);
  
 String lastName="KK";
 List<File>commonFiles=new ArrayList<>();
 for(File file: getOrderedFileByName(new File(DEST_FOLDER))){
  String fileName=file.getName();
  if (fileName.substring(0, fileName.lastIndexOf("_")).equalsIgnoreCase(lastName)) {
   commonFiles.add(file);
  } else {
   if (lastName.length()>5) {
    //1. Create last zip file
    String[] parts=lastName.split("_");
    String password=parts[3];
    ZipFile zipFile = new ZipFile(DEST_FOLDER+"/"+lastName+".zip", password.toCharArray());
    zipFile.createSplitZipFile(commonFiles, zipParameters, true, 10485760); // using Split size of 10MB in this example
   } 
   //2.Update lastName
   lastName=fileName.substring(0, fileName.lastIndexOf("_"));
   commonFiles=new ArrayList<>();
   commonFiles.add(file);
  }
 }
 if (lastName.length()>5) {
  String[] parts=lastName.split("_");
  String password=parts[3];
  ZipFile zipFile = new ZipFile(SOURCE_FOLDER+"/"+lastName+".zip", password.toCharArray());
  zipFile.createSplitZipFile(commonFiles, zipParameters, true, 10485760); // using Split size of 10MB in this example
 }
  
}


PDF con IText (2). Identicar un documento por el contenido

0. Introducción

Ahora ya tenemos los documentos separados, pero queremos saber a quien corresponde el documento.
Se dispone de una lista de personas obtenida de nuestra BD, y sabemos que los datos de las personas se imprimen siempre en la misma posición.

1. Sistemas de coordenadas de IText 7

Veamos:

  1. El sistema de coordenadas de IText 7 és bastante enfarragoso, pues en principio, el origen de coordenadas X,Y se encuentra en la parte inferior izquierda del papel (documento). 
  2. Se pueden modificar los orígines de coordenadas, pero no vamos a entrar en detalle.
  3. Las coordenadas van por unidades propias, de manera que en un A4, tenemos un rectángulo que va desde el punto (0,0) hasta el (595, 894)
Por tanto, se puede establecer una relación aproximada entre unidades de medida (mm) y unidades del IText.

2. Localizar la zona donde se imprimen los datos (por ejempo el DNI)

Con IText, se puede obtener el texto que hay en un rectángulo.
Según lo visto en el punto anterior se mide donde empieza y acaba dicho campo sobre el papel y se define un rectángulo.... Pero esto es una simple aproximación, pues parece ser que en PDF los textos son cuadros de texto bastante mas grandes de lo que se muestra en el papel impreso. Por tanto, dento del rectángulo que hemos medido del DNI, se cuela bastante más texto del deseado, por tanto hay que hacer varias pruebas de rectángulos por la zona donde hemos medido hasta que pillemos un rectángulo que nos devuelva poco texto y que al menos contenga el texto que queremos (el DNI).

En mi caso el rectángulo que me da esto es:


public static final Rectangle rect = new Rectangle(20, 725, 25, 750); 


3. Obtener el texto de dicho rectángulo

Para ello tengo esta función que me devuelve el texto de la zona afectada por el rectángulo en un PDF


protected static String getText(final String fileName, Rectangle rect) throws IOException {
   
     PdfDocument pdfDoc = new PdfDocument(new PdfReader(fileName));
     TextRegionEventFilter regionFilter = new TextRegionEventFilter(rect);
     ITextExtractionStrategy strategy = new FilteredTextEventListener(new LocationTextExtractionStrategy(), regionFilter);
     
     PdfPage page=pdfDoc.getFirstPage();
        
     String rectangleText = PdfTextExtractor.getTextFromPage(page, strategy);
        return rectangleText;
     
 }

4. Renombrar los documentos

De entrada partimos de una carpeta donde están los documentos y un lista con los datos de las personas que afectan los documentos.
Hemos creado una función que no voy a mostrar que nos da el nuevo nombre del documento (en ese nuevo nombre podemos meter el nombre de la persona, dni etc para aclaranos)
Hay que tener en ecuenta que puede haber más de un documento por persona.
Al final nos devuelve los documentos con los nuevos nombres que nos dan una idea de que persona son.
Como se ve, hay unas variables globales que guardan la carpeta origen(SOURCE_FOLDER) y destino (DEST_FOLDER)
Se ha hecho uso de las libreria de Apache Commons io para copiar las librerias.


protected static void renombrar(List<Persona> personas) throws IOException {
 File outDir =new File(DEST_FOLDER);
 outDir.mkdirs();
  
 File folder = new File(SOURCE_FOLDER);
 int i=0;
 for (File fileEntry:folder.listFiles( (dir, name) -> name.endsWith(".pdf")) ) {
  String text=getText(fileEntry.getCanonicalPath(),rect);
   
  for(Persona persona :personas) {
   if (text.contains(persona.getNif().trim())) {
    String newName=newFileName(DEST_FOLDER, persona,++i);
    FileUtils.copyFile(fileEntry, new File(newName));     
    } 
    
   }
  }
 }






PDF con Itext (1). Dividiendo un PDF en grupos de 2 páginas.

0. Introducción


Una aplicación externa nos genera un solo PDF con información relativa a varios trabajadores, con las siguientes particularidades:
  1.  El documento específico de cada trabajador ocupa 2 páginas.
  2.  La estructura del documento es la misma, solo cambian los datos específicos para cada trabajador.
  3. Por tanto si hay 100 páginas, sabemos que corresponden a 50 trabajadores
Por tanto hay que fraccionar el documento de 100 páginas en 50 documentos de 2 páginas.


1. Solución propuesta

  1. Utilizaremos la librería Java IText 7 Community.
  2. Partimos de la localización inicial del documento en la constante RESOURCE.
  3. Le indicamos la ruta donde guardar los ficheros generados y el formato (que consistirá en una numeración de los documentos.
  4. Recorremos el documento a grupos de 2 páginas y creamos un nuevo documento mediante el comando copyPagesTo(pág. desde, pág hasta)
  5. Al final cerramos todos los documentos implicados.
  6. Se podría modificar el programa para que se parametrizara el número de páginas por documento. 


 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
package u.requests;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfReader;
import com.itextpdf.kernel.pdf.PdfWriter;



public class SplitPDF {

 public static final String DEST = "/home/ximo/PDF/DESTINO/splitDocument1_%s.pdf";
  
 public static final String RESOURCE = "/home/ximo/PDF/documentoGordo.pdf";
  
 public static void main(String[] args) throws IOException {
     File file = new File(DEST);
     file.getParentFile().mkdirs();
  
     manipulatePdf(RESOURCE, DEST); 
 }
         
 /**
 * Divide el origen en varios documentos de 2 hojas
 * @param origin
 * @param dest
 * @throws IOException
 */
     
 protected static void manipulatePdf(final String origin, final String dest) throws IOException {
     //1. documento original
    PdfDocument pdfDoc = new PdfDocument(new PdfReader(origin));
         
    //2. lista de documentos
     List<PdfDocument>lDocs=new ArrayList<>();
      
    //3. Creamos los documentos de 2 páginass
     int i=1;
     int nPages=pdfDoc.getNumberOfPages();
     while (i<nPages) {
        //Creamos el documento numerado
         PdfDocument toDocument = new PdfDocument(new PdfWriter(new File(String.format(dest, (i+1)/2))));
         pdfDoc.copyPagesTo(i, i+1, toDocument);
         lDocs.add(toDocument);
         i=i+2;
     }
     //4. Cerramos los documentos creados
     for (PdfDocument doc : lDocs) doc.close();
   
     //5. Cerramos el documento original
     pdfDoc.close();
    }
}

Happy coding!

miércoles, 15 de abril de 2020

Javascript aplicado (2). Aplicación de tutorial de música sin instalaciones ni http server!

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:
  1. "index.html" que es el fichero de partida que hace referencia a los scrips.
  2. "style.js" para dar estilo a la hoja.
  3. "script.js" que és fichero que contiene el código principal de la carga del programa y eventos.
  4. "general-utils" funcionalidades básicas como manejo de arrays y tiempos de espera
  5. "interval-utils.js" que contiene constantes relativas a los intervalos.
  6. "component-utils" funcionalidades para crear componentes mediante javascript
  7. "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">&times;</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);

}