Musique avec Sonic Pi

« Sonic Pi » est un environnement de programmation basé sur le langage Ruby. Il a été créé comme un outil d’apprentissage à la fois de la musique et de la programmation. On doit son développement à Sam Aaron dans le cadre d’une collaboration entre l’Université de Cambridge (Computer Laboratory) et la « Raspberry Pi Foundation » .


http://sonic-pi.net/

L’environnement sert à programmer et exécuter des musiques, mais il peut être également servir d’instrument, puisque le code peut être modifié pendant l’exécution.

Pour tester Sonic Pi , je propose  une mise en œuvre des propriétés de musique aléatoire de Sonic Pi sur une grille classique.
L’ « instrument » sonic pi est basé sur un nombre d’  exécutants numériques : les threads qui jouent les rôles de musiciens.


Le code est composé de 5 threads. Le premier d’entre eux synchronise les autres.
La grille est rejouée à l’infini. Le thread principal incrémente un compteur à chaque exécution complète de la grille .
Les voix (mélodie et tempo) sont exécutées de manière aléatoire (plus ou moins …).

# Claude Guéganno - 2019
use_debug false

# grille
L = (ring chord(:C3, :major), chord(:G3, :major),  chord(:A3, :minor), 
          chord(:E3, :major), chord(:F3, :major),chord(:C3, :major), 
          chord(:F3, :major), chord(:G3, :major))
G =  (ring scale(:C3, :major), scale(:G3, :major),  scale(:A3, :minor), 
           scale(:E3, :major), scale(:F3, :major),scale(:C3, :major), 
           scale(:F3, :major), scale(:G3, :major))
N = 8 # nombre d'accords dans la grille

S = (ring "zawa", "prophet", "supersaw", "tb303", "pretty_bell")

i = 0 # accord courant ( de 0 à N-1 )
ni = 0 # nombre de boucles complètes effectuées
syntheCourant = choose(S)

# tempo
tempo  = 1.0

define :arpege do |accord, n, synth|
  use_synth synth
  n.times do
    12.times do
      play choose(accord), release: 0.3, cutoff: rrand(60, 120), amp: 0.5
      sleep tempo/8
    end
  end
end


define :melodie do |accord, n|
  n.times do
    T = [tempo/2, 3*tempo/4, tempo/4]
    T = shuffle T
    j = 0
    3.times do
      play choose(accord), release: 0.7, cutoff: rrand(60, 90), amp: 2, pitch: 12
      sleep T[j]
      j = j+1
    end
  end
end


define :melodie_2 do |accord, n|
  use_synth syntheCourant
  n.times do
    T2 = [3*tempo/4, tempo/2, tempo/4]
    T2 = shuffle T2
    j2 = 0
    3.times do
      play choose(accord), release: 0.5, cutoff: rrand(60, 100), amp: 2, pitch: 12
      sleep T2[j2]
      j2 = j2+1
    end
  end
end


define :melodie_gamme do |gamme, n|
  use_synth syntheCourant
  d = tempo/8
  sleep 4*d
  n.times do
    play_pattern_timed gamme, d, release: 1.0 , pitch: 12
  end
end


in_thread(name:  :boucle) do
  rapide = 0
  loop do
    with_fx :reverb do
      arpege L[i], 1, S[i]
      i = i+1
      if i%N == 0 then
        ni = ni + 1
        if ni%3 == 0 || rapide==1 then
          syntheCourant = choose(S)
          tempo = 0.5
          rapide = 1 - rapide
        elsif ni%7 == 0 then
          tempo = 2.0
        else
          tempo = 1.0
        end
      end
      cue :tick
    end
  end
end



in_thread(name:  :air) do
  use_synth :dsaw
  loop do
    with_fx :reverb do
      sync :tick
      melodie L[i], 1
    end
  end
end



in_thread(name:  :air_2) do
  use_synth :pretty_bell
  loop do
    with_fx :reverb do
      sync :tick
      if (ni+1)%6 == 0
        melodie_gamme G[i], 1
      elsif ni > 2
        melodie_2 L[i], 1
      end
    end
  end
end



in_thread(name:  :basse) do
  use_synth :hoover
  T2 = [3*tempo/4, tempo/2, tempo/4]
  loop do
    with_fx :reverb do
      sync :tick
      if ni > 4 then
        shuffle T2
        j2 = 0
        3.times do
          play L[i][0], pitch: 12, amp: 0.5
          sleep T2[j2]
          j2 = j2+1
        end
      elsif ni > 1 then
        play L[i][0], pitch: 12, amp: 0.5
      end
    end
  end
