Высокопроизводительный анализ звука в реальном времени
В основе приложения TPI для iPhone лежат его звуковые инструменты, и, возможно, наиболее полезными являются измеритель SPL (уровень звукового давления) и RTA (анализатор в реальном времени). Это были первые два инструмента, которые обсуждались, когда было дано первоначальное описание Cocoon, и на сегодняшний день являются наиболее сложными разделами приложения.
Чтобы понять, почему, возможно, будет разумно разбить то, что нужно делать приложению, чтобы выполнить требуемый анализ.
- Захватите звук с микрофона на устройстве пользователя.
- Выполните вычисления с быстрым преобразованием Фурье (БПФ) для захваченного звука.
- Преобразуйте значения из БПФ в правильные линейные значения и значения дБ-А или дБ-С.
- Отображайте эти данные в пользовательских представлениях со скоростью 60 кадров в секунду.
К счастью, Apple предоставляет основы для достижения всего этого. Фреймворк AVFoundation может использоваться для захвата звука. Accelerate можно использовать для быстрого выполнения этих вычислений БПФ. Доступна Core Graphics / Core Animation, чтобы быстро рисовать шкалы и диаграммы, разработанные Полом.
Эти фреймворки довольно низкоуровневые и требуют большого количества кода для получения конечного результата, но доступно элегантное решение.
Представляем AudioKit
Перед тем, как приступить к работе над самим приложением TPI, мы начали экспериментировать, чтобы увидеть, насколько возможно достичь желаемого результата. Во время этих экспериментов я напрямую использовал фреймворки Apple и добился приличных результатов. Однако я также нашел фантастический фреймворк с открытым исходным кодом под названием AudioKit.
Как и почти все в программировании, всегда есть кто-то, кто сделал это первым или лучше, и обработка звука в реальном времени на iOS - не исключение. AudioKit предоставляет полный набор инструментов для анализа и генерации звука в Swift. Он использует эти низкоуровневые фреймворки (AVFoundation и Accelerate) и превращает его в простое для понимания решение на основе узлов.
Это означает, что амплитуда или частота микрофонного входа могут быть захвачены с той же производительностью, что и при использовании фреймворков Apple напрямую (чертовски быстро), но с использованием нескольких строк кода.
Эксперименты
Это одни из самых ранних экспериментов, которые мы проводили до окончательной разработки Пола. На этом этапе мы не были уверены, как именно будет выглядеть приложение и как будут отображаться данные, но было бы неплохо сделать это вне проекта приложения, поскольку это означало, что мы могли бы быстро сбросить что-то и создать новый эксперимент, если бы это было не так. не работает.
Переход к приложению TPI
Когда мы были довольны подходом к созданию инструментов, и Пол окончательно согласовал свой дизайн с клиентом, пришло время создать измеритель SPL и RTA в рамках проекта приложения TPI.
Мой первоначальный подход заключался в том, чтобы инициализировать AudioKit для каждого представления отдельно, а затем пытаться выключить его, когда пользователь выходил из представления или приложения. Однако это вообще неправильный подход, и я быстро обнаружил, что это приведет к сбоям, которые казались довольно спорадическими.
В итоге я создал синглтон «AudioEngine», который будет обрабатывать все инструменты (SPL Meter, RTA и Sound Generator) в приложении TPI. Это позволило мне иметь центральное место для управления заметками, касаниями и трекерами, необходимыми для инструментов; а также возможность включать вспомогательные методы для преобразования в дБ-A / дБ-C и линейные значения для различных графиков / датчиков.
Вот очень урезанный пример того, что было создано.
Как только я увидел проекты, я понял, что пользовательский интерфейс нужно будет создавать с нуля, но я все еще пытался использовать граф с открытым исходным кодом для RTA. Через 10 минут стало ясно, что не получится. Мне нужно было его быстро обновить, чтобы обеспечить плавную скорость 60 кадров в секунду, тогда как графики, которые я пробовал, ожидали статических данных и не концентрировались на производительности.

