[091] Przejmowanie kontroli nad urządzeniami - multiefekt gitarowy - cz. 4
![[091] Przejmowanie kontroli nad urządzeniami - multiefekt gitarowy - cz. 4](/_next/image?url=https%3A%2F%2Farduino.pl%2Fimgproxy%2FUNVZz0cULxD8Sbq-kORvxTuLjYyyo3A497-1-dpmn3s%2Ff%3Awebp%2Fw%3A1200%2FbG9jYWw6Ly8vaW1hZ2VzLzBkLTY3Lzk5MDA2LzVkY2RjLzBkLTY3OTkwMDY1ZGNkY2UyNDM1OTAwOTkucG5n.webp&w=3840&q=75)
W tej, czwartej już części zakończymy projekt. Mając już działającą obsługę dwóch elementów multiefektu: regulacji niskich tonów i wysokich, możemy rozbudowywać sterownik w nieskończoność, a raczej do momentu przydzielenia gałkom wszystkich elementów. Jak wiadomo, płytka Uno ofiarowuje nam łaskawie sześć kanałów przetwornika i na razie poprzestaniemy na tychże. Istnieją dwie szkoły rozbudowywania programów, które pozornie robią to samo, ale w szczegółach – nie do końca. O ile regulacja basów i sopranów wyglądała identycznie: dziewięć pozycji z neutralną w środku, przy kolejnych efektach będzie inaczej. Jedna ze szkół mówi, by działać po kolei, dokładając po efekcie, druga – zróbmy wszystko naraz, może uda się od razu i nie trzeba będzie niczego poprawiać.
Ponieważ tutaj mamy owe tablice skoków, o których pisałem i kolejne ich modyfikacje byłyby nużące, postanowiłem realizować całą resztę naraz, bez uruchamiania etapów pośrednich. Przy czym tu także praca będzie dwuetapowa: najpierw rozmnożymy bloki sterujące zmianą parametrów kolejnych efektów, a potem uzupełnimy tablice i niezbędne zmienne.
Będziemy dokładać elementy kolejno, idąc w prawo, gdy spojrzymy na panel urządzenia. Zaraz za regulacją sopranów mamy blok modulacji – stąd skrót MOD w zmiennych. Ale jeszcze chwilę – wprowadźmy pewną zmianę: operacje wysyłania „klików” będziemy czynić już nie w chwili, gdy sąsiednie odczyty pozycji potencjometru przekroczą 3, a aż 7. Zyskamy jeszcze większe oddalenie od drgań ślizgacza, a rozdzielczość obrotu gałki to nadal 146 pozycji (1024 dzielone przez 7), dużo więcej niż nam potrzeba. Będzie jeszcze jedna zmiana, ale o tym za chwilę.
Wracamy do wspomnianego bloku regulacji efektów modulacyjnych. Instrukcja urządzenia wspomina aż o 109 pozycjach, które może przyjmować ten element:
OF - wyłączony
C1...C4/C5...C9 - Chorus1/Chorus2
F1...F4/F5...F9 - Flange1/Flange2
P1...P4/P5...P9 - Phaser1/Phaser2
t1...t4/t5...t9 - Tremolo1/Tremolo2
n1...n9 - Panner
b1...b9 - Vibrato
r1...r9 - Rotary Speaker
A1...A9 - AutoYa
E1...E9 - Envelope Filter
d1...d4/d5...d9 - Detune1/Detune2
H1...H9 - Pitch Shift (-12/-7/-5/-4/+3/+4/+5/+7/+12)
Y1...Y9 - Whammy (OctUp/2OctUp/OctDn/2OctDn/m3rd-Maj3rdUp/2nd-Maj3rdUp/3rd-4thUp/OctUp/OctDn)
Zaoferowano tu dwanaście algorytmów opóźniających, a każdy może przyjmować dziewięć wartości. Po przeanalizowaniu odrzuciłem wszystkie poza pierwszymi czterema. Oczywiście nie ma takiego obowiązku, po prostu nie używam pozostałych, a przyspieszy mi to operacje gałką potencjometru. Tak więc potrzeba tu 9*4 pozycji plus jedna, która wyłącza pracę tego bloku. Ponieważ mapowanie przeprowadzam od jedynki, ostatnią wartością będzie 38.
zmianaMOD = analogRead(potencjometrMOD);
if (abs(wartoscMOD - zmianaMOD) > 7) {
wartoscMOD = zmianaMOD;
skokMenu = tablicaMOD[numerPotencjometru];
przewin();
numerPotencjometru = 2;
byte przeskalowanyMOD = map(wartoscMOD, 0, 1024, 1, 38);
while (wirtualnyMOD > przeskalowanyMOD) {
wcisnijMinus(86);
wirtualnyMOD--;
}
while (wirtualnyMOD < przeskalowanyMOD) {
wcisnijPlus(86);
wirtualnyMOD++;
}
}
Okazało się, już później, że o ile regulacja basów i sopranów działała niezawodnie, kręcenie gałką efektów gubiło część pozycji. Opóźnienie 32 ms dla tego elementu było zbyt krótkie. Niestety, z tym będziemy spotykać się często. Już w przykładzie z monitorem był taki problem: niektóre operacje mogły przebiegać szybciej, inne – wolniej. Metodą prób i błędów ustaliłem ów czas na 86 ms. Ale jak teraz zarządzać czasem, żeby za każdym razem używać możliwie jak najkrótszego?
Do tej pory używaliśmy podprogramów w sposób najprostszy: skaczemy, coś tam robimy i wracamy. Tym razem dobudujemy możliwość przekazywania parametru, który będzie czasem opóźnienia. Wstawia się go w puste dotąd nawiasy i odzyskuje w podprogramie, powołując co życia zmienną lokalną y, również w nawiasie.
wcisnijMinus(32);
void wcisnijMinus(byte y) { // Procedura wciskania przycisku Minus.
digitalWrite(przyciskMinus, HIGH);
digitalWrite(led, HIGH);
delay(y);
digitalWrite(przyciskMinus, LOW);
delay(y);
}
W ten sposób wchodząc tu z regulacji basów załadujemy 32 ms, a z wyboru trybu modulacji – 86 ms. Oczywiście wspomniane 32 ms należy teraz dopisać w bloku basów i sopranów. To teraz popracujemy hurtem.
zmianaDEL = analogRead(potencjometrDEL);
if (abs(wartoscDEL - zmianaDEL) > 7) {
wartoscDEL = zmianaDEL;
skokMenu = tablicaDEL[numerPotencjometru];
przewin();
numerPotencjometru = 3;
byte przeskalowanyDEL = map(wartoscDEL, 0, 1024, 1, 29);
while (wirtualnyDEL > przeskalowanyDEL) {
wcisnijMinus(38);
wirtualnyDEL--;
}
while (wirtualnyDEL < przeskalowanyDEL) {
wcisnijPlus(38);
wirtualnyDEL++;
}
}
W czwartym bloku będziemy wybierać tryb echa. Mamy trzy jego rodzaje po 9 ustawień plus wyłączenie – razem 28 pozycji. Wykorzystamy wszystkie, więc wpisujemy tu 29. Empirycznie czas opóźnień ustaliłem na 38 ms.
zmianaTIM = analogRead(potencjometrTIM);
if (abs(wartoscTIM - zmianaTIM) > 7) {
wartoscTIM = zmianaTIM;
skokMenu = tablicaTIM[numerPotencjometru];
przewin();
numerPotencjometru = 4;
byte przeskalowanyTIM = map(wartoscTIM, 0, 1024, 1, 102);
while (wirtualnyTIM > przeskalowanyTIM) {
wcisnijMinus(32);
wirtualnyTIM--;
}
while (wirtualnyTIM < przeskalowanyTIM) {
wcisnijPlus(32);
wirtualnyTIM++;
}
}
W piątym bloku wybieramy czas powtórek echa. Tym razem pozycji jest aż 101, więc przewinięcie wszystkich chwilę potrwa. Na szczęście czas opóźnień jest tu znowu tak krótki, jak w przypadku korektora: 32 ms No i w końcu mamy ostatni element: pogłos.
zmianaREV = analogRead(potencjometrREV);
if (abs(wartoscREV - zmianaREV) > 7) {
wartoscREV = zmianaREV;
skokMenu = tablicaREV[numerPotencjometru];
przewin();
numerPotencjometru = 5;
byte przeskalowanyREV = map(wartoscREV, 0, 1024, 1, 56);
while (wirtualnyREV > przeskalowanyREV) {
wcisnijMinus(35);
wirtualnyREV--;
}
while (wirtualnyREV < przeskalowanyREV) {
wcisnijPlus(35);
wirtualnyREV++;
}
}
Różnych kombinacji mamy tutaj sześć w dziewięciu – jak zwykle – krokach plus odłączenie efektu, co daje 55 pozycji. Zmiany mogą być dość szybkie, wystarczy 35 ms. Czas zająć się deklaracjami i tablicami.
const byte tablicaBAS[] = { 0, 2, 3, 4, 5, 6 }; // Tablice skoków przez menu, zależne od ostatnio użytego potencjometru.
const byte potencjometrBAS = A0; // Adres portu potencjometru.
int wartoscBAS = 512; // Wartość odczytanego napięcia pochodzącego ze ślizgacza potencjometru.
int zmianaBAS = 512; // Tymczasowa wartość odczytanego napięcia do porównań.
byte wirtualnyBAS = 5; // Pozycja wirtualnego potencjometru odpowiedzialnego za poziom parametru.
W przypadku deklaracji zmiennych sprawa jest prosta: powielamy pierwszy blok, zmieniając tylko nazwy i adresy pinów kolejnych potencjometrów. Z tablicami tak prosto nie jest. Musimy wrócić do poprzedniego artykułu i przeanalizować ich logikę. Tu tylko przypomnę: kolejne liczby oznaczają ile razy należy wcisnąć przycisk menu, by znaleźć się tutaj z poszczególnych pozycji. A więc dla basów: zero – jeśli już tu jesteśmy, dwie – z sopranów, trzy – z efektów modulacyjnych, cztery – z typów echa, pięć – z czasów echa i sześć – z pogłosów. Dla sopranów będzie podobnie, z tym że wracając tu z regulacji basów trzeba przycisk menu wcisnąć aż 11 razy. I tak dalej, aż do ostatniego – pogłosu.
const byte tablicaTRE[] = { 11, 0, 1, 2, 3, 4 };
const byte tablicaMOD[] = { 10, 12, 0, 1, 2, 3 };
const byte tablicaDEL[] = { 9, 11, 12, 0, 1, 2 };
const byte tablicaTIM[] = { 8, 10, 11, 12, 0, 1 };
const byte tablicaREV[] = { 7, 9, 10, 11, 12, 0 };
Czas na kompilację i – gdy wszystkie wartości będą ustawione zgodnie z założeniami, będziemy mogli już regulować dowolny z sześciu parametrów. Do niektórych będziemy mieć dostęp prawie natychmiastowy, inne będą wymagać cierpliwości, ale zawsze w końcu sczytają ustawienie potencjometrów. W praktyce przeprowadza się drobne korekty i w tym wypadku opóźnienia nie przeszkadzają. Czy można tu coś poprawić? Jak najbardziej. Wystarczy dołożyć transoptor do przycisku menu pozwalającego poruszać się w prawo. Wówczas najdłuższy skok, między basami, a pogłosem, wymagałby sześciu wciśnięć menu, a średnia skoków wyniosłaby trzy. Wypadałoby kodować skoki w jedną stronę liczbami dodatnimi, w drugą – ujemnymi. Myślę, że większość czytelników, którzy dotarli do tego momentu, nie miałaby problemów z rozbudowaniem programu o tę opcję. Sześć gałek to jednak trochę mało. Spróbujmy użyć ośmiu – ale o tym już w kolejnym artykule, a tu, na koniec cały listing gotowego programu, obsługującego sześć gałek.
const byte led = 13; // Numer pinu, do którego podłączona jest dioda świecąca.
const byte przyciskMenu = 12; // Numer pinu, do którego podłączony jest przycisk wybierający parametr do edycji.
const byte przyciskPlus = 11; // Numer pinu, do którego podłączony jest przycisk podwyższający edytowany parametr.
const byte przyciskMinus = 10; // Numer pinu, do którego podłączony jest przycisk obniżający edytowany parametr.
byte numerPotencjometru = 0; // Numer ostatnio zmienianego potencjometru.
byte skokMenu = 7; // Ilość wciśnień klawisza Menu, zależna od ostatnio aktywnego potencjometru.
const byte tablicaBAS[] = { 0, 2, 3, 4, 5, 6 }; // Tablice skoków przez menu, zależne od ostatnio użytego potencjometru.
const byte potencjometrBAS = A0; // Adres portu potencjometru.
int wartoscBAS = 512; // Wartość odczytanego napięcia pochodzącego ze ślizgacza potencjometru.
int zmianaBAS = 512; // Tymczasowa wartość odczytanego napięcia do porównań.
byte wirtualnyBAS = 5; // Pozycja wirtualnego potencjometru odpowiedzialnego za poziom parametru.
const byte tablicaTRE[] = { 11, 0, 1, 2, 3, 4 };
const byte potencjometrTRE = A1;
int wartoscTRE = 512;
int zmianaTRE = 512;
byte wirtualnyTRE = 5;
const byte tablicaMOD[] = { 10, 12, 0, 1, 2, 3 };
const byte potencjometrMOD = A2;
int wartoscMOD = 512;
int zmianaMOD = 512;
byte wirtualnyMOD = 1;
const byte tablicaDEL[] = { 9, 11, 12, 0, 1, 2 };
const byte potencjometrDEL = A3;
int wartoscDEL = 512;
int zmianaDEL = 512;
byte wirtualnyDEL = 1;
const byte tablicaTIM[] = { 8, 10, 11, 12, 0, 1 };
const byte potencjometrTIM = A4;
int wartoscTIM = 512;
int zmianaTIM = 512;
byte wirtualnyTIM = 1;
const byte tablicaREV[] = { 7, 9, 10, 11, 12, 0 };
const byte potencjometrREV = A5;
int wartoscREV = 512;
int zmianaREV = 512;
byte wirtualnyREV = 1;
void setup() {
pinMode(led, OUTPUT); // Zadeklaruj porty jako wyjścia.
pinMode(przyciskMenu, OUTPUT);
pinMode(przyciskPlus, OUTPUT);
pinMode(przyciskMinus, OUTPUT);
przewin();
}
void loop() {
digitalWrite(led, LOW); // Wyłącz diodę.
zmianaBAS = analogRead(potencjometrBAS); // Odczytaj wartość napięcia ze ślizgacza potencjometru.
if (abs(wartoscBAS - zmianaBAS) > 7) { // Jeśli odczytana wartość jest większa od 3 od wartości poprzedniej...
wartoscBAS = zmianaBAS; // Uaktualnij wartość napięcia dla pozostałych modułów programu.
skokMenu = tablicaBAS[numerPotencjometru]; // Wyłuskaj z tablicy skoków przez menu ilośc konkretnych skoków w zależności od ostatnio używanego potencjometru.
przewin(); // Przeskocz do wybranego parametru.
numerPotencjometru = 0; // Ustaw numer ostatnio ruszonego potencjometru na bieżący.
byte przeskalowanyBAS = map(wartoscBAS, 0, 1024, 1, 10); // Przelicz położenie potencjometru na zakres zmian bieżącego parametru.
while (wirtualnyBAS > przeskalowanyBAS) { // Porównaj położenie potencjometru rzeczywistego z wirtualnymm.
wcisnijMinus(32); // Tyle raz wciskaj przycisk minus, aż oba będą miały tę samą wartość.
wirtualnyBAS--;
}
while (wirtualnyBAS < przeskalowanyBAS) {
wcisnijPlus(32);
wirtualnyBAS++;
}
}
zmianaTRE = analogRead(potencjometrTRE);
if (abs(wartoscTRE - zmianaTRE) > 7) {
wartoscTRE = zmianaTRE;
skokMenu = tablicaTRE[numerPotencjometru];
przewin();
numerPotencjometru = 1;
byte przeskalowanyTRE = map(wartoscTRE, 0, 1024, 1, 10);
while (wirtualnyTRE > przeskalowanyTRE) {
wcisnijMinus(32);
wirtualnyTRE--;
}
while (wirtualnyTRE < przeskalowanyTRE) {
wcisnijPlus(32);
wirtualnyTRE++;
}
}
zmianaMOD = analogRead(potencjometrMOD);
if (abs(wartoscMOD - zmianaMOD) > 7) {
wartoscMOD = zmianaMOD;
skokMenu = tablicaMOD[numerPotencjometru];
przewin();
numerPotencjometru = 2;
byte przeskalowanyMOD = map(wartoscMOD, 0, 1024, 1, 38);
while (wirtualnyMOD > przeskalowanyMOD) {
wcisnijMinus(86);
wirtualnyMOD--;
}
while (wirtualnyMOD < przeskalowanyMOD) {
wcisnijPlus(86);
wirtualnyMOD++;
}
}
zmianaDEL = analogRead(potencjometrDEL);
if (abs(wartoscDEL - zmianaDEL) > 7) {
wartoscDEL = zmianaDEL;
skokMenu = tablicaDEL[numerPotencjometru];
przewin();
numerPotencjometru = 3;
byte przeskalowanyDEL = map(wartoscDEL, 0, 1024, 1, 29);
while (wirtualnyDEL > przeskalowanyDEL) {
wcisnijMinus(38);
wirtualnyDEL--;
}
while (wirtualnyDEL < przeskalowanyDEL) {
wcisnijPlus(38);
wirtualnyDEL++;
}
}
zmianaTIM = analogRead(potencjometrTIM);
if (abs(wartoscTIM - zmianaTIM) > 7) {
wartoscTIM = zmianaTIM;
skokMenu = tablicaTIM[numerPotencjometru];
przewin();
numerPotencjometru = 4;
byte przeskalowanyTIM = map(wartoscTIM, 0, 1024, 1, 102);
while (wirtualnyTIM > przeskalowanyTIM) {
wcisnijMinus(32);
wirtualnyTIM--;
}
while (wirtualnyTIM < przeskalowanyTIM) {
wcisnijPlus(32);
wirtualnyTIM++;
}
}
zmianaREV = analogRead(potencjometrREV);
if (abs(wartoscREV - zmianaREV) > 7) {
wartoscREV = zmianaREV;
skokMenu = tablicaREV[numerPotencjometru];
przewin();
numerPotencjometru = 5;
byte przeskalowanyREV = map(wartoscREV, 0, 1024, 1, 56);
while (wirtualnyREV > przeskalowanyREV) {
wcisnijMinus(35);
wirtualnyREV--;
}
while (wirtualnyREV < przeskalowanyREV) {
wcisnijPlus(35);
wirtualnyREV++;
}
}
}
void wcisnijPlus(byte y) { // Procedura wciskania przycisku Plus.
digitalWrite(przyciskPlus, HIGH);
digitalWrite(led, HIGH); // Włącz diodę.
delay(y);
digitalWrite(przyciskPlus, LOW);
delay(y);
}
void wcisnijMinus(byte y) { // Procedura wciskania przycisku Minus.
digitalWrite(przyciskMinus, HIGH);
digitalWrite(led, HIGH);
delay(y);
digitalWrite(przyciskMinus, LOW);
delay(y);
}
void przewin() { // Procedura wciskania przycisku Menu.
for (byte x = 0; x < skokMenu; x++) {
digitalWrite(przyciskMenu, HIGH);
digitalWrite(led, HIGH);
delay(86);
digitalWrite(przyciskMenu, LOW);
delay(86);
}
}