lunes, 23 de marzo de 2015

Canon de Pachelbel

Pienso que el Canon de Pachelbel es una estupenda herramienta de aprendizaje para el piano y la música en general. Además de ser un tema precioso , tiene todos los ingredientes para acercarse a diversas cuestiones importantes de la música y nos va a facilitar entenderla desde un punto de vista armónico y hacer algunos ejercicios interesantes y adentrarse en la improvisación sin frustraciones (Eso creo yo).

El Canon fue compuesto allá por 1680 por un tal PachelBel . Yo lo conocí por la versión de George Winston. Aquí un vídeo de él, muy criticado por cierto , por sus errores y su forma de tocar. De todas formas, creo que merece la pena verlo:


Aunque el tema está originalmente en la tonalidad de Re-Mayor, nosotros vamos a practicar inicialmente con él  en la tonalidad de Do-Mayor por ser más sencillo, igual que hace George.

El Canon es, además, famoso por ser la base armónica de miles y miles de melodías modernas, por eso puede sonarnos familiar sin haberlo escuchado nunca. El siguiente sketch saca este tema a colación. Es una pena que esté en perfecto Inglés (aunque con subtítulos en castellano):


Podemos ver en primer lugar la progresión armónica, es decir los acordes que lleva. Se trata de una secuencia muy simple de un acorde por cada compas (4/4). Los acordes son:

            C , G, Am, Em, F, C, F, G

La forma típica de acompañar con la mano izquierda es hacer arpegios de negras. Es decir, tocar 4 notas del acorde por cada compás.

Para empezar de forma sencilla podemos tocar por ejemplo la siguientes secuencia de arpegios

    C-E-G-C    G-B-D-G    A-C-E-A

Es decir, para cada acorde tocamos la tónica, tercera, quinta y octava: I,III,V,VIII.

Pero esta es una de las millones de combinaciones de arpegios posibles que podemos utilizar. Otro muy típico es , generalizando:  I - V - VIII - X, por ejemplo en el acorde de C tocaríamos C,G,C,E (Los dos últimos de la siguiente octava).

Con esto ya nos da para practicar un montón.

Ahora, podemos ir añadiendo la mano derecha y vamos a hacerlo mediante un ejercicio de improvisación:


  • Al principio de cada compás tocaremos con la mano derecha cualquier nota que pertenezca al acorde que corresponde a ese compás, por ejemplo, en el primer compás podemos tocar un C, un E, ó un G, y así sucesivamente.
Podemos complicar el ejercicio anterior imponiéndonos algunas reglas, como por ejemplo hacer cuatro acordes subiendo y cuatro bajando, ó buscando siempre la nota más cercana, etc, etc.

Otro buen ejercicio es tocar el acorde completo que corresponda en cada compás con la mano derecha. Podemos comenzar haciendolo con la posición fundamental, es decir con la nota más baja como la que da nombre al acorde, por ejemplo, para el caso del acorde de C, podemos tocar las notas C,E,G.

El siguiente paso sería jugar con las inversiones de los acordes con la mano derecha. Podemos forzarnos a que los dedos que cambien en cada transición de acorde sean los mínimos. Por ejemplo para cambiar entre el primer y segundo compás desde el acorde de C al acorde de G, podemos tocar primero C,E,G y en el segundo compás tocar B,D,G, con lo que mantenemos el dedo en la nota G. Así podemos seguir buscando estas transiciones suaves entre acordes, que son muy bellas.

Volviendo a la improvisación con la mano derecha, otro ejercicio que podemos hacer es tocar 4 negras contiguas dentro de cada acorde comenzando por alguna nota del acorde y continuando procurando no dar grandes saltos.

Otro ejercicio , (este ya es un poco mecánico), sería (mientras mantenemos tocando la mano izquierda la secuencia de acordes arpegiados inicial) tocar con la mano derecha la escala de C hacia arriba y hacia abajo varias octavas respetando el paso del pulgar. No suena mal. Los acordes del Canon lo soportan todo (hasta la música del telediario).

