Keyboard Mouse Midi Serial ensemble sur Leonardo 32U4

Bonjour
Je me suis remis récemment (et sérieusement :laughing: )à Kicad qui comme chacun le sait utilise intensivement le clavier et la souris.

Suite à mon accident de 2020, il me reste quelques déficiences essentiellement au niveau de l'index et du majeur main droite, j'ai par exemple beaucoup de difficultés avec le double click left .

bref je me suis dit : "fais toi une console "main gauche" dédié KICAD genre
çà

Comme je suis assez pragmatique et que je n'aime pas réinventer la poudre , je me suis demandé si je ne pouvais pas réutiliser/detourner certaines choses plutôt de repartir de zéro.

Qu'est ce qui à des touches "importantes" et des potentiomètres/encodeurs chez toi ?

la réponse rapide est : des claviers/surfaces MIDI

le MIDI je ne connais pas trop mal , il me restait donc à faire de la translation MIDI (de préférence OVER USB) vers USB keyboard mouse (les commandes KICAD).

J'avais laissé de coté les 32U4 pour incompatibilité avec différents HID en simultané .

Je pensais m'en sortir avec un pico2040 , test fait avec plusieurs HID OK , mais la class MIDI n'est pas encore bien implémentée.

J'ai fais pas mal de tests et je ressors un Leonardo pour regarder quelles sont les libs usb utilisable en me résignant éventuellement à passer par du MIDI serial (31250)

Dans le feu des tests multiples et variés je vois à un moment avec Usbdeview que j'ai d'ouvert en USB par le leonardo un keyboard une mouse , un MIDI, un Serial :face_with_monocle:

J’approfondis un peu çà step by step

Il s’avère donc que l'ouverture avec de multiples HID (ce que je pensais impossible) est en réalité parfaitement fonctionnel avec du 32U4.

il me reste à configurer (table de translation) avec en entrée pour l'instant un axiom25

A terme je vais utiliser une petite surface comme celle ci
beaucoup moins encombrante

A utiliser sous windows avec MIDIOX
Je ne connais pas les équivalents pour linux ou mac mais çà doit bien exister ? :wink:

programme minimal de POC

/*
  Keyboard Mouse Serial MIDI sur USB ensemble
testé artouste 28/11/2021
necessite MIDIOX sur W10
*/


#include "Keyboard.h"
#include "Mouse.h"
#include "MIDIUSB.h" // V1.05


void setup() { 

  Serial.begin(115200); // pour debug


  Mouse.begin();
  Keyboard.begin();
  pinMode(LED_BUILTIN, OUTPUT);

}

void loop() {

  midiEventPacket_t rx;
  do {
    rx = MidiUSB.read();
    if (rx.header != 0) {  // il y a du midi à la porte
      
      digitalWrite(LED_BUILTIN, HIGH); // pour test
      Mouse.move(0, 10); // move mouse 10 down pour test
      Keyboard.write('P');// keyboard send P pour test
      
      Serial.print("Received: ");
      Serial.print(rx.header, HEX);
      Serial.print("-");
      Serial.print(rx.byte1, HEX);
      Serial.print("-");
      Serial.print(rx.byte2, HEX);
      Serial.print("-");
      Serial.println(rx.byte3, HEX);
      digitalWrite(LED_BUILTIN, LOW);
    }
  } while (rx.header != 0); 

}

fun !

Oui c'est finalement impecc , je suis surtout "dégouté" d’être intellectuellement resté depuis pas mal de temps sur une impossibilité d'utiliser simultanément plusieurs "HID" ensemble sur un 32U4, et j'aurais pu encore rester là dessus longtemps sans le constat "coup de bol" avec usbdeview . :blush:

çà permet d'avoir à peu de frais des claviers "main gauche" avec PAD et encodeur pour d'autres
logiciels genre photoshop,inkscape,fusion et autres.

Les tables de transcodages sont un peu "rébarbative" à réaliser , mais ce n'est à réaliser qu'une seule fois.

C'est dommage aussi de devoir utiliser un routeur soft MIDI (MIDIOX) sur le PC qui tourne KICAD (ou autre soft) , mais c'est vite masqué par le confort d'utilisation.

Pas (encore) testé sur Arduino micro 32U4 , mais aucune raisons que çà ne passe pas.

je ne connais pas MIDIOX, ça fait quoi concrètement ?

ensuite, j'essaye toujours de voir le bon côté des choses, c'est une belle découverte :slight_smile:

Sous windows MIDIOX c'est vraiment le couteau suisse du MIDI (c'est d'ailleurs son icône)
image

Concrètement , cela permet de router les entrés sorties MIDI , d'utiliser le clavier PC comme clavier MIDI, de visualiser les trames midi en transit et plein d'autres choses .

