Synthèse de son avec Mozzi

Lorsqu’on branche un buzzer sur un port numérique de l’Arduino, il est possible de jouer un son.

Si on utilise un buzzer actif, l’Arduino ne peut émettre que des bips (des son toujours de même fréquence), comme un four à micro-ondes.

S’il s’agit d’un buzzer passif (une sorte de petit haut parleur auquel on doit fournir un signal audio), on peut jouer différentes notes, en utilisant par exemple la fonction tone.

tone(pin, freq) // génère un signal rectangulaire de fréquence freq sur le port pin

 

Mais cette fonction ne peut jouer que des notes simples, avec un son pas très harmonique, car le signal fourni est carré (et pas sinusoïdal) !

 

Mozzi donne vie à votre Arduino en lui permettant de produire des grognements, des balayages et des effets atmosphériques beaucoup plus complexes et intéressants. Ces sons peuvent être créés rapidement et facilement à partir d’unités de synthèse familières telles que des oscillateurs, des délais, des filtres et des enveloppes.

Site officiel de Mozzi

Tutoriel de démarrage

 

Installation

L’option d’installation la plus simple consiste à installer Mozzi via le gestionnaire de bibliothèques de l’IDE Arduino :

  • Arduino➞Sketch➞Include Library➞Library Manager,
  • tapez « Mozzi » dans le champ de recherche,
  • puis cliquez sur « install ».

 

Matériel

Sur Arduino UNO, Mozzi fournit un signal sur le port numérique 9 (voir l’ensemble des ports utilisés selon le modèle de microcontrôleur)

Le buzzer passif fournit dans le kit Arduino n’a pas une très grande bande passante. Les sons de fréquence inférieure à 2000Hz sont peu audibles …

Le mieux est donc d’utiliser un module d’amplification audio pour Arduino.

Sinon, on peut connecter une petit haut parleur sur l’Arduino, mais surtout pas directement sur un port numérique ! Voici un câblage simple :

 

Première utilisation

Voici un petit programme simple pour générer une unique note, avec un signal sinusoïdal :

#include <Mozzi.h>
#include <Oscil.h>
#include <tables/sin2048_int8.h> // table des sinus

// Oscillateur
Oscil <SIN2048_NUM_CELLS, MOZZI_AUDIO_RATE> aSin(SIN2048_DATA);

void setup(){
  startMozzi();
  aSin.setFreq(3520); // définition de la fréquence de l'oscillateur (LA6)
}

void updateControl(){
  // put changing controls in here
}

AudioOutput updateAudio(){
  return MonoOutput::from8Bit(aSin.next());
}

void loop(){
  audioHook();
}

En plus des fonctions setup et loop, un programme avec Mozzi doit comporter les fonctions updateControl et updateAudio.

 

Quelques explications :

  • Les tables de données (ici sin2048.h) contiennent les valeurs pré-calculées de différents types de signaux. Cela évite de mobiliser le microcontrôleur pour les calculer …
    Il en existe de plusieurs formes : 

    • sinus
    • dents de scie
    • rectangulaire
    • bruits
    • guitare
  • L’oscillateur (ici nommé aSin) . On peut créer autant d’oscillateurs que

 

Contrôle du volume

Pour contrôler le volume sonore à partir d’une information extérieure (potentiomètre, capteur, …), on doit réaliser l’acquisition depuis la fonction Mozzi  updateControl.

De plus, pour faciliter les calculs, on utilise la fonction Mozzi mozziAnalogRead. Suivie de <8>, elle permet l’acquisition directement sur 8bits (au lieu de 10 pour la fonction Arduino analogRead).

On utilise le résultat de l’acquisition (ici la variable volume) pour moduler la valeur de la sortie (on multiplie le niveau du signal par le volume), dans la fonction updateAudio.

#include <Mozzi.h>
#include <Oscil.h>
#include <tables/sin2048_int8.h> // table des sinus
Oscil <SIN2048_NUM_CELLS, MOZZI_AUDIO_RATE> aSin(SIN2048_DATA);

const char INPUT_PIN = 0; // port analogique du potentiomètre

// volume sonore (0 à 255)
byte volume;

void setup(){
  Serial.begin(115200);
  aSin.setFreq(3520);  // définition de la fréquence de l'oscillateur (LA6)
  startMozzi();
}

void updateControl(){
  volume = mozziAnalogRead<8>(INPUT_PIN); // lecture du port analogique (sur 8 bits)
  Serial.print("volume = ");
  Serial.println((int)volume);
}

AudioOutput updateAudio(){
  return MonoOutput::from16Bit((int)aSin.next() * volume); // 8 bit * 8 bit --> 16 bits
}

void loop(){
  audioHook();
}

 

Modulation de la fréquence

De manière similaire au contrôle de volume, on peut modifier la fréquence d’un oscillateur, toujours depuis la fonction Mozzi updateControl.

Il suffit alors d’utiliser la méthode setFreq de l’oscillateur :

#include <Mozzi.h>
#include <Oscil.h>
#include <tables/sin2048_int8.h> // table des sinus

Oscil <SIN2048_NUM_CELLS, MOZZI_AUDIO_RATE> aSin(SIN2048_DATA);

const char INPUT_PIN = 0; // port analogique du potentiomètre

// fréquence de l'oscillateur
byte freq;

void setup(){
  Serial.begin(115200);
  startMozzi();
}

void updateControl(){
  freq = mozziAnalogRead<8>(INPUT_PIN) * 5 + 1000;
  aSin.setFreq(freq);
  Serial.print("freq = ");
  Serial.println((int)freq);
}

AudioOutput updateAudio(){
  return MonoOutput::from8Bit(aSin.next());
}

void loop(){
  audioHook(); // required here
}

 

Jouer des échantillons (samples)

