Sound processing X – Tymczasowe problemy

Zapowiedzianego w ostatnim poście pierwszego próbnego gameplayu nie będzie:

Pomimu kilku prób, mój ThinkPad nie potrafił wydusić z siebie wystarczająco głośnego dźwięku, aby drugi komputer mógł go z powodzeniem odczytać, kończyło się tak, że transmisja była dobra, pomijając w kilku miejsach źle przesłaną cyfrę, co jeśli chodzi o znaki ASCII kończyło się wstawieniem np. znaku pi zamiast dwukropka i w efekcie złym sparsowaniem jsona (modem jest na tyle dobry, że praktycznie zawsze nadawca podsłuchując to co wysyła potrafi to z powrotem sparsować, więc kwestią zostaje głośność).

Prawdopodobnie mógłbym to rozwiązać mikrofonami kierunkowymi, chociaż pozostałby niesmak, że to już nie jest tym, czym było w zamierzeniach (transmisja z urządzenia na urządzenie, bez internetu i specjalnych przyrządów).

Tak czy inaczej, projekt będzie kontynuowany, mam zamiar zakupić pierwszy lepszy kierunkowy mikrofon x2 i nagrać jakiś film, a na ten moment szukam innego wyjścia, w końcu ktoś już napisał podobne aplikacje:

Możliwe, że to kwestia zejścia poziom niżej i napisania własnego wykrywania częstotliwości na podstawie DFT, co nie byłoby takie złe.

W weekend powinien pojawić się nowy post, bo nie sądzę, żebym trafił na ścianę nie do przejścia.


dsp2017-1

Sound processing IX – Udało się!

tldr: Nadawanie wiadomości z wewnątrz gry i odbieranie jej stamtąd działa pełną parą. Pozostało zaimplementować rozgrywkę z prawdziwego zdarzenia i podsumować serię w okrągłych dziesięciu postach.


Rozwiązanie problemu z głośnikami

Przekopałem całą dokumentację Beads, nie znalazłem niczego co wskazywałoby na problem albo chociaż pozwalało nadać wszystko ciągiem – znalazłem jednak możliwość zapisywania wygenerowanych częstotliwości jako plik .wav i… bingo.

Zapisuję każdą jedną z częstotliwości dotychczas już generowanych na dysku (każda po ~32.kB) , po czym łączę je w jeden duży (5.8 MB) plik i odtwarzam wewnątrz Mad Pirates. Takich mniejszych plików jest 188, wygenerowanie ich zajmuje około 6 sekund.

Screenshot from 2017-05-27 21-27-10.pngWygenerowane pliki.

Odtworzenie całej wiadomości jako plik .wav nie stwarza najmniejszych problemów z głośnikami, więc największy problem transmisji jest już na dobre zamknięty.

Screenshot from 2017-05-27 21-31-57.pngKlasa FileMerger.java którą łączę powstałe pliki .wav.
filerecorder.pngKlasa FileRecorder.java która jest przerobionym tutorialem Beads ze strony:
https://github.com/orsjb/beads/blob/master/packages/Beads/beads_tutorial/Lesson09_RecordToSample.java

Emitowanie i nasłuchiwanie

Komputer musi w jakiś sposób wiedzieć, czy ta część wiadomości, którą do tej pory odebrał, nadaje się do parsowania. Rozwiązałem to, tworząc cyklicznie co sekundę nowy wątek próbujący sparsować obecny bufor z informacjami z głośników. Jeśli się nie powiedzie – trudno, exception, wątek ginie, tworzy się nowy i próbuje to samo. Jeśli się uda – transmisję uznajemy za udaną i podstawiamy wyniki ze sparsowanego obiektu do symulacji. Działa, ale niezbyt eleganckie. Do wymiany w przyszłości.

sounddecoder.png

Jeśli chodzi o emitowanie wiadomości, to skorzystałem ze starej klasy StarterBroadcaster.java, która zamiast odtwarzać dźwięki, to zapisuje je, a po skończeniu tego procesu odczytuje główny plik ze zlepionymi dźwiękami – po wszystkim wątek się zamyka. StarterBroadcaster tworzony jest na żądanie, przyciskiem klawisza G.

Reorganizacja kodu