Il doit trés surement exister la même chose chez linux ou maC, c'est du basique.

là par exemple c'est l'exemple du routage utilisé pour mes tests

  • en entrée MIDI utilisation clavier MIDI USB25 AXIOM
  • en sortie le Leonardo qui s'occupe de la translation MIDI ---> HID Keyboard/Mouse

image

En tous cas je suis effectivement bien content du résultat de mes essais du jour ! :innocent:

Il ne me reste plus qu'à ordonner un peu tout çà pour travailler avec # logiciels, mais c'est surtout Kicad qui m'importait en priorité.

OK votre clavier MIDI USB25 AXIOM est connecté au PC sur lequel tourne le soft. il reçoit un message MIDI et il le fait suivre au Leonardo qui le décode et transforme cela en commande clavier ou souris, qui est renvoyée au PC

c'est ça ?

Toutafé ! :sunglasses:

j'ai jamais essayé mais avec un USB Host on devrait pouvoir brancher directement le clavier sur l'arduino non ?

en fait il faudrait être capable de faire simultanément host(MIDI)/device (KM) , perso je ne vois pas avec quoi de relativement simple, peut être avec du plus évolué genre Zero ou Zero2 ?

Mais pour l'instant çà me convient parfaitement :yum:
Je vais rapatrier la petite surface Akaï ce sera déjà moins encombrant que le clavier Axiom

Raccordé l'nstrument midi sur une carte arduino (Samd 21 ou 51) serait sans doute possible avec cette librairie USB_Host_Library_SAMD sur github (malheureusement, elle n'est plus maintenue à jour). Mais du coup, il sera nécessaire d'avoir deux cartes, une pour le host midi et l autre pour l'émulation clavier.