Mozzi permet également de jouer des sons prédéfinis : des samples.

La bibliothèque Mozzi en propose plusieurs dans le dossier samples.

Avec un bouton

  • Câbler un bouton poussoir (en utilisant la résistance de tirage interne à l’Arduino).

Voici un exemple avec un bouton sur le port numérique 12, pour jouer un sample appelé « abomb » :

#include <Mozzi.h>
#include <Sample.h> // Sample template
#include <samples/abomb16384_int8.h>

Sample <ABOMB_NUM_CELLS, MOZZI_AUDIO_RATE> aSample(ABOMB_DATA);

const int BUTTON_PIN = 12;
char button_state, previous_button_state;

void setup(){
  pinMode (BUTTON_PIN, INPUT_PULLUP);
  startMozzi();
  aSample.setFreq((float)  ABOMB_SAMPLERATE     / (float) ABOMB_NUM_CELLS  );
}

void updateControl(){
  button_state = digitalRead(BUTTON_PIN);
  if(button_state != previous_button_state) {
    if (button_state == LOW) {
      aSample.start();
    }
  }
  previous_button_state = button_state;
}


AudioOutput updateAudio(){
  return MonoOutput::from8Bit((int) aSample.next());
}

void loop(){
  audioHook();
}

 

 

Avec une cellule piezo

On peut également utiliser une cellule piezo, ce qui permet de moduler le volume sonore selon la force appliquée sur la cellule.

Voici un exemple avec une cellule piézo sur le port analogique 3, pour jouer un sample appelé « bamboo » :

#include <Mozzi.h>
#include <Sample.h>
#include <samples/bamboo/bamboo_02_2048_int8.h>

const char PIEZO_PIN = 3;
const int threshold = 80;  // seuil de démarrage du son

Sample <BAMBOO_02_2048_NUM_CELLS, MOZZI_AUDIO_RATE>aSample(BAMBOO_02_2048_DATA);

boolean triggered = false;

// volume sonore (0 à 255) 
byte volume;

void setup(){
  Serial.begin(115200);
  aSample.setFreq((float) BAMBOO_02_2048_SAMPLERATE / (float) BAMBOO_02_2048_NUM_CELLS);
  startMozzi();
}


void updateControl(){
  int piezo_value = mozziAnalogRead<10>(PIEZO_PIN); // value is 0-1023
  Serial.print("piezo value = ");
  Serial.print(piezo_value);

  if (piezo_value > threshold) {
    if (!triggered){
      volume = piezo_value << 2; // division par 4 pour passer de int10 à int8
      aSample.start();
      triggered = true;
      Serial.print("\tvolume = ");
      Serial.print(volume);
    }
  }else{
    triggered = false;
  }
  Serial.println();
}

AudioOutput updateAudio(){
  return MonoOutput::from16Bit(aSample.next() * volume);
}

void loop(){
  audioHook();
}

 

Jouer plusieurs samples

Lorsqu’on veut jouer plusieurs samples à la fois (à partir de plusieurs capteurs, comme dans le cas d’un clavier), ces samples doivnet parfois être joué simultanément (car on peut presser plusieurs touches du clavier en même temps !).

L’exemple suivant montre comment faire (sans capteurs) :

#include <Mozzi.h>
#include <Sample.h> // Sample template
#include <samples/bamboo/bamboo_00_2048_int8.h>
#include <samples/bamboo/bamboo_01_2048_int8.h>
#include <samples/bamboo/bamboo_02_2048_int8.h>
#include <EventDelay.h>
#include <mozzi_rand.h>

Sample <BAMBOO_00_2048_NUM_CELLS, MOZZI_AUDIO_RATE>aBamboo0(BAMBOO_00_2048_DATA);
Sample <BAMBOO_01_2048_NUM_CELLS, MOZZI_AUDIO_RATE>aBamboo1(BAMBOO_01_2048_DATA);
Sample <BAMBOO_02_2048_NUM_CELLS, MOZZI_AUDIO_RATE>aBamboo2(BAMBOO_02_2048_DATA);
EventDelay kTriggerDelay;

void setup(){
  startMozzi();
  aBamboo0.setFreq((float) BAMBOO_00_2048_SAMPLERATE / (float) BAMBOO_00_2048_NUM_CELLS);
  aBamboo1.setFreq((float) BAMBOO_01_2048_SAMPLERATE / (float) BAMBOO_01_2048_NUM_CELLS);
  aBamboo2.setFreq((float) BAMBOO_02_2048_SAMPLERATE / (float) BAMBOO_02_2048_NUM_CELLS);
  kTriggerDelay.set(111); // countdown ms, within resolution of MOZZI_CONTROL_RATE
}

byte randomGain(){
  return rand(200) + 55;
}

struct gainstruct{
  byte gain0;
  byte gain1;
  byte gain2;
}
gains;

void updateControl(){
  if(kTriggerDelay.ready()){
    switch(rand(0, 3)) {
    case 0:
      gains.gain0 = randomGain();
      aBamboo0.start();
      break;
    case 1:
      gains.gain1 = randomGain();
      aBamboo1.start();
      break;
    case 2:
      gains.gain2 = randomGain();
      aBamboo2.start();
      break;
    }
    kTriggerDelay.start();
  }
}

AudioOutput updateAudio(){
  int asig= (int)
    ((long) aBamboo0.next()*gains.gain0 +
      aBamboo1.next()*gains.gain1 +
      aBamboo2.next()*gains.gain2)>>4;
  // clip to keep sample loud but still in range
  return MonoOutput::fromAlmostNBit(9, asig).clip();
}

void loop(){
  audioHook();
}

 

Et voir aussi l’exemple plus complet (Fichier/Exemples/Mozzi/08.Samples) Samples_Tables_Array

 

 

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *