[128] MIDI - język nie tylko instrumentów cz. 4
![[128] MIDI - język nie tylko instrumentów cz. 4](/_next/image?url=https%3A%2F%2Farduino.pl%2Fimgproxy%2F-wcrSF3jmGCnzqZA2fT-kGi3imlDLF_V9voHAOSvsUE%2Ff%3Awebp%2Fw%3A1200%2FbG9jYWw6Ly8vaW1hZ2VzLzEwLTY4L2NlOGI3L2E1NjE5LzEwLTY4Y2U4YjdhNTYxOTk5MDk4MDUxMTgucG5n.webp&w=3840&q=75)
W poprzednim artykule napisałem prosty szkic realizujący wysyłanie komunikatów MIDI za pomocą klawiaturki obecnej na płytce edukacyjnej TME. Program jednak nie przewidywał pracy polifonicznej. Dopóki był trzymany któryś z przycisków, całość blokowała się. Spróbujmy to naprawić.
const byte iloscKlawiszy = 5; // Liczba klawiszy.
const byte adresKlawiatury[] = { 5, 4, 8, 7, 6 }; // Adresy klawiszy.
const byte wysokoscNuty[] = { 55, 60, 64, 67, 72 }; // Wysokości nut przyporządkowane klawiszom w powyższej tablicy.
bool stanKomunikatu[] = { false, false, false, false, false }; // Wskaźniki zmieniane po zmianie stanu klawiatury.
byte indeks; // Zmienna licznika kolejnych pozycji tablic.
void setup() {
Serial.begin(31250); // Inicjuj port z nietypową szybkością 31250 bps
for (indeks = 0; indeks < iloscKlawiszy; indeks++) // Dla każdego pstryczka...
pinMode(adresKlawiatury[indeks], INPUT_PULLUP); // Deklaruj pin jako wejście podciągnięte wewnętrznie do wysokiego stanu.
}
void loop() {
for (indeks = 0; indeks < iloscKlawiszy; indeks++) { // Wybieraj kolejne klawisze.
if (digitalRead(adresKlawiatury[indeks]) == HIGH && stanKomunikatu[indeks] == false) { // Jeśli kolejny przycisk został wciśnięty i nie był wciśnięty wcześniej...
stanKomunikatu[indeks] = true; // Ustaw flagę informacji o wciśnięciu przycisku.
noteOn(); // Włącz nutę.
}
if (digitalRead(adresKlawiatury[indeks]) == LOW && stanKomunikatu[indeks] == true) { // Jeśli kolejny przycisk został puszczony i nie był wciśnięty wcześniej...
stanKomunikatu[indeks] = false; // Kasuj flagę informacji o wciśnięciu przycisku.
noteOff(); // Wyłącz nutę.
}
}
}
void noteOn() { // Włącz nutę.
Serial.write(144); // Wyślij komunikat "włącz nutę" na kanale pierwszym.
Serial.write(wysokoscNuty[indeks]); // Wyślij wysokość nuty.
Serial.write(64); // Wyślij głośność nuty.
}
void noteOff() { // Wyłącz nutę.
Serial.write(128);
Serial.write(wysokoscNuty[indeks]);
Serial.write(0);
}
Dołożyłem trzecią tablicę stanKomunikatu, w której będą dwustanowe flagi stanu komunikatu: fałsz, jeśli nuta nie gra i prawda, jeśli gra. Wypełniłem ją zmiennymi false dla porządku, w praktyce wpisuje się zera.
W pętli głównej znajdziemy dwa niezależne zdarzenia o bliźniaczym działaniu. Pierwsze sprawdza czy klawisz nie został wciśnięty i jaki jest stan flagi stanKomunikatu. Jeśli został wciśnięty i flaga ma stan niski, włączamy odpowiednią nutę i zmieniamy fladze stan na wysoki. Dzięki temu ponowne wejście w tę pętlę zignoruje konieczność wysyłania komunikatu włączenia nuty, bo flaga na to nie pozwoli.
Bliźniaczy warunek działa na odwrót: jeśli klawisz puszczono i flaga była ustawiona – co zrobił za pierwszym razem warunek poprzedni – wysyłamy komunikat końca grania nuty i zerujemy flagę, dzięki czemu gdy znowu wciśniemy ten klawisz, nuta zagra. Co byłoby, gdyby nie było flag? W przypadku puszczonych klawiszy atak komunikatów MIDI wyłączających nuty – to jeszcze instrument powinien przetrwać, choć retro maszyny mogą trochę wariować. Ale po wciśnięciu któregoś z klawiszy zacznie się bardzo szybka repetycja wciskania i w zależności od instrumentu, dojdzie do katastrofy dźwiękowej albo wręcz zawiesimy maszynę.
Po skompilowaniu kodu możemy już zagrać akordami. Hejnał jednak ma nikłą przydatność, o wiele bardziej praktyczny będzie na przykład sterownik perkusji.

Do zabaw, jak widać, używam Rolanda JD-Xi, ponieważ Casio z poprzednich artykułów nie ma dostępu do perkusji przez MIDI. Tutaj, jak to zwykle bywa, bębenki dostępne są na kanale dziesiątym – taki jest standard, ale zawsze należy to sprawdzić w dokumentacji instrumentu. Dlatego też podprogramy będą wymagały zmiany kanałów. Zwiększyłem też dynamikę stuków, bo wartość 64 dawała odgłosy niemrawe. Na panelu instrumentu wydrukowano ściągę, więc wybrałem pięć bębnów i wstawiłem ich adresy w tablicy.
const byte iloscKlawiszy = 5; // Liczba klawiszy.
const byte adresKlawiatury[] = { 5, 4, 8, 7, 6 }; // Adresy klawiszy.
const byte wysokoscNuty[] = { 54, 36, 41, 42, 46 }; // Wysokości nut przyporządkowane klawiszom w powyższej tablicy.
bool stanKomunikatu[] = { false, false, false, false, false }; // Wskaźniki zmieniane po zmianie stanu klawiatury.
byte indeks; // Zmienna licznika kolejnych pozycji tablic.
void setup() {
Serial.begin(31250); // Inicjuj port z nietypową szybkością 31250 bps
for (indeks = 0; indeks < iloscKlawiszy; indeks++) // Dla każdego pstryczka...
pinMode(adresKlawiatury[indeks], INPUT_PULLUP); // Deklaruj pin jako wejście podciągnięte wewnętrznie do wysokiego stanu.
}
void loop() {
for (indeks = 0; indeks < iloscKlawiszy; indeks++) { // Wybieraj kolejne klawisze.
if (digitalRead(adresKlawiatury[indeks]) == HIGH && stanKomunikatu[indeks] == false) { // Jeśli kolejny przycisk został wciśnięty i nie był wciśnięty wcześniej...
stanKomunikatu[indeks] = true; // Ustaw flagę informacji o wciśnięciu przycisku.
noteOn(); // Włącz nutę.
}
if (digitalRead(adresKlawiatury[indeks]) == LOW && stanKomunikatu[indeks] == true) { // Jeśli kolejny przycisk został puszczony i nie był wciśnięty wcześniej...
stanKomunikatu[indeks] = false; // Kasuj flagę informacji o wciśnięciu przycisku.
noteOff(); // Wyłącz nutę.
}
}
}
void noteOn() { // Włącz nutę.
Serial.write(153); // Wyślij komunikat "włącz nutę" na kanale pierwszym.
Serial.write(wysokoscNuty[indeks]); // Wyślij wysokość nuty.
Serial.write(100); // Wyślij głośność nuty.
}
void noteOff() { // Wyłącz nutę.
Serial.write(137);
Serial.write(wysokoscNuty[indeks]);
Serial.write(0);
}
W tym wydaniu jest to oczywiście sztuka dla sztuki, gdyż instrument ma klawiaturę, do tego dynamiczną i taki mało wygodny dubel nie ma sensu. Ale te styki mogą mieć zupełnie inny wymiar, na przykład elektronicznych bębnów wykonanych z mat przewodzących. Na scenie coś takiego może wyglądać bardzo efektownie, a Arduino – choćby małe Nano – pozamienia zwarcia takich mat na sygnały MIDI i pozwoli wyzwalać już dowolne źródło dźwięku.

Na koniec pragnę jeszcze zwrócić uwagę na polifonię, która przydaje się także w brzmieniach perkusyjnych, choćby po to, by uzyskać jednocześnie brzmienie stopy i talerza. Dobrze byłoby również zadbać o pewność przełączeń, byśmy nie dostali serii wystrzałów. Co prawda samo MIDI blokuje działanie programu na mniej więcej milisekundę dla każdego zdarzenia, ale dla klawiatur to za mało i wypadałoby dołożyć pętle opóźniające. Myślę, że z tym to już każdy sobie poradzi, jak również z rozbudowaniem klawiatury ponad moje pięć przycisków. W kolejnym artykule wyprodukujemy kilka pożytecznych przełączników MIDI.