BOITIER MUSICAL

Introduction

Ce projet a pour but de tester le pilotage par WiFi de la carte Wemos D1 mini, celle-ci étant reliée à un bipeur piézoélectrique. On lui fera jouer différents airs de musique célèbres.

Câblage

L'image ci-dessous représente le schéma de câblage, dessiné avec le logiciel Fritzing :

La mise en place des composants et quelques soudures produisent l'assemblage suivant :

Programme

La carte se programme avec l'EDI Arduino en y ajoutant le module adéquat. Le code est conçu pour que le boîtier musical se comporte en point d'accès Wifi. L'utilisateur se connecte à ce point d'accès, nommé BOITIER_MUSICAL sans mot de passe. Puis il entre 192.168.0.222 dans la barre d'adresse d'un navigateur. Il obtient alors cette interface Web :

Les notes se nomment selon la désignation latine, suivie du numéro de l'octave. Conformément à la norme internationale ISO 16, le la3 vibre à la fréquence de 440 hertz. La durée de la note se définit par un nombre, 2 pour un demi-temps, 4 pour un quart de temps, etc. Un nombre négatif symbolise une note pointée, c'est à dire allongée de moitié.

#include <ESP8266WiFi.h>
const char* ssid = "BOITIER_MUSICAL";
//const char* password = "987654321";
IPAddress local_IP(192,168,0,222);
IPAddress gateway(192,168,4,9);
IPAddress subnet(255,255,255,0);
WiFiServer server(80);

int bipeur = 2;  // connecteur D4 sur la carte

// notes de musique et leurs fréquences en Hz
#define do2  65
#define doD2 69
#define re2  73
#define reD2 78
#define mi2  82
#define fa2  87
#define faD2 93
#define sol2 98
#define solD2 104
#define la2  110
#define laD2 117
#define si2  123
#define do3  131
#define doD3 139
#define re3  147
#define reD3 156
#define mi3  165
#define fa3  175
#define faD3 185
#define sol3 196
#define solD3 208
#define la3  220
#define laD3 233
#define si3  247
#define do4  262
#define doD4 277
#define re4  294
#define reD4 311
#define mi4  330
#define fa4  349
#define faD4 370
#define sol4 392
#define solD4 415
#define la4  440
#define laD4 466
#define si4  494
#define do5  523
#define doD5 554
#define re5  587
#define reD5 622
#define mi5  659
#define fa5  698
#define faD5 740
#define sol5 784
#define solD5 831
#define la5  880
#define laD5 932
#define si5  988
#define do6  1047
#define doD6 1109
#define re6  1175
#define reD6 1245
#define mi6  1319
#define fa6  1397
#define faD6 1480
#define sol6 1568
#define solD6 1661
#define la6  1760
#define laD6 1865
#define si6  1976
#define silence 0

