Sound Processing V – Synchronizacja wątków, suma kontrolna, odchudzony json

Ostatnia część, w której będę skupiał się na modemie. Następne części będą już skupiały się na praktycznym zastosowaniu, tj. będziemy pisać turową grę multiplayer, którą obsłuży nasz modem. Jakiekolwiek zmiany w backendzie (modem) będą pisane adhoc, bez specjalnych postów. Zapraszam.


Na czym skończyliśmy

Ostatni post zakończyliśmy 3 problemami do rozwiązania:

  • Dostęp do głośników realizowany w jak najkrótszym czasie, bez błędu dostępu który pojawia się co jakiś czas (java.lang.IllegalStateException: Mixer is already open, tj. program próbuje odtworzyć nowy dźwięk, zanim obiekt zakończy odtwarzanie poprzedniego).
  • Weryfikacja poprawności przesyłu danych – metoda która pozwoli stwierdzić, czy pakiet informacji przesłano bez przekłamań
  • Opracowanie sposobu na szybkie przesyłanie jsonów, tj. odchudzenie go o informacje, których obecności program może się sam domyślić.

Zacznijmy od pierwszego.

Usunięcie buga dostępu

Trochę pomyślałem i stwierdziłem, że błędem było nie korzystanie z metody synchronized klasy Thread, ze względu na modyfikowanie obiektu z zewnątrz klasy przez inny wątek (np. zatrzymywanie generowania dźwięku z wątku Broadcaster). Doprowadziło to do paru zmian:

SoundWaveGenerator teraz rozszerza klasę Thread, zamiast jak wcześniej implementować klasę Runnable (aby móc korzystać z metody join).

Jeśli kogoś zastanawiało, kiedy tworzyć obiekty thread-safe, a kiedy odpuścić sobie synchronizację, odsyłam:

http://stackoverflow.com/questions/234341/should-i-always-make-my-java-code-thread-safe-or-for-performance-reasons-do-it
https://www.tutorialspoint.com/java/java_thread_synchronization.htm
http://stackoverflow.com/questions/15956231/what-does-this-thread-join-code-mean
http://www.tutorialspoint.com/java/java_multithreading.htm

Do tego w Broadcaster.java już nie korzystam z:

while (!soundWaveGenerator.getAudioContext().isRunning()) {…czekaj 500 ms….}

tylko z:

soundWaveGenerator.join();

Czym różnią się obie metody?

Poza tym, że druga to wygodny one-liner, a pierwsza jest nieczytelna, bo polega na pętli sprawdzającej czy jakiś wątek nadal pracuje, a jeśli tak, to idzie na chwilę spać, to w zasadzie niczym (jeśli się mylę, to proszę o informacje).

Obie sprawiają, że wątek w którym zostały wywołane czeka, aż drugi wątek skończy pracę, a dopiero potem przechodzą do dalszego kodu. Było to potrzebne, bo kiedy chciałem wyłączyć generator dźwięku, to wyłaczenie nie działo się dokładnie w momencie, w którym o to poprosiłem. Musiałem więc poczekać, aż wątek na pewno skończy działanie, zanim znowu będę chciał skorzystać z dźwięku (co jak widać po błędach “Mixer already opened” nadal nie zawsze przed tym chroniło).

Do tego usunąłem:

audioContext.stop();

Które o ile było potrzebne wcześniej, bo zatrzymywało odgrywany dźwięk, to teraz zwyczajnie było źródłem błędów przy generowaniu dźwięku z bardzo krótkimi odstępami.

Sam nie wiem które dokładnie z powyższych naprawiło sprawę, ale teraz byłem w stanie przesłać pomyślnie literę w ciągu 750ms! W dodatku bez żadnego błędu dostępu, również przy przesyłaniu całych zdań i wyrazów.

W porównaniu do ostatniej wersji, gdzie “napis ćwiczebny” przesyłał się 26 sekund, czas przesyłania skrócił się do 12 sekund, w dodatku z mniejszą liczbą błędów transmisji.

napisc

Polecam sklonować z gita i sprawdzić samemu.

Suma kontrolna

Przejdźmy teraz do problemu weryfikacji przesłanych danych.

Skąd odbiorca może wiedzieć, czy nadawca wysłał pakiet informacji i np. po drodze przez zakłócenia nie dotarł kompletny lub przekłamany?

Na przykład, można przesłać takie informacje dwukrotnie, by porównać je po odebraniu. Ale przesyłanie informacji dwukrotnie to spore marnotrawienie czasu.

Co innego można więc zrobić?

Dołączać do wiadomości sumę kontrolną.

Za Wikipedią:

Suma kontrolna (ang. checksum) – liczba uzyskana w wyniku sumowania lub wykonania innych operacji matematycznych na przesyłanych danych, przesłana razem z danymi i służąca do sprawdzania poprawności przetwarzanych danych.

Wspomniane działania matematyczne mało nas interesują, sami lepszego algorytmu niż już stworzone nie wymyślimy, a sam fakt, że to po prostu nasz pakiet danych (w postaci zer i jedynek, bitów) po działaniach matematycznych, które mają na celu jak najmniejsze ryzyko kolizji powinien nam wystarczyć. Czym jest kolizja i jak długa ma być suma kontrolna?

Im większa suma kontrolna tym mniejsze prawdopodobieństwo kolizji, tj. sytuacji, kiedy ta sama suma kontrolna pasuje do więcej niż jednego układu bitów z których jest obliczana. Powiedzmy, że w trakcie przesyłu kilka zer zostanie odebranych jako jedynki i akurat dla takiego układu suma kontrolna przesłana przez nadawcę również będzie pasowała, wtedy odbiorca po obliczeniu swojej sumy kontrolnej i porównaniu z otrzymaną pomyśli, że dostał prawidłowy pakiet.

No dobrze, jak więc obliczyć tę sumę kontrolną? Szczegóły tego również nas nie obchodzą – Java udostępnia klasę która z podanej na wejściu tablicy bajtów utworzy 32 bitowej długości sumę kontrolną (algorytmem CRC, bo trzeba wiedzieć, że są też inne, nawet bardzo prymitywne sumy kontrolne jak bit parzystości).Tę sumę możemy dołączyć do naszej wiadomości.

crcWykonanie powyższego kodu wygeneruje na każdym komputerze ciąg 101110010001101101010101011110

Tu pojawia się kolejny problem:

Dla przykładu, 32 jedynki i zera – 10001111110100001001111011100111 – to trochę sporo do przesłania dźwiękiem. Rozwiążemy to znowu zmieniając system liczbowy – tym razem na szesnastkowy.  Podana wyżej liczba jest zapisana binarnie, natomiast zapisana hexadecymalnie będzie stanowić już tylko 8FD09EE7. Skrócenie 32 znaków do 8 to całkiem dobry wynik. W zasadzie, to skoro już dodajemy 6 liter alfabetu aby przesyłać szesnastkowo, to może dodać całą resztę liter i nie przesyłać ich żadnymi kodami ASCII? Dobry pomysł do wypróbowania, częstotliwości w naszym paśmie nadającym się do nadawania i odczytu powinno wystarczyć.

Checksum od właściwej części wiadomości będziemy oddzielać dodatkową częstotliwością nadawaną tak jak częstotliwość startu i końca wiadomości przez 3 * interwał pomiędzy znakami wiadomości, dla pewności, że odbiorca na pewno dostanie ten znak, bo inaczej źle zrozumie całą wiadomość.

Odchudzamy json

Jak już wspomniałem w poprzednim poście, struktura jsonu wygląda w ten sposób:
json

Dobre pytanie brzmi więc: jeśli oba komputery wiedzą jak wygląda przesyłany model i że pojawiają się w nich ciągi takie jak “jabłko” albo “miecz”, to czy opłacalne jest przesyłanie za każdym razem tych wyrazów? Nie lepiej dla nich zarezerwować jakiejś częstotliwości? (robi się trochę ciasno, jeśli mowa o wykorzystaniu kolejnych częstotliwości w naszym paśmie)

Oczywiście, że tak.

W jaki sposób to zrealizujemy?

Utworzymy tablicę z powiązanymi ze sobą nazwami zmiennych (np. “jabłko”) i odpowiadającymi im częstotliwościami. Następnie, po zamianie obiektu który ją zawiera na ciąg znaków (json), poszukamy w nim jakichś pod-ciągów które będą się pokrywać z tymi z tablicy, jeśli tak, to zamieniamy je na “$” + częstotliwość + “$”. Znak dolara jest po to, aby przesyłając ten ciąg do klasy Broadcaster, klasa wiedziała kiedy ma przesyłać znak jako znak ASCII a kiedy ma do czynienia ze zmienną i ma ją przesłać jako pojedyńczą częstotliwość.

Zrealizowałem to w ten sposób:

model1

Powyżej jest klasa, z której będziemy generować jsona. Jak widać, dużo w niej nie ma, jeśli chcemy dorzucić jakiś klucz z wartością, to dodajemy go metodą putVariable.

Prawdziwa praca znajduje się niżej:

gameinformationf

Tutaj deklaruję odpowiednie nazwy zmiennych wraz z odpowiadającymi im częstotliwościami.

gameinformation2

Jeśli w metodzie translateJSONToSound natrafię na jakąś zmienną zadeklarowaną wcześniej w tablicy, to zamieniam ją na odpowiadającą jej częstotliwość.

Jak to działa w praktyce?

Oto kod, który wrzuca do obiektu z którego generujemy jsona miecz, jabłko, 50 złota i liczbę punktów życia. Do tego generuję z niego sumę kontrolną.

code2

Wynikiem takiego programu jest:

console1

Ale niekoniecznie musimy mieć akurat takie przedmioty w ekwipunku. Json jest bardzo elastyczny, mogę mieć np. tylko jabłko. Wtedy zwrócone zostanie:

console3

Litery składające się z 2-3 znaków decymalnych przesyłam 750-850ms, więc mając do przesłania 7 znaków (zakładam, że mam oddzielną częstotliwość dla ‘{‘, ‘:’,’}’ i cudzysłów włączam do wiadomości już po stronie odbiorcy, bo wiadomo, że nazwa zmiennej będzie objęta cudzysłowiem) plus do tego 8 znaków sumy kontrolnej, powyższy json powinienem przesłać w około 10 sekund. Spora różnica, w porównaniu do pierwszej wersji modemu, gdzie sam pojedyńczy znak przesyłałem ponad 4 sekundy.

Zostało dopisać interpretację kodów pomiędzy znakami dolara przez Broadcaster i Sniffer, ale nie jest to nic specjalnie skomplikowanego a ten post staje się już na to za długi. Zrobimy to przy okazji dorzucania modemu do gry.


W następnej części piszemy grę, zaczynając od pomysłu, wykonania prototypu, a dopiero na koniec połączymy to z modemem, aby można było grać w dwie osoby (chociaż w zasadzie, to gdyby się uprzeć, to tak jak w ramce sieci ethernet można dołączać nagłówek z informacją o tym kto nadaje pakiet i do kogo, grając w ten sposób w kilka osób, chociaż to mocno przedłużyłoby czas wysyłania).


 

Odnośniki:

Jak zawsze kod dostępny jest na GitHubie

https://github.com/dbeef/soundcoding


dsp2017-1

Różne I – Java na olimpiadzie fizycznej

Programowanie jest chyba najbardziej uniwersalnym narzędziem do rozwiązywania problemów naukowych. Od data processingu, gdzie mamy duże ilości próbek pomiarowych po niemożliwe (ze względu na czasochłonność) do zrealizowana ręcznie obliczenia. Umiejętność zrzucenia pracy na komputer daje sporo możliwości.

W tegorocznej olimpiadzie fizycznej w której brałem udział, w drugiej części zadań pierwszego etapu pojawiło się zadanie, co do którego sami autorzy zachęcali do napisania programu w arkuszu kalkulacyjnym lub dowolnym języku programowania i dostarczenie działającej, opisanej wersji.

Za to zadanie dostałem 15/20 punktów, więc ten post raczej mógłby być przykładem dla przyszłych uczestników, jak mniej więcej rozwiązywać zadania numeryczne OF.


Wstęp do problemu

zadanieCałość dostępna jest na stronie KGOF.

Aby uniknąć nieporozumień, oto ilustracja brachistochrony (czerwony tor) znaleziona na Wikipedii:

Braquistócrona.gif

Zacznijmy od krótkiego wstępu fizycznego, dla osób głebiej zainteresowanych tematem, niżej w odnośnikach załączam omówienie problemu i wyników z programu które wysłałem na OF.

Mamy koralik, wiemy że będzie poruszał się po torze opisanym parabolą. Tor z definicji jest zbiorem punktów odwiedzonych przez ciało w czasie ruchu, co oznacza, że do jakiejś chwili t można przypisać punkt na tej paraboli (i na odwrót).

Z twierdzenia o pracy i energii (w układzie mamy siłę tarcia, więc musimy uwzględnić jej pracę w równaniu energetycznym) możemy obliczyć ile energii straciło/zyskało ciało przemieszczając się z jednego punktu toru do drugiego i przeliczyć tę energię na prędkość  ciała.

W podpowiedzi od autora otrzymaliśmy formułę na długość bardzo małego odcinka paraboli, co jest nam potrzebne, bo w przybliżeniu ten odcinek jest linią prostą – a jak liczymy czas poruszania się po prostej, w przybliżeniu ruchem jednostajnym? Zwykłym t = S / V.

Teraz co nam zostało – przeiterować po współczynnikach ‘a’ funkcji kwadratowej i dobrać taki, przy którym suma czasów na wszystkich małych odcinkach będzie jak najmniejsza (i zrobić to dla każdego z podanych współczynników tarcia).

Zostało nam jedno do spostrzeżenia – koralik startuje na pozycji (0,0) a musi dolecieć do (100,-1). Co oczywiste, jeśli nie ma prędkości początkowej, to musi po prostu spaść metr w dół, aby nabrać prędkość do przemieszczenia się 100 metrów dalej. Dlatego odrzucam ujemne współczynniki, bo wtedy koralik musiałby już na początku wzlecieć w górę, co jest niemożliwe.

Teraz, skoro już wiemy to wszystko, przejdźmy do programu.

Kod programu

Program zaczynam od zapisania stałych podanych w zadaniu, dla czytelności kodu, a także zainicjalizowania co ważniejszych zmiennych:

program1

Warto zwrócić uwagę, że w funkcji zapisującej najkrótszy czas, przyrównuję wartość aktualnego czasu z wartością najmniejszego czasu odnalezionego do tej pory. Jeśli aktualny czas jest mniejszy niż najmniejszy zapamiętany do tej pory czas, to zapisuję go do zmiennej najmniejszy czas. Dlatego też na sam start programu zapisuję do zmiennej najmniejszy_czas maksymalną wartość jaką można zapisać w typie double – aby warunek ten był spełniony przynajmniej raz.

Następnie przyjmuję kilka kluczowych stałych od użytkownika. Nie ma tu zbyt wiele finezji:
inputPo przyjęciu danych, przechodzę do pętli która iteruje od współczynnika początkowego do końcowego, dodając przy każdym przejściu bardzo małą deltę – 0.001. Dobieram następnie do otrzymanego tak współczynnika ‘a’ pozostałe współczynniki (współczynnik c jest zawsze zerowy).  Warte podkreślenia jest, że sprawdzam, czy mam do czynienia z funkcją liniową (a = 0) nie poprzez operator porównania, a dopisaną przeze mnie funkcją – robię to, ze względu na to, że komputery mają kłopoty z reprezentowaniem ułamków binarnie (binarny to w ogóle słaby system na ułamki), odsyłam zaciekawionych poniżej:

https://docs.python.org/3/tutorial/floatingpoint.html

a także do:

http://fulmanski.pl/news/materials/fpn.pdf

Za każdym przejściem pętli uruchamiam również funkcję obliczającą czas przejścia dla zadanej paraboli.

petla whileWspomniana pętla while.

iszero

Wspomniana funkcja isZero.

W tym momencie przechodzimy do wisienki na torcie – funkcji obliczającej czas przejścia. Jest tu dużo matematyki, a w zasadzie zero wartych odnotowania sztuczek programistycznych. Zainteresowani mogą zajrzeć w kod (jest w odnośnikach) i rozwiązanie które wysłałem.

Warte odnotowania jest natomiast to, jak dużo obliczeń wykonuje program, aby znaleźć ten najlepszy wynik.

Powiedzmy, że na start programu podaliśmy współczynnik początkowy a = 0 i końcowy a = 2. Ponieważ nasza delta a jest równa 0.001, to do zbadania mamy 2000 funkcji kwadratowych. Musimy więc 2000 razy obliczyć współczynniki funkcji kwadratowej i pochodną. Dla każdej z tych 2000 funkcji, mamy do przejścia od x początkowego = 0 do x końcowego = 1, ale delta x jest równa z kolei 0.0001f, czyli do przejścia mamy 10000 punktów, a dla każdego z nich liczymy wartość funkcji, równanie energetyczne, prędkość, odcinek drogi i na końcu – czas przejścia przez tę drogę.

Po włączeniu programu, podaniu ‘a’ początkowego = 0 i ‘a’ końcowego = 1 oraz także tarcia beta 1, obliczenie współczynnika ‘a’ paraboli najkrótszego czasu zajęło prawie 60 sekund (przy podanych wcześniej w poście deltach).

Teraz wyobraźcie sobie zrobienie tego wszystkiego ręcznie, na papierze.

Odnośniki:

PDF z fizycznym spojrzeniem na problem i omówieniem wyników programu, który załączyłem do zgłoszenia do OF:

T4-Daniel-Zalega-OF66-I-283-nowe

Repozytorium z kodem, instrukcją uruchomienia i jarem, który wysłałem do OF:

https://bitbucket.org/dbeef/66-olimpiada-fizyczna

Moje rozwiązania zadań doświadczalnych dla drugiego zestawu zadań wraz z punktacją (żeby wiedzieć na ile można się sugerować moją odpowiedzią), być może komuś się przyda:

D3-Daniel-Zalega-OF66-I-283   10/40

Zadanie D1 Daniel Zalega OF66-I-283   24/40


 

dsp2017-1

Sound Processing IV – Przesyłamy dane szybciej

W tej części cyklu zoptymalizujemy proces wysyłania danych, zapoznamy się z pojęciem kompresji i formatami wymiany danych. Zapraszam.


Na koniec ostatniej części wspomniałem o możliwym sposobie przyśpieszenia transmisji dźwiękiem, czyli wykorzystaniem innego systemu liczbowego którym odwzorowujemy znaki ASCII. Ponieważ plusy przesyłania decymalnie lub hexadecymalnie (szesnastkowo) zamiast binarnie przedstawiłem już w poprzednim poście, przejdę od razu do paru zmian jakie zaszły w kodzie (nie jest ich dużo, wystarczyło 5 minut na odpowiednie zmiany).

