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

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s