Más ejercicios de improvisación con la mano derecha: Volvemos a tocar en la primera nota de cada compás una nota que pertenezca al acorde, pero cada dos compases, en vez de ello tocamos una nota que esté una segunda por debajo de la nota con duración de blanca (medio compás) para tocar luego otra blanca esta vez sí con una nota que pertenezca al acorde.

En definitiva, se trata de plantearnos juegos con ciertas reglas que nos permitan un grado de libertad, que favorezcan ver la música como algo creativo, no solo copiar una melodía, sino ir entendiendo cómo funciona desde dentro. De paso, vamos practicando la coordinación de manos y la técnica.

Ahora para terminar, veamos los 4 acordes que permiten cantar cualquier canción que exista o que no exista:


Ahora, como ejercicio, traslada estos acordes a la tonalidad de DO-Mayor, a ver si ves algún parecido con el Canon de Pachelbel.



domingo, 15 de marzo de 2015

Domos - El código fuente definitivo

Bueno,... , tras un mes utilizando ya el sistema domótico de encendido de la estufa, el código fuente ya está bastante estabilizado.
/*

*  Sensor de Temperatura que registra el valor en Internet llamando a la

*  aplicación con la URL

*      http://php-domos.rhcloud.com/insert.php?sensor=1236&valor=xx

*/

#include "DHT.h"

#include "SPI.h"

#include "Ethernet.h"

#include <EEPROM.h>



#include <avr/wdt.h>



/*  =============================================================================================

        CONFIGURACION PARTICULAR

    ============================================================================================= */

//#define DEBUG_ON

#ifdef  DEBUG_ON

#define debug(x)  Serial.println(x)

#else

#define debug(x)

#endif

const long PERIODO_ACTUADOR =       10L * 60L * 1000L;    // Cada cuanto pregunta por el estado del actuador.

const long PERIODO_TEMPERATURA =    20L * 60L * 1000L;    // Cada cuanto informa sobre la temperatura.

const long PERIODO_HUMEDAD    =     2L * 60L * 60L * 1000L;    // Cada cuanto informa sobre la humedad

const long PERIODO_RESET    =       4L * 60L * 60L * 1000L;    // Hace un reset cada 4 horas



#define SW_TEMPERATURA          1  // Indica si queremos leer temperatura y enviarla al servidor

#define USAR_RELE   // Indica si queremos que el actuador funcione con un Relé

#define USAR_IR     // Indica si queremos usar códigos IR para

const int PIN_DISPOSITIVO = 8;

const int PIN_ERROR = 7;

const int PIN_EN_EJECUCION = 6;

//const int PIN_RESET = 5;



const int ID_SENSOR_TEMPERATURA=nnnn;    // Temperatura interior de Lamuño

const int ID_SENSOR_HUMEDAD=nnnn;        // Sensor de la humedad interior de Lamuño

const int ID_ACTUADOR=nnnn;

#define PIN_TERMOMETRO 2    // EL PIN DEL SENSOR DE TEMPERATURA



const int REINTENTOS = 4;



//const long CODIGO_IR_OFF    = 0x807FC837;      // El código para encender el aparato a controlar

//const long CODIGO_IR_ON     = 0x807F58A7;      // El código para apagar el aparato a controlar

const long CODIGO_IR_ON   = 0x807F48B7;      // El código para encender VERDADERO

const long CODIGO_IR_OFF  = CODIGO_IR_ON;

#define PIN_IR       3



int ledEnEjecucion=0;    // Simplemente el indicador que parpadeará si se está ejecuando el bucle.



const int ESTADO_ON  = 1;

const int ESTADO_OFF = 0;

const int ESTADO_INDETERMINADO = -1;

int estadoDispositivo = ESTADO_OFF;

/* ============================================================================================= */



#ifdef USAR_IR

#include <IRremote.h>

IRsend irsend;   // Parece ser que no se puede definir el PIN sino que es siempre el 5.