end



in_thread(name:  :mer) do
  loop do
    sync :tick
    if ni%N > 4 and ni%N < 6 then 
        with_fx :reverb, mix: 0.5 do 
          s = synth [:bnoise, :cnoise, :gnoise].choose, amp: rrand(0.5, 1.5), attack: rrand(0, 4), sustain: rrand(0, 2), release: rrand(1, 5), cutoff_slide: rrand(0, 5), cutoff: rrand(60, 100), pan: rrand(-1, 1), pan_slide: rrand(1, 5), amp: rrand(0.5, 1) 
          control s, pan: rrand(-1, 1), cutoff: rrand(60, 110)
          sleep rrand(2, 4) 
        end 
    end 
  end 
end 

in_thread(name: :percu) do
  loop do 
    with_fx :echo, phase: 0.125, mix: 0.4 do 
       sync :tick 
       if ni>3 then
          sample :bd_tek
          sample  :drum_cymbal_soft, sustain: 0, release: tempo/3
       end
      sleep tempo/2
    end
  end
end


Pepper 2019 – Localisation wifi par ESP8266

Il n’est pas possible d’utiliser les commandes systèmes de Pepper en programmation normale. En effet, les accès « sudo » sont verrouillés par le constructeur. Pour localiser le robot en fonction des signaux wifi, il faut donc y embarquer un matériel communicant supplémentaire, un peu comme un GPS dans une voiture.



Taille : 75mm x 35mm
Le principe retenu utilise une carte ESP8266 qui scanne le réseau wifi en permanence,
et publie le résultat sur une page HTML de son site internet local.

Le logiciel embarqué dans la carte ESP :

  1. s’initialise pour avoir une adresse IP fixe
  2. se connecte au Wifi local
  3. initialise un serveur web
  4. scanne en permanence le wifi local
  5. la page HTML d’accueil est recrée en fonction des résultats du scan, avec
    les 4 signaux les plus forts.



Page renvoyée par l’ESP

Initialisation de l’ESP


void setup() {
  Serial.begin(115200);
  // IP statique
  IPAddress ip(192, 168, 1, 222);
  IPAddress gateway(192, 168, 1, 254);
  IPAddress subnet(255, 255, 255, 0);
  IPAddress dns(192, 168, 1, 254);
  WiFi.config(ip, dns, gateway, subnet);
  WiFi.begin(ssid, password);
  Serial.println("");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.print("\nConnexion ");
  Serial.println(ssid);
  Serial.print("Addresse IP: ");
  Serial.println(WiFi.localIP());
  ...
}

Scan réseau

La fonction Wifi.scanNetworks() renvoie le nombre de wifi détectés. Les fonctions Wifi.SSID(i) et Wifi.RSSI(i) renvoient respectivement
le i-ème SSID détecté ainsi que la force du signal (le rssi)

void loop() {

    Serial.println("scan ...");
    // WiFi.scanNetworks renvoie le nombre de reseaux trouves
    int n = WiFi.scanNetworks();
    Serial.println("scan ok");
    if (n == 0)  Serial.println("Pas de reseau");
    else {
      Serial.print(n);
      Serial.println(" Wifi : ");
      SCAN=1;
      raz(); 
      for (int i = 0; i < n; ++i)  {
          ajoute((WiFi.SSID(i)).c_str(), WiFi.RSSI(i)); 
          Serial.print(i + 1);
          Serial.print(": ");
          Serial.print(WiFi.SSID(i));
          Serial.print(" (");
          Serial.print(WiFi.RSSI(i));
          Serial.println(")\n");
      }
      SCAN=0;
    }
   server.handleClient();
}

 

Stockage des n meilleurs résultats

Les wifi détectés sont stockés dans un tableau de structures.
Chaque structure contient un SSID et son RSSI. On ne conserve que les NWIFI les meilleurs

#define NWIFI 4

struct Swifi {
  char wf[128];
  int rssi;
} TW[NWIFI];


// RAZ du tableau des meilleurs wifi
void raz() {
  for (int i=0; i<NWIFI; i++) {
     TW[i].rssi=-1000;
     TW[i].wf[0] = 0;
  }
}

