Wysokowydajna analiza dźwięku w czasie rzeczywistym

Sercem aplikacji TPI na iPhone'a są narzędzia dźwiękowe, a być może najbardziej przydatne są miernik SPL (poziom ciśnienia akustycznego) i RTA (analizator w czasie rzeczywistym). Były to pierwsze dwa narzędzia omówione podczas wstępnego briefingu dla Cocoon i stanowią one zdecydowanie najbardziej złożone sekcje aplikacji.

Aby zrozumieć dlaczego, rozsądnie jest rozłożyć na czynniki pierwsze, co aplikacja musiała zrobić, aby przeprowadzić wymaganą analizę.

  1. Przechwytuj dźwięk z mikrofonu na urządzeniu użytkownika.
  2. Wykonuj szybkie obliczenia transformacji Fouriera (FFT) na przechwyconym dźwięku.
  3. Konwertuj wartości z FFT na prawidłowe wartości liniowe i dB-A lub dB-C.
  4. Wyświetlaj te dane w niestandardowych widokach przy 60 fps.

Na szczęście Apple zapewnia ramy umożliwiające osiągnięcie tego wszystkiego. Do przechwytywania dźwięku można wykorzystać platformę AVFoundation. Przyspieszenie może służyć do szybkiego wykonywania obliczeń FFT. Dostępna jest podstawowa grafika/rdzeniowa animacja, umożliwiająca szybkie rysowanie wskaźników i wykresów zaprojektowanych przez Paula.

Frameworki te są dość niskopoziomowe i wymagałyby dużo kodu, aby uzyskać efekt końcowy, ale dostępne jest eleganckie rozwiązanie.

Przedstawiamy AudioKit

Przed rozpoczęciem pracy nad samą aplikacją TPI zaczęliśmy eksperymentować, aby zobaczyć, jak wykonalne byłoby osiągnięcie pożądanego rezultatu. Podczas tych eksperymentów bawiłem się bezpośrednio frameworkiem Apple i osiągnąłem przyzwoite wyniki. Jednakże znalazłem także fantastyczny framework open source o nazwie AudioKit.

Jak prawie we wszystkim w programowaniu, zawsze jest ktoś, kto zrobił to pierwszy lub lepiej, a obsługa dźwięku w czasie rzeczywistym na iOS nie jest wyjątkiem. AudioKit zapewnia kompleksowy zestaw narzędzi do analizy i generowania dźwięku w Swift. Korzysta z frameworków niskiego poziomu (AVFoundation i Accelerate) i zamyka je w łatwym do zrozumienia rozwiązaniu opartym na węzłach.

Oznacza to, że amplitudę lub częstotliwość sygnału wejściowego mikrofonu można uchwycić z taką samą wydajnością, jak przy użyciu bezpośrednio frameworków Apple (cholernie szybko), ale przy użyciu kilku linii kodu.

Eksperymenty

Oto niektóre z najwcześniejszych eksperymentów, które przeprowadziliśmy przed ostatecznym projektem Paula. W tym momencie nie byliśmy pewni, jak dokładnie będzie wyglądać aplikacja i jak będą wyświetlane dane, ale dobrym pomysłem było zrobienie tego poza projektem aplikacji, ponieważ oznaczało to, że moglibyśmy szybko coś zrzucić i utworzyć nowy eksperyment, gdyby tak było nie działa.

Przejście do aplikacji TPI

Kiedy już byliśmy zadowoleni z podejścia do budowania narzędzi i Paul sfinalizował projekt z klientem, przyszedł czas na stworzenie miernika SPL i RTA w ramach projektu aplikacji TPI.

Moje początkowe podejście polegało na zainicjowaniu narzędzia AudioKit osobno w każdym widoku, a następnie próbie jego zamknięcia, gdy użytkownik opuści widok lub aplikację. Nie jest to jednak właściwe podejście i szybko przekonałem się, że takie postępowanie spowodowałoby awarie, które wydawały się dość sporadyczne.

Skończyło się na stworzeniu singletonu „AudioEngine”, który obsługiwałby wszystkie narzędzia (miernik SPL, RTA i generator dźwięku) w aplikacji TPI. Dzięki temu mogłem mieć centralne miejsce do zarządzania notatkami, dotknięciami i modułami śledzącymi wymaganymi dla narzędzi; a także możliwość dołączenia metod pomocniczych do konwersji na dB-A/dB-C i wartości liniowe dla różnych wykresów/wskaźników.

Oto bardzo uproszczony przykład tego, co zostało stworzone.

Gdy tylko zobaczyłem projekty, wiedziałem, że interfejs użytkownika będzie musiał zostać zbudowany od podstaw, ale mimo to próbowałem użyć wykresu open source dla RTA. Już po 10 minutach było jasne, że to nie zadziała. Potrzebowałem go do szybkiej aktualizacji, aby zapewnić płynne 60 klatek na sekundę, podczas gdy wykresy, które wypróbowałem, oczekiwały danych statycznych i nie koncentrowały się na wydajności.