// notes et leurs durées, 2 pour un demi-temps, 4 un quart de temps, etc.
// un nombre négatif signifie une note pointée (allongée de moitié)
int air1[] = { // Jeux interdits
  si4,4, si4,4, si4,4,  si4,4, la4,4, sol4,4,  sol4,4, faD4,4, mi4,4,
  mi4,4, sol4,4, si4,4,  mi5,4, mi5,4, mi5,4,  mi5,4, re5,4, do5,4,
  do5,4, si4,4, la4,4,  la4,4, si4,4, do5,4,  si4,4, do5,4, si4,4,
  reD5,4, do5,4, si4,4,  si4,4, la4,4, sol4,4,  sol4,4, faD4,4, mi4,4,
  faD4,4, faD4,4, faD4,4,  faD4,4, sol4,4, faD4,4,  mi4,4, mi4,4, mi4,4,
  mi4,-2, silence,-2,
  solD4,4, solD4,4, solD4,4,  solD4,4, faD4,4, mi4,4,  mi4,4, reD4,4, reD4,4,
  reD4,4, re4,4, reD4,4,  doD5,4, doD5,4, doD5,4,  doD5,4, reD5,4, doD5,4,
  doD5,4, si4,4, si4,4,  si4,4, doD5,4, reD5,4,  mi5,4, mi5,4, mi5,4,
  mi5,4, reD5,4, re5,4,  doD5,4, doD5,4, doD5,4,  doD5,4, si4,4, la4,4,
  solD4,4, solD4,4, solD4,4,  solD4,4, la4,4, faD4,4,  mi4,4, mi4,4, mi4,4,
  mi4,-2, silence,-2,
};
int air2[] = { // Boléro 
  do5,2, si4,16, do5,16, re5,16, do5,16, si4,16, la4,16, do5,8, do5,16, la4,16, do5,2,
  si4,16, do5,16, la4,16, sol4,16, mi4,16, fa4,16, sol4,2,
  fa4,16, mi4,16, re4,16, mi4,16, fa4,16, sol4,16, la4,16, sol4,2,
  la4,16, si4,16, la4,16, sol4,16, fa4,16, mi4,16, re4,16, mi4,16, re4,16, do4,4,
  do4,16, re4,16, mi4,8, fa4,8, re4,4, sol4,2,
  re5,2, do5,16, si4,16, la4,16, si4,16, do5,16, re5,16, do5,16, si4,4,
  do5,16, si4,16, la4,16, do5,16, si4,16, la4,16, fa4,8, fa4,16, fa4,16, fa4,8,
  la4,8, do5,16, la4,16, si4,16, sol4,16, fa4,8, fa4,16, fa4,16, fa4,8,
  la4,8, si4,16, sol4,16, la4,16, fa4,16, re4,8, re4,16, do4,16, re4,2,
  re4,16, re4,16, re4,8, fa4,8, la4,16, fa4,16, sol4,16, mi4,16, re4,8, re4,16, do4,16, re4,2,
  re4,16, re4,16, re4,8, mi4,16, fa4,16, sol4,2, fa4,16, mi4,16, re4,16, do4,1
};
int air3[] = { // Carmen - Habanera
  //re3,4, la3,16, fa3,8, la3,8, re3,4, la3,16, fa3,8, la3,8, re3,4, la3,16, fa3,8, la3,8, re3,4,
  re5,8, doD5,8, do5,16, do5,8, do5,16, si4,8, laD4,8, la4,4, la4,16,
  solD4,8, sol4,8, fa4,8, mi4,16, fa4,16, sol4,8, fa4,8, mi4,4,
  re5,8, doD5,8, do5,16, do5,8, do5,16, si4,8, laD4,8, la4,4, la4,16,
  sol4,8, fa4,8, mi4,8, re4,16, mi4,16, fa4,8, mi4,8, re4,4,
  //re5,8, doD5,8, do5,16, do5,8, do5,16, si4,8, laD4,8, la4,4, la4,16,
  //solD4,8, sol4,8, fa4,8, mi4,16, fa4,16, sol4,8, fa4,8, mi4,4,
  //re5,8, doD5,8, do5,16, do5,8, do5,16, si4,8, laD4,8, la4,4, la4,16,
  //sol4,8, fa4,8, mi4,8, re4,16, mi4,16, fa4,8, mi4,8, re4,4,
  la4,16, re4,8, mi4,8, faD4,4, la4,16, faD4,8, mi4,8, re4,4, mi4,16, faD4,8, sol4,8,
  la4,16, la4,16, la4,16, la4,16, si4,8, la4,8, sol4,4,
  si4,16, mi4,8, faD4,8, sol4,4, si4,16, sol4,8, faD4,8, mi4,4, faD4,16, sol4,8, la4,8,
  si4,16, si4,16, si4,16, si4,16, doD5,8, si4,8, la4,4,
  la4,16, re4,8, mi4,8, faD4,4, la4,16, faD4,8, mi4,8, re4,4, mi4,16, faD4,8, sol4,8,
  la4,16, la4,16, la4,16, la4,16, re5,8, doD5,8, sol4,4,
  si4,16, mi4,8, faD4,8, sol4,4, si4,16, sol4,8, faD4,8, mi4,4, faD4,16, sol4,8, la4,8,
  doD5,16, si4,16, solD4,16, la4,16, faD5,8, mi5,8, re5,2,  la4,8, re5,8, silence,2
};
int air4[] = { // Clair de Lune
  sol4,8, sol5,2, mi5,2, la4,8, re5,8, mi5,8, re5,-2,
  sol4,8, do5,8, re5,8, do5,-4, mi5,-4, do5,8, fa4,8, si4,8, do5,8, si4,-2,
  fa4,8, la4,8, si4,8, la4,8, re5,8, la4,8, sol4,8, la4,8, sol4,8, re4,8,
  fa4,8, sol4,8, fa4,-4, mi4,-4, do4,8, mi4,8, fa4,8, mi4,8, la4,8, mi4,8,
  re4,8, mi4,8, re4,8, sol3,8, do4,8, re4,8, do4,-4, si3,-4, sol3,8, do3,8, silence,-8,
  sol3,8, sol4,-4, mi4,-4, silence,8, re4,8, mi4,8, re4,-2, silence,8,
  do4,8, re4,8, sol4,-4, mi4,-4, silence,8, re4,8, mi4,8, re4,-4, do4,-4, silence,8,
  do4,8, re4,8, la4,-4, mi4,-4, silence,8, fa4,8, sol4,8, do5,-4, la4,-4, silence,8,
  la4,8, si4,8, mi5,-4, sol4,-4, silence,8,
  do3,16, mi3,16, sol3,16, do4,16, mi4,16, sol4,16,
  mi3,16, sol3,16, si3,16, mi4,16, sol4,16, si4,16,
  sol3,16, laD3,16, reD4,16, sol4,16, laD4,16, reD5,16, sol5,-4, do3,-4, silence,-4
};
int air5[] = { // Marche funèbre
  la3,4, la3,-8,la3,16, la3,4, fa3,4,  la3,4, la3,-8,la3,16, la3,4, fa3,4,
  la3,4, la3,-8,la3,16, la3,4, do4,-8,si3,16,  si3,-8,la3,16, la3,-8,la3,16, la3,4, fa3,4,
  do4,4, do4,-8,do4,16, do4,4,  mi4,-8,re4,16,  re4,-8,do4,16, do4,-8,do4,16, do4,4, la3,4,
  la4,-8,sol4,16, fa4,-8,mi4,16, mi4,4, do4,4,  la4,-8,sol4,16, fa4,-8,mi4,16, mi4,4, do4,4,
  la3,4, la3,-8,la3,16, la3,4, do4,-8,si3,16,  si3,-8,la3,16, la3,-8,la3,16, la3,4, fa3,4, //
  la4,-8,sol4,16, fa4,-8,mi4,16, mi4,4, do4,4,  la4,-8,sol4,16, fa4,-8,mi4,16, mi4,4, do4,4,
  la3,4, la3,-8,la3,16, la3,4, do4,-8,si3,16,  si3,-8,la3,16, la3,-8,la3,16, la3,4, fa3,4, //
  mi4,-8,fa4,16, sol4,-8,la4,16, si4,-8,do5,16, mi5,4,  mi4,2, re4,-4, silence,16, fa5,16,
  mi5,4, mi5,-8,faD5,16, solD5,-8,la5,16, si5,-8,do6,16,  do6,2, si5,4, silence,4,
  sol4,4, sol4,4, fa4,4, silence,4,  mi4,4, mi4,4, do4,2
};