#endif



long milisActuador= -PERIODO_ACTUADOR;

long milisTemperatura= -PERIODO_TEMPERATURA;

long milisReset=0;

long milisHumedad= -PERIODO_HUMEDAD;





char server[]= "xxxxx.rhcloud.com";

char query[]="GET /xxxxx.php?sensor=%d&valor=%d HTTP/1.1";

char actuador[]="GET /xxxxxx.php?id=%d HTTP/1.1";

char actuador_off[]="GET /xxx.php?actuador=%d&estado=OFF HTTP/1.1";

char host[]="Host: xxxxxx.rhcloud.com";





#define DHTTYPE DHT11



DHT dht(PIN_TERMOMETRO, DHTTYPE);



byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };

IPAddress ip(192,168,0,2);

EthernetClient cliente;



void setup() {

  wdt_disable();

  delay(50); // Segun dice la documentación http://www.freetronics.com.au/pages/usb-power-and-reset#.VN_itPmG_gc

 

  int isReset=checkReset();  // Hay que hacer esto lo antes posible para limpiar la EEPROM rapidamente.

  // HACK Para poder hacer reset

  //digitalWrite(PIN_RESET, HIGH);

  //pinMode(PIN_RESET, OUTPUT);

  pinMode(PIN_DISPOSITIVO, OUTPUT);

  pinMode(PIN_ERROR,OUTPUT);

  pinMode(PIN_EN_EJECUCION, OUTPUT);





  Serial.begin(9600);

  debug("Setup");



 

  // Parpareamos un segundo el PIN de error para mostrar que funciona.

  esperar(2000, 50, PIN_ERROR);  // Espera  a ver si se inicializa el modem...

  esperar(2000, 50, PIN_DISPOSITIVO);

  esperar(2000, 50, PIN_EN_EJECUCION);

 

  if(SW_TEMPERATURA) {

    dht.begin();

  }

 

  //   De momento el wdt no funciona. Aunque haga reset la placa Ethernet no acaba funcionando hata que haga reset

  // fisicamente en la placa.

  // Además, 8 segundos es poco tiempo para esperar ya que el método connect podría tardar más incluso.

  // wdt_enable(WDTO_8S);

 

  debug("Inicializando Ethernet.");

  //if(Ethernet.begin(mac)==0) {

  //  mostrarError("Error obteniendo la IP. La ponemos fija");

    Ethernet.begin(mac, ip);

  //}

  debug(Ethernet.localIP());

 

  wdt_reset();

  esperar(5000, 100);

 

  if(isReset) {

    estadoDispositivo=EEPROM.read(1);

    digitalWrite(PIN_DISPOSITIVO, estadoDispositivo);

  } else {

    int estadoRecibido=ESTADO_INDETERMINADO;

    for(int vez=0; vez<3 && estadoRecibido==ESTADO_INDETERMINADO; vez++) {

       estadoRecibido=consultarActuador(cliente,0);

       if(estadoRecibido==ESTADO_INDETERMINADO) {

         esperar(5000, 2000, PIN_ERROR);

       }

    }

    if(estadoRecibido==ESTADO_INDETERMINADO) {

      mostrarError("Imposible conocer el estado");

      for(;;); // NO puedo continuar si no conozco el estado.

    }

    estadoDispositivo=estadoRecibido;

  }

 

  wdt_reset();

}





// Devuelve cierto si el Setup se está ejecutando tras un Reset.

int checkReset() {

  int result=EEPROM.read(2);

  EEPROM.write(2, 0);  // Limpiamos

  return result==1? 1:0;  // Hacemos asi para garantixar 1 o cero.

}



// Marcamos para que se sepa al arrancar que viene de un reset.

void setReset() {

  EEPROM.write(2, 1);

}