Zmiany w kodzie

W klasie Variables zadeklarowałem nowe częstotliwości, tym razem mamy ich więcej, więc ułatwiłem sobie korzystając z tablicy:

variables.pngZmieniona klasa Variables.

Dodatkowo też zadeklarowałem zmienną TOLERANCE z której korzystam w odpowiednio zmodyfikowanym modelu DetectedFrequency – wcześniej była tam hardkodowana wartość 50, tym razem warto ją gdzieś zapisać dla czytelności i łatwiejszych zmian. Zmieniony konstruktor modelu przedstawia się następująco:

detectedfreq

Iteruję po elementach tablicy i sprawdzam czy wykryta częstotliwość zawiera się w przedziale: (częstotliwość z elementu tablicy minus tolerancja, częstotliwość z elementu tablicy plus tolerancja). Jeśłi tak, to przypisuję tę wartość do obiektu, jeśli nie, przechodzę do następnego elementu tablicy. Jeśli nadal po przeiterowaniu nie ma żadnego dopasowanego znaku (porównanie matchedSign == null), to sprawdzam dalej, czy wykryta częstotliwość nie jest pauzą albo początkiem/końcem transmisji. W przeciwnym razie, jeśli jest to szum, przypisuję pusty ciąg znaków.

Do tego takie drobiazgi, jak:

broadcaster.sendMessage(Integer.toString(takeInput()));

Zamiast wcześniejszego:

broadcaster.sendMessage(Integer.toBinaryString(takeInput()));

I analogicznie przy odczytywaniu znaku z liczby:

int code = Integer.parseInt(formattedMessage, 10);

Zamiast wcześniejszego:

int code = Integer.parseInt(formattedMessage, 2);

argument == 10 to wskazanie parserowi że ma do czynienia z liczbą zapisaną decymalnie, argument == 2 – binarnie.

Do tego zmieniłem czas nadawania znaku początku transmisji z 10*INTERVAL na 3*INTERVAL, co również ma duży wpływ na szybkość bez widocznego zwiększenia awaryjności.

Czas na testy

Przesłanie znaku ‘i'(1101001, 7 bitów) zajęło 1578 milisekund.

Dla porównania, kiedy ostatnim razem przesyłaliśmy 7 bitów, zajęło nam to ponad 4 sekundy. Dzielenie 4 przez 1.578 daje 2.53 i tyle razy szybciej wysyłamy znaki teraz.

Tym razem daje to już 1.92 kB na godzinę, więc przesłanie 17 kB zdjęcia kaczki z ostatniego posta zajęłoby już tylko niecałe 9 godzin.

Trochę eksperymentując, ustawiłem czas nadawania pojedyńczej częstotliwości na tylko 50 milisekund. Co oczywiste, skraca to niezawodność, ale w ten sposób przesłałem bez problemu 5 znaków pod rząd osiągając w każdym ~1050 milisekund.

Granicą takiego schodzenia coraz niżej jest niestety nadal błąd dostępu do głośników który sporadycznie się wtedy pojawia i blokuje cały program. Z jednej strony zrozumiałe, w końcu wywołuję dostęp do nich co bardzo krótki czas, a z drugiej raczej muszę robić coś źle, ktoś musiał już przewidzieć takie sytuacje.

Prześlijmy wyraz

Po prostej zmianie polegającej na przyjęciu całego stringa i przesyłaniu go znak po znaku do naszego Broadcastera, wyniki przesyłania ciągu “napis ćwiczebny” są następujące:

formatted

Litery ‘i’ i ‘z’ nie przesłały się pomyślnie, a sama transmisja zajęła aż 26 sekund. O ile więc nie znajdę sposobu rozwiązania problemu czasu dostępu do głośników, podczas przesyłania wyników gry między komputerami będzie można zaparzyć sobie herbatę.

Co jeszcze może pomóc?

Kompresja, chociaż nie w każdym przypadku. Zadziała wtedy, kiedy mamy do czynienia z tekstem w którym pewne ciągi pojawiają się wielokrotnie w tym samym ułożeniu. Dobry przykład jest tutaj.
Mało prawdopodobne jest, żeby wśród przesyłanych przez 2 gry danych pojawiał się aż tak podatny materiał,  dlatego ten sposób zostawię na koniec pracy z naszym modemem.

Do głowy przychodzi mi już tylko zakodowanie pewnych często powtarzających się znaków/kombinacji jako dodatkowe częstotliwości, co rzeczywiście mogłoby znacząco skrócić czas nadawania.

W jaki sposób będziemy wymieniać dane między grami?

Przesyłanie zdań albo liter to jedno, a przesłanie powiązanych ze sobą zmiennych i ich wartości, a później dopasowanie ich to drugie. Moim pomysłem jest skorzystanie z gotowego formatu wymiany danych, który zrobi to za mnie – JSON. Korzystałem z niego już przy okazji przesyłania testów między serwerem a klientem w aplikacji Speechlist (odsyłam do poświęconemu jej posta).

Dla przykładu skorzystam z webowego generatora jsonów:

json

Po przesłaniu ciągu znaków:

{“pozycjaGraczaX”: 10,”pozycjaGraczaY”: 20,”punktyZdrowia”: 99,”ekwipunek”: {“jabłko”: 10,”pieniądze”: 40,”miecz”: 1}}

Program mógłby oddzielić odpowiednie zmienne i załadować je do ich odpowiedników w swojej pamięci. Dodatkowo, kiedy będę chciał odwzorować jakikolwiek swój obiekt w postaci jsona, są już do tego odpowiednie biblioteki (np. gson), które przyjmując obiekt w argumencie oddadzą go w postaci stringa.

Co dalej

W następnej części postaram się usunąć ograniczenie jakie narzuca czas dostępu do głośników i przesłać jakiegoś jsona między komputerami. Do tego przydałby się jakiś system kontroli błędów.

Potem została do napisania już tylko gra wykorzystująca modem.

Odnośniki:

Jak zawsze, kod dostępny jest na GitHubie:

https://github.com/dbeef/soundcoding

dsp2017-1

Pokaż kod IV – Nigdy niedokończone projekty

Kolejna część serii z napisanym przeze mnie kodem. Zapraszam.


Ostatnio przypomniało mi się o paru niedokończonych grach i mając okazję podpiąłem stary dysk do komputera, żeby je zbackupować na kiedyś, zanim do reszty o nich zapomnę, a szkoda, bo całkiem miło do nich wrócić. Krótko tu podsumuję co odzyskałem.

7DaysToColonise

output

Gra którą zacząłem robić tuż po wrzuceniu Memo Boxes, mocno pod wpływem gry AdVenture Capitalist. Miał to być typowy klikacz, ale ponieważ baaardzo lubiłem i do teraz z sentymentem wspominam przeglądarkowe Ogame, postanowiłem, że będzie w kosmosie.

Mechanika gry – budować fabryki na startowej planecie Ziemi, pieniądze generowane w fabrykach można za kliknięciem zebrać i zainwestować w kolejne (będą generować więcej pieniędzy), lub też w kolonizację następnej planety. Do gracza należałaby decyzja co bardziej się na dany moment opłaca, pamiętając, że w ciągu 7 dni (prawdziwych 7 dni) musi skolonizować Układ słoneczny. Pieniądze z fabryk zbierałoby się za każdym ponownym otwarciem gry na telefonie, pieniądze które w tym czasie powinny się wygenerować obliczałbym z delty czasu pomiędzy ostatnim a aktualnym uruchomieniem. Jeśli ktoś przestawiłby datę i wykryłbym to (musiałby w końcu kiedyś przestawić ją ponownie na aktualną), musiałby liczyć się z dwoma problemami: Komunikatem od gry – twoje finansowe imperium zostało zbombardowane przez nieznaną, międzygwiezną cywilizację, a także zwykłą nieopłacalnością takiego procederu – w końcu budowa fabryk też trwałaby swoje i byłaby to strata czasu, gdyby dla 1 dnia generowania się pieniędzy stracić powiedzmy 12 godzin w których nic by się nie budowało.

Prototyp zrobiłem na paru darmowych assetach z internetu, kod, poza tym, że jest porażką, nie kryje nic specjalnego. Mogę w charakterze ciekawostki pokazać obliczanie pozycji w ruchu eliptycznym planet:

pozycjaplanet

ileDodacX i ileDodacY to zwyczajne offsety na układzie współrzędnych, height i width to szerokość i wysokość takiego zakreślonego okręgu/elipsy, po pierwszym if-ie uaktualniam również obrót planety wokół własnej osi.

Mad Pirates

Gra pisana przez chwilę, pare miesięcy przed Speechlist. W gimnazjum sporo grałem w Empire: Total War, gdzie w bardzo przyjemny sposób zrealizowano bitwy morskie, dla przykładu:

Chciałem zrobić coś podobnego, również byłoby możliwe sterowanie okrętem ręcznie, ustawianie żagli, rodzaju kul (kartacze, łańcuchowe), oddawanie salw lewą lub prawą burtą, do tego dodałbym możliwość zarządzania załogą, ekwipunkiem i losowy świat – Karaiby. Całość oczywiście dwuwymiarowo, jeśli ktoś jeszcze miał złudzenia, że porwałbym się na trójwymiar.

Całość zarzuciłem na rzecz sporo ciekawszego Speechlist które faktycznie skończyłem po paru miesiącach.

Ciekawe rozwiązania w kodzie?