// une note se code sur 2 entiers, sizeof() retourne le nombre d'octets
int nb_notes1 = sizeof(air1)/sizeof(air1[0])/2;
int nb_notes2 = sizeof(air2)/sizeof(air2[0])/2;
int nb_notes3 = sizeof(air3)/sizeof(air3[0])/2;
int nb_notes4 = sizeof(air4)/sizeof(air4[0])/2;
int nb_notes5 = sizeof(air5)/sizeof(air5[0])/2;

// durée d'une note ronde
int temps1 = 2400;
int temps2 = 3800;
int temps3 = 3600;
int temps4 = 3800;
int temps5 = 4000;

int diviseur = 0;
int duree_note = 0;
int freq_note = 0;;
int mode_air = 0; // 0 pour stop, 1 pour air 1, 2 pour air 2...
int note = 0; // numéro de la note jouée

void jouer() {
  tone(bipeur, freq_note, duree_note*0.9);
  delay(duree_note); // la fonction tone() n'est pas bloquante...
  note++;
}

// page au format html
String html="<!DOCTYPE html>"
"<html>"
"<head>"
"<meta charset=\"utf-8\">"
"<style>"
"body { background-color: #003; font-family: sans-serif; }"
"a {"
"display: block;"
"text-align: center;"
"background-color: #cfc;"
"padding: 10px;"
"margin: 20px;"
"color: #009;"
"font-size: 3em;"
"text-decoration: none;"
"border: 4px solid #888;"
"border-radius: 25px 25px 50px 50px;"
"}"
"a:hover { opacity: 0.8; }"
".stop { background-color: #fcc; border-radius: 50px 50px 25px 25px; }"
"</style>"
"</head>"
"<body>"
"<a href=\"/AIR=1\">Jeux interdits</a>"
"<a href=\"/AIR=2\">Boléro</a>"
"<a href=\"/AIR=3\">Carmen - Habanera</a>"
"<a href=\"/AIR=4\">Clair de lune</a>"
"<a href=\"/AIR=5\">Marche funèbre</a>"
"<a href=\"/AIR=0\" class=\"stop\">Stop</a>"
"</body>"
"</html>";

