Егор Долгов
Егор Долгов
(обновлено )
За все время: 635 просмотров, 238 посетителей.
За последние 30 дней: 2 просмотра, 2 посетителя.

Создание бит-машины во Flutter

Эта статья о том, как построить бит-машину с секвенсором в Dart/Flutter.

Автор оригинального текста Ken Reilly. Оригинальная статья тут.

Пример приложения

Вступление

С первого релиза Flutter в 2018 году прошло не так много времени, однако он уже набрал довольно-таки большую популярность. Компании и предприниматели стараются не тратить много денег на разработку приложений, так что поиск наилучшего и наиболее эффективного пути для создания мобильных приложений и программного обеспечения в целом не прекращается. Flutter работает на всех основных мобильных платформах, а поддержка WEB и всех основных настольных операционных систем находятся в стадии активной разработки.

В этой статье мы рассмотрим довольно-таки простую демо-версию драм-машины и узнаем о базовых шаблонах, используемых при разработке такого приложения.

Чтобы получить и запустить окружение Flutter, посетите эту страницу загрузки. А чтобы получить копию исходного коды этого проекта, зайдите на этот репозиторий.

Ключевые понятия

В создании этого демо-приложения мы будем использовать несколько ключевых понятий Flutter для использования всех возможностей Dart и во избежание написания множества ненужных шаблонов или копирования. Все это окажет большое влияние на читаемость, надежность и производительность кода.

Получение возможного максимума из языка может дать большую разницу между созданием неказистого, подверженного ошибкам приложения и настоящим произведением искусства. Dart обладает множеством функций, помогающих создавать интерактивный асинхронный UX, обладающий управляемым состоянием.

Ключевые понятия:

Все вышеперечисленное комбинируется различными способами для достижения дизайна, в котором рендеринг пользовательского интерфейса и логика управления аккуратно организованы в классы с простыми в использовании интерфейсами и свойствами.

Обзор

Архитектура и пользовательское взаимодействие бит-машины очень просты для того, чтобы имитировать драм-машины 70-х и 80-х годов, в которых были ограничены такие ресурсы, как механические переключатели, схемы и 8-битные процессоры. Также создание машин, доступных для музыкантов, требует минимизированных затрат на проектирование и сборку.

Каркас основного пользовательского интерфейса разделен на четыре виджета «передней панели», каждый из которых обеспечивает интерактивность, а машинная логика содержится в сервисе воспроизведения сэмплов и в сервисе управления звуком.

Это аналогично тому, как компоненты настоящего аппаратного инструмента передают данные через патч или MIDI-кабели (оба эти варианта до сих пор широко используются в аудиотехнике и создании музыки).

Начало приложения

Приложение инициализируется в main.dart:

Сначала функция main фиксирует приложение в портретной ориентации, убеждаясь в том, что все виджеты инициализированы, а затем настраивая ориентацию устройства. Строение пользовательского интерфейса остается простым благодаря столбцу, отображающему четыре основных виджета интерфейса. Рассмотрим виджеты и классы, используемые для обработки ввода данных пользователя, эффективного рендеринга и обновления пользовательского интерфейса по мере необходимости.

Основные виджеты

Четыре главных виджета в Scaffold расширяют обычный базовый класс для соединения с движком аудио. Этот класс находится в views/base-class.dart:

Классы BaseWidget и BaseState расширяют StatefulWidget и State соответственно и реализуют внутренний Stream, который подсоединяет слушателя к AudioEngine при инициализации и обновляет состояние при получении сигнала от сервиса. Так что каждый виджет, расширяющий BaseWidget, будет перестраиваться каждый раз, когда звуковой сервис отправляет сигнал о том, что внутри него произошло событие и пользовательский интерфейс необходимо перестроить.

Панель индикации

Самый верхний компонент в построении это views/display.dart:

DisplayPanel отображает BPM и положение шага в верхней части экрана, а также автоматически обновляется при получении основным классом сигнала. При нажатии на индикатор BPM открывается диалоговое окно BPMSelector со списком вариантов от 1 до 256. Выбор одного из них настроит BPM на аудио-сервис.

Индикаторы шагов генерируются, и каждый из них «загорается» при работающем сервисе и при соответствии текущего шага индексу в каждом цикле рендеринга.

Паттерн sequencer (секвенсор)

Виджет редактора секвенсора располагается в views/sequencer.dart:

Секвенсор — это StatelessWidget, поскольку сам он не предоставляет никакой интерактивности, но зато рендерит виджеты Track, которые дают UX каждому треку.

Для каждого сэмпла создается расширенная строка с меткой слева и дорожкой, которая автоматически расширяется, чтобы соответствовать оставшемуся пространству в строке.

Sequencer Track

Описание редактора Sequencer Track находится в views/track.dart:

Виджет Track расширяет BaseWidget, и теперь он автоматически перестраивается при получении сигнала из аудио-сервиса. Каждая дорожка включает в себя сгенерированный список из восьми индикаторов нот, которые при нажатии передают событие в звуковой движок, который затем переключает состояние включения / выключения ноты внутри и сигнализирует об обновлении. Цвет каждого индикатора блока нот определяется тем, существует ли нота в текущей позиции и воспроизводится ли она в данный момент. Когда ноты нет, цвет меняется в каждом столбце для видимости и взаимодействия.

Transport Control

Виджет контроля перемещения находится в views/transport.dart:

Класс Transport создает ряд кнопок контроля перемещения, каждая из которых вызывает onTap при нажатии, запуская событие изменения состояния для сервиса, который, в свою очередь, сигнализирует об обновлении виджета через его базовый класс. Когда кнопка соответствует текущему состоянию механизма, она отключается путем передачи null методу onPressed для MaterialButton.

Pad Bank

Pad Bank определяется в views/pad-bank.dart:

PadBank расширяет StatelessWidget, и, так как он не обладает изменяемыми свойствами, то и не требует State. Этот виджет выводит Container в треть высоты доступного пространства на родительском элементе с двумя строками виджетов Pad, каждый с определенным размером и значением, полученным из текущего индекса List.

Drum Pad

Виджет drum pad определен в views/pad.dart:

Виджет Pad не имеет состояния и берет три final параметра за аргумент. Три геттера определяются для получения DRUM_SAMPLE вместе с соответствующими цветом и именем сэмпла. Когда пад нажимается, PadEvent передаются аудио-сервису для дальнейшей работы.

Теперь давайте взглянем на внутреннюю работу бит-машины.

Sampler

Определения сэмплов, загрузка и воспроизведение находятся в services/sampler.dart:

Типы сэмплов определяются с DRUM_SAMPLE, а соответствующие имена файлов и цвета инициализируются в службе, которая загружает аудиофайлы во время инициализации приложения. Когда воспроизведение вызывается на сэмплере из звукового движка, воспроизводится соответствующий кэшированный аудиофайл.

AudioEngine (Аудио-сервис)

Код аудио сервиса находится в services/audio-service.dart:

Сервис AudioEngine управляет состоянием управления перемещения, обрабатывает входные события, выполняет quantization входящих нот при записи, сохраняет данные дорожек и сигнализирует всем прослушивающим виджетам о необходимости обновления пользовательского интерфейса.

Классы событий определены для каждого типа требуемого события звукового сервиса, а класс Signal определен для использования в качестве универсального сигнала для обновления пользовательского интерфейса. В более сложных сценариях класс Signal может быть расширен для передачи различных типов сигналов пользовательскому интерфейсу.

Определяются разрешение и шаг, а также состояние управления, BPM, начальные данные трека, вычисления Timer / Watch / _tick , а также StreamController со слушателем, позволяющим прослушивающим виджетам получать сигналы.

Когда виджет управления (например, drum pad) вызывает событие экземпляра Event, метод использует универсальные шаблоны для переключения типа события и выполнения правильного действия. Таким образом, все входящие сообщения маршрутизируются через одно место и обрабатываются соответствующим образом. Каждый тип события соответствует некоторому набору операций внутри сервиса. Каждый из control, edit, next, и synchronize запускает сигнал в пользовательский интерфейс после завершения всех обновлений.

Архитектура звукового сервиса позволяет производить обновления на лету без перезапуска сервиса, например, включение записи с простым изменением состояния, которое приведет к тому, что будущие входящие ноты будут передаваться в метод process, а также возможность настройки темпа в середине паттерна и синхронизации (synchronize) текущего запущенного таймера с новым BPM.

При получении EditEvent, данные события используются для изменения логического значения для состояния включения / выключения ноты для этой дорожки и позиции шага. Когда звуковой сервис запускается, создается периодический таймер, который продвигает секвенсор один раз на значение _tick и вызывает next, который либо увеличивает, либо сбрасывает таймер, а затем проверяет примечание для каждой дорожки на текущем шаге, наконец сбрасывая quantization _watch и сигнализируя пользовательскому интерфейсу.

Заключение

Этот проект демонстрирует возможности Flutter SDK для быстрого проектирования и разработки приложений.

Flutter — это отличный выбор для создания интерактивных кроссплатформенных приложений с высокими производительностью и надежностью. Благодаря поддержке мобильных устройств, ПК и веб-приложений разработчики могут создавать высококачественные приложения, которые везде отлично работают и просты в обслуживании, с общим синтаксисом и почти универсальной поддержкой библиотек и пакетов.

Все это значительно упрощает задачу по поддержанию актуальности большой мультиплатформенной платформы с постоянными функциями, безупречной надежностью и сверхбыстрой производительностью.

Желаю успехов в ваших будущих проектах Flutter!

Подборка заметок