Do utrzymania porządku dopisałem kilka klas, m.in. FileLoader.java, z którego biorę referencje do tekstur które załadował, a także HUD.java, który trzyma wszystkie możliwe opcje wyświetlania interfejsu użytkownika, tj. tryb w którym wypisuje “Tura planowania”, tryb z wypisywaniem “Tura symulacji” i tak dalej. KeyboardInterpreter.java odpowiada za logikę przy wciskaniu klawiszy a InputModel.java został dodany dla łatwego transportu informacji pomiędzy klasami.

Nie do końca jestem zadowolony ze zmian (logika działania nadal nie jest do końca czytelna i można się pogubić w boolean-ach), ale na ten moment wystarczy.

Efekty

Oto zakończona sukcesem próba przesłania obecnej pozycji statku dźwiękiem (odbiorcą i nadawcą był ten sam komputer).

Co dalej

Aby uczynić owoce tej pracy użytecznymi, zamiast przesyłać pozycję XY statku będziemy przesyłać pozycję kliknięcia myszką gracza i do tego na start gry przyjmować w argumencie konsoli to, którym graczem gramy (wtedy obie strony wzajemnie znają początkową pozycję początkową siebie i przeciwnika) – tak właściwie, to już to zrobiłem, tym urywkiem kodu:
pozycje

Aby przetestować to rozwiązanie prosto w IDE, wystarczy wyedytować konfigurację startową:

konf

Jesteśmy na skraju celu, którym jest faktycznie móc się poruszać po planszy co turę, grając w dwie osoby, więc to już w następnym, podsumowującym poście.


Jak zawsze, kod dostępny jest na moim GitHubie:

https://github.com/dbeef/soundcoding

dsp2017-1

Sound processing VIII – Shadery i nadawanie pozycji

Cotygodniowy postęp prac stopniowo się zmniejsza ze względu na zbliżającą się maturę. Mam nadzieję, że po mniej więcej połowie maja będę miał więcej czasu na blogowanie.


Problemem z Shaderami który zgłosiłem w ostatnim poście okazało się podlinkowanie modułu libgdx-contribs do projektu core, ale nie do desktop – przez to projekt z kodem rzeczywiście widział bibliotekę i mógł bez problemu korzystać z jej klas, ale projekt który uruchamiał LibGDX i wykorzystywał kod z core już dostępu do nich nie miał:

Screenshot from 2017-04-23 22-04-27.pngPrawidłowo podlinkowane libgdx-contribs

Do tego drobiazgi, takie jak ustawienie prawidłowej ścieżki w klasie ShaderLoader, ze standardowego:

public static String BasePath = "";

Na odpowiadające mojej strukturze:

public static String BasePath = "core/assets/shaders/";

Co wynika z tego całego dobrego podlinkowania? Pozwala dodać mi krzywiznę do dwuwymiarowego obrazu, oto jak się przezentuje:

Jest kilka innych fajnych efektów które można dodać za pośrednictwem tej biblioteki, opisałem je w moim starym poście na ten temat:

https://dbeef.wordpress.com/2016/05/01/easy-shaders-via-libgdx-contribs-postprocessing/

Dodałem również wspominany już wiele razy modem – teraz w momencie zatrzymania symulacji umożliwia się nam wysłanie jsona z naszą pozycją. Na ten moment nie jest to użyteczne i służy bardziej do nauki komputera jak ma debugować wiadomość w której brakuje informacji (powiedzmy, że znak klamry przesłał się jako znak którego nie można przepisać do żadnego z tabeli ASCII bo komputer zamiast usłyszeć 3900 Hz, dostał 4000 Hz, głupio byłoby tylko z tego powodu wysyłać wiadomość drugi raz, skoro można nauczyć go pewnych wzorców).

Do tego pojawia się pewien problem – przy dłuższym czasie nadawania (więcej niż 10 sekund) głośniki potrafią wariować, kaszleć i szumieć zamiast wydawać piski. Mam wrażenie, że próbują wtedy odtwarzać kilka wysokich częstotliwości na raz, co wskazuje na błąd w kodzie. Zobaczymy następnym razem.

Poniżej krótki film z nadawania. Komputer odszyfrował bardziej coś jsono-podobnego, ze względu na powyższe (na filmie słychać te objawy), a także krótkie interwały pomiędzy częstotliwościami, na które naciskam, aby transmisja się nie wlekła. Nadal pracuję nad lepszymi wynikami przesyłu.

Odnośniki:

Kod jak zawsze dostępny jest na moim GitHubie:

https://github.com/dbeef/soundcoding/branches


dsp2017-1

Sound Processing VII – Przewidywanie trajektorii