Wskaźnik SPL

Wiedziałem, że utworzenie wskaźnika po lewej stronie będzie najtrudniejsze z dwóch, więc zacząłem najpierw zajmować się tym. Nigdy wcześniej nie tworzyłem okrągłego paska postępu, więc byłem nieco zaniepokojony.

Stworzenie go okazało się prostsze niż przewidywano, a konstrukcję można podzielić na dwie części: pierścień zewnętrzny i kleszcze po wewnętrznej stronie. Zaznaczenia utworzyłem bezpośrednio w metodzie draw(_ rect: CGRect) widoku, używając UIBezierPath. Ponieważ znałem liczbę wymaganych taktów, mogłem użyć Pi do obrysowania każdego określonego punktu. Zrobiłem to 4 razy zarówno dla małych, jak i dużych kleszczy w kolorze czerwonym i szarym. Ponieważ okrąg miał wycięty u dołu przekrój, obliczony kąt pomnożyłem przez ułamek 0,86.

Aby przyspieszyć proces, stworzyłem prototyp na placach zabaw Swift. Gorąco polecam to podejście, ponieważ pozwoliło mi sprawdzić wszystko bez konieczności kompilowania całej aplikacji i za każdym razem przechodzić do odpowiedniego widoku.

Początkowo narysowałem również pierścienie zewnętrzne, stosując tę ​​samą metodę, ale ze względu na potrzebę zastosowania gradientu stało się jasne, że łatwiej byłoby użyć niektórych CALayers

Użyłem CAShapeLayer, aby utworzyć pełny okrąg dla każdego z pierścieni i użyłem go jako ścieżki początkowej dla UIBezierPath. Wystarczyło wtedy po prostu ustawić strokeStart i strokeEnd odpowiednio na 0 i 0,86 i pogłaskać je na szaro dla pierwszego dzwonka. Pierścień postępu był podobny, ale bieżący postęp (tj. 0,5 dla 50%) został pomnożony przez 0,86, aby uzyskać koniec skoku. Oprócz tego do stworzenia pożądanego projektu użyto CAGradientLayer, a ścieżkę wykorzystano jako maskę na tej warstwie.

Wykres RTA

Patrząc na wskaźnik SPL, wykres ten był znacznie prostszy. Jak powiedziałem wcześniej, próbowałem to stworzyć przy użyciu wykresów typu open source i wypróbowałem dwa lub trzy, zanim się poddałem i stworzyłem własne.

Były to cztery rdzenie CALayers z podwarstwami lub ścieżkami dodanymi do tych warstw. Te warstwy to: barLayer, maxLayer, gridLayer i gradientLayer. Stworzyłem metodę renderowania, którą mogłem wywołać, gdy potrzebowałem aktualizacji wartości. Pierwotnie wywoływało to automatycznie za każdym razem, gdy wartości data lub maxData były ustawiane przy użyciu fantastycznych didSet obserwatorów Swifta, ale powodowało to pewne problemy ze zmianą po wstrzymaniu wartości przez użytkownika, więc zdecydowałem się wywołać ją ręcznie, gdy zajdzie taka potrzeba.

Podsumujmy

Podsumowując, podczas tworzenia miernika SPL i RTA dla TPI napotkaliśmy wiele problemów.

Po stronie interfejsu użytkownika występowały problemy z wydajnością przy użyciu gotowych wykresów, co powodowało widoczne zacinanie się podczas aktualizacji wartości. Musiałem także odświeżyć sobie wiedzę z matematyki GCSE, jeśli chodzi o liczbę Pi!

Jednak prawdopodobnie największym problemem, który wstrzymywał rozwój, była kalibracja narzędzi. Początkowo zakładałem, że wartość dostarczaną przez urządzenie można przeliczyć na dB za pomocą podstawowego 20 * log10(amplitude)przeliczenia, ale było to wyjątkowo naiwne.

W trakcie projektu dowiedziałem się o ważeniu dB-A, db-B i dB-C, miałem okazję bawić się sprzętem do analizy dźwięku, który kosztuje tysiące, i stawiał czoła wielu wyzwaniom. To najlepszy rodzaj projektu. Nie ma dla mnie nic bardziej ekscytującego niż nauczenie się czegoś nowego lub otrzymanie wyzwania, przed którym początkowo się wzbraniasz i myślisz „wow, jak ja to do cholery mam zrobić?” i konieczność rozwiązania go. Dla mnie to jest to, co kocham w programowaniu: rozwiązywanie zagadek.

Możesz także zapoznać się z artykułami „TPI: Tworzenie aplikacji na iPhone'a” i „TPI: Proces projektowania”.

🐦 Śledź nas na Twitterze

🔗 Odwiedź naszą witrynę

💌 Wyślij do nas e-mail: [email protected]