Przez jakiś czas walczyłem z ruchem statku w kierunku ostatniego kliknięcia, niby banał, ale w końcu postawiłem na rozwiązanie z internetu, bo w rozwiązaniu z mojej ręki statek zamiast powoli się obracać w nakazanym kierunku – twerkował:

outpu3t

Po poprawkach:

output

Na ten moment mam pomysł na recykling tego kodu, w końcu potrzebna nam gra do dźwiękowego backendu z serii Sound Processing. Czemu by więc nie przerobić Mad Pirates na klona Steam Birds, gdzie między turami za pomocą dźwięku wymieniane byłyby wektory po których miałyby poruszać się statki graczy.

sidetorotate

Kod, który obliczał  na podstawie X i Y miejsca kliknięcia kąt, o jaki powinien obrócić się statek, aby tam zmierzać.

Black Hat

W styczniu 2016 zebraliśmy się w 4 osoby w celu zrobienia gry pod konkurs którego nazwy już nawet nie pamiętam. Celem było stworzenie gry takiej jak Uplink, ale mocno fabularyzowanej, z wielotorową historią, różnymi zakończeniami etc. Cała gra miała dziać się na ekranie komputera wewnątrz gry, stąd nasz cel numer jeden – stworzyć dobrą mechanikę i niebanalną historię, aby zapełnić deficyt jaki tworzyła nasza prosta grafika.

Całkiem poważnie się za to zebraliśmy, spotkaliśmy się raz lub dwa na dłużej, gdzie jeden robił za skrybę a reszta dzieliła się pomysłami, do tego na dysku mam ~300 MB rozmowy na Skype, gdzie robimy dokładnie to samo.

Jak szybko zaczęliśmy, tak szybko całość zarzuciliśmy na etapie prac pokazanym na filmiku wyżej. Czekało nas mnóstwo pracy, którą można było podsumować smutnym “nawet nie wiadomo czy to wypali”.

Poniżej przedstawiam jeden z kilku skryptów naszych ustaleń:

Wstęp do gry

Po włączeniu gry włącza się intro wykonane w komiksowy sposób – przedstawia ona historię poruszając kamerą po statycznych obrazkach, z lektorem w tle. Być może lektor będzie ważną częścią rozgrywki, w tle będziemy słyszeli myśli bohatera gry, jego wewnętrzne przemyślenia wobec trudnych decyzji. Być może chwilowe cutscenki również w komiksowy sposób, a na ekranie pojawią się też “mrugnięcia” oczu bohatera gdy będzie mówił i jego spoglądanie po pokoju.

Ale wracając:

Po intrze płynnie trafia się przed komputer do pokoju głownego bohatera, przed jego komputer (z jednym lub dwoma monitorami)(Miejscem całej rozgrywającej się akcji jest jego pokój i komputer) – Kamera skierowana jest na monitor, pokój jest zaciemniony, monitor wyłączony. Świeci się jedynie na czerwono przycisk włączenia monitora.

Cała akcja stoi, aż do momentu właczenia monitora przez gracza.

Wtedy odgrywa się nieco przerażający dźwięk (8 bitowy, włączania komputera).

Po paru sekundach monitor się lekko rozjaśnia ale nadal jest ciemny. Pojawia się logo EnvyOS.

Czekanie kilku sekund (w tle dźwięki pracy dysku, komputera, przydadzą się takie efekty dźwiękowe).

Ekran jaśnieje na kolor kremowy/beżowy.

Pojawiają się na nim pytania (a raczej wyostrzają się, są na początku rozmyte, lekko przeźroczyste).

Rozpoczyna się kwestionariusz.

Są to pytania, kreujące przeszłość bohatera jakim kierujemy, początkowy podział jego punktów umiejętności i możliwe że część początkowej fabuły.

Kwestionariusz znajduje się w dokumencie kwestionariusz.
Po wykonaniu kwestionariusza ekran znowu ciemnieje.

Tym razem gdy po paru sekundach się rozjaśnia trafiamy na pulpit.

Dalszą część obejmuje dokument fabuła.

Aby zobaczyć wygląd systemu i jego działanie, zajrzyj do dokumentu system.

Czyste pomysły

Wśród znalezisk znalazłem również krótki plik tekstowy z koncepcjami do użycia w nieokreślonej przyszłości, znalazły się tam m.in:

Klon “Miecze i sandały z fabułą osadzoną gdzieś w czarodziejskim świecie, analogicznie ulepszamy ekwipunek – różdżki, runy, cokolwiek, a zdobyte punkty ładujemy w jakieś drzewko umiejętności. Być może dodałbym multiplayer, nie byłoby to specjalnie trudne ze względu na to, że gra byłaby turowa, jak chociażby Hearthstone, wystarczyłoby więc zwyczajnie postować i getować co jakiś czas jakieś RESTowe API jak to robię w Speechlist przy pobieraniu testów.

Dwuwymiarowy, pixel-artowy klon League Of Legends; miałem w planach przełożyć mechanikę gry praktycznie 1:1, z tą różnicą, że na dwuwymiarowy świat. Pomijam, że nawet teraz ambitne byłoby się za to zabrać, mogąc faktycznie kupić jakieś assety i przejmować się jedynie programowaniem (co ze względu na aspekt interakcji graczy na żywo, gdzie liczyłyby się dziesiętne części sekundy, mogłoby mnie przerosnąć).

Klon Don’t Starve. Tu chyba nie muszę tłumaczyć, takich gier robionych w różnej maści Game Makerach jest aż za dużo.


dsp2017-1

Sound processing III – Przesyłamy dane

Trzecia część serii. Tym razem prześlemy litery pomiędzy komputerami za pomocą naszego dźwiękowego modemu. Zapraszam


W ostatniej części napisaliśmy modulator, który zamienia ciąg zer i jedynek na odpowiadające mu częstotliwości i wytwarza je w głośniku w ustalonym wcześniej porządku, tj. z częstotliwością pauzy pomiędzy nimi.

Co nowego w kodzie

Przed nami zostało napisanie części kodu odpowiadającej za  interpretację częstotliwości które docierają do mikrofonu. Do tego celu skorzystam z biblioteki TarsosDSP, którą wykorzystaliśmy już w ostatnim poście do wykazania, że nasze częstotliwości da się odczytać na komputerze.

Nie przewidziałem wtedy jednej rzeczy. Nasłuchujący program nie będzie wiedział w którym momencie zaczynam, a kiedy kończę transmisję, w końcu nie każdy znak ASCII będzie miał binarnie 7 bitów, do tego (np. z powodu szumów) nie wszystkie znaki muszą trafić. Z tego powodu dodałem dodatkową częstotliwość 3500[Hz] którą wysyłam na początku i końcu transmisji.

Skorzystałem z kodu wykorzystującego Tarsos obsługującego panel pokazany na poprzednim odcinku. W momencie wykrycia dowolnej częstotliwości, tworzy nowy obiekt typu DetectedFrequency i dodaje go do tablicy. W obiekcie DetectedFrequency, który napisałem wcześniej, zapisane są na stałe częstotliwości dla zera, jedynki, pauzy i znaku początku/końca transmisji, odczytana częstotliwość porównywana jest z nimi i przypisuje obiektowi odpowiednią literę. Dla częstotliwości nieopisanej naszymi zmiennymi (np. 123 [Hz] albo 456 [Hz]) przypisywany jest pusty znak, co jest istotne w późniejszej części kodu.

detectedfreq

Przypisywanie znaku do danej częstotliwości

Aby w miarę na bieżąco analizować nadchodzące znaki, ustawiłem timer i co 5 sekund sprawdzam zawartość tablicy z obiektami DetectedFrequency. Wtedy tworzę nowy obiekt String do przechowywania tekstu i sumuję w nim wszystkie litery z tablicy DetectedFrequency. W ten sposób, dla przykładu, otrzymuję ciąg znaków:

000000     11111      00                11111

Dlaczego dostaję wielokrotności znaków? Ponieważ Tarsos tworzy obiekt za każdym razem, gdy wykryje jakąś częstotliwość. Ponieważ wykrywa ją praktycznie za każdym swoim odświeżeniem, a ja nadaję ją dłuższą chwilę, to właśnie z czymś takim kończę.

Następnie trafia to do obiektu typu DetectedMessageFormatter, gdzie pętlą while załatwiam sprawę i tworzę z tego ciąg znaków:

0101

formatstring

Formatowanie otrzymuję w bardzo prosty sposób, iteruję po każdym elemencie ciągu znaków i sprawdzam czy poprzedni element jest taki sam jak obecny. Jeśli tak, to nie dodaję go do nowego, sformatowanego ciągu. Cała magia.

Następnie, jeśli w otrzymanym ciągu można wyróżnić 2 znaki początku/końca transmisji (zapisywane jako ‘/’), to próbuję zinterpretować znaki między nimi, po czym dodaję efekt do tablicy odczytanych wiadomości i szukam odpowiedniego kodu ASCII dla tej liczby.

Należy pamiętać, że w odczytanym w danym momencie ciągu wcale nie muszą znajdować się dwa znaki ‘/’. W końcu odświeżamy bufor co 5 sekund, bardzo możliwe że akurat sekundę przed odświeżeniem ktoś rozpoczął nadawanie i jeszcze go nie skończył. W takiej sytuacji w buforze znajdzie się ciąg liter, powiedzmy:

/// 0000 11

Z którego nie powinniśmy niczego interpretować, bo transmisja jeszcze się nie skończyła.