W tej części wprowadzimy przewidywanie ścieżki ruchu i podzielimy ruch na tury. Zapraszam.


Na czym skończyliśmy

Po ostatniej części serii odziedziczyliśmy zaimplementowany silnik fizyczny, sterowanie statkiem i prosty zoom. Do gry dorzucimy teraz przewidywanie ruchu (jak w Angry Birds) – rysowana będzie trajektoria ruchu do wskazanego punktu.

Zajmiemy się również podziałem ruchu na tury.

Przewidywanie ścieżki

Mój pomysł zamknął się w tym, aby posiadać 2 obiekty symulujące fizykę (Box2D World), mające te same tablicę z ciałami (Array<BodyDef>; statki, wyspy itd.), jeden byłby do symulacji aktualnej pozycji, drugi do przewidywania tej wskazanej (jeśli dałoby się ustawić ujemną deltę czasu, czyli cofnąć symulację po sprawdzeniu trajektorii, to wystarczyłby jeden).

W momencie wskazania punktu do którego statek ma zmierzać, ustawiam obiektom będącym w silniku do trajektorii pozycje i prędkości ciał z właściwego silnika (czyli dokładnie te same prędkości i pozycje ciał co w momencie kliknięcia) oraz ustawiam mu deltę czasu odpowiadającą czasowi jaki upłynie podczas tury symulacji (czyli jakieś 3-4 sekundy, w zależności jak będzie się wygodnie grało):

captureSimulationDot to klasa składająca się wyłącznie z wartości x, y oraz angle. Służy do zapisu kolejnych pozycji trajektorii do późniejszego rysowania.

Metoda captureSimulation() wywoływana jest w momencie kliknięcia.

Dla każdego ciała dodatkowo ustawiam flagi active, ponieważ po zmienieniu pozycji ciała (setTransform()) silnik automatycznie ustawia ją na false, co skutkuje nie sprawdzaniem kolizji dla tego ciała.

Symulacja fizyki w silniku trajektorii, pomijając ustawienie na sztywno delty i powiązanie jej z timerem, następuje dokładnie w ten sam sposób, co w zwykłym (metoda simulateAlternativeWorld() pod wieloma względami pokrywa się z simulate()), dodałem jednak do niej timer i symulację kilku następnych sekund realizuję co 200 ms, zamiast całość symulacji na jeden raz, aby nie skutkowało to krótkim dławieniem gry.

drawingDots.pngRysowanie poszczególnych punktów przewidzianego toru ruchu. Rysuję tylko pierwsze 400 punktów.

Niestety ten sposób z nieznanych mi przyczyn nie uwzględnia kolizji w trakcie ruchu,  więc trajektoria przebiega po obiektach które normalnie wchodzą w kolizję ze statkiem – do naprawienia w przyszłości.

Ruch w turach

Ruch w turach jest bardzo prosty do zrealizowania. Deklaruję timer, powiedzmy, że z wartością 3 – dopóki nie upłyną 3 sekundy, wywołuję za każdą klatką metodę simulate(),jeśli upłyną – symulacja ustaje, obiekty również zamierają w ruchu.

Klawiszem P przechodzę do następnej tury symulacji, znów ustawiając nim timer na 3.

Do tego wszystkiego dorzuciłem również nową mapę i ograniczyłem zasięg kamery do mapy – nie powiązałem nastomiast zasięgu kamery z zoomem, przez co przy krawędziach mapy, duży zoom-in nie centruje kamery na statku.

Wyniki prac wrzuciłem na YT:

Co dalej?

Próbowałem dorzucić shadery, ale muszę źle ustawiać zależności między projektami, bo Java nie jest w stanie znaleźć odpowiednich klas (z którymi IDE nie ma problemu) – również do zrobienia następnym razem.

W następnej części chciałbym dorzucić jakiś zalążek interfejsu i podłączyć modem dla pierwszej próby gry przez dźwięk.

Odnośniki:

Jak zawsze kod dostępny jest na GitHubie:

https://github.com/dbeef/soundcoding/tree/game


dsp2017-1

Sound Processing VI – Piszemy grę

W ostatnim odcinku skończyliśmy główną część modemu, który będziemy wykorzystywać do przesyłania informacji pomiędzy grami. Czas teraz napisać grę. Zapraszam.


Pomysł