Il existe aussi des petits claviers pas cher de chez Pimonori (n'acheter pas directement chez eux - frais de douane important), il s'agit du Pico RGB Keypad Base, il fontionne avec un Pico RP2040 et l'avantage c'est son utilisation avec circuitpython ainsi vous pourrez faire des modification à la volée sans la nécessité de devoir compiler. J'en possède un qui aide mon fils à jouer à Minecraft.

bonjour
Pour linux Qmidiroute semble rendre le service fait par Midiox (pas testé)

pour Mac , je n'ai aucune idée

dans les softs payants on doit pouvoir faire des choses avec Bidule

sinon il y a longtemps (au moins 10 ans) j'avais joué avec "midi patchbay" mais je ne sais pas si c'est maintenu et il y avait aussi MidiPipe et MIDI Monitor

EDIT: rajout de liens

Bonjour

Un peu dans la continuité
le mainteneur de la lib tinyusb
travaille sur du HOST MIDI pour le pico2040 :yum:

C'est encore un peu(beaucoup) usine à gaz mais çà a le mérite d'exister.

Ce serait donc l’idéal dans le meilleur des monde si host et HID peuvent cohabiter pour faire une "passerelle" Clavier/surface MIDI ----> HOST PICO2040 ---->emulation HID Keyboard/Mouse sans passer par un soft tiers comme Midiox

Je viens de regarder un vieux test que j'avais effectué avec un "Funduino USB Host Shield" connecté en SPI sur un Arduino avec un clavier midi

Clavier Midi <-- USB --> USB Host Shield <--- SPI + qques pin ---> Arduino <-- USB --> Mac

il est assez simple de décoder les messages en provenance du clavier, j'ai retrouvé mon code de test et en branchant un vieux clavier midi (M-Audio Keystation 61 es)

// installer USB_Host_Shield_Library_2.0 => https://github.com/felis/USB_Host_Shield_2.0
#include <usbh_midi.h>
#include <usbhub.h>

USB Usb;
USBHub Hub(&Usb);
USBH_MIDI  usbMidi(&Usb);

enum t_Status : uint8_t {
  s_unknown         = 0b0000,       // not in the spec
  s_NoteOff         = 0b1000,       // Key released
  s_NoteOn          = 0b1001,       // Key pressed
  s_AfterTouch      = 0b1010,       // Key pressure is changing
  s_Controller      = 0b1011,       // CC/controller motion
  s_ProgramChange   = 0b1100,       // Patch/bank or program change
  s_ChannelPressure = 0b1101,       // Average key pressure is changing
  s_PitchBend       = 0b1110,       // Pitch wheel motion
  s_System          = 0b1111,       // SysEx message of some sort
};

void printNibble(uint8_t aNibble) {
  for (int8_t i = 3; i >= 0; i--) Serial.write(bitRead(aNibble, i) ? '1' : '0');
}

void printByte2(uint8_t aByte) {
  for (int8_t i = 7; i >= 0; i--) Serial.write(bitRead(aByte, i) ? '1' : '0');
}

void printByte16(uint8_t aByte) {
  if (aByte < 0x10) Serial.write('0');
  Serial.print(aByte, HEX);
  Serial.write(' ');
}

void printMidiMessage(uint8_t midiMessage[], uint8_t  messageLength) {
  if (messageLength != 0) {
    for (uint8_t i = 0; i < messageLength; i++) {
      printByte2(midiMessage[i]);
      Serial.write(' ');
    }
    Serial.println();
  }
}

/*
  ----------------------------------------------------------------------------------
  Status    Byte 1    Byte 2    Message           Legend
  ----------------------------------------------------------------------------------
  1000nnnn  0kkkkkkk  0vvvvvvv  Note Off          n=channel k=key v=velocity
  1001nnnn  0kkkkkkk  0vvvvvvv  Note On           n=channel k=key v=velocity
  1010nnnn  0kkkkkkk  0ppppppp  AfterTouch        n=channel k=key p=pressure
  1011nnnn  0ccccccc  0vvvvvvv  Controller Value  n=channel c=controller v=value
  1100nnnn  0ppppppp  [none]    Program Change    n=channel p=preset
  1101nnnn  0ppppppp  [none]    Channel Pressure  n=channel p=pressure
  1110nnnn  0fffffff  0ccccccc  Pitch Bend        n=channel c=coarse f=fine (14 bit)
  1111ssss                      SysEx message     s = see below
  ----------------------------------------------------------------------------------

  SysExStart      = 0xF0,        // Start of SysEx stream
  SysExEnd        = 0xF7,        // End of SysEx stream
  MTCQuaterFrame  = 0xF1,        // MTC quarter frame time code
  SongPosition    = 0xF2,        // Ask slave to position playback cue
  SongSelect      = 0xF3,        // Select a certain song and cue to beginning
  TuningRequested = 0xF6,        // Being asked to self-tune
  MidiClock       = 0xF8,        // Being kept in sync with a tempo (24 clocks per quarter note)
  MidiTick        = 0xF9,        // Being kept in sync with a tick (every 10ms)
  MidiStart       = 0xFA,        // Master asking for playback from the beginning
  MidiContinue    = 0xFB,        // Master asked that we continue playback from cue
  MidiStop        = 0xFC,        // Master asked to stop playback and retain cue point
  ActiveSense     = 0xFE,        // Keepalive data to let us know things are still connected - i.e. "nervous" devices
  Reset           = 0xFF,        // Reset to default, just turned on, no keys pressed, cue to beginning etc

*/

void decodeMidiMessage(uint8_t midiMessage[], uint8_t  messageLength) {
  t_Status statusNibble = s_unknown;
  uint8_t channel = 0;

  if (messageLength != 0) {
    statusNibble = (t_Status) (midiMessage[0] >> 4); // message type
    channel = midiMessage[0] & 0b1111; // channel information
    Serial.print(F("Channel = ")); Serial.print(channel); Serial.write('\t');

    switch (statusNibble) {
      case s_NoteOff:              // Key released
        Serial.print(F("Key released.\tkey: ")); Serial.print(midiMessage[1]);
        Serial.print(F("\tVelocity: ")); Serial.println(midiMessage[2]);
        break;

      case s_NoteOn:               // Key pressed
        if (midiMessage[2] == 0) Serial.print(F("Key released.\tkey: "));
        else Serial.print(F("Key pressed.\tkey: "));
        Serial.print(midiMessage[1]);
        Serial.print(F("\tVelocity: ")); Serial.println(midiMessage[2]);
        break;

      case s_AfterTouch:           // Key pressure is changing
        Serial.print(F("Key pressure.\tkey: ")); Serial.print(midiMessage[1]);
        Serial.print(F("\tPressure: ")); Serial.println(midiMessage[2]);
        break;

      case s_Controller:           // CC/controller motion
        Serial.print(F("Motion.\tController: ")); Serial.print(midiMessage[1]);
        Serial.print(F("\tValue: ")); Serial.println(midiMessage[2]);
        break;

      case s_ProgramChange:        // Patch/bank or program change
        Serial.print(F("program change.\tPreset: ")); Serial.println(midiMessage[1]);
        break;

      case s_ChannelPressure:      // Average key pressure is changing
        Serial.print(F("Channel pressure.\tValue: ")); Serial.println(midiMessage[1]);
        break;

      case s_PitchBend:            // Pitch wheel motion
        Serial.print(F("Pitch wheel.\tFine: ")); Serial.print(midiMessage[1]);
        Serial.print(F("\tCoarse: ")); Serial.println(midiMessage[2]);
        break;

      case s_System:               // SysEx message of some sort
        Serial.println(F("SysEx message (ignored)."));
        break;
      default:
        Serial.println(F("unknown status (ignored)."));
        break;
    }
  }
}

void getMidi() {
  uint8_t midiMessage[3];
  uint8_t  messageLength = usbMidi.RecvData(midiMessage);
  decodeMidiMessage(midiMessage, messageLength);
}

void onInit()
{
  uint16_t vid = usbMidi.idVendor();
  uint16_t pid = usbMidi.idProduct();
  Serial.print("Vendor ID: 0x"); Serial.print(vid, HEX);
  Serial.print(", Product ID: 0x"); Serial.println(pid, HEX);
  Serial.println();
}

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

  if (Usb.Init() == -1) {
    Serial.println(F("Usb.Init error, halting."));
    while (true) yield(); //halt
  }
  delay(200);
  usbMidi.attachOnInit(onInit);  // Register onInit() function
  Serial.println(F("Prêt"));
}

