Skip to content

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.

c
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

f [Hz]=440×2(p69)/12

Manipulation 2

En utilisant la formule, créez en C une table de correspondance nommée pitch2freq de 128 éléments permettant de convertir le pitch en fréquence.

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.

c
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:

  1. Importation de l'adresse de l'oscillateur dans le fichier src/midi_processor.c:
c
extern WavetableOsc oscW;
  1. A la réception d'un message MIDI Note On, extraction du pitch puis calcul de la fréquence fondamentale frequency de l'oscillateur.

  2. 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 :

a=v/127

où :

  • a désigne l'amplitude de l'oscillateur,
  • v 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.

a=amin+(amaxamin)×(v127)γ
  • amin et amax désignent les valeurs minimales et maximales de a,
  • 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 amin=0 et amax=1.

Non Linear Mapping

Manipulation 6

Modifiez votre code pour permettre l'utilisation d'un mapping non-linéaire. Pour amin=0 et amax=1, testez le rendu sonore avec les valeurs γ=2, γ=3, etc.

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 γ. Notons qu'en fonction de l'état d'avancement du projet, cette structure peut être ammenée à être étoffée.

c
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.

c
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.