[129] MIDI - język nie tylko instrumentów cz. 5
![[129] MIDI - język nie tylko instrumentów cz. 5](/_next/image?url=https%3A%2F%2Farduino.pl%2Fimgproxy%2F-0V2wBCUcmdgKwJ4MhXSYvBi8e7R7I4eJaq-H7DNU2c%2Ff%3Awebp%2Fw%3A1200%2FbG9jYWw6Ly8vaW1hZ2VzLzIwLTY4L2NlOGI3L2E5YjI3LzIwLTY4Y2U4YjdhOWIyNzYwNDY0NzI4NjkucG5n.webp&w=3840&q=75)
Klawiatura MIDI nie ma aż takiej użyteczności, gdyż zwykle muzycy mają już jakąś klawiaturę. Tutaj raczej rozwiązania takie mogą się przydać do różnego rodzaju wyzwalaczy dźwięków samplowanych, które można zbudować w postaci efektownych konstrukcji scenicznych. Za to tam przydają się tak zwane przydasie MIDI, z których najpopularniejszym – zwłaszcza wśród gitarzystów – są przełączniki efektów. Co prawda tańsze multiefekty są zarazem przełącznikami i rzadko znajdziemy w nich gniazdko MIDI, ale część tych najwyższej klasy stanowi osobny moduł, który należy wyzwalać zdalnie. Niezależnie tak samo bywa z syntezatorami. Gdy utwór wymaga zmiany brzmień między fragmentami, sprawniej zrobić to przełącznikiem nożnym, a nie z panelu instrumentu, wymagającego oderwania rąk od klawiatury. Wróćmy więc do naszego HT700 z pierwszego artykułu serii i zaprojektujmy kilka wersji przełączników programów. Na początek – wersja najprostsza i przydatna najczęściej, którą ja nazywam zwrotkorefrenem.
Czym jest piosenka, chyba każdy wie. Miewa zwrotki, refreny i czasem chwilę na solo. Bywa często tak, że zwrotki gra się brzmieniem łagodniejszym, refren – bardziej wyrazistym. Albo oba podobnym, a solo zdecydowanie innym. Czy to będzie gitarzysta, czy klawiszowiec, dobrze byłoby, gdyby miał pod nogą przełącznik: puszczony – brzmienie podstawowe, wciśnięty – brzmienie alternatywne.
const byte pstryczek = 8; // Adres pstryczka.
void setup() {
Serial.begin(31250); // Inicjuj port z nietypową szybkością 31250 bps
pinMode(pstryczek, INPUT_PULLUP); // Deklaruj pin pstryczka jako wejścia podciągnięte wewnętrznie do wysokiego stanu.
}
void loop() {
if (digitalRead(pstryczek) == HIGH) { // Jeśli pstryczek został wciśnięty...
Serial.write(192); // Wyślij komunikat "zmień program" na kanale pierwszym.
Serial.write(1); // Wyślij pierwszy numer programu.
delay(50); // Zabezpieczenie na drgające styki.
while (digitalRead(pstryczek) == HIGH) {} // Czekaj aż przycisk zostanie puszczony.
Serial.write(192); // Wyślij komunikat "zmień program" na kanale pierwszym.
Serial.write(0); // Wyślij zerowy numer programu.
delay(50); // Zabezpieczenie na drgające styki.
}
}
Program jest ogromnie prosty. Deklarujemy tylko adres przełącznika – ja tu korzystam jak zwykle z płytki edukacyjnej TME. Następnie definiujemy szybkość MIDI, pin – jako port wejściowy, a w głównej pętli nadsłuchujemy czy aby przełącznik nie został wciśnięty. Jeśli tak, to wysyłamy komunikat zmień program – jest on dwu, a nie trzybajtowy, jak to było w przypadku włączaniu dźwięków. Zaczyna się od adresu 192 – dla kanału pierwszego, więc taką wartość umieściłem. W drugim bajcie określa się numer programu – od zera do 127. Po wciśnięciu przycisku wyślemy prośbę o włączenie programu pierwszego. Następnie będziemy czekać na puszczenie przycisku i gdy to nastąpi, wyślemy prośbę o włączenie programu zerowego.