void loop()
{
  Usb.Task();
  if (usbMidi) getMidi();
}

quand on appuie et relâche une touche du clavier j'ai ça

Channel = 0	Key pressed.	key: 50	Velocity: 41
Channel = 0	Key released.	key: 50	Velocity: 0

Quand je bouge le pitch:

Channel = 0	Pitch wheel.	Fine: 0	Coarse: 63
Channel = 0	Pitch wheel.	Fine: 0	Coarse: 62
Channel = 0	Pitch wheel.	Fine: 0	Coarse: 61
Channel = 0	Pitch wheel.	Fine: 0	Coarse: 60
Channel = 0	Pitch wheel.	Fine: 0	Coarse: 59
Channel = 0	Pitch wheel.	Fine: 0	Coarse: 58
Channel = 0	Pitch wheel.	Fine: 0	Coarse: 57
Channel = 0	Pitch wheel.	Fine: 0	Coarse: 56
...

quand je bouge la Modulation

Channel = 0	Motion.	Controller: 1	Value: 42
Channel = 0	Motion.	Controller: 1	Value: 41
Channel = 0	Motion.	Controller: 1	Value: 40
Channel = 0	Motion.	Controller: 1	Value: 39
Channel = 0	Motion.	Controller: 1	Value: 38
Channel = 0	Motion.	Controller: 1	Value: 37
Channel = 0	Motion.	Controller: 1	Value: 36
Channel = 0	Motion.	Controller: 1	Value: 35
Channel = 0	Motion.	Controller: 1	Value: 34
...

quand je bouge le volume

Channel = 0	Motion.	Controller: 7	Value: 122
Channel = 0	Motion.	Controller: 7	Value: 121
Channel = 0	Motion.	Controller: 7	Value: 120
Channel = 0	Motion.	Controller: 7	Value: 119
Channel = 0	Motion.	Controller: 7	Value: 118
Channel = 0	Motion.	Controller: 7	Value: 117
Channel = 0	Motion.	Controller: 7	Value: 116
Channel = 0	Motion.	Controller: 7	Value: 115
Channel = 0	Motion.	Controller: 7	Value: 114
...

etc

En branchant un module USB Host sur un Arduino Micro, et en modifiant un peu la partie décodage de mon code, on peut directement envoyer un mouvement de souris ou des touches clavier à l'ordinateur au Travers du câble USB.

Le shield qui a le format "UNO" a besoin des 2 GND connectés, reset, 3.3V, 5V, le port ICSP (SCK, MOSI, MISO; RESET) ainsi que les pins 9 et 10 (9 sert pour le INT) et 10 est le SS qui fait aussi partie du port ICSP.

Bonsoir JML
Super C'est déjà un trés bon point puisque çà evacue le(s) soft tiers
c'est quoi ton usb host shield ?

J'ai aussi pas mal avançé avec un pico2040 sous circuitpython et les libs adafruits circuitpython (MIDI HID)

Je n'ai pas le réflexe des interpréteurs , mais à l'évidence çà fonctionne plutôt bien.

Soi disant un "Funduino USB Host Shield" comme sur la photo ci dessus mais acheté il y a un moment en aide donc pas sûr que ce soit un vrai

Il y a une lib de fournie avec des exemples ?
EDIT vu dans ton prog plus haut
dans ton cas tu recupere comment les trames midi arrivant sur le host shield ?

C’est la librairie mentionnée dans le code il suffit de faire messageLength = usbMidi.RecvData(midiMessage); quand on sait qu’on a un message en attente

Oui , j'avais parcouru trop vite

pour l'instant j'ai ce ce qu'il me faut avec "mon usine à gaz

je vais voir comment muri le "MIDI-HOST" sur pico2040 avant de me procurer un usb host shield