Датчик SPL
Я знал, что создать датчик слева будет сложнее всего из двух, поэтому я начал разбираться с ним в первую очередь. Раньше я никогда не создавал круглую шкалу выполнения, поэтому немного беспокоился.
Создать его оказалось проще, чем предполагалось, и конструкцию можно разбить на две части: внешнее кольцо и клещи на внутренней стороне. Я создал галочки непосредственно в draw(_ rect: CGRect) методе представления, используя UIBezierPath. Поскольку я знал необходимое количество штрихов, я мог использовать Пи, чтобы обводить каждую конкретную точку. Я проделал это 4 раза и для маленьких, и для больших отметок красного и серого цвета. Поскольку у круга было вырезано сечение внизу, я умножил вычисленный угол на дробь 0,86.
Чтобы ускорить процесс, я создавал прототип на игровых площадках Swift. Я настоятельно рекомендую этот подход, поскольку он позволял мне проверять вещи без необходимости компилировать все приложение и каждый раз переходить к правильному представлению.
Изначально я нарисовал и внешние кольца, используя тот же подход, но из-за необходимости градиента стало очевидно, что было бы проще использовать некоторые CALayers
Я использовал CAShapeLayer , чтобы создать полный круг для каждого из колец, и использовал его как начальный путь для UIBezierPath. Тогда это был просто случай установки strokeStart и strokeEnd на 0 и 0,86 соответственно и обводки серым цветом для первого кольца. Кольцо прогресса было аналогичным, но текущий прогресс (т.е. 0,5 на 50%) был умножен на 0,86, чтобы получить конец хода. Наряду с этим CAGradientLayer использовался для создания желаемого дизайна, а путь использовался в качестве маски на этом слое.
График RTA
Исходя из уровня звукового давления, эта диаграмма была значительно более простой. Как я уже говорил ранее, я действительно пытался создать это, используя некоторые диаграммы с открытым исходным кодом, и попробовал два или три, прежде чем сдаться и свернуть свой собственный.
Это было четыре основных CALayers с подслоями или путями, добавленными к этим слоям. Эти слои: barLayer, maxLayer, gridLayer и gradientLayer. Я создал метод рендеринга, который мог вызывать всякий раз, когда мне нужно было обновить значение. Первоначально у меня это вызывалось автоматически всякий раз, когда значения data или maxData устанавливались с помощью фантастических didSet наблюдателей Swift, но это вызывало некоторые проблемы с его изменением после того, как пользователь приостановил значение, поэтому я решил вызывать его вручную, когда это необходимо.
Подведем итоги
Напомним, что при создании измерителя SPL и RTA для TPI мы столкнулись с рядом проблем.
На стороне пользовательского интерфейса были проблемы с производительностью при использовании готовых диаграмм, что приводило к видимым заиканиям при обновлении значений. Мне также пришлось освежиться в математике GCSE, когда дело дошло до Пи!
Однако, возможно, самой большой проблемой, которая задержала разработку, была калибровка инструментов. Первоначально я предполагал, что значение, выдаваемое устройством, можно преобразовать с помощью базового 20 * log10(amplitude) вычисления в дБ, но это было крайне наивно.
В ходе проекта я узнал о взвешивании dB-A, db-B и dB-C, поигрался с оборудованием для анализа звука, которое стоит тысячи, и столкнулся с серьезными проблемами. Это лучший проект. Для меня нет ничего более захватывающего, чем узнать что-то новое или столкнуться с проблемой, от которой ты сначала отказываешься и думаешь "эй, как, черт возьми, я собираюсь это сделать?", и вынужден ее решать. Для меня это то, что я люблю в программировании: решение головоломок.
Вы также можете ознакомиться с статьями TPI: создание приложения для iPhone и TPI: процесс проектирования.