Nasz układ dekodowania komunikatów MIDI, ma już gotowy sekwencer, możemy jeszcze do niego dołożyć arpegiator – zestaw procedur pozwalający grać akord – po jednej nucie.
Współczesne syntezatory mają bardzo rozbudowane układy arpeggia, które można uczyć, lub które posiadają bardzo wiele schematów. Nic nie stoi na przeszkodzie by czytelnik (lub autor, jeśli wymyśli jak to można łatwo obsłużyć) coś takiego napisał. Na początek jednak będzie musiał nam wystarczyć arpegiator posiadający tylko trzy, najbardziej przydatne i popularne – schematy:
- Nuty grane są od najniższej nuty po kolei w górę, gazu dojdziemy do końca – zmieniamy kierunek.
- Nuty grane są od najwyższej do najniższej.
- Nuty grane są od najniższej do najwyższej, po czym zmieniają kierunek i grane są „w dół”, po czym znów „w górę”
Wyboru odpowiedniego programu użytkownik będzie dokonywał przełącznikiem.
Nie będziemy to rozważać, zostawiając to na następny odcinek, kiedy zaczniemy wszystko integrować:
- Kiedy należy zacząć grać arpeggio?
- Czym je taktować
Warto jednak zastanowić się nad tym jak zareagujemy na sytuacje dodawania naciśniętych klawiszy do granego arpeggia, lub ich zwalniania. Warto się także zastanowić co mamy zrobić gdy ktoś w czasie gry zmieni kierunek arpeggia.
Jeśli nie będziemy się przejmowali naciskaniem i puszczaniem klawiszy w czasie gry, wędrując zawsze po aktualnie naciśniętych klawiszach – nie musimy zapamiętywać akordu, a jedynie szukać poprzedniego i następnego naciśniętego klawisza, co znacznie uprości nam program, zwłaszcza że procedury te mamy przygotowane (pisaliśmy o nich w poprzednim odcinku). W takim przypadku musimy jedynie pamiętać jaki klawisz jest aktualnie grany i w którym kierunku powinniśmy szukać następnego:
.CSEG
.def R_ARPEGIO = r23
; contain last played key or $7f (none)
; bit 7 indicated direction - 0: up
Użyjemy to pojedynczego rejestru. Siedem bitów przeznaczymy na numer klawisza, a ostatni - bit znaku na kierunek.
Potrzebujemy zainicjować arpegiator. Do rejestru w którym pamiętamy stan arpeggia zapiszemy specjalna wartość która będzie oznaczała – że funkcja pobierająca następny klawisz:
ARP_init: ldi r16, $7f
mov R_ARPEGIO, r16
ret
Funkcja czytająca kolejną zagraną nutę – będzie zwracała ją w rejestrze r16. Jeśli żaden klawisz nie jest naciśnięty – funkcja zwróci wartość 255 (szestanstkowo –ff). Odpowiedni warunek musimy sprawdzić na początku:
ARP_read: tst PRESSED_COUNT
brne arp_i
arp_nothing: ldi r16, $ff
ret
Sprawdźmy teraz, czy nie jest to pierwsza grana nuta tego akordy – to znaczy czy nie jest to pierwsze wywołanie tej funkcji po inicjalizacji. Jeśli nie – skoczymy dalej.
arp_i: mov r16, R_ARPEGIO
andi r16, $7f
cpi r16, $7f
brne arp_01
Teraz sprawdźmy jaki jest wybrany program, sprawdzając które przełączniki są włączone. Konstruując instrument, zdecydowałem się na pojedynczy przełącznik trójpozycyjny – jako najbardziej intuicyjny. Tu musimy jedynie sprawdzić – czy nie jest on w pozycji „w dół”:
; init arpegio
mov r16, SWITCHES
andi r16, $08
brne arp_i_down
Jeśli nie – to rozpoczynamy od dołu klawiatury – szukając od zera. Niestety – zero w pewnych przypadkach także może być naciśnięte, a funkcja getNext – zwraca następny. Dlatego sprawdzimy jeszcze czy przed znalezionym nie ma poprzedniego: To co znaleźliśmy do grany klawisz:
; init up
arp_i_up: push r17
ldi r16, 0
rcall keymap_getNext
push r16
rcall keymap_getPrev
cpi r16, $ff
pop r17
brne arp_first
mov r16, r17
arp_first: pop r17
arp_set_u: mov R_ARPEGIO, r16
ret
Znalezienie najwyższej granej nuty nie wymaga tego dodatkowego kroku, ponieważ możemy zapytać o nutę poprzedzającą taką która już nie istnieje. Nasza mapa pracuje na wartościach do 127, a najwyższa nuta ma numer 120:
arp_i_down: ldi r16, 127
rcall keymap_getPrev
arp_set_d: ori r16, $80
mov R_ARPEGIO, r16
andi r16, $7f
ret
Przy liczeniu kolejnej granej nuty – musimy sprawdzić – czy gramy w górę czy w dół:
arp_01: ; play next note
mov r16, R_ARPEGIO
andi r16, $80
brne arp_prev
Jeśli gramy w górę, sprawdźmy czy muzyk nie przełączył arpegiatora w trym „w dół” – a jeśli tak – mamy grać w dół
arp_next: ; check if down is not forced
mov r16, SWITCHES
andi r16, $08
brne arp_prev
Teraz wystarczy wywołać funkcję zwracającą następną nutę z mapy naciśniętych klawiszy, i jeśli nie zwróci informacji o nie znalezieniu – zwracamy graną nutę, i zapamiętujemy jej wartość:
mov r16, R_ARPEGIO
andi r16, $7f
rcall keymap_getNext
cpi r16, $ff
brne arp_set_u
Jeśli skończyliśmy – nie ma wyższych nut – sprawdzamy czy jest sens coś grać, to znaczy czy naciśniętych jest więcej niż jeden klawisz, i jeśli tak – inicjalizujemy granie w górę (a to mamy już napisane}, chyba że wybrano granie w górę i w dół – wtedy zapamiętujemy klawisz wraz z dodatkową informacją – mamy od teraz grać w dół:
mov r16, PRESSED_COUNT
cpi r16, 1
breq arp_one
mov r16, SWITCHES
andi r16, $10
brne arp_i_up
Dokładnie ten sam scenariusz powtarzamy pisząc program poszukiwania niższej od aktualnie granej – nuty
{arp_prev: ; check if up is not forced
mov r16, SWITCHES
andi r16, $10
brne arp_next
mov r16, R_ARPEGIO
andi r16, $7f
rcall keymap_getPrev
cpi r16, $ff
brne arp_set_d
mov r16, PRESSED_COUNT
cpi r16, 1
breq arp_one
mov r16, SWITCHES
andi r16, $08
brne arp_i_down
rjmp arp_next
arp_one: mov r16, R_ARPEGIO
andi r16, $7f
ret