Jest pewien problem z tymi zerami. Zero w MIDI oznacza zwykle jedynkę na panelu instrumentu, bo programy zwykło się numerować od jedynki, a nie zera. Trzeba o tym pamiętać i brać poprawki na owo przesunięcie. Jak widać, mój instrument ma dziesięć przycisków, ale mamy jeszcze trzy przełączniki, dające w sumie dostęp do 6 banków, czyli 60 programów. Co oznacza, że sensowny zakres wysyłany zawiera się w przedziale 0-59. Wysłanie sześćdziesiątki nie zrobi cudownie kolejnego banku, tylko zostanie zignorowane. Ale różnie to bywa i tę rzecz trzeba sprawdzać w instrukcji obsługi konkretnego urządzenia.
W programie znajdziemy jeszcze dwa opóźnienia eliminujące niestabilności przełącznika. Ponieważ my tu tylko zmieniamy brzmienia, takie uproszczenie, czy jak by to powiedzieli niektórzy, marnotrawstwo, jest dopuszczalne. Kompilujemy i teraz wciskanie przycisku na płytce zmienia program, co jest i słyszalne, i widzialne. No i fajnie, ale nie do końca: aktywacja pierwszego programu wymaga trzymania pstryczka. Czasem lepiej byłoby go po prostu wciskać i dostawać zmiany – raz program zero, raz jeden. A więc do dzieła.
const byte pstryczek = 8; // Adres pstryczka.
bool flaga = false; // Zmienna negowana po każdym wciśnięciu pstryczka.
void setup() {
Serial.begin(31250); // Inicjuj port z nietypową szybkością 31250 bps
pinMode(pstryczek, INPUT_PULLUP); // Deklaruj pin pstryczka jako wejścia podciągnięte wewnętrznie do wysokiego stanu.
}
void loop() {
if (digitalRead(pstryczek) == HIGH) { // Jeśli pstryczek został wciśnięty...
flaga = !flaga; // Zaneguj tzw. flagę.
Serial.write(192); // Wyślij komunikat "zmień program" na kanale pierwszym.
Serial.write(flaga); // Wyślij numer programu zgodny ze stanem flagi (zerowy albo pierwszy).
delay(50); // Zabezpieczenie na drgające styki.
while (digitalRead(pstryczek) == HIGH) {} // Czekaj aż przycisk zostanie puszczony.
delay(50); // Zabezpieczenie na drgające styki.
}
}
Powołamy do życia zmienną dwustanową, zwaną flagą – i tak ją nazwałem. Po wciśnięciu pstryczka będziemy ją negować, więc po każdym nieparzystym wciśnięciu będzie prawdą – czyli jedynką, a po parzystym – nieprawdą, czyli zerem. Teraz już można będzie ją wysłać jako argument polecenia zmiany programu.
Teraz obsługa przycisku działa jak długopis. Raz wciskamy – program pierwszy, drugi raz wciskamy – program zerowy. I tak w kółko. Kilka słów wyjaśnień. Tutaj oczywiście mamy wybór między programem zero i jeden, ale możemy tam wpisać dowolne przesunięcie czy funkcję, by uzyskać dostęp do każdego programu. Przyjęło się jednak ładować użyteczne programy na scenę na najniższych pozycjach, by mieć do nich łatwy dostęp i pewną rutynę: w czasie grania nie ma czasu na zastanawianie się gdzieśmy upchali to właśnie brzmienie, które jest teraz potrzebne.
Zróbmy teraz wersję pięciokanałową. Akurat taką, bo mam tyle przełączników na płytce, ale dopasowanie ich liczby będzie bardzo proste.
const byte iloscKlawiszy = 5; // Liczba klawiszy.
const byte adresKlawiatury[] = { 5, 4, 8, 7, 6 }; // Adresy klawiszy.
const byte numerProgramu[] = { 0, 1, 2, 3, 4 }; // Numery programów przyporządkowane klawiszom w powyższej tablicy.
void setup() {
Serial.begin(31250); // Inicjuj port z nietypową szybkością 31250 bps
for (byte x = 0; x < iloscKlawiszy; x++) // Dla każdego pstryczka...
pinMode(adresKlawiatury[x], INPUT_PULLUP); // Deklaruj pin jako wejście podciągnięte wewnętrznie do wysokiego stanu.
}
void loop() {
for (byte x = 0; x < iloscKlawiszy; x++) // Wybieraj kolejne klawisze.
if (digitalRead(adresKlawiatury[x]) == HIGH) { // Jeśli kolejny przycisk został wciśnięty...
Serial.write(192); // Wyślij komunikat "zmień program" na kanale pierwszym.
Serial.write(numerProgramu[x]); // Wyślij numer programu.
delay(50); // Zabezpieczenie na drgające styki.
while (digitalRead(adresKlawiatury[x]) == HIGH) {} // Czekaj aż przycisk zostanie puszczony.
delay(50); // Zabezpieczenie na drgające styki.
}
}
Sięgniemy po wersję z tablicą, która wystąpiła w poprzednim artykule. Przypomnę: będziemy bazować na dwóch równoległych tablicach. W jednej będą spisane adresy pinów, z których zrobimy port klawiatury, w drugiej – numery programów. U mnie – po kolei, ale można tu wpisać cokolwiek.
Program bazuje na pętlach powtarzających się tyle razy, ile mamy przycisków, czyli tutaj: pięć. W pierwszej deklarujemy piny, robiąc z nich porty wejściowe, w drugiej, już w zasadniczej części programu, przepytujemy kolejno wszystkie przyciski. Jeśli któryś z nich został wciśnięty, wysyłamy komunikat zmiany programu i jego numer według indeksu drugiej z tablic. Potem jeszcze 50 milisekund przerwy, oczekiwanie na puszczenie przycisku, znowu 50 ms przerwy i powrót do pętli. Zauważmy, że indeksujemy dane zmiennymi lokalnymi, a nie jak poprzednio – globalną, ponieważ nie musimy już korzystać z podprogramu.
Działa to znakomicie, ale na scenie, zwłaszcza gdy jest ciemno, dobrze mieć wyraźny podgląd co akurat jest włączone. W kolejnym artykule zrobimy użytek z diody RGB.