[W] Własny kontroler MIDI, cz. 3

Potencjometr obrotowy - enkoder

Po suwaku potencjometr obrotowy to drugi element, który chciałbym wykorzystać w swoim kontrolerze - cztery suwaki i cztery potencjometry, to będzie to. I z potencjometrami jest pewna zagwozdka, bo trzeba bardzo uważać, co się kupuje. Nie chodzi mi nawet o same parametry potencjometru (że powinien być liniowy o rezystancji maksymalnej 10kOhm), ale o to, że na rynku istnieją bardzo podobnie wyglądające elementy: enkodery. Zewnętrzne różnica jest niewielka (choć liczba wyprowadzeń większa niż 3 powinna wzbudzić naszą czujność), ale funkcjonalnie to zupełnie co innego.

Nie wchodząc w szczegóły: potencjometr "zwraca" wartość, która jest aktualnie na nim ustawiona, czyli robi dokładnie to, co suwak (tyle, że zamiast "długości" używa "kąta obrotu"). Co za tym idzie, potencjometr taki ma swój "początek" (najmniejsza wartość) i "koniec" (największa wartość).

Enkoder w roli potencjometru

Enkoder z kolei działa "przyrostowo". Obrócenie go generuje impulsy i to od programisty zależy, czy i jak będzie je zliczał oraz czy... wykryje kierunek obrotu. Oprogramowanie takiego scenariusza nie jest skomplikowane, ale jeśli można coś uprościć, upraszczajmy. Dlatego skorzystałem z biblioteki SimpleEncoder, która dodatkowo potrafi przechować "zliczoną" wartość, więc można używać enkodera jak normalnego potencjometru, jeśli się ktoś uprze:

#include <SimpleEncoder.h>
#include <MIDIUSB.h>

const int CHANNEL = 0;
const int CC = 3;

int S1 = 9;
int S2 = 8;
int BUTTON = 7;

int current_value;
const int MAX_VALUE = 127;
const int MIN_VALUE = 0;
const int STEP = 2;

SimpleEncoder encoder(BUTTON, S1, S2, current_value, MIN_VALUE, MAX_VALUE, STEP);

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

void loop() {
  if(encoder.changing()) {
    controlChange(encoder.getValue());
  }
  if (encoder.buttonPressed() && encoder.getValue() > MIN_VALUE) {
    encoder.setValue(MIN_VALUE);
    controlChange(MIN_VALUE);
  }
  delay(2);
}

void controlChange(byte value) {
  midiEventPacket_t event = {0x0B, 0xB0 | CHANNEL, CC, value};
  MidiUSB.sendMIDI(event);
  MidiUSB.flush();
}

Jak widać, kod nie jest skomplikowany - wyjścia S1 i S2 enkodera odpowiedzialne za obrót podpiąłem do pinów 9 i 8 Arduino, wyjście przycisku (bo mój enkoder jest "klikalny") podpięte jest do pinu 7. Program tworzy obiekt SimpleEncoder, podając w parametrach używane piny oraz - co jest już opcjonalne - zmienną do przechowywania wartości oraz minimalną i maksymalną wartość, jaką można ustawić enkoderem. Ja jeszcze przekazałem wartość skoku, bo skok w moim enkoderze jest spory, więc żeby przejść od zera do 127, trzeba się było sporo nakręcić.

Potem idzie już łatwo, w głównej pętli sprawdzam, czy zmieniło się położenie enkodera lub użyto go jako przycisku i wysyłam stosowny komunikat Continous Controller, tu o numerze 3 na sztywno na kanale 0. Przycisk zaprogramowałem tak, że restuje enkoder, to znaczy ustawia mu wartość MIN_VALUE.

Potencjometr obrotowy - zwykły

Z "normalnym" potencjometrem sprawa jest prosta - ma trzy wyprowadzenia, dwa to masa i napięcie +5V, trzecią nóżkę podłączamy do analogowego pinu w Arduino.

I potencjometr... w roli potencjometru

I tyle, program jest banalny:

#include <MIDIUSB.h>

int POT_PIN = A0;
int last_state = 0;
const byte CHANNEL = 0;
const byte CC = 1;

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

void loop() {
  int curr_state = analogRead(POT_PIN);
  int diff = abs(curr_state - last_state);
  if (diff > 0) {
    int value = map(curr_state, 0, 1023, 0, 127);
    controlChange(value);
  }
  delay(5);
}

void controlChange(byte value) {
  midiEventPacket_t event = {0x0B, 0xB0 | CHANNEL, CC, value};
  MidiUSB.sendMIDI(event);
  MidiUSB.flush();
}

Definiujemy tradycyjnie pin, numer komunikatu CC i kanał MIDI oraz zmienną przechowującą bieżącą wartość parametru, żeby niepotrzebnie nie wysyłać komunikatów MIDI i tyle. No, jeszcze wartość potencjometru trzeba przekodować z tradycyjnych 0-1023 na 0-127 i tyle.

Analogowy joystick jako kontroler XY

Zamówiłem też prosty komponent analogowego joysticka - jest całkiem dużo wirtualnych instrumentów, które posiadają pole XY do sterowania brzmieniem. Jedna oś odpowiada za jeden parametr, druga za drugi. Docelowo chciałbym wykorzystać do podobnego sterowania jakiś panel dotykowy, ale na razie posłużyłem się tanim "grzybkiem", znanym choćby z padów do gier.

Wbrew pozorom, prototyp kontrolera wykorzystującego joystick jest dość prosty. Musimy połączyć cztery piny wychodzące z joysticka z płytką stykową: GND do masy, VCC do +5V, a piny odpowiadające osiom X i Y - do analogowych wejść Arduino (ja wykorzystałem A0 i A1):

Analogowy joystick to jak dwa potencjometry

Kod programu do obsługi również nie jest skomplikowany:

#include <MIDIUSB.h>

const int AXIS = 2;
const int CCS[AXIS] = {1, 11};
const int AXIS_PINS[AXIS] = {A0, A1};
int axis_states[AXIS] = {0, 0};
int axis_curr_states[AXIS] = {0, 0};
int value = 0;

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

void loop() {
  for (int i = 0; i < AXIS; i++) {
    value = analogRead(AXIS_PINS[i]);
    axis_curr_states[i] = map(value, 0, 1023, 0, 127);
    value = abs(axis_curr_states[i] - axis_states[i]);
    if (value > 0) {
      axis_states[i] = axis_curr_states[i];
      controlChange(CCS[i], axis_states[i]);
    }
  }
  delay(5);
}

void controlChange(byte control, byte value) {
  int channel = 0;
  midiEventPacket_t event = {0x0B, 0xB0 | channel, control, value};
  MidiUSB.sendMIDI(event);
  MidiUSB.flush();
}

Dałoby się go pewnie nieco skrócić, bo trochę nadmiarowo wykorzystałem kilka tablic - ale to dlatego, że chciałem mieć możliwość zmiany np. numerów CC czy analogowych pinów, bez śledzenia całego kodu programu.

Program cyklicznie (co 5 milisekund) odczytuje kolejno pin A0 i A1 (kody tych pinów są w tablicy AXIS_PINS) i przelicza je na wartości używane w komunikatach CC, czyli 0-127. Sam joystick wystawia w obu osiach wartości 0-1023, stąd wykorzystanie wygodnej funkcji map(), przyjmującej wartość do przeliczenia oraz dwa zakresy: wejściowy (0-1023) i wyjściowy (0-127).

Komentarze