SCHEMA

Pour l'électronique de commande, j'ai choisi une Arduino pro mini. Il n'y a pas d'USB, mais ce n'est pas une nécessité. En effet, il suffit d'avoir 2 entrées analogiques pour le joystick et une sortie digitale pour la prise midi. De plus, car carte coute 1.5 euros. Contrepartie, lors du développement il faut utiliser un câble spécial pour la programmation : https://www.ftdichip.com/Products/Cabl||schemac_bb.png, déc. 2018))es/TTL234XSerial.htm , mais j'en disposais déjà d'un.

Pour le joystick, j'en ai récupéré un d'assez bonne qualité (du genre de celui-ci) avec des potentiomètres de 5K, ce qui semble être le standard. Il est toutefois possible de monter à peu près n'importe quoi. De nombreux joysticks de remplacement de manettes de jeux sont en vente et feront parfaitement le job pour quelques euros (exemple: ICI) . Un joystick démonté d'une manette de drone HS peut le faire également.

La sortie midi out ne nécessite que 2 résistances de 220 ohms., l'une au 5V et l'autre en série sur la sortie TTL et un connecteur DIN 5 broches..

schemac_bb.png.

FONCTIONNEMENT

Le joystick avec retour au centre pose un problème car si c'est le fonctionnement attendu pour le pitch bend), pour l'autre fonction (modulation), c'est un inconvénient, la position de repos éant au zéro et non au centre.

Le SE-02 gérant également l'aftetouch donc le clavier K25-m n'est pas non plus pourvu il m'a semblé intéressant que ce second axe puisse être utilisé différemment en le divisant en deux : la partie supérieure pour la modulation, et la partie inférieure pour l'aftertouch. Ainsi le retour au centre n'est plus un problème et le joystick y gagne une fonction. J'ai choisi l'axe X pour le pitch bend, car c'est plus naturel avec le pouce gauche de pousser à droite pour un bend up. Ves le haut, la modulation, et vers le bas, l'aftertouch. Seul inconvénient de cette configuration, c'est qu'il est impossible d'avoir aftertouch et modulaton en même temps.

PROGRAMME

Plusieurs problèmes ont étés à régler d'un point de vue logiciel :

  • La calibration du joystick
  • La plage de variation / conversion sur chaque axe
  • La création d'une "zone morte" au centre, afin d'éviter l'envoi de spontané de contrôleurs sur les erreurs de conversions

Coté calibration, l'angle de variation du potentiomètre n'étant que d'un fragment de sa course, la tension de sortie n'évolue pas entre 0 et 5V. La conversion ne se fait donc pas en plein échelle. Il faut donc relever les valeurs de conversion min et max de chaque axe afin de les reporter dans le code (Xmini/Xmaxi/Ymini/Ymaxi). J'ai fait le choix de ne pas intégrer de logique de calibration, qui aurait nécessité un poussoir pour l'armer et l'utilisation de l'E2prom pour sauvegarder les réglages.

Le résultat de conversion - avec le joystick choisi - donne environ 256 valeurs; C'est suffisant pour l'aftertouch et la modulation, qui évoluent entre 0 et 127, mais ne le serait pas pour le pich bend, qui devrait être codé sur 14 bits (16384). J'ai donc choisi de n’utiliser que les 7 bits de poids fort pour le bend. A l'usage, ce n'est pas dérangeant.

En utilisant directement les valeurs converties, et à cause des jeux mécaniques sur le joystick, on assiste à une variation du point milieu de plusieurs unités. C'est également ce qu'on relève sur une manette de jeu. Cela a pour inconvénient de provoquer l'envoi quasi permanent de code midi.

J'ai donc créé une zone "morte" au centre, qui tant que les variations sont dans cette zone ne déclenchent pas l'envoi de code midi (hystérisis) et établissent une "zone de repos" au lieu d'un "pojnt de repos". Elle se règle avec la variable "blind" et vaut actuellement +- 4 unités. Une fonction spécifique "coupe" cette bande centrale pour inhiber le bruit et créer cet hystérisis.

Les valeurs résultantes sont stockées, afin de les comparer aux précédentes valeurs envoyées. Ainsi l'envoi de code midi n'est fait que si il y a une variation réelle des valeurs. Cela limite la transmission au strict nécessaire. Le canal midi est codé en dur sur le canal 1 mais peut être changé (variable "midichannel").

Un petit délai de 2 ms (réglable dans la variable mididelay) a été introduit après chaque envoi de code midi, pour limiter la densité de l'envoi.

Enfin, j'ai utilisé la led interne pour :

  • S'allumer en permanence pour signaler que le Joystick est dans la zone morte / zone de repos
  • S'éteindre lors qu'il est en dehors
  • S'allumer brièvement à chaque transmission série / midi.

Voici le code :

