Synthé Midi Arduino Ultra Simple

De Wiki LOGre
Révision de 4 octobre 2016 à 17:05 par Yruama Lairba (discuter | contributions) (Théorie : la gamme (bien) tempérée et la fréquence d'une note : gamme tempérée et gamme bien tempérée semble être 2 chose différentes)

(diff) ← Version précédente | Voir la version courante (diff) | Version suivante → (diff)
Aller à : navigation, rechercher


page en construction

Projet réalisé par Yruama_Lairba

Présentation

Le but de ce projet est de réaliser un synthétiseur midi très simple, basé sur un Arduino Uno, et avec le minimum de composants quitte à ne pas respecter les spécifications et recommandations midi :p . Les premières versions du synthé ne répondent qu'aux instructions midi note on et note off, et par la suite, les fonctions "timbre" et "pitch bend" ont été implémentées. La génération du son se fait en changeant plus ou moins rapidement le niveau logique d'une sortie en fonction de la note jouée. Le synthé est donc monophonique.

Récupération des sources

git clone https://github.com/YruamaLairba/midiSynth.git

Schéma électrique

note : le schéma suivant ne respecte pas les spécifications/recommandations électriques du midi, cependant cela devrait fonctionner dans la plupart des cas si les connexions midi ne sont pas trop longues.

Schéma de branchement

Dans le schéma ci-dessus, la résistance sert à limiter le courant passant par la pin de l'Arduino. Il faut limiter le courant à 40mA maximum, soit une impédance total (haut parleur + résistance) de 125 ohms minimum. Dans mon cas, j'utilise un haut-parleur de casque de 32 ohm d'impédance, il me faut ajouter au minimum 93 ohm en série. J'ai donc pris la valeur supérieure disponible la plus proche soit 100 ohm (série E24). J'ai aussi ajouté un potentiomètre en série pour pouvoir régler le niveau sonore.

Théorie : la gamme tempérée et la fréquence d'une note

La gamme tempérée (ou gamme au tempérament égal) est de nos jours la gamme utilisée quasiment universellement dans la musique "occidentale". Elle est composée de 12 notes régulièrement réparties sur la gamme, avec les dièses et les bémols. À chaque changement d'octave, c’est-à-dire entre la première et la dernière note d’une octave, on double la fréquence. Si on fixe une note de référence on déduit la fréquence des autres notes avec la relation suivante :

[math] Fréquence_{note} = Fréquence_{noteRef} \times 2 ^{\frac{indiceNote - indiceNoteRef}{12} } [/math]

Comme dans le programme arduino je manipule des durées, j'utilise la formule équivalente suivante :

[math] Période_{note} = Période_{noteRef} \times 2 ^{\frac{indiceNoteRef - indiceNote}{12} } [/math]

Et je la manipule de nouveau pour regrouper tous les termes constants :

[math] Période_{note} =\underbrace { Période_{noteRef} \times 2 ^{\frac{indiceNoteRef}{12}} }_{Constante} \times 2 ^ {\frac{- indiceNote}{12} } [/math]

ce qui se traduit par le pseudo code C

#include <math.h> //for pow function
periode = constante * pow(2.0, (-indiceNote/12.0))

Remarque : Ceci peut se simplifier en

periode = exp(c1 * indiceNote + c2);

avec :

  • c1 = - log(2)/12
  • c2 = log(constante)

où le logarithme est népérien.

Protocole midi : quelques indications

à compléter

Pour information, les spécifications du midi sont disponibles sur ce site : https://www.midi.org/specifications/

Transmission et récéption de signaux

Du point de vue transmission et réception de signaux, le midi fonctionne quasiment comme une liaison série asynchrone à 31250 bauds, cela permet d'utiliser l'USART de l'atmega328P et les fonctions 'Serial' de l'Arduino. Du coup, j'utilise la ligne suivante dans le setup pour configurer l'USART :

 Serial.begin(31250);//initialize Serial at 31250 bauds

Et pour lire les octets dans la boucle loop :

if(Serial.available()){
    char inByte = Serial.read();
    ...
}

Trame midi

un message midi commence presque toujours par un octet status (>0x80 ou 0b1xxx xxxx) éventuellement suivi d'un ou plusieurs octets de données (<0x80 ou 0b0xxx xxxx). Cependant, dans le cas des "channels messages" l'octet status d'un message peut ne pas être émis s'il est identique au message précédent (running status). Pseudo-code à mettre dans la fonction "loop"

/*
Dans le pseudo code suivant
"midiStatus" est une variable de type char (un octet) servant à stocker le dernier status reçu
"data" est un tableau de char servant à enregistrer les octets de données en cour de réception ou traitement.
Il a ici une taille de 2 qui correspond à la taille de message maximale traité par mon synthé.
"dataNumber" est une entier servant à connaître le nombre d'octets déjà présent dans le tableau data
"inByte" est l'octet qui viens d'être reçu" 
*/
 
//si on reçoit un octet status, l'enregistrer et s'assurer que le tableau data est vide
if(inByte & 0b10000000)
{
    midiStatus = inByte;
    dataNumber = 0; //équivalent à vider le tableau data
}
//si on reçoit un octet de donnée
else
{
    //si il reste de la place dans le tableau data, enregistrer l'octet de donnée
    if(dataNumber <2){
    data[dataNumber]=inByte;
    dataNumber++; 
}
if (/* message valide et géré */)
{
    .../*traiter le message*/
    dataNumber=0; //équivalent à vider le tableau data
}

Message interprété par le synthétiseur

note : Dans le tableau suivant "nnnn" représente les bits de l'octet status servant à coder le canal midi. Par simplicité j'ai décidé de les ignorer et de répondre aux messages quelque soit le canal utilisé (mode omni).

Status Data(s) Description
1000nnnn 0kkkkkkk
0vvvvvvv
Note off: arrêter une note "0kkkkkkk" désigne la note à arrêter. "0vvvvvvv" non utilisé ici.
1001nnnn 0kkkkkkk
0vvvvvvv
Si "0vvvvvvv" = 0 : Note off arrêter une note. Si "0vvvvvvv" différent de 0 : Note on, jouer une note. "0kkkkkkk" désigne la note à jouer ou arrêter.
1011nnnn 01000111
0vvvvvvv
Timbre. 0vvvvvvv est la valeur du timbre. la valeur par défaut est 127 (01111111).
1110nnnn 0lllllll
0mmmmmmm
Pitch Bend. la valeur du pitch est déterminée par (0llllll + 0mmmmmmm<<7) - 8192. amplitude du pitchBend a ici été choisie à l'arrache (± une octave ?) .