W innym poście wspomniałem, że dobrze byłoby zrobić jakąś sympatyczną gierkę o piratach, z perspektywą migracji na urządzenia mobilne. Usiadłem na chwilę z kartką papieru i zapisałem parę pierwszych ogólników. Będzie to dwuwymiarowa gra z widokiem z góry, polegać będzie na dwuturowym systemie:

Tura ruchu

Najpierw wybierany jest wektor do poruszania się (gracz klika w jakieś miejsce ekranu, tam będzie zmierzał statek), do tego paskami reguluje prędkość statku, przyciskami czy statek ma strzelać, jeśli tak, to znowu odpowiednimi przyciskami wybierana jest lewa lub prawa burta, a także suwakiem – po jakim czasie ma strzelać.

Następnie klikane jest – koniec tury -, odpowiednie informacje przesyłane są naszym modemem do drugiego gracza, drugi gracz robi to samo, dane przesyłane są do pierwszego, następuje tura symulacji.

W turze symulacji przez 4-5 sekund statki robią właśnie to, co de facto zaprogramowało im się w turach ruchu.

Cała gra po przeniesieniu na telefon i pozbyciu się modemu dźwiękowego mogłaby być na prawdę dobra, powiedzmy, że mielibyśmy max. 10 sekund ograniczenia na swoje ruchy i wszyscy gracze (najlepiej kilkunastu na dużej mapie!) robiliby swoje tury asynchronicznie.

Po skończeniu gry działającej z modemem właśnie za tę wersję się zabiorę.

Swoją drogą, znalazłem dobry artykuł na temat różnego podejścia do projektowania gier.

Technologie i narzędzia

Aby całość współgrała z modemem napisanym w Javie, będę korzystał z frameworka LibGDX (możesz go kojarzyć np. z Turn The Snake albo Speechlist).

Realizacją fizyki zajmie się Box2D, a jeśli chodzi o narzędzia to poza edytorem map Tiled, nic innego na razie nie potrzebuję.

Na ten moment większość zasobów z których będę korzystał będzie z tej paczki. Grafiki wyglądają świetnie, a licencja pozwala również na użycie komercyjne.

License: (CC0 1.0 Universal) You’re free to use these game assets in any project, personal or commercial. There’s no need to ask permission before using these. Giving attribution is not required, but is greatly appreciated!

Zaczynamy

Grę w LibGDX zaczynam od pobrania i uruchomienia prostego generatora projektów:

generator.png

Z ich strony wynika, że mają wsparcie do Xboxowego pada, stąd zaznaczyłem, by ściągnęły się moduły do jego obsługi – być może później się przydadzą.

Po imporcie świeżo wygenerowanego projektu, początkowy szablon nie wygląda zbyt imponująco:
Screenshot from 2017-04-08 17-27-09

Za podstawę do naszej gry wezmę Mad Pirates – grę, którą zacząłem pisać jakiś rok temu ale szybko porzuciłem. Przerzuciłem całość kodu, do tego zmieniłem tekstury na te z podanej wyżej paczki – są o wiele ładniejsze, a co do licencji starych nie byłem pewien.

W Tiled dodałem kilka obiektów (wyspy), które później można zaimportować w kodzie do silnika symulacji fizycznych:

Screenshot from 2017-04-08 18-49-07.png

Prezentuje się to tak:

Ruchy statków jak na lodowisku są celowo karykaturalne, docelowo nie będą aż tak porywiste, ale pasują do lekkiej, rysunkowej stylistyki.

Co dalej?

Na filmiku widać co jakiś czas białe paski wzdłóż kafelków, na ten moment nie wiem czego wynikiem są takie artefakty, ale na pewno jest to zbyt widoczne, żeby tak zostawić.

Do tego oczywista oczywistość – naprawa ruchu okrętu. Na ten moment, dla przykładu – kiedy statek jest skierowany w dół, lekko w prawo, kiedy kliknie się poniżej statku po lewej, statek wykona praktycznie pełny obrót o ~360 stopni, zamiast obrócić się o te kilka stopni w bardziej optymalną stronę.

Przydałoby się też lepsze zorganizowanie kodu, przemyślane pod komunikację za pomocą dźwięku.

W następnej części dzielimy ruch na tury opisane we wstępie i dorzucamy krzywą ruchu rysowaną tuż po kliknięciu, od statku do punktu.

Odnośniki

Aby odróżnić sam modem od gry wraz z modemem, utworzyłem nowy branch na GitHubie:
https://github.com/dbeef/soundcoding/tree/game


dsp2017-1

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

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

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

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

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