/*
 * A MIDI Joystick controller
 ******************************************************************
 * Developed on Arduino Pro mini 5V/16Mhz, but any arduino works...
 * 
 * Description
 ******************************************************************
 * A joytick with center return is used as midi controlle.
 * X Axis : Bend (from < to >)
 * Y Axis:
 *    Up drives modulation
 *    Down drives aftertouch
 *    
 * Hardware / Wiring
 ******************************************************************
 *  - digital in 1 connected to MIDI jack pin 5
 *  - MIDI jack pin 2 connected to ground
 *  - MIDI jack pin 4 connected to +5V through 220 ohm resistor
 *  - For X and Y potentiometers, one ends go to +5v, the other end to 0V. 
 *  - X potentiometer cursor is wired to A0
 *  - Y potentiometer cursor is wired to A1
 *  
 *  SENT MIDI CODES 
 ******************************************************************
 * Pitch bend : 0xe0
 * data1 = 0b0xxxxxxx LSB (always zero in our case)
 * data2 = 0b0yyyyyyy MSB (MSB=0x40 = center)
 * 
 * Channel Pressure : 0xD0
 * data  = 0b0vvvvvvv Pressure value (7 bits)
 * 
 * Controller change : 0xB0
 * data1 = control number (0 > 119) - Modulation Wheel is 1
 * Data2 = 0b0vvvvvvv Control value (7 bits)
 * 
 ******************************************************************
 * (c) JP CIVADE 12/2018
 * jp@civade.com / http://www.civade.com
 * Licence : GPL V3
 ****************************************************************** 
 */
// Onboard led
const int ledPin =  LED_BUILTIN;// the number of the LED pin
// Analog pins
const int axex = A0;  // X axis
const int axey = A1;  // Y axis
// Joystick Calibration. Minimul and maximum value read on ADC.
const int Xmini=399;
const int Xmaxi=615;
const int Ymini=397;
const int Ymaxi=616;
// Mifi channel
const int midichannel=1; // From 1 to 16

// blind spot 
const int blind=4;

// Delay after midi send. Allow led to blink a little more... 
const int mididelay = 2;

// Vars
int vx = 0;                     // value read from the pot
int vy = 0;
int outputx = 0;                // Linearized values
int outputy = 0;
// Computed values
unsigned char bend = 0;
unsigned char modulation = 0;
unsigned char aftertouch = 0;
unsigned char oldbend = -1;
unsigned char oldmodulation = -1;
unsigned char oldaftertouch = -1;

void setup() {
  // initialize serial communications for debug:
  // Serial.begin(115200);
  // Serial communications for Midi
  Serial.begin(31250);
}

void loop() {
  // read the analog in value:
  vx = analogRead(axex);
  vy = analogRead(axey);

  // map it to the range of the analog out:
  outputx = map(vx, Xmini, Xmaxi, -(127+blind), 127+blind);
  outputy = map(vy, Ymini, Ymaxi, -(127+blind), 127+blind);

  // remove the blind windows :
  // -(max+blind) ---- [-(Min+blind) 0 +(min+blind)] ----  +(max+blind) 
  // becomes
  // -max                            0                     +max 
  if (abs(outputx)<blind)  {
    outputx =0; 
    }
    if (outputx<=-blind) {
      outputx += blind;
      } 
  if (outputx >=blind) {
      outputx -= blind;
      }
  if (abs(outputy)<blind)  {
    outputy =0; 
    }
  if (outputy<=-blind) {
      outputy += blind;
      } 
  if (outputy >=blind) {
      outputy -= blind;
      }

  // crate value for bend
  bend = (outputx/2)+64;

  // Create modulation (+y) and after touch (-y)
  if (outputy>0) { // modulation
    aftertouch=0;
    modulation=outputy;
    if (modulation > 127) 
      modulation=127;
    }
  else if (outputy<0) { // after touch
    modulation=0;
    aftertouch = abs (outputy);
    if (aftertouch > 127)
      aftertouch=127;
    }
  else { // 0
    aftertouch=0;
    modulation=0;
    }

  if (bend!= oldbend) {
    oldbend=bend;
    // send bend
    digitalWrite(ledPin, HIGH);
    Serial.write (0xe0|(midichannel-1));
    Serial.write (0x00);
    Serial.write (bend);
    delay(mididelay);
    digitalWrite(ledPin, LOW);
    }
  if (modulation!=oldmodulation) {
    oldmodulation=modulation;
    // send modulation
    digitalWrite(ledPin, HIGH);
    Serial.write (0xB0|(midichannel-1)); // CC   
    Serial.write (0x01); // Modulation Wheel Coase
    Serial.write (modulation);
    delay(mididelay);
    digitalWrite(ledPin, LOW);
    }
  if (aftertouch!=oldaftertouch) {
    oldaftertouch=aftertouch;
    // send aftertouch
    digitalWrite(ledPin, HIGH);
    Serial.write (0xd0|(midichannel-1));
    Serial.write (aftertouch);
    delay(mididelay);
    digitalWrite(ledPin, LOW);
    }

    // 
    if ((aftertouch==0) && (bend==64) && (modulation==0) )
      digitalWrite(ledPin, HIGH);
    else
      digitalWrite(ledPin, LOW);
    
/* Debug  
  // print the results to the Serial Monitor:
  Serial.print("vx = ");
  Serial.print(vx);
  Serial.print("\t vy = ");
  Serial.print(vy);
  Serial.print("\t bend = ");
  Serial.print(bend);
  Serial.print("\t oy = ");
  Serial.print(outputy);
  Serial.print("\t modulation = ");
  Serial.print(modulation);
  Serial.print("\t aftertouch = ");
  Serial.println(aftertouch);
*/
  
  // wait 2 milliseconds before the next loop for the analog-to-digital
  // converter to settle after the last reading:
  delay(2);
}