// Ajout d'un wifi dans la sélection.
// Seulement s'il fait partie des meilleurs ..
void ajoute(const char *ssid, int rssi){
   int r = 0;
   while (rr; i--) TW[i]=TW[i-1];
   TW[r].rssi = rssi;
   strcpy(TW[r].wf, ssid);
}

 

Fabrication de la page HTML

La page HTML est fabriquée à partir de la dernière mesure de wifi.
La fonction handleRoot() renvoie le message à chaque client web
qui a fait la requête http://192.168.1.222/

 


// page html
char html[128*NWIFI];

void handleRoot() {
  char b[128];
  html[0] = 0;
  while (SCAN==1) delay(10);
  for (int i=0; i<NWIFI; i++) {
     sprintf(b,"%s;%d\n", TW[i].wf, TW[i].rssi);
     strcat(html, b);
  }
  server.send(200, "text/plain", html);
}

 

Code complet

ScannerDoc.ino

Récupération des informations en python

Le programme python utilise le module requests. Le code est trivial :


La trace d’exécution est dans la partie droite

« Pepper 2019 » – Détection de wifi

Le problème de la localisation.

Le guidage par le robot implique que l’on connaisse à chaque instant sa localisation précise. Le principe retenu est la triangulation de position en fonction de la force des signaux wifi du voisinage. La surface du CHBS est recouverte par plus de 900 bornes wifi. Un sous ensemble de ces bornes précisément identifiées et placées sur un plan sera suffisant pour permettre une géolocalisation convenable.

Sachant que ce principe de guidage par wifi est une clé fondamentale du projet, quelques mesures ont été faites afin de vérifier la faisabilité.
Dans la figure ci dessous, sont pointées quelques bornes wifi dans le couloir qui conduit de l’accueil au « pôle femme, mère, enfant ». Seules sont mentionnées les bornes visibles dans le couloir. Il en existe de nombreuses autres …

 

Un programme de test détaillé plus bas a permis de faire des relevés de forces des signaux wifi présents dans le niveau 0.
Dans le tableau suivant, seules quelques bornes sont sélectionnées :

Ces bornes suffisent pour obtenir la position de l’utilisateur dans le couloir principal : la courbe obtenue en prenant le maximum de la famille des courbes sélectionnées paraît facilement exploitable

Programme de test

Préliminaire

Le programme de test est écrit en python et nécessite le module wifi. Installation :
pip install wifi
Le module date un peu, mais est toujours fonctionnel : https://github.com/rockymeza/wifi

# -*- coding: utf-8 -*-
# Claude G. - 2018

import wifi


def Search():
    wifilist = []
    cells = wifi.Cell.all('wlp3s0')
    for cell in cells:
        wifilist.append(cell)
    return wifilist

"""
 calcule la qualité q d'un signal sous forme d'entier
  0 < q <= 70 à partir de n = "q/70" 
""" 
def quality(n) : 
    q = int((n.quality.split('/'))[0]) 
    return q 

""" reçoit un tableau de dictionnaires (de wifi) renvoie le tableau rangé par 
    ordre décroissant de force de signal 
""" 
def triBulle(a) : 
    n = len(a) 
    print(n) 
    for i in range(n-1) : 
        for j in range(n-1,i,-1) :
            print(a[j]) 
            if quality(a[j]) > quality(a[j-1]) :
                a[j],a[j-1] = a[j-1],a[j]


"""
 Reçoit une liste de wifi du voisinage
 et renvoie la liste des wifi appartenant à l'établissement
"""
def selectionneWifi(liste):
    return liste
    

if __name__ == '__main__':
    # Récupération de la liste des wifi
    lw = Search()
    print(lw)
    # .. rangement par ordre décroissant de force
    triBulle(lw)
    # on ne garde que les wifi connus
    lw = selectionneWifi(lw) 
    for lwi in lw:
       print(lwi.ssid + " " + lwi.address + " " + lwi.quality)
       print( str(quality(lwi)) )

Extrait de la sortie du programme en un point donné :


wifi-chbs 34:BD:C8:AC:49:32 25/70
25
wifi-chbs-mobiles 34:BD:C8:AC:4A:2F 24/70
24
\x00 34:BD:C8:AC:4A:2E 23/70
23
wifi-chbs-patients 34:BD:C8:AC:49:34 23/70
23
\x00 34:BD:C8:AC:4A:29 23/70
23