void setup() {
  pinMode(bipeur, OUTPUT);
    
  Serial.begin(9600);
  delay(500);

  Serial.println("");
  Serial.print("Configuration en point d'accès Wifi ... ");
  Serial.println(WiFi.softAPConfig(local_IP, gateway, subnet) ? "Prêt" : "Erreur!");
  Serial.print("Démarrage du point d'accès Wifi ... ");
//Serial.println(WiFi.softAP(ssid,password) ? "Prêt" : "Erreur!");
  Serial.println(WiFi.softAP(ssid) ? "Prêt" : "Erreur!");
  Serial.print("Adresse IP du point d'accès Wifi : ");
  Serial.println(WiFi.softAPIP());

  // Démarrer le serveur
  server.begin();
  Serial.println("Serveur Web démarré");
  Serial.println("");  
}

void loop() {
  // Vérifier si le client est connecté
  WiFiClient client = server.available();
  if (!client) {
    if (mode_air == 1) {
      diviseur = air1[2*note+1];
      duree_note = temps1/abs(diviseur);
      if (diviseur < 0) { duree_note *= 1.5; }
      freq_note = air1[2*note];
      jouer();
      if (note >= nb_notes1) { note = 0; }
    }
    if (mode_air == 2) {
      diviseur = air2[2*note+1];
      duree_note = temps2/abs(diviseur);
      if (diviseur < 0) { duree_note *= 1.5; }
      freq_note = air2[2*note];
      jouer();
      if (note >= nb_notes2) { note = 0; }
    }
    if (mode_air == 3) {
      diviseur = air3[2*note+1];
      duree_note = temps3/abs(diviseur);
      if (diviseur < 0) { duree_note *= 1.5; }
      freq_note = air3[2*note];
      jouer();
      if (note >= nb_notes3) { note = 0; }
    }
    if (mode_air == 4) {
      diviseur = air4[2*note+1];
      duree_note = temps4/abs(diviseur);
      if (diviseur < 0) { duree_note *= 1.5; }
      freq_note = air4[2*note];
      jouer();
      if (note >= nb_notes4) { note = 0; }
    }
    if (mode_air == 5) {
      diviseur = air5[2*note+1];
      duree_note = temps5/abs(diviseur);
      if (diviseur < 0) { duree_note *= 1.5; }
      freq_note = air5[2*note];
      jouer();
      if (note >= nb_notes5) { note = 0; }
    }
    return;
  }

  // Attendre jusqu'à ce que le client envoie des données
  Serial.println("Nouveau client");
  while (!client.available()) { delay(100); }

  // Lire la première ligne de la requête
  String request = client.readStringUntil('\r');
  Serial.println(request);
  client.flush();

  // Vérifier la requête pour la musique numéro 1
  if (request.indexOf("AIR=0") != -1) {
    mode_air = 0;
    note = 0;
    Serial.println("Musique stoppée");
  }
  if (request.indexOf("AIR=1") != -1) {
    mode_air = 1;
    note = 0;
    Serial.println("Jeux interdits");
  }
  if (request.indexOf("AIR=2") != -1) {
    mode_air = 2;
    note = 0;
    Serial.println("Boléro");
  }
  if (request.indexOf("AIR=3") != -1) {
    mode_air = 3;
    note = 0;
    Serial.println("Carmen - Habanera");
  }
  if (request.indexOf("AIR=4") != -1) {
    mode_air = 4;
    note = 0;
    Serial.println("Clair de Lune");
  }
  if (request.indexOf("AIR=5") != -1) {
    mode_air = 5;
    note = 0;
    Serial.println("Marche funèbre");
  }

  // Retourner la réponse
  client.println("HTTP/1.1 200 OK");
  client.println("Content-Type: text/html");
  client.println(""); //  A ne pas oublier!
  client.println(html);

  delay(1000);
  Serial.println("Client déconnecté");
  Serial.println("");
}

Pour arrêter la musique à n'importe quel instant, plusieurs solutions existent. Dans ce programme, une note d'un air est jouée, puis, si un client ne se connecte pas, la note suivante est jouée, et ainsi de suite. A la fin du morceau, le programme revient à la première note. Si un client se connecte, le programme joue l'air demandé en commençant par la première note.

Boîtier

Le boîtier comporte deux pièces imprimées en 3D :

Les deux pièces sont modélisées avec le logiciel FreeCAD, version 0.20.2 :

Les deux modèles volumiques sont ensuite exportées au format STL, compatible avec les imprimantes 3D :

Tests

Les tests réalisés avec un ordinateur portable ou une tablette donnent des résultats satisfaisants.

Par contre, les essais effectués avec des téléphones portables se concluent diversement :

Conclusion

La démonstration avec le téléphone portable d'un spectateur pris au hasard conduit tantôt à la réussite, tantôt à l'échec. Ces dysfonctionnements restent à éclaircir et à corriger. Au cours de la réalisation de cet objet, des idées d'améliorations apparaissent :