Co do samego tworzenia wiadomości, obeszło się bez większych zmian względem wcześniejszej części. Po każdym wysłaniu wiadomości, jesteśmy znowu pytani o kolejną literę do wysłania, aż do wyłączenia programu, przy czym jednocześnie w drugim wątku uruchomione jest nasłuchiwane i co 5 sekund daje znać o odczytanych wiadomościach.

snifferKlasa Sniffer obsługjąca przechwytywanie dźwięku i interpretowanie go w tle.

Nasz program skopiowałem na inny komputer wyposażony w mikrofon i zestawiłem je dość blisko siebie. Efekty przesyłania pojedynczych liter nagrałem i wrzuciłem poniżej:

Jak widać, przy 4 przesłanych znakach mieliśmy 100% dokładności. Pomyłki czasem się zdarzają (np. z 7 znaków przyjdzie tylko 6, co już zmieni liczbę względem której program będzie próbował odczytać znak), ale przy dużej głośności i małej odległości między komputerami zdarza się to bardzo rzadko. Do tego czasem trafiają się błędy dostępu do głośnika z nadal nieznajomych mi powodów.

Co dalej?

TarsosDSP ma całkiem dobrą dokładność, kiedy moduluję dźwięk o (teoretycznie) częstotliwości 2500 [Hz], Tarsos odczytuje wartości wahające się o 0-20 [Hz] od tej modulowanej. To daje całkiem spore pole do popisu. Na ten moment przesłanie 7 bitów zajmuje około 4 sekundy. 7*(60/4)*60 = 6300 bitów w ciągu godziny, podzielić przez 8 czyli 787.5 bajta na godzinę, podzielić na 1024 daje 0.76 kB na godzinę. Dla skali, ile danych można przesłać naszym programem w ciągu godziny (zakładając 100% bezbłędności przy transmisji) – poniższy śmieszny obrazek waży 17 kB.

zbrodniarz

Jaki jest mój pierwszy pomysł na przyśpieszenie tego – wykorzystanie dziesięciu częstotliwości. Ponieważ Tarsos ma sporą dokładność, to zmieszczenie 10 częstotliwości w nadal wystarczająco dużych odstępach na paśmie 2-4 [kHz] nie będzie problemem. Ale dlaczego 0-10? Ponieważ tym sposobem, będzie można wysyłać dane nie tak jak teraz, binarnie, ale decymalnie, czyli w ludzkim, dziesiątkowym systemie. Dla przykładu, aby wysłać literę A (w kodzie ASCII jest to 65) na ten moment musimy wysłać 1000001 + dodatkowe częstotliwości na pauzę i znak początku i końca transmisji. Przy decymalnym wysyłaniu, byłyby to tylko znaki 6 i 5 (+ te pomocnicze, czyli łącznie 5 zamiast 15)!

Do tego przydałoby się zabezpieczyć przed awaryjnością transmisji, chociażby zamiast tego samego znaku dla początku i końca transmisji wprowadzić 2 oddzielne.

Ale to już w następnym odcinku. Przy okazji, coraz bliżej praktycznego zastosowania naszego modemu.


Odnośniki:

Kod pisany w tej serii jak zawsze dostępny jest za darmo na moim GitHubie:

https://github.com/dbeef/soundcoding

Kilkuminutowy filmik opisujący odczytywanie znaków ASCII z ciągu z liczbami zapisanymi binarnie, czyli dokładnie to co robi na ten moment nasz program:

https://www.youtube.com/watch?v=wCQSIub_g7M

(polecam cały kanał)

dsp2017-1

Pokaż kod III – Turn The Snake

Trzecia część przeglądu kodu moich gier/aplikacji. Zapraszam.


Gra której większość napisałem w zasadzie w ciągu weekendu. Niepotrzebne były jakiekolwiek assety z Open game art czy temu podobnych stron stron (poza dźwiękami kliknięcia i czcionką). Wariacja na temat Snejka i kręcących w głowie kolorowych gierek.

Pisałem to dość dawno, stąd od razu ostrzegam przed powtarzaniem złych praktyk pisania kodu, jaki jest w tym repozytorium.

Przed dalszym czytaniem polecam ściągnąć tutaj, lub obejrzeć poniższy filmik.

Gameplay

Film mocno klatkuje, prawdopodobnie ze względu na niezmierzone pokłady mocy mobilnego celerona.

Technologie:

Przez jakiś czas wspierałem również reklamy poprzez AdMob (wykupione przez Google w 2009) i darmowe Google support library do współpracy z Androidem. Działało O.K (chociaż zwiększały wagę całej gry z 1 MB do >5MB), ale nie było powodu by z niego korzystać – na dłuższą metę reklamy przeszkadzały w grze a pieniądze z reklam…

Monetyzacja

Po ponad roku od wrzucenia TTS i półtora roku po wrzuceniu Memo Boxes, moje konto AdMob wygląda następująco:

admob

admob

admob

admob

Nie, żebym liczył na więcej, ale takie są realia. Nie ma co liczyć na randomowe pobierania osób kręcących się po Google Play, jeśli nie porozsyłasz gry ani nie dotrzesz w żaden inny sposób do odbiorcy to licznik pobrań stanie na 50. Wydaje mi się, że nawet zatrudniając grafików i tworząc zawartość postawioną na jakość, jest to bariera nie do przeskoczenia. Co stawia nas przed kolejnym problemem.

Marketing

Napisałem kiedyś osobny post o tym jak zerowym kosztem podnieść sobie licznik pobrań, tymczasowo wskoczyć na top kilkaset według pobrań danego dnia i mieć trochę szansy na wybicie się. Żeby nie powtarzać się, krótko podsumuję to, co sam próbowałem:

Udostępnianie linków do pobrania na gamedevowych grupkach na Facebooku, m.in:

Po wrzuceniu na grupki odnotowałem +200 pobrań w ciągu kilku następnych dni.

Są serwisy, które same szukają zawartości do wrzucenia i mają formularze kontaktowe, gdzie można podesłać swoją grę. Wysłałem Memo Boxes i po 3 miesiącacach dostałem w zwrocie odpowiedź:

playplayfun

Plus LibGDX jest taki, że piszesz raz i uruchamiasz w przeglądarce, Androidzie, na desktopie i przy odrobinie szczęścia na iOS. Udało mi się wyeksportować wersję .war Memo Boxes i im podesłać, przy czym coś musiałem zepsuć przy eksportowaniu ewentualnie wynikało to z niedoskonałości przenoszenia libGDX na stronę, ale MemoBoxes działało okropnie. Za to dostałem darmową reklamę i coś w rodzaju recenzji.

Do tego próbowałem podesłać TTS do Ketchappu (wydawali kiedyś właśnie takie proste, kolorowe gierki), zero odzewu po automatycznym zwrocie:

ketchapp

Do tego gdzieś w internecie znalazłem kiedyś listę maili (w tabeli Excela) do dziesiątek serwisów o telefonach które piszą o aplikacjach/grach (głównie anglojęzyczne), myślę że taka aktualna lista nadal jest gdzieś do pobrania po odpowiednim wyszukaniu.


Sekrety działania

Sposób poruszania węża

Gdyby wyłączyć obrót figury, wąż poruszałby się po funkcji liniowej równolegle do jednej ze ścian danej figury, po kliknięciu zmieniałby ścianę do której równolegle się porusza na następną. W zasadzie to robiłem to bardzo na wyczucie, renderując obrazek pięciokąta i zmieniając współczynnik funkcji tak, aby wąż nie zbliżał się, ani nie oddalał od śledzonego boku. W kodzie wyglądało to tak:

if(timerMove > 0.0000005f){
snakeX = (float) snakeX + waysX[way]*(float)(ogniwa -1f) ;
snakeY = (float) snakeY + waysY[way]*(float)(ogniwa -1f) ;

Gdzie waysX i waysY wyglądały następująco:

waysX[0] = 1;
waysX[1] = -1.5f;
waysX[2] = 1;
waysX[3] = -0.32f;
waysX[4] = 1;

waysY[0] = -1.74f;
waysY[1] = 0;
waysY[2] = 1.74f;
waysY[3] = 1;
waysY[4] = 0.725;

Jak zrealizowałem jednoczesny obrót figury i zmianę funkcji po której poruszał się wąż?
Przy każdym kliknięciu iterowałem po elementach tablicy zawierającej współczynniki kierunkowe, przechodząc do następnego, odpowiadającego kolejnemu boku.

Co do obrotu całości:

updatecamera

Wygląda to bardzo archaicznie i nie polecam pisać w ten sposób, ale działa tak:

w momencie kliknięcia ekranu, ustawiam buforCam na wartość 120, timerem steruję odpowiednią szybkość odejmowania, za każdym odliczeniem odejmując 8 od bufora i jednocześnie obracając całość o 8 stopni. To (w przypadku pięcioboku) da całkowity obrót o jeden bok. Bardzo nieczytelne i przekombinowane.

Kolory

Zdefiniowałem kilka barw skali 0/255 w które mogłem wcześniej sprawdzić np. tutaj. Następnie z każdym kliknięciem przesuwam się o jeden element po tablicy, stopniowo zmieniając aktualną barwę tła.

kolory

regulatecolors

Realizacja kolizji:

Nie korzystałem z chociażby Box2D (po co wyważać drzwi buldożerem). Zamiast tego, zdefiniowałem obiekty Rectangle dostępne w LibGDX i korzystając z gotowych funkcji liniowych przypisałem ich kilkaset wzdłuż boków. Do tego tworzę obiekt typu Rectangle na każdej z części węża, można więc przy każdej klatce zwyczajnie iterować po tablicach z Rectanglami i sprawdzać czy części wężą nie kolidują ze sobą albo z bokami.

Poniżej mocno nieczytelny przykład:

collisions

Odnośniki:

Link do pobrania gry:

Google Play

Udostępniam całość kodu za darmo tutaj, jakość pozostawia sporo do życzenia, nie polecam do nauki.

Repozytorium

Soundcloud kolegi który podsunął pomysł na TTS:

Soundcloud

 

Sound Processing II – Piszemy modem

W poprzednim poście przedstawiłem teorię i pokazałem, że da się wygenerować odpowiedniej częstotliwości dźwięk i odczytać tę częstotliwość na komputerze. Teraz to wykorzystamy w praktyce. Zapraszam.


Możliwe, że słyszałeś już kiedyś, że dane przechowywane na komputerze reprezentowane są binarnie, tj. w postaci zer i jedynek. Mogło ci wtedy przyjść na myśl – “skoro kiedy włączę notatnik i zapiszę sobie w nim listę zakupów, to jak to się dzieje, że komputer potrafi je odczytać i wyświetlić z powrotem w postaci liter, skoro zapisuje zera i jedynki?”.

Z pomocą przychodzą systemy kodowania znaków takie jak ASCII.

Computers can only understand numbers, so an ASCII code is the numerical representation of a character such as ‘a’ or ‘@’ or an action of some sort.

Przypisują każdemu ze znaków odpowiednią liczbę, a tę można już odwzorować w postaci zer i jedynek (jeśli jeszcze nie wiesz jak działa przeliczanie liczb z systemu dziesiętnego na binarny, kliknij tutaj).

Poniżej przedstawiam tabelę kodów, gdzie liczbie z chociażby systemu dziesiętnego (kolumna dec, od decimal czyli właśnie dziesiętny) przypisywany jest konkretny znak.

Ascii Table
Źródło: http://www.asciitable.com/

Wróćmy więc do problemu przed którym stoimy – napisania programu który zamieniać będzie odpowiedni znak na możliwe do wysłania fale dźwiękowe.

Do generowania odpowiedniej częstotliwości dźwięku w Javie wykorzystałem darmową bibliotekę Beads(wrzuciłem ją już do repozytorium naszego projektu na GitHubie).

Następnie zrobiłem dokładnie to, co na wstępie ostatniego posta o pisaniu modemu: przypisałem częstotliwości do poszczególnych znaków. Przedstawia się to następująco:

private static final int PAUSE_FREQUENCY = 3000;
private static final int ZERO_FREQUENCY = 2500;
private static final int ONE_FREQUENCY = 2000;
W domyśle powyższe wartości są w [Hz].

Do modelu który omawiałem wtedy dorzuciłem jeszcze jedną rzecz, czyli częstotliwość która będzie oznaczać odstęp między znakami. Nie byłem pewny, czy komputer poradzi sobie z odczytywaniem przerwy (ciszy) pomiędzy wysyłanymi znakami, w następnych wersjach w których będziemy chcieli przyśpieszyć wymianę informacji.

Do zamieniania znaku na odpowiedni ciąg zer i jedynek skorzystałem z Javowej klasy Integer:

String message = Integer.toBinaryString(<twój string>);

Automatycznie zamienia on ciąg znaków na przypadające na niego liczby z kodu ASCII. Możecie zobaczyć to na nagraniu które wrzuciłem poniżej, porównajcie liczbę która była wypisywana z tą z tabeli ASCII.

Efekty działania programu nagrałem i wrzuciłem na YT:

Audio na filmiku jest trochę skopane. W rzeczywistości dźwięk był słyszalny dobrze, dało się ze słuchu odczytać wiadomość.

Jak widać po otworzonym na ekranie detektorze częstotliwości, nie było problemu z rodzieleniem przez komputer poszczególnych składowych (częstotliwości którą zdefiniowaliśmy dla pauzy, dla zera i dla jedynki), co jest istotne w kontekście dekodera który napiszemy w następnym poście (i w końcu przetestujemy przesyłanie znaków pomiędzy dwoma komputerami w praktyce!).

Krótkie słowo co do kodu.

Niektóre miejsca w klasie MessageSynthesiser.java opatrzyłem komentarzem z powodu problemów które napotkałem, domyślam się, że z powodu braku obeznania w Beads.

Przede wszystkim nie mogłem znaleźć nigdzie metody, która pozwoliłaby mi generować dźwięk przez podany czas, przeniosłem więc generowanie dźwięku do oddzielnej klasy którą można uruchamiać jako wątek i w poprzednim kontrolować czas jaki potrzebujemy.

Do tego, jeśli zbyt szybko tworzę nowe obiekty bezpośrednio tworzące dźwięk, to program wyrzuca kilka wyjątków. Być może uda mi się to w międzyczasie naprawić (dojść do tego skąd się to bierze), domyślam się, że problem tkwi we wzajemnym blokowaniu się obiektów w dostępie do głośników.

Gdyby kogoś zastanawiało, dlaczego wczytuję dane za pomocą:

static String readString() throws IOException {
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    System.out.print("Enter one character:");
    String s = br.readLine();
    return s;
}

…a nie na przykład za pomocą klasy Scanner;

ta metoda jest uniwersalna, zadziała zarówno w IDE jak i konsoli kiedy wyodrębnicie jar. Generalnie

System.out.print(“Enter something:”);

String input = System.console().readLine();

Zadziała włączając program tylko i wyłącznie w konsoli systemu, w IDE zwróci null.
Natomiast:

Scanner in = new Scanner(System.in); int i = in.nextInt();

String s = in.next();

  Zadziała tylko w IDE, w konsoli systemu zwróci null.

Repozytorium projektu:

https://github.com/dbeef/soundcoding

Odnośniki:

http://evanxmerz.com/soundsynthjava/Sound_Synth_Java.html

http://www.beadsproject.net/

dsp2017-1

Pokaż kod II – Memo Boxes

Moja pierwsza w miarę rozbudowana gra w Javie napisana parę lat temu, studium przypadku pt. Jak napisać brudny nieczytelny kod. To jest tutorial JAK NIE PISAĆ KODU. Przed czytaniem polecam zapoznanie się z grą aby wiedzieć za co odpowiedzialne są przytaczane fragmenty kodu. Google Play.


Pisanie językiem polskim w kodzie. Najgorsze co może być. Nawet jeśli masz problem z podstawowym angielskim, włącz sobie translator gdzieś w tle i alt-tabuj za każdym razem gdy potrzebujesz odpowiedniego słowa. Jak nie wiesz do końca jak nazwać zmienną aby dobrze opisała do czego służy – myśl nawet dłuższą chwilę. Wbrew pozorom, może i wygląda to głupio, ale to potrzebne. Konserwacja kodu boli, tym bardziej, jeśli ktoś to po tobie będzie czytał.

Untitled.png

Przykład 1. O co mi tu właściwie chodziło?

Z tego co pamiętam, to metoda odlicza i wypisuje sekundy w momencie wpisywania odpowiedniej kolejności kart (odsyłam do gry). Po if-ie którego sensu kompletnie już nie rozumiem następuje prawdopodobnie zmiana pozycji sprajta z odpowiednią sekundą, następnie… Wypisujemy go?
przyklad3

Przykład 2. Odpowiednik nazewnictwa po angielsku w Speechlist.

Zapominanie o OOP

Przenoś do klas co się da, przenoś do funkcji to co powtarzasz. To powinno być wkładane do głowy od pierwszego programu. Poniżej kod który min. zmienia teksturę przycisku skinu do kart który aktualnie jest wybrany – DLA POJEDYNCZEGO PRZYCISKU. Czyli powtórzyłem to dokładnie 6 razy. Dzisiaj stworzyłbym publiczną metodę dla przycisku która przyjmowałaby odpowiednie flagi (czy karta jest kupiona, czy aktualnie jest otwarte podmenu kupowania) i ją wywoływał dla każdego z przycisków (za pomocą jakiegoś prostego fora, wcześniej wrzucając je do tablicy).

2.png

Wszystko to co tutaj załatwiane jest w sposób rzemieślniczy, w skali masowej robię w klasie Screen projektu Speechlist, zamiast przejmować się ustawianiem przeźroczystości czcionki (w przejściach pomiędzy ekranami), robi to za mnie automatycznie klasa, dla każdego tekstu który do niej przypisałem:

przyklad5

Przykład 3. Odpowiednik w którym obiektowo realizuję cele.

Formatowanie i komentowanie
Tak właściwie, to jest gorsze od polskiego w kodzie. IDE SFORMATUJE ZA CIEBIE KOD JEDNYM SKRÓTEM (defaultowo InteliJ -> ctrl + shift +L, w Eclipsie zdaje się że ctrl + a żeby zaznaczyć cały plik a potem ctrl + shift + F – skróty mają to do siebie że wchodzą w pamięć mięśniową i plus minus uda ci się odpowiednio ułożyć palce ale jednocześnie możesz nie móc odpowiedzieć na pytanie jaka to kombinacja, nie mając klawiatury dosłownie pod rękami). Do tego te komentarze które niby miały coś przypomnieć, chwilowo usunąć linię kodu która coś psuła, ale właściwie to są na wieczne nieusunięcie w limbo pomiędzy żyjącym a nieżyjącym kodem.

3.png

Przykład 4. Niechlujnie sformatowany kod.

Przykład ze Speechlist, jak robić to dobrze; w zasadzie nie ma tu nawet czego komentować.

formatowanie

Przykład 5. Dobrze sformatowany kod.

Magic numbers

Ooo zapiszę sobie tutaj pozycję tego, w sumie to na chwilę, a nawet jeśli, przecież będę pamiętał co to miało oznaczać…

4.png

Przykład z kodu Speechlist jak robić to prawidłowo, w tym przypadku zadeklarowałem publiczną klasę na stałe:

przyklad

Przykład 6. Zadeklarowanie stałych czytelnymi nazwami w specjalnie do tego stworzonym pliku.

Całe repozytorium mogłoby być cytowane na jakichś wykładach, złoża przykładów są obszerne.Początkowym purystom językowym polecam ksiązkę Clean Code, przykłady w Javie ale mocno uniwersalnego zastosowania.Repozytorium z całym projektem do pobrania. Dla porównania, na odtrutkę, wrzucę prawidłowe odpowiedniki powyższych w moim ostatnim projekcie, Speechlist.

https://bitbucket.org/dbeef/speechlist


PS: Sorry za kod w postaci zdjęć, darmowy WP nie pozwala na wtyczki koloryzujące składnie, w sumie zrozumiały model biznesowy.

Bonus

Znalazłem w telefonie film pokazujący prototyp pierwszego Memo Boxes, minęło kilka miesięcy zanim wersja z filmiku stała się tą, która jest obecnie na Google Play.

Do tego parusekundowy filmik przedstawiający jakiś miesiąc przed wrzuceniem gry na Google Play, jak widać po monitorze w tle pracowałem nad cząsteczkami.

dsp2017-1

Pokaż kod I – Speechlist

Pierwszy post z serii o moich aplikacjach od zaplecza.

Zapraszam!


tl;dr
Stworzyłem aplikację na telefon do ćwiczenia języka angielskiego, polegającą na uzupełnianiu luk w tekście, z możliwością pobierania kolejnych, nowych testów z serwera.
https://play.google.com/store/apps/details?id=com.dbeef.speechlist.android


Przez kilka tygodni wakacji rozwijałem aplikację; cały pomysł zawiera się w rozwiązywaniu testów przez uzupełnianie luk gotowymi wyrazami.
O ile idea jest prosta (mnóstwo tego typu zadań w podręcznikach, na każdym etapie edukacji), rozwinąłem ją – w przeciwieństwie do ćwiczeń w podręcznikach, zeszytach, gdziekolwiek indziej – testy w telefonie mają 3 podstawowe plusy:

  • są w telefonie, zawsze pod ręką
  • wykonasz je wielokrotnie
  • co jakiś czas pula twoich testów się powiększa – możesz je pobrać z serwera, prosto w aplikacji, na który będę wrzucał co jakiś czas kolejne

Postawiłem też od razu na samo ćwiczenie i obycie z językiem;
Nie jest to zdecydowanie samouczek, który od podstaw nauczy języka.
Na pewno można jednak powiedzieć, że jest to coś pomiędzy czytaniem tekstu po angielsku, a uczeniem się na pamięć słówek, coś czego mi osobiście brakowało.

Czuję się zobowiązany wstawić link do materiału osoby która często pojawiała się w znaleziskach na wykopie, a wrzuca mnóstwo materiałów do nauki Javy pod względem pracy.
tl;dr wnioski bez oglądania tego filmu – pomysł na aplikację językową dokładnie pasuje w model aplikacji na staż, co napędzało pracę, bo przynajmniej z mojej perspektywy, często nad pracą nad tym projektem wpadała do głowy myśl z rodzaju “jak to bardzo bez sensu, co mi to niby da”.

Same testy staram się robić sam, w większości w oparciu o artykuły z Wikipedii, co zdaje się być wystarczające, ale:
testy podzielone są na 4 kategorie – vocabulary, idioms, tenses i various.
Jak można zauważyć – testy oparte o uzupełnianie ciągu tekstu, gdzie stawia się na naukę nowych słówek pasują do tej pierwszej – resztę uzupełniłem na podstawie materiałów z innych stron uzyskanych za mailową zgodą autorów.

Co do materiałów graficznych – skorzystałem z ikon dostępnych na prawach licencyjnych jasno pozwalających na takie użycie, każda jest z jednej konkretnej strony, toteż nie było zbyt dużo problemów z wstawianiem do aplikacji… hm. Uznania autorstwa? Tj. po prostu informacji o źródłach z których skorzystałem.

Koszty stworzenia takiej aplikacji i publikacji:

Publikacja na Google Play – 25 USD, płatne kartą, miałem już wcześniej bo robiłem proste gierki.
Wynajęcie serwera – za 1 miesiąc – 18 zł
Reszta – własna praca.

Tutaj kończą się ogólniki, a zaczynają szczegóły techniczne.


Szczegóły techniczne – co i jak działa (albo jak co czasem nie działa).

Szybko skacząc w głęboką wodę, technologie z których korzystałem:

  • Java SE
  • Hibernate
  • PostgreSQL
  • LibGDX
  • Tomcat8 (o ile to dodać jako technologię)
  • Jersey framework

Szybko tłumacząc;
Java SE, język w którym pisałem zarówno backend jak i frontend
Hibernate, framework z którego korzystałem aby połączyć web service z bazą danych, jaśniej;
aplikacja zajmująca się przesyłaniem odpowiednich testów w odpowiedzi na zapytania z jakiegoś telefonu, aby odpowiedzieć na zapytanie – jak wygląda test o id = 15 – łączy się z bazą danych na tym samym serwerze, pobiera z niego listę testów, sprawdza który ma identyfikator równy 15 i odsyła.
PostgreSQL – wspomniana baza danych
LibGDX – framework z którego korzystałem do napisania frontendu, zajmuje się wyświetleniem wszystkiego na właściwym miejscu na ekranie telefonu, teoretycznie do pisania gier – natomiast ja skorzystałem z niego ponieważ wcześniej miałem pewne doświadczenie w nim (dwie małe gierki i kilka takich których nie publikowałem, skazanych na wieczne niedokończenie), nie chciałem więc uczyć się od podstaw innego bo zbyt długo by to zajęło (zależało mi na czasie, nie chciałem żeby skończyło się jak z programami które zaczyna się i w którymś momencie odechciewa nad nimi pracować)
Tomcat8 – serwer aplikacji javowych, na nim uruchomiłem web service (jeśli już się poplątałeś, przypominam -> na komputerze (nazywanym też w innych miejscach tekstu serwerem) uruchomiłem tomcat, który jest z kolei serwerem aplikacji javowych, na którym uruchomiłem web service, który komunikuje się z bazą danych na tym właśnie komputerze, baza danych to po prostu inny program którego zadanie to dysponowanie danymi)
Jersey – po raz kolejny – framework, który odpowiada za konkretnie sieciowy aspekt backendu, czyli odbieranie/wysyłanie danych do klienta

Przechodząc do sedna , oto co dzieje się po uruchomieniu aplikacji:

  • Telefon wysyła zapytanie do serwera;
    podaj mi identyfikatory wszystkich testów jakie masz
  • Serwer odsyła odpowiednie dane
  • Telefon wczytuje testy które posiada na miejscu, w pamięci i porównuje ich identyfikatory z tymi z serwera – tworzy wtedy listę testów które są na serwerze, a których sam nie ma – wtedy wysyła kolejne zapytanie do serwera
    wyślij mi nazwy testów, o identyfikatorach o numerach { id testów których nie ma w telefonie }
  • Serwer odsyła odpowiednie nazwy
  • Telefon tworzy na ich podstawie przyciski z odpowiednimi podpisami, wkleja je do ekranu “downloads” i gdy użytkownik je klika, telefon zna ich identyfikator, więc wyśle po prostu kolejne zapytanie do serwera, o test o zadanym id.

Jak wygląda sam test?

Najłatwiej chyba będzie wkleić jeden z nich dla przykładu, potem omówić strukturę:

Jest to json, parsowany w kliencie by uzyskać konkretne dane, z odpowiednimi znacznikami (<<||>>) dla aplikacji aby wiedziała gdzie wstawić przycisk do wstawienia luki.
Słówka są podane w kolejności odpowiadającej lukom, w aplikacji natomiast ich wyświetlanie jest losowane, tak aby po prostu nie wstawiać słówek z listy jedno za drugim.
Różnica między id a uniqueID – id jest przypisywane przez bazę danych, odpowiada kolejności w liście z testami, natomiast uniqueID to id które samemu przypisuję, każdemu testowi z osobna, aby uniknąć konfliktu między klientem a serwerem, gdzie jeden nazywa swój test tak, a drugi inaczej. Z zasady przydzielam w jsonie wartość id taką samą jaką ma uniqueID, to bez znaczenia bo i tak ulegnie zmianie.

Perspektywy rozwoju (co działa lub nie i czy można to jeszcze bardziej… ulepszyć)

  • Zmieściłem aplikację w 3.7 mb, ale na pewno da się upakować ją w mniejszych rozmiarach (są odpowiednie narzędzia do odchudzania takich plików)
  • Graficzne zmiany, takie jak renderowanie tekstu w większej rozdzielczości i skalowanie go do mniejszej, tak aby uzyskać na klarowności, pasek przewijania pokazany podczas poruszania się po liście, płynniejsza praca kamery, wsparcie dla starszych Androidów
  • Wydanie aplikacji na iOS, co akurat jest trochę trikowe (na razie korzystając z maca kolegi dotarłem do momentu, gdzie za pomocą odpowiedniej wtyczki, kod z Javy konwertowany jest na odpowiednik w C#, potem należy do otworzyć w xcode i mając licencję dewelopera testować na urządzeniach Apple)

Co się tyczy materiałów do nauki:

Jeśli chodzi o Javę, ja zacząłem od Head First Java i z ręką na sercu mogę powiedzieć –na pewno nie od deski do deski.
Skończyłem gdzieś w rozdziale pisania aplikacji sieciowych, natomiast wydaje mi się że trochę wyćwiczyłem w praktyce, pisząc od tamtego czasu (jakieś 2-3 lata temu).

Jersey i Hibernate – proste do bólu tutoriale z sieci, prowadzące praktycznie za rękę do napisania własnego rest service
LibGDX – to samo co poprzednie, z tym że najważniejsze to ćwiczyć i ćwiczyć.

Inne

Przez jakiś czas miałem pomysł, aby Speechlist był sterowany… Głosem. Skorzystałem z Javowego API do Sphinxa i rezultaty możecie zobaczyć tutaj:

Pomimo niemal 100% rozpoznawalności słów (ograniczyłem Sphinxowy słownik tylko do słów z testu, dawału mu to bardzo małą bazę danych do porównania co przekładało się na szybkość i trafność) miałem trudności z przeniesieniem działającej wersji na telefon. Co prawda istnieje CMUSphinx na Android, ale sprawiał mnóstwo kłopotów.

Pokaż kod!!!111jeden

Mam pomysł aby nagrać kilka filmów, gdzie piszę Speechlist od podstaw, natomiast na ten moment zostawiam tu moje repozytorium z gotową aplikacją:

Frontend:

Główny branch – “new libgdx version”

Branch z rozpoznawaniem mowy – “master”

Branch “no speech recognition” – śmieci

https://bitbucket.org/dbeef/speechlist

Backend:
https://bitbucket.org/dbeef/speechlist-rest-service


PS: Wiecie co mnie denerwuje w blogerach? Skoro mam okazję to o tym napiszę: memiczność na siłę. Wrzucanie hehe śmiesznych obrazków gdzie tylko się da w taki sposób, że ma się ochotę rzygnąć. Stop.


dsp2017-1.png

Sound processing I

Pierwszy post z serii o przetwarzaniu i modulowaniu dźwięku.

Niedługo pojawi się wersja wideo.

Zapraszam!


Jak dźwięk odbiera komputer?

Źródło ilustracji: https://processing.org/tutorials/sound/

Zmiana ciśnienia zamieniana jest na prąd zmienny będący odpowiednikiem fali akustycznej, potem trafia on do przetwornika analogowo-cyfrowego (ADC) który generuje z niego sygnał cyfrowy (czyli to etap w którym fala dźwiękowa odwzorowana jest już w postaci zer i jedynek), dalej trafia to do pamięci komputera i… Proces odwrotny.

Te fakty przydadzą się nam dalej, być może już się domyśliłeś, że skoro konkretna montonna częstotliwość wygeneruje charakterystyczny tylko dla niej ciąg zer i jedynek, to można przesłać w ten sposób zakodowaną informację, dla przykładu:

1000 Hz – litera A

2000 Hz – litera B.

Załóżmy też, że dla niezawodności, częstotliwość będzie modulowana przez dokładnie jedną sekundę, takie też będą przerwy pomiędzy emisjami. Wtedy, aby przesłać wyraz ABBA, przez sekundę generowane byłoby 1kHz, potem sekundowa przerwa, 2kHz, przerwa, 2kHz, przerwa 1kHz. Na ten moment by to zadziałało, chociaż celowo pominąłem pewne aspekty o których wspomnę później.

Na tej stronie możesz wygenerować monotonną falę dźwiękową o podanej częstotliwości i ją usłyszeć.

Być może przypomniało ci to kodowanie Morse’a, bardzo dobra analogia.

Skoro cała magia już odczarowana, przesiadamy się do następnego wagonu.

Jak działa modem.

W bardziej zaawansowanej formie, to co przeczytałeś w poprzednim paragrafie było jeszcze jakiś czas temu implementowane w modemach telefonicznych aby przesyłać internet.

Szum i zbieranina dziwnych dźwięków to słyszalne dla człowieka odwzorowanie danych które są w tym momencie przesyłane, zupełnie jak w przykładzie powyżej.

Gdyby interesowała cię dokładna definicja, to za Wikipedią:

Modem (od modulator-demodulator) – urządzenie elektroniczne, które moduluje sygnał w celu zakodowania informacji cyfrowych, tak by mogły być przesyłane w wybranym medium transmisyjnym, a także demoduluje tak zakodowany sygnał w celu dekodowania odbieranych danych.

Najbardziej znanym przykładem jest modem akustyczny zamieniający cyfrowe dane z komputera osobistego na modulowany sygnał elektryczny w zakresie częstotliwości akustycznej kanału telefonicznego. Te sygnały mogą być przekazywane przez linie telefoniczne i demodulowane przez inny modem po stronie odbiornika, aby odzyskać dane cyfrowe.

Jak przesyłana jest informacja.

W sieci komputerowej zera i jedynki kodowane są przez niskie i wysokie napięcia. W dźwiękowym odwzorowaniu które napiszemy, będą to możliwie wysokie częstotliwości. Dlaczego?

Zauważ jak duże zanieczyszczenie dźwięków może być w tle. Rozmowy ludzi, szuranie po podłodze, przejazd tramwaju/samochodu obok będzie zarejestrowany na komputerze, jeśli w tym czasie będzie odbierał transmisję, może mu to sporo namieszać (będzie miał problemy z interpretacją wiadomości). Teraz pytanie główne, ile z naturalnych źródeł dźwięku generuje wysokie częstotliwości, powiedzmy powyżej 2kHz? (sprawdź koniecznie jak brzmią 2kHz stroną z paragrafu I)

No właśnie. Dlatego korzystamy z takiej częstotliwości, by możliwie jak najmniej szumu mogło się wkraść, ale nadal byłaby słyszalna dla zwykłego mikrofonu i możliwa do wygenerowania zwykłymi głośnikami.

O czym zapomnieliśmy?

W sieci komputerowej informacja kodowana jest za pomocą standardu Ethernet. Jest to umowa (protokół komunikacji) pomiędzy komputerami, w jaki sposób dane będą podróżowały po sieci. Z czego więc składa się taki umowny pakiet danych poza właściwą informacją którą chcemy przesłać?

Źródło ilustracji: https://pl.wikipedia.org/wiki/Ethernet#Ramka_sieci_Ethernet
Ramka sieci Ethernet wersja 1
  • Preambuła – składająca się z 7 bajtów złożonych z naprzemiennych jedynek i zer:
10101010101010101010101010101010101010101010101010101010

co w zapisie szesnastkowym daje:

AAAAAAAAAAAAAA

Taki ciąg liczb pozwala na szybką synchronizację odbiorników.

  • SFD – (ang. start frame delimiter), czyli znacznik początkowy ramki w postaci sekwencji 8 bitów (1 bajt):
10101011

w zapisie szesnastkowym

AB
  • typ (2 bajty) – jeżeli wartość jest równa lub większa od 1536 (w zapisie szesnastkowym 0x0600), to określa typ protokołu który jest używany, jeżeli mniejsza to oznacza długość danych w ramce
  • dane (46 – 1500 bajtów) – jeżeli dane mniejsze niż 46 bajtów, to uzupełniane są zerami
  • suma kontrolna (4 bajty) CRC[1]

W sieci może być wiele komputerów, dorzucamy więc kto przesyła i do kogo. Do tego suma kontrolna, która jest znacznikiem dla komputera, wskazującym, czy podczas przesyłania coś poszło nie tak i otrzymał zepsutą porcję danych (np. z powodu zakłóceń).

W pierwszej wersji naszego pomysłu pominiemy te problemy, ponieważ przesyłanie po mikrofonie i głośnikach danych jednocześnie przez dwa albo więcej komputerów byłoby… Powiedzmy, że kłopotliwe, biorąc pod uwagę ile szumów by wygenerowały i ograniczoną liczbę wystarczająco różniących się od siebie częstotliwości.

Będzie więc jeden nadawca i jeden odbiorca.

 Pomysł i pierwszy szkic tzw. proof of concept naszego programu.

Samo wykrywanie częstotliwości dźwięku jest na tyle złożonym problemem, że poświęcę temu osobny post, tymczasem skorzystam z gotowego rozwiązania udostępnionego na Githubie.

Wynik działania programu zamieściłem tutaj:

Jak widać program bez problemu obliczał wysokie częstotliwości z bardzo dużą dokładnością, chociaż przy ~400Hz i > 4kHz zaczynał mieć problemy.

Kod programu z powyższego filmu umieściłem tutaj.

Co dalej?

W następnym poście z tej serii napiszę i nagram na Youtubie proces pisania pierwszej wersji backendu do naszej gry na podstawie TarsosDSP i podejmę próbę przesłania porcji danych pomiędzy komputerami, do tego trochę teorii na temat wykrywania częstotlwiości.

W dalszej przyszłości – piszę grę którą obsłuży nasz dźwiękowy system wymiany informacji.

Działające projekty innych osób.

https://www.youtube.com/watch?v=gNddvmLRhvc

https://www.youtube.com/watch?v=ZgyWEFu8_HQ

https://github.com/quiet/quiet-lwip

 Odnośniki:

https://github.com/dbeef/soundcoding

https://processing.org/tutorials/sound/

https://pl.wikipedia.org/wiki/Ethernet#Ramka_sieci_Ethernet

dsp2017-1.png