Высокопроизводительный анализ звука в реальном времени

В основе приложения TPI для iPhone лежат его звуковые инструменты, и, возможно, наиболее полезными являются измеритель SPL (уровень звукового давления) и RTA (анализатор в реальном времени). Это были первые два инструмента, которые обсуждались, когда было дано первоначальное описание Cocoon, и на сегодняшний день являются наиболее сложными разделами приложения.

Чтобы понять, почему, возможно, будет разумно разбить то, что нужно делать приложению, чтобы выполнить требуемый анализ.

  1. Захватите звук с микрофона на устройстве пользователя.
  2. Выполните вычисления с быстрым преобразованием Фурье (БПФ) для захваченного звука.
  3. Преобразуйте значения из БПФ в правильные линейные значения и значения дБ-А или дБ-С.
  4. Отображайте эти данные в пользовательских представлениях со скоростью 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: процесс проектирования.

🐦 Следуйте за нами в Twitter

🔗 Посетите наш сайт

💌 Напишите нам: [email protected]