Popularny post ethanak Napisano Wrzesień 15, 2021 Popularny post Udostępnij Napisano Wrzesień 15, 2021 (edytowany) Pisałem niedawno, że marzyłem kiedyś i takim przenośnym urządzeniu, które czytałoby mi książki w czasie spaceru czy jazdy pociągiem. I to nie audiobooki, ale zwyczajne ebooki. Cóż - kilkanaście lat temu było to raczej mało realne. Minęły lata (kilkanaście jak wspomniałem), realia się zmieniły, i chociaż rzadziej wychodzę na spacery czy wsiadam do pociągu - postanowiłem zrealizować swoje marzenie. Co prawda jakieś pierwotne założenia sobie poczyniłem, jednak pozostały z nich tylko dwa: rozmiar nie przekraczający paczki papierosów i ESP32 WROVER jako serce urządzenia. Miałem w planach kartę microSD - ale po drodze stwierdziłem, że na moim ESP mam jakieś 8MB flasha do dyspozycji na FatFS, książka po skompresowaniu zajmuje nie więcej niż pół mega, czyli kilkanaście książek powinienem zmieścić bez dodatkowych urządzeń typu czytnik kart. Kilka problemów musiałem rozwiązać zanim zabrałem się do jakiejkolwiek roboty. Najważniejsza była klawiatura: musiała być intuicyjna (tak żebym nie musiał wyciągać urządzenia z kieszeni żeby znaleźć klawisz pauzy), a jednocześnie odporna na jakieś "samoczynne" wciskanie klawiszy w kieszeni. Postanowiłem umieścić ją razem z gniazdem słuchawek i wyłącznikiem na górnej (najkrótszej) ścianie obudowy; pozwoli to na bezwzrokowe obsługiwanie czytaka, a jednocześnie jest nikłe prawdopodobieństwo że klawisz sam się wciśnie. Trochę musiałem pokombinować z ilością klawiszy (początkowo miało ich być 13, ale po ograniczeniu do 9 okazało się, że wszystkie potrzebne funkcje są dostępne). Trochę kombinowania - i wyszło mi coś takiego: Dla porównania wielkości - moje pudełko na papierosy. Jest to oczywiście tylko próbny wydruk, ale wszystkie klawisze są wlutowane do płytki, a gniazdo słuchawek jest już na swoim miejscu. Drugą niemniej ważną sprawą był rozmiar aplikacji. Tego się obawiałem najbardziej - bo o ile tworząc microlenę mogłem sobie pozwolić na dość duże uproszczenia z uwagi na przewidywalność stosunkowo krótkich komunikatów (vide "poziomica") - o tyle tutaj musiałem zastosować pełne słowniki i wzorce rozpoznawania zwrotów. Dodatkowo microlena nie miała modułu rozpoznawania czasowników - tu musiałem go stworzyć praktycznie od nowa (wersja pecetowa korzysta albo z Morfologika, albo z wbudowanego kilkunastomegabajtowego słownika czasowników). I tu niespodzianka - wszystkie dane potrzebne do przetworzenia tekstu z książki na zapis fonetyczny zmieściły się w ok. 800 kB. Co prawda analizator morfologiczny nie jest tak dokładny jak w wersji PC, ale wystarczający aby nie było rażących błędów prozodii. Przynajmniej słuchając przez pół godziny przygód Anastazji Kamieńskiej nie zauważyłem błędów (poza kompletnie popsutą odmianą liczebników, ale to kwestia błędów w programie, a nie niewystarczających danych). Tak więc testowa aplikacja, zawierająca prowizoryczny serwer www (do uploadu i ogólnie manipulowania "księgozbiorem") oraz syntezator ma ok. 1.8 MB. Ponieważ nie przewiduję jakichś "flashożernych" procedur powinienem zejść poniżej 3 MB. Biorąc pod uwagę zajmujące nieco ponad 5 MB próbki głosu potrzebne Mbroli do syntezy - powinienem móc połowę z 16 MB pozostawić na FatFS. Opiszę jeszcze klawiaturę urządzenia (w trybie odtwarzania): Klawisze po lewej stronie (piątka): dwa klawisze regulacji głośności klawisz pauzy dwa klawisze regulacji prędkości Klawisze po prawej stronie (romb) która godzina/stan baterii (krótkie lub długie wciśnięcie) dwa klawisze przeskoku o zdanie/akapit w tył lub do przodu gdzie jestem? (tytuł, rozdział, akapit) W trybie pauzy można przeskoczyć o akapit lub rozdział lub (poprzez długie naciśnięcie klawisza "pauza") przejść do stanu "stop" (tu jeszcze nie mam opracowanego znaczenia klawiszy). Ponieważ urządzenie będzie miało wbudowany zegarek - wybrałem moduł zawierający pamięć EEPROM, w której mam zamiar trzymać zarówno wszystkie ustawienia programu, jak i informacje o aktualnie czytanej książce (rozdział/akapit/offset). Cóż - zobaczymy co będzie dalej 🙂 Na pewno opiszę efekty moich bojów z wbudowanymi w ROM funkcjami kompresji/dekompresji, muszę tylko doprowadzić ten fragment kodu do postaci umożliwiającej publiczne pokazanie go bez narażania się na inwektywy oraz skrobnąć parę słów opisu typu "dlaczego tak a nie inaczej". Aha: program jest pisany pod Arduino, tak że być może ktoś wykorzysta jakieś jego fragmenty. Postaram się publikować co ciekawsze kawałki. Jeśli ktoś miałby jakieś sugestie - jestem bardzo ciekaw, bo na tym etapie mogę jeszcze dużo zmienić. Stay tuned! Edytowano Wrzesień 15, 2021 przez ethanak 8 Link do komentarza Share on other sites More sharing options...
Popularny post ethanak Wrzesień 18, 2021 Autor tematu Popularny post Udostępnij Wrzesień 18, 2021 Ktoś tu kiedyś powiedział, że pisanie workloga jest pomocne nie tylko dla czytelników, ale również dla samego piszącego. Pisząc tę część musiałem zrobić parę poprawek w kodzie, który przed tymi poprawkami co prawda działał, ale nie nadawał się do pokazania 🙂 Ale do rzeczy. Ponieważ zrezygowałem z karty microSD naturalne wydało mi się przechowywanie książek w postaci skompresowanej. Po przeanalizowaniu kilku bibliotek okazało się, że albo są jakieś wielgaśne (przy czym większość funkcji pozostałaby nieużywana), albo zbyt uproszczone. Okazało się jednak, że jakieś funkcje kompresji znajdują się w ROM-ie ESP32. Rzut oka na dokumentację - tak, to jest dokładnie to czego potrzebuję! Szybciutko naskrobałem kawałek programu, znalazłem gdzieś w Internecie testowy fragment skompresowanego stringu, uruchomiłem, i... I nic. Funkcja tinfl_decompress_mem_to_callback uparcie zwracała mi -1. O co chodzi? I tu ciekawostka: okazało się, że tej funkcji po prostu nie ma. A ściślej jest, tyle że ogranicza się do: return -1; Znów poszukiwania. Aha, znalazłem kod źródłowy, czyli jestem w domu! Wkleiłem kod do swojego programu, i co? Znów -1. Ki diabeł? Tym razem poszukiwania potrwały nieco dłużej, ale znalazłem wyjaśnienie. Po prostu w kodzie istnieje odwołanie do funkcji MZ_MALLOC, a ta zdefiniowana jest jako: #define MZ_MALLOC(x) NULL Dlaczego? Po prostu wybrany przez autorów Arduinowej wersji model zakładał, że ta funkcja nie będzie używana. Teoretycznie mógłbym to zmienić, ale nie chciałem ingerować w to, co owi autorzy wymyślili (bo pewnie skończyłoby się na tym, że wykrzaczy się coś innego). Sprawdziłem, jaki obszar pamięci powinien być przydzielany. 32 kB - czyli można użyć zwykłego malloc! Drobna poprawka w programie, aktualny kod wygląda tak: static int mytinfl_decompress_mem_to_callback (const void *pIn_buf, size_t *pIn_buf_size, tinfl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags) { int result = 0; tinfl_decompressor decomp; mz_uint8 *pDict = (mz_uint8 *) malloc (TINFL_LZ_DICT_SIZE); size_t in_buf_ofs = 0, dict_ofs = 0; if (!pDict) return TINFL_STATUS_FAILED; tinfl_init (&decomp); for (;;) { size_t in_buf_size = *pIn_buf_size - in_buf_ofs, dst_buf_size = TINFL_LZ_DICT_SIZE - dict_ofs; tinfl_status status = tinfl_decompress (&decomp, (const mz_uint8 *) pIn_buf + in_buf_ofs, &in_buf_size, pDict, pDict + dict_ofs, &dst_buf_size, (flags & ~(TINFL_FLAG_HAS_MORE_INPUT | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF))); in_buf_ofs += in_buf_size; if ((dst_buf_size) && (!(*pPut_buf_func) (pDict + dict_ofs, (int) dst_buf_size, pPut_buf_user))) break; if (status != TINFL_STATUS_HAS_MORE_OUTPUT) { result = (status == TINFL_STATUS_DONE); break; } dict_ofs = (dict_ofs + dst_buf_size) & (TINFL_LZ_DICT_SIZE - 1); } free (pDict); *pIn_buf_size = in_buf_ofs; return result; } Uruchomiłem - i tym razem program się wykrzaczył dokumentnie, na odmianę uprzejmie kończąc się resetem. Na szczęście ESP ma tę miłą zaletę, że powody owego resetu są wypisywane na wyjściu serial. Tym razem chodziło o za mały stos. Gdzieś widziałem funkcję pozwalającą wywołać jakąś procedurę podając jej zaallokowany stos. Dokumentacja... jest! esp_execute_shared_stack_function to jest to, czego potrzebuję! Tak, a ściślej byłoby, gdyby ktoś tę funkcję uprzejmie udostępnił. Niestety - nikt taki uprzejmy nie był, więc trzeba było sobie poradzić samemu. Zamiast kombinowania postanowiłem po prostu stworzyć task, który uruchomi funkcję dekompresji i skończy się po jej wykonaniu. Kilka prób - i po przydzieleniu 32 kB stosu dekompresor wreszcie ruszył. No, to teraz kolej na kompresor. Już wiedziałem czego szukać, kod funkcji tdefl_compress_mem_to_output wkleiłem do programu, podmieniłem MZ_MALLOC na malloc, uruchomiłem... No i jak można było się spodziewać, funkcja zwróciła false. Co znowu... sprawdziłem ile RAM-u chce sobie zaalokować - no tak, chcieć to ona może, ponad 120 kB to trochę za dużo! Na szczęście mój WROVER wyposażony jest w 8 MB (no, powiedzmy 4, bo aby dostać się do drugiej połówki trzeba jakiejś wyszukanej ekwilibrystyki). Zmiana malloc na ps_malloc - działa! Czyli kod wygląda tak: static mz_bool mytdefl_compress_mem_to_output (const void *pBuf, size_t buf_len, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags) { tdefl_compressor *pComp; mz_bool succeeded; if (((buf_len) && (!pBuf)) || (!pPut_buf_func)) return MZ_FALSE; pComp = (tdefl_compressor *) ps_malloc (sizeof (tdefl_compressor)); if (!pComp) return MZ_FALSE; succeeded = (tdefl_init (pComp, pPut_buf_func, pPut_buf_user, flags) == TDEFL_STATUS_OKAY); succeeded = succeeded && (tdefl_compress_buffer (pComp, pBuf, buf_len, TDEFL_FINISH) == TDEFL_STATUS_DONE); free (pComp); return succeeded; } Teraz mogłem już zabrać się za funkcje zapisywania i odczytu książki. Ponieważ funkcje są dość podobne, pokażę tylko jak rozwiązałem odczyt. Przede wszystkim zdefiniowałem sobie taką pomocniczą funkcję: void xfree(char **b) { if (*b) { free(*b); *b=NULL; } } Jako że w programie mam trochę globalnych zmiennych (treść książki, słownik itp) pomaga mi to utrzymać porządek w pamięci, kiedy np. chcę zastąpić jedną zawartość inną. Konstrukcja typu: char *napis; ... xfree(&napis); napis = (char *) malloc(ileśtam); nigdy nie spowoduje wycieku pamięci, ani też próby ponownego zwolnienia zasobu. Wystarczy tylko pamiętać, aby ją stosować... Teraz musiałem stworzyć jakąś funkcję, która będzie kolejne fragmenty zdekompresowanego pliku umieszczać w RAM-ie. Użyłem zmiennych globalnych (bo tak wynikało z konstrukcji całej aplikacji), ale równie dobrze mógłbym stworzyć jakąś strukturę zawierającą wszystkie potrzebne zmienne i przesłać ją do funkcji przy pomocy nieużywanego parametru pUser: char *inflBody; int inflBodyLen, inflBodySize; #define INFLATE_MARGIN 1 static int cbkInflator(const void *pBuf, int len, void *pUser) { char *cs; if (!inflBody) { inflBody=(char *)ps_malloc(inflBodyLen = len + INFLATE_MARGIN); downOutLen =0; if (!inflBody) return 0; } else { if (inflBodyLen + len + INFLATE_MARGIN> inflBodySize) { cs = (char *)ps_realloc((void *)inflBody, inflBodyLen + len + INFLATE_MARGIN); if (!cs) { xfree(&inflBody); return 0; } inflBody=cs; } } memcpy((void *)(inflBody + inflBodyLen), pBuf, len); inflBodyLen += len; return 1; } I tu uwaga: użycie ps_realloc jest w tym miejscu bezpieczne, ponieważ w w chwili odczytu i dekompresji pliku cały PSRAM jest pusty, czyli mogę wczytać nawet dwumegabajtową książkę (a takie rzadko się zdarzają). Nie oznacza to, że w jakiejś innej aplikacji będzie tak samo! Kompletna funkcja realizująca dekompresję z pliku do PSRAM wygląda więc tak: static int decompress(File &inflfile) { xfree(&inflBody); char *fileBody; int filelen; filelen = inflfile.size(); if (!(fileBody=(char *)ps_malloc(filelen))) { inflfile.close(); return -1; } int n = inflfile.read((uint8_t *)fileBody, filelen); inflfile.close(); if (n != filelen) { free(fileBody); return -1; } inflBodyLen=0; inflBodySize=0; size_t bins = filelen; int rc = mytinfl_decompress_mem_to_callback( (void *)fileBody, &bins, cbkInflator, NULL, TINFL_FLAG_PARSE_ZLIB_HEADER); free(fileBody); if (!rc) { xfree(&inflBody); return -1; } inflbody[inflBodyLen]=0; // kończące zero stringu return inflBodyLen; } No i na koniec tak jak wspomniałem, nie znalazłem funkcji pozwalającej wywołać jakąś procedurę z własnym stosem, musiałem trochę pokombinować. Nie znalazłem niestety funkcji typu WaitForTask(), użyłem więc po prostu zdarzenia generowanego przez task dekompresora i oczekiwania na zdarzenie w tasku wywołującym: static File extf; static int extrc; static TaskHandle_t decompressTaskHandle = NULL; EventGroupHandle_t compev; StaticEventGroup_t compEventGroupBuffer; static void decompressTask(void *unused) { extrc = decompress(extf); xEventGroupSetBits(compev, 1); vTaskDelete(NULL); } static bool decompressExt(File &f) { extf=f; if (!compev) compev=xEventGroupCreateStatic(&compEventGroupBuffer); xTaskCreatePinnedToCore(decompressTask, "decompressor", 32768, NULL, 1, &decompressTaskHandle, 1); xEventGroupWaitBits(compev, 1, pdTRUE, pdFALSE, portMAX_DELAY); return extrc >= 0; } W ten sposób mogę użyć decompressExt() w programie nie martwiąc się, że nie wystarczy stosu. Dość przynudzania - publikuję te fragmenty kodu z nadzieją, że komuś to może pomóc. Zobaczymy, co będzie dalej - pewnie opiszę co najmniej dwa ciekawe błędy który popełniłem pisząc aplikację 🙂 Stay tuned! 5 Link do komentarza Share on other sites More sharing options...
Gieneq Wrzesień 20, 2021 Udostępnij Wrzesień 20, 2021 (edytowany) @ethanak przeczytałem i projekt wygląda bardzo ambitnie 🙂 ciekaw jestem jak ma się wspomniana prozodia: Dnia 15.09.2021 o 20:22, ethanak napisał: Co prawda analizator morfologiczny nie jest tak dokładny jak w wersji PC, ale wystarczający aby nie było rażących błędów prozodii Pisałeś że dodasz przyciski regulacji prędkości odczytywania. Jak to będzie się mieć w porównaniu z poziomicą, tamta wydaje się mówić nieco za szybko. Edytowano Wrzesień 20, 2021 przez Gieneq Link do komentarza Share on other sites More sharing options...
_LM_ Wrzesień 20, 2021 Udostępnij Wrzesień 20, 2021 (edytowany) Takie pytanko, skoro nie używasz kart pamięci, to jak ładujesz książkę do czytnika? A dobra przeczytałem Cytat Tak więc testowa aplikacja, zawierająca prowizoryczny serwer www (do uploadu i ogólnie manipulowania "księgozbiorem") Edytowano Wrzesień 20, 2021 przez _LM_ Link do komentarza Share on other sites More sharing options...
Polecacz 101 Zarejestruj się lub zaloguj, aby ukryć tę reklamę. Zarejestruj się lub zaloguj, aby ukryć tę reklamę. Produkcja i montaż PCB - wybierz sprawdzone PCBWay! • Darmowe płytki dla studentów i projektów non-profit • Tylko 5$ za 10 prototypów PCB w 24 godziny • Usługa projektowania PCB na zlecenie • Montaż PCB od 30$ + bezpłatna dostawa i szablony • Darmowe narzędzie do podglądu plików Gerber Zobacz również » Film z fabryki PCBWay
ethanak Wrzesień 20, 2021 Autor tematu Udostępnij Wrzesień 20, 2021 (edytowany) 3 godziny temu, Gieneq napisał: Jak to będzie się mieć w porównaniu z poziomicą, tamta wydaje się mówić nieco za szybko. Odpowiem nie wprost 🙂 Swego czasu autorzy espeaka-ng (nie mylić z oryginalnym programem Duddingtona) założyli, że maksymalna prędkość będzie wynosiła (o ile pamiętam) 400 wpm (słów na minutę). Od razu pojawiły się protesty niewidomych, że smędzi, że za wolny, aż w końcu po długich dyskusjach z autorami (którzy byli bardzo pewni swoich racji i argumenty niespecjalnie docierały) ktoś wreszcie napisał patcha, pozwalającego zwiększyć prędkość do 700 wpm (co uznano za wystarczające). A weź pod uwagę to, że normalna prędkość to 150 do 200 wpm... Prędkość jest sprawą względną i najlepsza to taka, przy której masz komfort słuchania. Poziomica ma przyciski szybciej/wolniej (chociaż służą w tym przypadku do ustalenia jednej słusznej prędkości) i każdy może sobie dostosować prędkość do własnych upodobań. Ja np. słucham zwykłych audiobooków z przyspieszeniem 1.3x (powyżej tego sonic ma problemy z niektórymi spółgłoskami), dodatkowo ograniczam długość pauz między zdaniami do max. 800 msec (odnoszę wrażenie, że lektorom płaci się od minuty przeczytanego tekstu i stąd długaśne pauzy w audiobooku). Przy słuchaniu książek nagranych Mileną używam wbudowanego w playera audiobooków regulatora prędkości (też sonic) zależnie od warunków, w których słucham książkę. W playerze jest to niestety niezbyt wygodne - trzeba wyjąć telefon, tapnąć na "regulację" i wybrać albo któreś ze standardowych ustawień albo dopasować swoje. Stąd uznałem za ważne wyprowadzenie regulatora na jakieś łatwo dostępne klawisze. Przykładowo: idąc ulicą mam ustawione tempo X (wolniejsze, mniej więcej takie jak w filmikach o poziomicy), ale siedząc dajmy na to w kawiarni przy kawie[1], gdzie nic mnie nie rozprasza i nie muszę mieć podzielnej uwagi - przyspieszam dodatkowo o współczynnik 1.15. A co do prozodii: pamiętaj, że nie piszę programu od początku, a korzystam z syntezatora, który przez paręnaście lat udoskonalałem, a który od początku był pisany z myślą o czytaniu książek jako głównym zastosowaniu. Tak że teraz mogę pominąć całą stronę teoretyczno-badawczą (choćby tabele translacji fonetycznej czy wzorce rozpoznawania wyrażeń i odmiany liczebników). Po prostu: już wiem co program ma robić, muszę tylko rozwiązać sprawę jak - i czy uproszczone algorytmy są dużo gorsze od zbyt wolnych i nie mieszczących się w pamięci urządzenia oryginalnych. Chociaż jako ciekawostkę mogę podać, że intonator (czyli fragment programu ustalający melodię na podstawie rozłożenia akcentów i typu frazy) pochodzi z oryginalnego espeaka, i to z jednej z wcześniejszych wersji (ta wydała mi się najlepsza) i od początku nic tu nie zmieniałem (poza wywaleniem fragmentów kodu dotyczących języków tonalnych, to ma czytać po polsku a nie po chińsku). Dużym uproszczeniem jest to, że w języku polskim praktycznie nie występuje pojęcie iloczasu. Wprowadziłem tu jednak kilka swoich założeń: Samogłoska '@' (schwa, występująca w angielskojęzycznych nazwach tylu 'Beatles' -> bit@ls) jest krótsza niż typowa samogłoska języka polskiego Samogłoski akcentowane są dłuższe o 10 msec (akcent podstawowy) lub 5 msec (akcent pomocniczy) - oczywiście ten czas jest mnożony przez współczynnik prędkości. W rzadkich wypadkach mogę przy tworzeniu słownika użyć samogłoski przedłużonej (np. elfickie imię Gandalfa 'Olórin' będzie przetłumaczone na fonetyczny 'olo:rin' z przedużonym 'o') Nie chcę tu za dużo pisać o akcentowaniu wyrazów, ale dam Ci przykład zastosowania klasyfikatora słów. Weźmy takie zdanie: Janek zawołał kelnera i zamówił kawę. Gdyby nie było klasyfikatora, syntezator powiedziałby to jednym ciągiem (tak mówi Ivona). Po włączeniu klasyfikatora program wykrywa, że w zdaniu są dwa czasowniki (prawdopodobnie orzeczenia) połączone spójnikiem, uznaje to za zdanie złożone i wymawia zupełnie inaczej. W załączniku kelner.zipmasz dwie wersje - standard.mp3 bez klasyfikatora i lektor.mp3 z klasyfikatorem. Przesłuchanie obu wersji powinno powiedzieć więcej, niż kilobajty opisu 🙂 1 godzinę temu, _LM_ napisał: jak ładujesz książkę do czytnika A, to nie jest takie proste. Pliki epub nie mają żadnej logicznej struktury i nie nadają się do bezpośredniej konwersji na audio. Jedynym formatem który by na to pozwolił jest fb2 - ale polskojęzyczne książki w tym formacie raczej nie występują, a konwersja z epuba (np. przez calibre) sama z siebie tej logicznej struktury nie stworzy. Na szczęście mam własny program do konwersji (Milena_ABC), który pozwala na szybkie i w miarę dokładne poprawienie zarówno logicznej struktury (podział na rozdziały i akapity), jak i tworzenie słowników wymowy. Dlatego epuba czy rtf-a przepuszczam najpierw przez ABC, a do urządzenia wgrywam pliki txt i dic (treść książki oraz słownik wymowy). Program na ESP po uploadzie przetwarza oba pliki na ISO-2 (o ile upload był w UTF-8), normalizuje treść, usuwa niepotrzebne linie ze słownika i tworzy mapę książki. Wbrew pozorom jest to lepsze niż wgrywanie książek na kartę SD, bo wtedy musiałbym mieć dodatkową aplikację na pececie do tych czynności, które w tej chwili robi program na ESP. A nie zawsze muszę mieć dostęp do swoich aplikacji - tymczasem konwersję z epuba na txt mogę zrobić na dowolnym kompie z jakimkolwiek konwerterem (np. calibre), bez słownika jako-tako mogę się obejść, i sprawę mam załatwioną. Dodatkowo planuję możliwość korekty słownika już wrzuconego na urządzenie (np. gdy nie zauważyłem jakiegoś często powtarzającego się zwrotu który jest wymawiany nieprawidłowo), muszę tylko wymyślić jakiś prosty interfejs który umożliwi mi wykonanie takiej operacji "w plenerze" (czyli komórka i tryb AP na ESP). Jest to czynność którą co prawda wykonuje się rzadko, ale brak takiej możliwości bywa denerwujący 🙂 No - ale to pieśń odległej przyszłości... --- [1] słowa "kawiarnia" i "kawa" zostały użyte z uwagi na możliwość czytania tematu przez osoby nieletnie 🙂 Edytowano Wrzesień 20, 2021 przez ethanak 2 Link do komentarza Share on other sites More sharing options...
ethanak Wrzesień 21, 2021 Autor tematu Udostępnij Wrzesień 21, 2021 Ech... właśnie się okazało, że syntezator i kompresja plików nie mogą działać razem - ESP nie może uruchomić tasku kompresora. Spróbowałem poszukać winnych - niestety, WiFi i AsyncWebServer na spółkę robią niezłą sieczkę w RAM-ie. No cóż, w czasie kompresowania pliku raczej nie muszę wysłuchiwać żadnych radosnych komunikatów 🙂 1 Link do komentarza Share on other sites More sharing options...
ethanak Wrzesień 22, 2021 Autor tematu Udostępnij Wrzesień 22, 2021 Miało być o błędach... ale błędy mogą poczekać, może się jeszcze coś ciekawego zdarzy. Natomiast dostałem jakiegoś turbonapędu i w ciągu niecałych dwóch dni napisałem część odpowiadającą za edycję słowników wymowy. Nie bawiłem się w jakieś piękne CSS-y, ważniejsze było to, żeby się dało jakoś prosto obsługiwać z komórki. Tak więc wygląda to tak (screenshoty z Chrome na Androidzie): Na razie działa... No cóż - tyle mogłem zrobić na płytce eksperymentalnej, teraz trzeba by wytrawić jakąś PCB... chociaż nie jestem specjalnie dobrej myśli, bo raster 1.25 mm to nie jest to co mi powinno wyjść za pierwszym razem 😞 Trzymajcie kciuki! 1 Link do komentarza Share on other sites More sharing options...
ethanak Wrzesień 28, 2021 Autor tematu Udostępnij Wrzesień 28, 2021 Ech... Urządzenie zmontowane, wsadzone do obudowy, prowizoryczny program wgrany, ruszyło... i podziałało jakieś dwie godziny. Pacjent: ESP32WROVER. Objaw: uparcie wchodzi w tryb oczekiwania na download Diagnoza: pin GPIO0 jakimś dziwnym sposobem dostał zwarcia do masy. Tzn. nie pełnego zwarcia, ale pomiar na rezystorze podciągającym wskazuje na wartość kilkunastu omów. Zwarcie na płytce wykluczam - nie ma z czym zwierać. Ki diabeł? Ktoś coś może wie na ten temat? Jutro spróbuję jeszcze raz z innym egzemplarzem ESP32. Trzymajcie kciuki! Link do komentarza Share on other sites More sharing options...
Popularny post ethanak Październik 13, 2021 Autor tematu Popularny post Udostępnij Październik 13, 2021 Cóż - okazało się, że za bardzo zaufałem autorouterowi Eagla, który tak ładnie poprowadził mi ścieżki że drobne przesunięcie elementu skutkowało jakimiś przebiciami. Pięknie wytrawiona płytka poszła do śmietnika, jej miejsce zajął ucięty kawałek płytki uniwersalnej, do której przykleiłem "na plecach" ESP i połączyłem kynarem. Tym razem wszystko pięknie zadziałało... przynajmniej od strony kabelkowo-elektronicznej. Wygląda to bardzo niefachowo więc może oszczędzę pokazywania zdjęć, ale najważniejsze że działa i dało się wreszcie zamknąć obudowę. No i oczywiście ESP o którym myślałem że jest uszkodzony okazał się całkowicie sprawny, czyli zostałem szczęśliwym właścicielem zapasowego ESP. Wniosek: projektując takie ustrojstwo do ręcznego lutowania (szczególnie przez takich sokolookich artystów jak ja) należy darować sobie współpracę z autorouterem i ręcznie poprowadzić ścieżki tak, aby wyeliminować możliwość jakichkolwiek zwarć czy przebić. To akurat jest proste i pewnie trzeba będzie tak zrobić... Przy okazji - zrezygnowałem całkowicie z automatycznego odłączania głośniczka. Po pierwsze docelowo powinien być rzadko używany, po drugie już w czasie prób okazało się, że dobrze by było mieć słuchawki cały czas w gniazdku i włączyć sobie głośniczek np. żeby coś tam ustawić. W związku z tym głośniczek podłączony jest po prostu przez wyłącznik. Natomiast z ciekawostek: otóż jako że w telefonach mikrofon jest podłączony do 2V przez rezystor, postanowiłem zrobić tak samo. Napięcie wziąłem z wbudowanego DAC, sprawdziłem, zadziałało, ładnie odróżnił wszystkie trzy klawisze słuchawek... dopóki nie włączyłem czytania. W tym momencie zwariował. Nie wiadomo dlaczego uparł się, że na pewno chcę mieć zero na wyjściu DAC-a... Na razie poradziłem sobie tak, że przed pomiarem napięcia na mikrofonie po prostu wymuszam te 2V na wyjściu DAC-a. Na razie działa... i można się na poważnie brać za program. A szczególnie za obiecany opis błędów 🙂 5 Link do komentarza Share on other sites More sharing options...
ethanak Październik 14, 2021 Autor tematu Udostępnij Październik 14, 2021 (edytowany) I jeszcze jedna ciekawostka. Po aktualizacji w Arduino IDE płytki ESP32 do wersji 2.0 i uporaniu się ze wszystkimi "deprecated' program przestał czytać książki z FatFS. Co się okazało: w poprzedniej wersji file.name() zwracał napis typu "/nazwa.txt', w bieżącej jest to 'nazwa.txt'. Na razie po prostu zamieniłem: strcpy(namebuffer, file.name() + 1); na strcpy(namebuffer, file.name()); ale chyba trzeba będzie sprawdzać pierwszy znak, bo czort wie co będzie w wersji 2.1 i następnych... Problem w sumie żaden, ale trzeba o nim wiedzieć... A tak to teraz wygląda: Udało mi się zachować konieczne wymiary - urządzenie jest nawet o parę milimetrów cieńsze od pudełka na papierosy (z poprzedniego zdjęcia). Przednia część (ta z głośnikiem) będzie jeszcze poprawiana. Z boku widać wyłącznik głośnika (nie wystaje poza obudowę żeby się nie zaczepiał o coś w kieszeni). Edytowano Październik 14, 2021 przez ethanak 2 Link do komentarza Share on other sites More sharing options...
Popularny post ethanak Październik 14, 2021 Autor tematu Popularny post Udostępnij Październik 14, 2021 Program obsługi klawiatury właśnie wszedł w etap końcowych testów - jest trochę nietypowy, bo niektóre klawisze zachowują się różnie zależnie od aktualnego stanu czytaka - ale wrzucę go tu jak skończę testowanie, bo wydaje mi się interesujący. Ale cóż: po to się między innymi workloga pisze, aby pokazać zarówno swoje sukcesy, jak i porażki. A jako że całe forum ma charakter taki dość mocno edukacyjny - trzeba pamiętać, że człowiek najlepiej uczy się na błędach, a najbezpieczniej na cudzych. Tak więc... babol numer jeden czyli lepsze jest wrogiem dobrego Trochę wprowadzenia: Ponieważ program operuje napisami w ISO-8859-2, proste funkcje z ctype.h są tu nieprzydatne. Ponadto potrzebuję dokładniejszego określenia klasy znaku: czy to litera, jeśli tak to czy samogłoska, czy z zakresu polskich znaków i tak dalej. Najprostsze wydało mi się zrobienie 256-elementowej tablicy typu uint8_t, gdzie każdy element odpowiadający znakowi miałby ustawione odpowiednie bity. Działało to w wersji na komputer bezbłędnie przez paręnaście lat, więc postanowiłem nie zmieniać koncepcji, a jedynie dopasować to do mikrokontrolerowej wersji. O ile poprzednio np. funkcja w C wyglądała po prostu tak: int my_isspace(int a) { return znaki[a & 255] & BIT_BLANK; } O tyle tutaj postanowiłem zmienić funkcję na makro, czyli: #define my_isspace(a) (znaki[(a) & 255] & BIT_BLANK) Proste i gdzie tu można zrobić błąd? Zadowolony z siebie zacząłem dostosowywać cały NLP do potrzeb mikrokontrolera. Wszystko było w porządku, dopóki nie spróbowałem wrzucić do procesora fragmentu książki... No tak. W pecetowej wersji program pobierał sobie cały akapit do nowego stringu zakończonego zerem, czyli wystarczyło sprawdzić czy do tego zera już dotarliśmy. Teraz nie ma pobierania akapitów, po prostu każdy akapit kończy się znakiem nowej linii. Czyli potrzebne są dwie funkcje: przykładowo my_isspace (zwracająca true dla każdego białego znaku oprócz znaku nowej linii) i my_iswhite (zwracająca true dla każdego białego znaku). Najprostsze pewnie by było wykorzystanie różnych bitów dla obu tych funkcji. Niestety - wszystkie osiem bitów było zajęte. No, ale przecież to nic trudnego, po prostu sprawdźmy czy to znak nowej linii! Nowe makro wyglądało tak: #define my_isspace(a) ((znaki[(a) & 255] & BIT_BLANK) && (a) != '\n') Teoretycznie proste, nawet działało... no, prawie. A jak wiadomo 'prawie' robi wielką różnicę. Po prostu dokładnie w jednym miejscu w programie miałem konstrukcję w stylu: if (my_isspace(*napis++)) cośtam cośtam... Chyba nie muszę tłumaczyć dlaczego w tym jednym miejscu program działał źle. Oczywiście - zamienienie makra na funkcję inline pomogło, ale zacząłem trochę baczniej przyglądać się swoim "ulepszeniom" kodu 😉 ...którą to czynność (czyli przyglądanie się) wszelkim, którym się wydaje że "coś będzie działało lepiej" bardzo mocno polecam. 2 1 Link do komentarza Share on other sites More sharing options...
ethanak Październik 15, 2021 Autor tematu Udostępnij Październik 15, 2021 No i na razie nici z obsługi klawiszy na mikrofonie. Powód prosty: wykorzystanie tylko połówki wzmacniacza skutkuje dość silnymi zakłóceniami. Przy podłączeniu słuchawek do mostka zakłóceń nie ma - prawdopodobnie niwelują się (w końcu to mostek H i producent nie przewidział podłączania tylko połówki). Na razie podłączam słuchawki tak jak producent przykazał, a brakiem obsługi tych klawiszy będę się martwić jak się okaże, że bez nich życie jest niemiłe. To już chyba ostatnia niespodzianka... Link do komentarza Share on other sites More sharing options...
_LM_ Październik 15, 2021 Udostępnij Październik 15, 2021 (edytowany) Próbowałeś jakiegoś filtrowania na wyjściu przycisków? Ewentualnie skoro i tak masz mikrofon to może polecenia głosowe? Tutaj link do tematu w którym rozmawialiśmy z @ethanak na temat podłączenia słuchawek. Przy okazji: winszuję świetnego i ciekawego projektu! Edytowano Październik 15, 2021 przez _LM_ 1 Link do komentarza Share on other sites More sharing options...
ethanak Październik 15, 2021 Autor tematu Udostępnij Październik 15, 2021 Tu nie chodzi o przyciski, bo te działają bezbłędnie, tylko o słuchawki które wydają z siebie dość dziwne dźwięki. Po prostu: słuchawki trzeba podłączać do tego modułu zgodnie z tym co założył producent, i nic tu się nie poradzi. Tak że na razie będzie bez klawiszy na mikrofonie, a w międzyczasie będę szukać jakiegoś zgrabnego transformatorka. Link do komentarza Share on other sites More sharing options...
ethanak Październik 15, 2021 Autor tematu Udostępnij Październik 15, 2021 Akumulatorek się ładuje, więc mogę z czystym sumieniem kontynuować opisy swoich walk z błędami. A więc: babol numer dwa czyli "dlaczego to nie działa"? Zacznę tym razem od objawów. Otóż czytakowi nie spodobał się miesiąc październik. Nie - bo nie. Czytał pięknie "piąty maja", "jedenastego listopada", ale np. "5 października b.r." uparcie wymawiał jako "piąty bieżącego roku". I już. Tym razem muszę trochę więcej wyjaśnić. Przy przetwarzaniu książek na mowę najbardziej złożona jest odmiana liczebników i skrótowców. O ile każdy szanujący się TTS wie, że "5 km" to "pięć kilometrów", a "2 km" to "dwa kilometry" - o tyle konstrukcje typu "w 1992 r." czy "od 1992 r." to z reguły dla nich zbyt skomplikowane. Niestety - przy czytaniu powieści syntezator musi zachowywać się jak uczciwy lektor, czyli potrafić właściwie odmienić "w tysiąc dziewięćset dziewięćdziesiątym drugim roku" i "od tysiąc dziewięćset dziewięćdziesiątego drugiego roku". Nic dziwnego, że duża część danych to wzorce rozpoznawania i odmiany liczb. Przykładowy wzorzec rozpoznawania daty rozpoznaje między innymi trzy liczby (dzień, miesiąc, rok). Każdy rozpoznany fragment określany jest strukturą, wyglądającą w skrócie tak: struct { int typ; // czyli co to jest, np. liczba int intval; // wartość, jeśli to liczba całkowita const char *str; // wskaźnik do miejsca w tekście gdzie znaleziono wzorzec int len; // długość rozpoznanego tekstu } W przypadku miesiąca wymawia się po prostu nazwę miesiąca zamiast liczby - i to tyle. Natomiast do wymowy liczb istnieją dwie funkcje: jedna dokładna, uwzględniająca m.in.odmianę, druga przybliżona, stosowana przede wszystkim do bardzo dużych liczb. Jako maksymalną wielkość dla dokładnej procedury przyjąłem 999999999999. Wszystko ładnie działało, ale pojawiła się konieczność rozpoznawania dat pisanych częściowo słownie, np. "5 maja 1992 r.". Ponieważ tworzenie dodatkowych wzorców byłoby raczej nieefektywne, nazwy miesięcy w dopełniaczu w takim kontekście traktowane są jak liczby - czyli np "lutego" to 2, "marca" to 3 i tak dalej. Zadziałało. I tak działało sobie, działało... i nagle przestało! Dość długo nie mogłem znaleźć przyczyny niechęci programu do jednego tylko miesiąca - ale w końcu sprawa okazała się bardzo prosta. Podsystem wymowy rozpoznanego wzorca sprawdzał w przypadku liczby, czy nie jest ona za duża dla dokładnej procedury. Ponieważ najprostszym sposobem, który nie jest ograniczony ilością cyfr (bo liczby mogą wychodzić poza zakres int) jest sprawdzenie ilości znaków w zapisie liczby - wszystko co miało powyżej 12 znaków wędrowało do funkcji przybliżonej. I działałoby tak dalej, gdybym nie postanowił ograniczyć wielkości liczby do 999999999 (czyli dziewięciu znaków). I co się okazało? Program sprawdzał długość napisu reprezentującego liczbę. W wersji uproszczonej (czyli na mikrokontroler) uznawał, że jeśli kazałem mu powiedzieć liczbę, to wprowadzony napis jest jakimś zapisem liczby. Ponieważ jedynie słowo "października" ma powyżej 9 liter, pozostałe miesiące były wymawiane prawidłowo, a ich numerek był brany z pola intval. Niestety – program uznał słowo "października" za zbyt długie, i zastosował funkcję uproszczoną. Ta natomiast po prostu wymawia poszczególne cyfry aż do napotkania końca napisu lub znaku nie będącego cyfrą. Jako że "października" nie rozpoczyna się od cyfry – program nie mówił nic 🙂 Przy okazji wyszło na to, że również niektóre rzymskie liczby były dla programu nie do przełknięcia – np. 1333 (zapisane jako MCCCXXXIII). Naprawa również nie była zbyt skomplikowana - ponieważ liczby rozpoznawane we wzorcach zawsze są ograniczone jakimś zakresem nie przekraczającym możliwości dokładnej wymowy, wystarczyło wprowadzić warunek: jeśli została rozpoznana liczba całkowita, program ma bezwzględnie korzystać z wartości intval i nie przejmować się rzeczywistym zapisem owej liczby. Myślę, że to bardzo dobitnie pokazuje, jak zmiana jakiegoś założenia wpływa na "objawienie się" błędu w części programu zdałoby się zupełnie niezależnej od owego założenia 🙂 Tego typu błędy są bardzo trudne do wyłapania, szczególnie gdy ich objawy występują tak rzadko, że mogą pozostawać niezauważone przez dłuższy czas. Tak więc kolejna nauczka (mam nadzieję że dotyczy to nie tylko mnie, ale również szanownych czytelników): jeśli wydaje Ci się, że jakiś fragment programu jest bezbłędny – z reguły masz rację: wydaje Ci się 🙂 Tyle na razie o błędach, ale jeśli jeszcze jakiś ciekawy wyskoczy to na pewno go opiszę. 2 Link do komentarza Share on other sites More sharing options...
Pomocna odpowiedź
Bądź aktywny - zaloguj się lub utwórz konto!
Tylko zarejestrowani użytkownicy mogą komentować zawartość tej strony
Utwórz konto w ~20 sekund!
Zarejestruj nowe konto, to proste!
Zarejestruj się »Zaloguj się
Posiadasz własne konto? Użyj go!
Zaloguj się »