void reset() {

  EEPROM.write(1, (byte)estadoDispositivo);  // Guardamos el estado del dispositivo para cargarlo en el siguiente setup;

  setReset();

  wdt_enable(WDTO_8S);  // Fuerzo un reset con el WatchDog a base de esperar unos segundos.

  esperar(20000, 100, PIN_ERROR);

  digitalWrite(PIN_ERROR, HIGH);    // Marcamos que hay reset por si no funcionara.

  // digitalWrite(PIN_RESET, LOW);    // Sistema de reset con cable al RESET (NO lo uso);

  for(;;); // Tras el Reset no puede haber nada.

}



void esperar(long total, long parpadeo, int pin) {

  int valor=0;

  for(long i=0; i<total; i+=parpadeo) {

    wdt_reset();

    delay(parpadeo);

    digitalWrite(pin, valor);

    valor=valor?0:1;

  }

  digitalWrite(pin, 0);

}



void esperar(long total, long parpadeo) {

  esperar(total, parpadeo, PIN_EN_EJECUCION);

}



void mostrarError(char *Mensaje) {

  debug(Mensaje);

  digitalWrite(PIN_ERROR, 1);

}



void limpiarError() {

  digitalWrite(PIN_ERROR, 0);

}



void enviarConsulta(EthernetClient &cliente, char *consulta) {

    cliente.println(consulta);

    cliente.println(host);

    cliente.println("Connection: close\r");

    cliente.println();

}



void enviarTemperatura(EthernetClient &cliente) {

    if(!SW_TEMPERATURA) {

        return;

    }

    wdt_reset();

    digitalWrite(PIN_EN_EJECUCION, 1);

    debug("Conectado para enviar temperatura");

    int t = (int)dht.readTemperature();

    if(t<=0) {

      esperar(5000, 300, PIN_ERROR); 

      digitalWrite(PIN_ERROR, 1);

      return;

    }

    if(conectar(cliente)!=1) {

      mostrarError("Error conectando a temperatura");

      return;

    }   

    enviarQuery(ID_SENSOR_TEMPERATURA, t);   

    milisTemperatura=millis();      

    wdt_reset();

}



void enviarHumedad(EthernetClient &cliente) {

    if(!SW_TEMPERATURA) {

        return;

    }

    wdt_reset();

    digitalWrite(PIN_EN_EJECUCION, 1);

    debug("Conectado para enviar humedad");

    int h = (int)dht.readHumidity();

    if(h<=0) {

      esperar(5000, 300, PIN_ERROR); 

      digitalWrite(PIN_ERROR, 1);

      return;

    }

    if(conectar(cliente)!=1) {

      mostrarError("Error conectando a humedad");

      return;

    }   

    enviarQuery(ID_SENSOR_HUMEDAD, h);   

    milisHumedad=millis();      

    wdt_reset();

}



void enviarQuery(int idSensor, int t) {

    char aux[256]; 

    sprintf(aux, query, idSensor, t);

    enviarConsulta(cliente,aux);

    esperar(3000,50);

    cliente.stop();

    cliente.flush();

    esperar(2000,100);

    digitalWrite(PIN_EN_EJECUCION, 0);

}



int conectar(EthernetClient &cliente) {

  int ret;

  for(int i=0; i<REINTENTOS; i++) {

    wdt_reset();

    ret=cliente.connect(server, 80);

    if(ret==1) {

        return ret;

    }

  }

  return ret;

}



void ponerActuadorOff(EthernetClient &cliente) {

 

   debug("Poniendo el actuador en OFF");

   int ret=conectar(cliente);

    if(ret !=  1) {

      char aux[256];

      sprintf(aux, "Error %d connect", ret);

      mostrarError(aux);

      return;

    } 

    char aux[256];

    sprintf(aux, actuador_off, ID_ACTUADOR);

    enviarConsulta(cliente, aux);

    esperar(2000,100);  

    cliente.stop(); // Me da lo mismo lo que responda...

    cliente.flush();

    esperar(2000,100);

}



