Gestion du Midi
Dans cette section, nous allons réceptionné le flux MIDI via le port micro USB de la carte puis contrôler le moteur de synthèse audio à partir des données MIDI reçues.
Principe
Le flux midi transite entre le contrôleur MIDI et la carte via USB. A chaque réception d'une information MIDI, une fonction callback nommée USBH_MIDI_ReceiveCallback
est appelée. Cette fonction appelle alors une fonction process_midi_data()
, puis relance une écoute sur le port USB.
void process_midi_data(void)
{
uint8_t *ptr = MIDI_RX_Buffer;
uint16_t numberOfPackets;
uint8_t midi_info[4];
char displayBuffer[27];
numberOfPackets = USBH_MIDI_GetLastReceivedDataSize(&hUSBH) / 4; // 4 bytes per midi message
while (numberOfPackets--)
{
for (int i = 0; i < 4; i++)
{
midi_info[i] = *ptr++; // extract each byte
}
if (midi_info[1] != 0)
{
snprintf(displayBuffer, sizeof(displayBuffer), "c:%u e:%u v:%u",midi_info[1], midi_info[2], midi_info[3]);
render_lcd(displayBuffer);
}
}
}
void USBH_MIDI_ReceiveCallback(USBH_HandleTypeDef *phost) {
process_midi_data();
USBH_MIDI_Receive(&hUSBH, MIDI_RX_Buffer, USB_MIDI_RX_BUFFER_SIZE);
}
Lorsque un message MIDI est envoyé en USB, le message est composée de 4 octets:
- le premier octet est lié à la spécification du MIDI via USB,
- les 3 derniers octets correspondent à la spécification du protocole MIDI.
Reception des messages Note On / Note Off
Dans un premier temps, nous souhaitons allumer une LED à la réception d'un message Note On. A la réception d'un message NoteOn, le standard MIDI spécifie que midi_info[1]=0x9?
. Notons qu'En C, l'extraction des 4 bits de poids fort peut être réalisé en utilisant un masque.
Manipulation 1
Modifiez votre code pour :
- allumer la LED verte à la reception d'un message MIDI Note On (utilisation de
BSP_LED_On
) - éteindre la LED verte à la reception d'un message MIDI Note Off (utilisation de
BSP_LED_Off
)
Gestion du Pitch
Table de correspondance
Le premier octet d'un message Note On contient la valeur du pitch. Cette valeur est liée à la fréquence de l'oscillateur par la relation
Manipulation 2
En utilisant la formule, créez en C une table de correspondance nommée pitch2freq
de
Lorsque la fréquence frequency
est déterminée, il est possible d'afficher sa valeur sur l'écran LCD en utilisant les instructions suivantes.
char displayFreqBuffer[20]; // Assuming 20 characters are enough for the frequency string.
snprintf(displayFreqBuffer, sizeof(displayFreqBuffer), "Frequency: %.2f Hz", frequency);
render_lcd(displayFreqBuffer);
Manipulation 3
A la reception d'un message MIDI Note On, récupérez la valeur du pitch puis affichez la valeur de la fréquence correspondante sur l'écran LCD.
Interaction avec le moteur audio
Pour lier le pitch à la fréquence de l'oscillateur, une technique possible consiste à appeler la fonction wavetableOscSetDelta(WavetableOsc* osc, float frequency)
avec la bonne fréquence à la réception d'un nouveau message MIDI Note On. Cette technique est décrite ci dessous:
- Importation de l'adresse de l'oscillateur dans le fichier
src/midi_processor.c
:
extern WavetableOsc oscW;
A la réception d'un message MIDI Note On, extraction du pitch puis calcul de la fréquence fondamentale
frequency
de l'oscillateur.Après calcul de la fréquence fondamentale, appel de la fonction
wavetableOscSetDelta(WavetableOsc* osc, float frequency)
.
Manipulation 4
Modifiez votre code pour gérer le pitch de votre oscillateur via le contenu des message NoteOn.
Gestion de la vélocité
Une note est définie par un pitch et par une vélocité. La vélocité impacte le volume du son synthétisé. Le protocole MIDI spécifie une vélocité comprise entre 0 et 128 (exclu).
Mapping Linéaire
Un mapping possible entre l'amplitude du signal et la vélocité est donné par la relation linéaire :
où :
désigne l'amplitude de l'oscillateur, désigne la vélocité.
Après calcul de l'amplitude, il est possible de modifier l'amplitude de l'oscillateur via l'appel de la fonction wavetableOscSetAmp(WavetableOsc* osc, float amp)
.
Manipulation 5
En vous basant sur la gestion du pitch, modifiez votre code pour permettre la gestion de la vélocité.
Mapping Non-Linéaire
La mapping linéaire n'est pas nécessairement le plus approprié en pratique pour contrôler l'amplitude ou les autres paramètres d'un synthétiseur. Un mapping plus général est donné par la fonction non-linéaire suivante.
et désignent les valeurs minimales et maximales de , - la valeur
est une puissance permettant de contrôler l'asymétrie. Cette valeur est appelée skew factor.
La figure suivante présente l'allure de la fonction non-linéaire pour des valeurs différentes de skew factor lorsque
Manipulation 6
Modifiez votre code pour permettre l'utilisation d'un mapping non-linéaire. Pour
Structure MonoSynth
Définition de la structure
Pour centraliser les paramètres relatifs à la gestion des informations MIDI, nous allons définir une structure permettant de stocker l'état du synthétiseur. Dans le contexte d'un synthétiseur monophonique (une seule note à la fois), cet état peut contenir des informations concernant la présence ou non d'une note, la note en cours et le coefficient
typedef struct {
bool isPlaying; // note en cours de lecture ou non.
uint8_t currentPitch; //note en cours de lecture (ou dernière note jouée)
float skewFactorGain;
} MonoSynth;
Manipulation 7
Ajoutez la définition de la structure MonoSynth
dans votre programme.
Fonctions associées
Pour améliorer l'organisation du code, il est ensuite possible d'utiliser des fonctions permettant de contrôler l'état du synthétiseur.
void monoSynthInit(MonoSynth* synth, float skewFactorGain); //Initialisation du contenu de la structure (isPlaying=false, currentPitch=0)
void monoSynthNoteOn(MonoSynth* synth, uint8_t pitch, uint8_t velocity); // Reception d'un message Note On
void monoSynthNoteOff(MonoSynth* synth, uint8_t pitch); // Reception d'un message Note Off
Manipulation 8
Implémentez le code de la structure MonoSynth
, puis les fonctions associées monoSynthInit
, monoSynthNoteOn
, monoSynthNoteOff
.