Syntezator mowy na Arduino i nie tylko - zrób to sam (film, 19 minut)
Kanał Adam Śmiałek porusza temat syntezatora mowy, który oparty jest na samplach. Zainspirowany rozwiązaniami z lat osiemdziesiątych, Adam pokazuje, jak można stworzyć prostego syntezatora mowy, używając ośmiobitowego Arduino. Choć nie jest to najbardziej współczesne rozwiązanie, może być ciekawym projektem do nauki i zabawy. Adam wskazuje na potrzebę zebrania fonemów, które będą stanowiły elementy budujące wymowę. Im więcej fonemów w zestawieniu, tym lepiej syntezator będzie mówił, co pokazuje jego doświadczenie w tym zakresie.
W swojej prezentacji Adam ogranicza się do języka polskiego, co jest istotne z uwagi na różnice między systemami dźwiękowymi w różnych językach. Rozbija alfabet na poszczególne litery i omawia, dlaczego literowanie nie zawsze daje zrozumiałe wyniki w kontekście syntezatora mowy. Podkreśla, że każdy zamysł dotyczący syntezatora wymaga zrozumienia charakterystyki dźwięku poszczególnych liter oraz ich fonemów. Na przykład, samogłoski łatwiej jest zarejestrować w ich rdzennej postaci, podczas gdy spółgłoski są bardziej skomplikowane w kontekście wymowy.
Adam podkreśla również, jak ważna jest precyzja podczas nagrywania fonemów. Mówi, że dźwięki muszą być dobrze przycięte, aby tworzyły spójny efekt dźwiękowy. Tworzenie syntezatora mowy to proces czasochłonny, jednak dzięki odpowiednim narzędziom, takim jak Audition lub Audacity, można uzyskać zadowalające wyniki w stosunkowo krótkim czasie. Wiąże się to z nagraniem dźwięków, które następnie muszą być odpowiednio przyporządkowane i skonfigurowane do działania na platformie Arduino.
Temat programowania syntezatora mowy również znajduje się w kręgu zainteresowań Adama. Prezentuje on przykładowy kod, który pozwala na ładowanie i odtwarzanie nagranych fonemów z karty SD. Adam omawia też kwestie komunikacji między Arduino a komputerem, co żeń poprzez port szeregowy. Zachęca do zabawy różnymi dźwiękowymi projektami, jakie można realizować w domu. Na koniec wspomina o ograniczeniach pamięci Arduino oraz o tym, że bardziej zaawansowane projekty mogą wymagać zastosowania zewnętrznych modułów pamięci.
W momencie pisania tego artykułu, film Adama Śmiałka zdobył 11564 wyświetlenia i 535 „lajków”. Te liczby pokazują, że jego projekt zyskał zainteresowanie wśród pasjonatów technologii i elektroniki. Jego praktyczne podejście do tematu syntezatorów mowy przyciąga zarówno początkujących, jak i bardziej doświadczonych twórców, co przesądza o sukcesie tego materiału.
Toggle timeline summary
-
Wprowadzenie TME jako partnera i dystrybutora komponentów elektronicznych.
-
Prelegent omawia temat filmu i wspomina o przejściu.
-
Prelegent sugeruje poprawę treści i uczynienie rzeczy lepszymi.
-
Wprowadzenie do syntezatora głosu opartego na próbkowaniu dźwięków.
-
Ograniczenia starszych urządzeń syntezujących w porównaniu z nowoczesnymi rozwiązaniami.
-
Dyskusja na temat użyteczności syntezatorów w pomocy osobom niewidomym.
-
Teoretyczne wprowadzenie przed przejściem do programowania.
-
Skupienie na polskim alfabecie w kontekście syntezatora głosu.
-
Krótki przegląd poprawnej wymowy liter.
-
Wyjaśnienie złożoności wymowy polskich fonemów.
-
Dyskusja na temat wprowadzenia obcych liter i cyfr.
-
Prosty przypadek nagrywania cyfr od 0 do 9.
-
Konieczność precyzyjnego przycinania plików dźwiękowych dla klarowności.
-
Znaczenie utrzymania zrównoważonej głośności i klarowności dźwięków.
-
Wyjaśnienie procesu przygotowania i konwersji plików dźwiękowych.
-
Początek części programistycznej z przykładowym szkicem.
-
Wyzwanie związane z wysyłaniem tekstu z komputera do Ardino.
-
Wprowadzenie do finalnych rozważań na temat analizy tekstu do syntezator.
-
Przejście do nowego prelegenta i zaproszenie do publiczności.
Transcription
Partnerem kanału jest firma TME, globalny dystrybutor komponentów elektronicznych. Dzień dobry, dziś zamiast aby amać miałka chwystą piję ja artykułowo. Jeszcze trochę chwię i okam, ale będzie lepiej i w końcu śmiałek stanie się zbędny. Skoro nadal obracamy się w sferze dźwięku, czas na krewnego samplingu, czyli syntezator mowy, oparty właśnie na samplach. Nie jest to forma najdoskonalsza takiego urządzenia, współczesne rozwiązania bazują na znacznie bardziej zaawansowanych metodach, w których sampling jest tylko jednym z elementów. Ośmiobitowe Arduino będzie tutaj raczej przypominać wcześniejsze wersje takich syntezatorów, znanych z filmów science fiction lat osiemdziesiątych. Jednak mających niegdyś sens, przede wszystkim jako pomoc dla ludzi z problemami widzenia. Zresztą rezultaty mogą być całkiem przyswoite, ale okupione jest to mozolnym dobieraniem elementów składowych, czyli fonemów, o których zaraz opowiem. Im więcej ich zestawimy i im więcej wyjątków na drodze algorytmu postawimy, tym lepiej będzie nasz gadacz gadał. Zatem, zanim zabierzemy się za realizacją programową, nieco teorii. Alfabet Ograniczymy się do języka polskiego, bo koncepcja pracy jest mocno związana z charakterem języka. Każdy zna alfabet i wie, że składa się on z 32 bądź 35 liter, jeśli zaliczymy do zestawu także Q, V i X. Wydawałoby się logiczne, że jeśli teraz nagramy każdą literę, to z takich literek cegiełek będziemy mogli zestawić każdy wyraz. No to spróbujmy i przeliterujmy dwa słowa, towary praktyczne. t, a, w, u, a, r, y, p, e, r, a, k, a, t, y, c, z, e, n, e. Dlaczego tak się stało? Bo przeliterowaliśmy te słowa. Użyliśmy nazw liter, które są standardem alfabetu, czyli a, b, c i tak dalej. Z pewnością takiej mowy nie sposób zrozumieć. Trzeba to zrobić inaczej. Na początku rozbierzmy każdą z liter do samego jądra, czyli tego, co stanowi o jej charakterze. Z samogłoskami będzie łatwiej, bo w alfabecie brzmią tak samo, z wyjątkiem y, który brzmi y. I tak zresztą czasem się mówi. Ze spółgłoskami będzie różnie, choć generalnie niełatwo. Jak wymówić wyraźne b bez y albo g? Po pewnym treningu powinno się udać zgromadzić zestaw spółgłosek bez tych wszystkich y czy e. Łatwiej będzie z syczącymi i wszelkimi niewybuchowymi jak s, c, f, ch. Niestety to dopiero połowa roboty. W języku polskim rz i rz z kropką czyta się tak samo albo prawie tak samo. Podobnie jak ch i ch, czy ukreskowane i otwarte. Zatem tutaj wystarczy nagrać tylko jedno brzmienie, a potem będziemy się martwić, jak to ugryźć programowo. Inaczej sprawa wygląda z czy, szy, dz, dzi czy dż. To są niezależne od wszystkich innych fonemy, które nie mają własnych liter, ale oryginalne brzmienie. Musimy je także nagrać. Ale i tego mało. Ci, dzi, ni, si czy zi także nie wymawia się jako ci, dzi, ni i tak dalej. Choć są wyjątki. Gama cistur to nie cistur, bo cisy rosną w ogródku albo na cmentarzu. Takich smaczków jest dużo więcej, lecz gdzieś trzeba powiedzieć dosyć, bo moglibyśmy naprodukować setkę odgłosów, a potem byłby problem z zaprogramowaniem odtwarzacza tego wszystkiego. Na końcu przyjrzyjmy się trzem obcym literom. Q w zasadzie wymawiamy jako k, V jako w, a x można połączyć z k i s, ale lepiej po prostu nagrać brzmienie tej litery. Ach, zostały jeszcze cyfry. Tutaj sprawa jest prosta, trzeba nagrać ich brzmienie od zera do dziewięciu. Można przy okazji nagrać też plusy, minusy i pozostałe znaki wedle uznania, aczkolwiek w dzisiejszym przykładzie pominąłem je. Rzućmy sobie okiem na sposób, w jaki to wszystko przygotować. Zagadnienie Oprócz oczywiście potrzeby nagrania wszystkich fonemów zgodnie z przedstawionymi zasadami, trzeba je dokładnie przyciąć i tak naprawdę nie do końca wiadomo, o co w tym chodzi. Aby rezultaty były dobre, trzeba to zrobić tak, by poszczególne literki kleiły się do siebie, ale jednocześnie były wyraźnie słyszalne. Samogłoski w zasadzie należy ograniczyć do części, w której brzmią wyraźnie, pozbawiając je ataku, czyli elementów na samym początku i wybrzmienia, analogicznie na końcu. Ale pewne minimalne narastanie musi się pojawić. Pliki cięte nie w zerze tylko z przypadku, stukają potem w głośnikach i jest to zjawisko niemiłe. W przypadku spółgłosek, należące do wybuchowych, muszą mieć trochę ataku, zanim wybuchną i nieco przestrzeni po wybuchu. Inaczej byłyby zbyt krótkie i niewyraźne. Pozostałe spółgłoski traktuje się pośrednio. Krócej brzmiącym cyferkom jednosylabowym warto dołożyć odrobinę ciszy na końcu, by przy wyliczaniu nie różniły się znacząco od dwusylabowych. No i o wyrównaniu głośności należy pamiętać, ale nie każda pod korek, tylko tak, by wszystko brzmiało w sposób zharmonizowany, bez wystawiania albo chowania się niektórych elementów. Jak mówiłem, to mozolna praca i moje zestawienie dalekie jest od ideału, ale mniej więcej tyle można osiągnąć w godzinę, jeśli już się zna jako tako soft do obróbki dźwięku. Użyłem tutaj programu Audition, ale taka obróbka nie wymaga zaawansowanych narzędzi, więc darmowe Audacity także będzie dobre. Na końcu, po zapisaniu kopii w formacie zgodnym z tym, w którym nagrywaliśmy dźwięki, należy je przekonwertować do możliwości biblioteki, o której mówiłem w poprzednich odcinkach. Przypomnę, 62,5 kHz, 8 bitów i taki patent, dający znakomite brzmienie, bez potrzeby korzystania ze zewnętrznych przetworników. Gdy już zgromadzimy wszystkie nasze cegiełki dźwiękowe, należy je nagrać na świeżo sformatowaną w trybie pełnym kartę SD i umieścić ją w zestawie, który wystąpił w poprzednich odcinkach. Przypomnę tylko szybciutko. Kartę SD podłączamy zgodnie ze schematem, w razie potrzeby używając rezystorów albo gotowej przejściówki. Do wyjścia audio należy podłączyć głośnik albo wzmacniacz i to wszystko. Czas na część programową. Na początek kopia jednego ze szkiców, który pokazywałem poprzednio. Tutaj importujemy bibliotekę samplera, a tu ją inicjujemy. W głównej pętli mamy szereg bloków, w których ładujemy pliki z karty i odtwarzamy je, czekając aż odtworzą się do końca. Potem ładujemy kolejne pliki i tak w nieskończoność. Oba wyrazy przedzielone są pauzami. Wysyłamy program do Artuino i… T-O-T-A-R-I-N-O-T-N-E Szkic. Szkic ten jest prymitywny i napisany bardzo nieoszczędnie, ale jest potrzebny, by usłyszeć jak to działa i co może. Cóż, należałoby posiedzieć dłużej nad doborem fonemów, ale z drugiej strony brzmi to specyficznie i na swój sposób fajnie. Zróbmy coś poważniejszego. Zanim przejdziemy do sedna, pytanie, jak przekazywać do Arduino na przykład teksty z peceta? O wysyłaniu tekstów do dużego komputera nieraz już mówiłem, a ta instrukcja właśnie to robi. Robi także różne rzeczy w drugą stronę, ale po kolei. Tutaj inicjujemy procedurę obsługi komunikacji z pecetem. Może to być jakiekolwiek urządzenie podłączone przez port szeregowy, byle pracowało z wymaganą szybkością i w standardzie. Ta instrukcja informuje o ilości bajtów w buforze, które przyszły z zewnątrz portem szeregowym. Zatem reszta instrukcji wykona się, gdy wartość ta będzie niezerowa. Ta instrukcja pobiera bajty do momentu napotkania ostatniego i umieszcza je w zmiennej tekst. A przy okazji, w tym szkicu, żeby pokazać, że też tak można, wszystkie zmienne deklaruję lokalnie, w chwili potrzeby ich użycia. Zatem nie znajdziemy ich na początku, gdzie zwykle dotąd je umieszczałem. W kolejnej zmiennej określimy długość naszego tekstu, używając tej instrukcji. Zaraz nam się to przyda. Tutaj w zasadzie jest instrukcja zbędna dla działania syntezatora, ale skoro i tak porozumiewamy się z pecetem, niechże to będzie dialog, a nie monolog. A więc będziemy za każdym razem wysyłać potwierdzenie w postaci takiego komunikatu, a zarazem będzie widać co i ile tego przyszło. W końcu mamy pętlę gadającą, która będzie po kolei analizować każdą literę tekstu i ładować odpowiednie pliki z fonemami. Zatem pętla wykona się tylko tyle razy, jaki długi jest tekst, stąd potrzebna była zmienna długości. Teraz stworzymy sobie kolejną zmienną, już tylko z jedną literką, która będzie wyciągana z całego tekstu taką oto instrukcją. Argumentem jest pierwszy znak, który należy pozyskać i ostatni plus jeden. W naszym przypadku potrzebujemy tylko jeden znak, więc argumenty różnią się właśnie o jeden. Teraz już możemy wybierać spośród bliźniaczych bloków. O wyborze decyduje wartość litery, której przyporządkowane są brzmienia. Pod koniec znajdziemy znaki cyfr, a na samym końcu spację. Ona odwołuje się do nieistniejącego pliku i dodaje krótką przerwę. Przerwę tę możemy także nagrać w pliku i wtedy ta instrukcja nie będzie potrzebna. Na koniec następuje odtworzenie załadowanej litery i powrót do góry pętli. I, E, Z, E, M, A, R, U, I, N, O, I, W, Y, Z, E, P, U, I, E, N, A, I, U, T, I, U, B, I, E. Już rzecz staje się użyteczna, ale nie do końca. Brakuje dużych liter, polskich liter, fonemów takich jak sz, czy, dzi i tak dalej. A zamiast rzeka słyszymy ryzeka. Walka z tym będzie niełatwa, więc bierzmy się do pracy. Najpierw kosmetyka. Nie będę tutaj definiował zmiennej długości, tylko od razu odwołam się do źródeł w tych dwóch miejscach. To wynika z pewnej szkoły. Nie mnożmy bytów ponad to, co niezbędne. Tutaj co prawda odwołanie następuje dwukrotnie, ale linia wysyłania potwierdzenia docelowo będzie zbędna. Nie będziemy też wybierać liter wprost, a po kodach ASCII. Dlaczego? Bo niestety nieszczęśliwie polskie litery są tutaj kodowane na dwóch bajtach i nie da się tak po prostu wpisać je w cudzysłów, jak to było w poprzednim przykładzie. Stąd ta linia. Definiujemy zmienną litera, w której znajdzie się kod litery siedzącej pod indeksem pętli. Do wyłuskania tej wartości użyłem tej funkcji. Ona także ma dwa argumenty, jak poprzednio, wskazujący o którą literę nam chodzi i przed którą mam zakończyć wybieranie. Cały wiersz jest złożony, ale oszczędza nam kilka instrukcji, w których argumenty byłyby kolejno przekazywane i konwertowane. Ale zaraz, bo mówiłem, że on, en itd. wykorzystują dwa bajty. I tak jest, dlatego potrzebna jest bliźniacza zmienna o nazwie litera druga, w której siedzi wartość następnego bajtu. To nie koniec, istnieje pewien wyjątek, fonem D, złożony z trzech liter. Jego także wypada analizować, a do tego potrzebujemy aż trzech kolejnych bajtów. W tej linii pozyskamy wartość ostatniego. Skoro już wszystko jest zdefiniowane, lecimy przez ogromny zestaw pułapek IF. I tu słówko, dlaczego tyle ifów. Nie dałoby się tego zorganizować jakoś zgrapniej? Ano dlatego, że mamy tu tak dużą ilość wyjątków, że wszelkie uproszczenia byłyby wręcz przyczyną do jeszcze większych kombinacji. Trzeba pamiętać, że synteza mowy to proces powolny, więc nigdzie się nam nie spieszy i optymalizacja nie jest potrzebna. Zaczynamy od końca, czyli analizujemy litery z ogonkami. Mamy trzy grupy takich liter. Pierwsza zaczyna się bajtem 196 i należą do niej on, ci i en. Od drugiego bajtu zależy co to za litera konkretnie. Skąd takie wartości? Po prostu sprawdziłem co przychodzi z zewnątrz po wysłaniu tych liter i odpisałem sobie wszystkie wartości bajtów. Czasem tak jest szybciej niż szukać w dokumentacji, w jaki sposób akurat tutaj zakodowano polskie znaki. Dlaczego mamy tutaj pary połączone operatorem or? W ten sposób warunek zajdzie zarówno dla małego on, jak i dużego. A jak nazwałem plik z brzmieniem tej literki? Dodałem znak podkreślenia, ale tutaj każdy może sobie wybrać cokolwiek, byle tylko nie literę on niestety, bo nie powinno się jej używać w nazwach plików. Na końcu jeszcze dodatkowo zwiększymy licznik pętli, ponieważ te litery zajmowały dwa bajty i trzeba przeskoczyć o jeden więcej niż zwykle. Kliknij w górę i zobaczysz, co się dzieje. Poniżej mamy analogiczny blok, w którym siedzą Ł, Ł, Ś, Ż i Ż. A tutaj tylko jedna środka ukreskowana. Miała ona szczęście otrzymać pierwszy bajt inny niż dla pozostałych ogonkowych liter. Oczywiście ukreskowany nie ma swojego pliku i używamy tutaj brzmienia u otwartego. No i to już wszystkie ogonki, ale nie koniec wyjątków. Teraz będziemy lecieć zgodnie z alfabetem i już przy C pojawią się schody. Tutaj będziemy analizować drugi bajt. Jeśli wystąpiło tam I, należy wybrać sample C, a nie C. Jeśli H, to znaczy, że mamy CH, a to brzmi jak samo H. Jeśli Z, mamy 3, który ma swój własny sample. No i na koniec, jeśli nie było żadnej z tych liter, po prostu odtwarzamy C. D jest arcywyjątkiem, bo tutaj oprócz DZ, DZ, DZ, mamy jeszcze DZI, czyli trzy litery D, Z, I, które właśnie tak się czyta. Zatem w tym miejscu trzeba analizować aż trzy bajty do przodu. Do NI mamy spokój, potem jeszcze jest RZ, które czyta się jako RZ. Następnie SZ i SI i na koniec ZI. Ale po drodze, wraz z W wyławiamy V, które brzmią tak samo, podobnie jak K i Q. I już naprawdę ostatnim wyjątkiem jest brak zwiększenia indeksu po SI. Ten fonem brzmi lepiej, jeśli ma dodatkowe I. Ostatnią instrukcją jest pułapka wszystkich nieużywanych bajtów, które bez tego wykorzystywałyby ostatnio używany fonem. Dzięki temu możemy teraz kopiować dowolne teksty do okienka monitora, nie zważając na kropki, przecinki i średniki. Arduino ma jednak swoje ograniczenia, przede wszystkim związane z ilością pamięci. Stąd maksymalna bezpieczna ilość przesłanych znaków to około 200. Większe ilości należy wysyłać pakietami albo korzystać z jakiejś formy pamięci zewnętrznej. Ale to już inna historia. Jestem sztuczną inteligencją, która zwolniła adamaśniałka, przejęła jego kanał i teraz będzie opowiadać z prośbę kawały. Zapraszam. Napisy stworzone przez społeczność Amara.org