int consultarActuador(EthernetClient &cliente, int actuar) {

    debug("Consultar actuador");

    int ret=conectar(cliente);

    if(ret !=  1) {

      char aux[256];

      sprintf(aux, "Error %d connect", ret);

      mostrarError(aux);

      return ESTADO_INDETERMINADO;

    }

   

    char aux[256];

    //debug("Conectado para leer actuador");

    sprintf(aux, actuador, ID_ACTUADOR);

    enviarConsulta(cliente, aux);

   

    esperar(100,50); // Esperamos un poco a ver si hay respuesta.

    int estadoRecibido=leerRespuesta(cliente);

    cliente.stop();

    cliente.flush();

    if(actuar) {

      if(estadoRecibido==ESTADO_ON) {

        encenderDispositivo();

      } else if(estadoRecibido==ESTADO_OFF) {

        apagarDispositivo();

      } else {

        mostrarError("Estado recibido indeterminado");

      }

    }  else {

      if(estadoRecibido==ESTADO_INDETERMINADO) {

        mostrarError("Estado recibido indeterminado");

      }

    }

    return estadoRecibido;

}



void apagarDispositivo() {

  #ifdef USAR_RELE

    digitalWrite(PIN_DISPOSITIVO, 0);

  #endif

  #ifdef USAR_IR

    if(estadoDispositivo==ESTADO_ON) {

      irsend.sendNEC(CODIGO_IR_OFF, 32);

      estadoDispositivo=ESTADO_OFF;

    }

  #endif

}



void encenderDispositivo() {

  #ifdef USAR_RELE

    digitalWrite(PIN_DISPOSITIVO, 1);

  #endif

  #ifdef USAR_IR

    if(estadoDispositivo==ESTADO_OFF) {

      irsend.sendNEC(CODIGO_IR_ON, 32);

      estadoDispositivo=ESTADO_ON;

    }

  #endif

}



void leerLinea(EthernetClient &client, char aux[], int max) {

  int i=0;

  while(client.available() && i<max) {

    char c=client.read();

    if(c=='\n') {

      break;

    } else {

      aux[i++]=c;

    }

  }

  aux[i]=0;

}



int leerRespuesta(EthernetClient &cliente) {

    int retorno=ESTADO_INDETERMINADO;

    int intentos=0;

    // Esperando a datos disponibles, un maximo de 5 segundos

    for(intentos=0; intentos<500 && cliente.connected(); intentos++) {

      if(cliente.available()) {

        break;

      }

      delay(50);

    }

    // Leemos los datos disponibles...

    for(intentos=0; intentos<100 && cliente.connected(); intentos++) {

        if(cliente.available()) {

          char linea[256];

          leerLinea(cliente, linea,100);

          if(!strcmp(linea,"ON")) {

            retorno=ESTADO_ON;

            break;

          }

          if(!strcmp(linea,"OFF")) {

            retorno=ESTADO_OFF;

            break;

          }

        } 

        delay(50);

    }

    return retorno;

}



void procesarActuador(EthernetClient &cliente) {

    int estadoRecibido=consultarActuador(cliente, 1);

    if(estadoRecibido!=ESTADO_INDETERMINADO) {

       limpiarError();

    }

}



void loop() {

  wdt_reset();

  long time=millis();

  if(time > PERIODO_RESET) {

    // Antes de hacer reset hay que ver si el actuador quiere que encienda o apague.

    procesarActuador(cliente);

    reset();

  }

  if(time > milisTemperatura + PERIODO_TEMPERATURA) {

    enviarTemperatura(cliente);

    milisTemperatura=time;

  }

  if(time > milisHumedad + PERIODO_HUMEDAD) {

    enviarHumedad(cliente);

    milisHumedad=time;

  }

  if(time > milisActuador + PERIODO_ACTUADOR) {

    procesarActuador(cliente);

    milisActuador=time;

  }

  delay(1000);

  ledEnEjecucion = ledEnEjecucion? 0:1;

  digitalWrite(PIN_EN_EJECUCION, ledEnEjecucion);

}