Создаём секвенсор в PureData

В предыдущей статье мы ознакомились с основами работы в PureData на примере создания несложных источников звука. Продолжим. Сегодня разговор пойдёт о работе с MIDI, об организации логики и о создании пользовательского интерфейса. Мы сделаем в PureData шаговый секвенсор.


Постановка задачи


Итак, наша цель: создать секвенсор на 16 шагов (под размер 4/4) с возможностью задавать темп, высоту ноты для каждого шага (как вручную так и с MIDI клавиатуры), и с MIDI-выходом. Ещё, наш секвенсор должен быть самостоятельным объектом, так, чтобы была возможность легко использовать его в других проектах.

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

*Массив из 16-ти элементов для хранения высоты нот;
*Какой-либо индикатор текущего шага;
*Кнопка Play/Stop;
*Какое-нибудь средство для задания темпа в BPM (ударах в минуту);
*Способ ввода нот с MIDI входа для записи и вывода их в MIDI-выход при проигрывании.


Поехали!


Ну что-ж, круг задач очерчен, вперёд! Запустим PureData и создадим новый проект. Добавим на пустое поле новый объект и введём в поле имени строку [pd sequencer]. В окне проекта появится прямоугольник нашего объекта:

Изображение

Также, откроется новое окно. Мы только что создали sub-patch (“подпатч”) – патч внутри патча. Создать sub-patch в своём проекте можно в любой момент, просто добавив объект с именем [pd], после которого, через пробел, можно написать имя нашего sub-patch (в данном случае мы ввели "sequencer"). По сути, создав sub-patch, мы создали новый объект, находящийся на тех же правах, что и стандартные объекты Pd, которые мы использовали до этого. Но, в данном случае, мы имеем возможность сами задавать его внутреннюю структуру.

Открывшееся пустое окно должно будет содержать внутренности нашего объекта. Пока что там пусто. Вспомним, какие входы и выходы должны быть у нашего секвенсора. Как минимум, это один MIDI вход для записи высоты ноты с внешнего источника, и два выхода – высота ноты и громкость (нужная в том числе и потому, что в стандарте MIDI окончание ноты задаётся той же нотой с громкостью 0).

Добавим нашему секвенсору входы и выходы. Для этого создадим объект [inlet] (он задаёт вход) и два объекта [outlet] (это выходы). Заметим, что имена этих объектов — без тильды на конце, так как эти входы/выходы предназначены для команд. Теперь внутри sub-patch у нас вот такое:

Изображение

А в основном окне видно, что наш объект приобрёл точки подсоединения – один вход и два выхода:

Изображение


Создание пользовательского интерфейса


Оставим на время основное окно в покое. Дальнейшие действия будем совершать во внутренностях нашего секвенсора. Если вы закроете окно sub-patch, войти в него всегда можно будет, кликнув на прямоугольнике объекта в родительском окне правой кнопкой мыши, и выбрав там Open. Или, как вариант, просто кликнув на объекте в режиме исполнения.

Тут стоит заметить, что отображение sub-patch в виде обычного блока-объекта — вовсе не единственный возможный вариант. В Pd также можно выводить объект в виде блока произвольного размера, содержащего элементы графического пользовательского интерфейса. Нам бы пригодилась эта функция, поскольку в дополнение к MIDI входам-выходам у нашего секвенсора должен быть и графический интерфейс для управления им, а также индикации режима работы.

Сделать такой интерфейс вовсе несложно. В окошке нашего sub-patch кликаем правой кнопкой на пустом месте и выбираем Properties. Откроется окошко "canvas":

Изображение

Установим в нём галку в пункте "graph on parent". В окне sub-patch незамедлительно появится красный прямоугольник, а в родительском окне — серый прямоугольник на месте нашего объекта. Идея состоит в том, что все органы пользовательского графического интерфейса, которые в окне sub-patch будут помещены внутрь границ красного прямоугольника, будут отображены в соответствующем прямоугольнике родительского окна. Отображаться там они будут, только когда окно sub-patch закрыто. При его открытии в родительском окне опять будет отображён просто серый прямоугольник.

Размеры нашего интерфейса задаются в окне настройки в полях с именем "size". Также, в полях с именем "margin" можно задать смещение интерфейсного прямоугольника относительно начала координат.

В нашем случае, мы введём размеры 270 на 200. А ещё, мы хотим иметь в нашем интерфейсе симпатичный фон. Для этого добавим объект Canvas (“полотно”) (Put->Canvas). Подхватим его мышкой за верхний левый угол и установим его в верхний левый угол нашего интерфейсного прямоугольника. Кликнем правой кнопкой и войдём в его свойства. Откроется окошко с настройками:

Изображение

Здесь можно задать размеры и цвет полотна, а также параметры шрифта и текст, который будет выведен в указанном месте полотна. Настроим полотно так, чтобы оно заняло всю площадь интерфейсного прямоугольника, оставив лишь узкие полоски сверху и снизу (исключительно для того, чтобы не закрыть собой точки подключения входов и выходов). В качестве текста введём, например, “step sequencer”. У нас должно получиться что-то вроде этого:

Изображение

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

Сначала добавим Array (“массив”) (Put->Array). Откроется окошко с настройками:

Изображение

В этом массиве мы будем хранить значения высоты ноты для всех шестнадцати шагов секвенсора. Назовём наш массив "sequence", а в поле "size" изменим значение на 16. Также, включим опции в пунктах "save contents" (чтобы содержимое массива сохранялось вместе с сохранением проекта) и "draw as points" (чтобы содержимое массива отображалось не в виде непрерывного сигнала, а каждый элемент был отдельной точкой на графике).

Нажмём OK, и в окне появится визуальное отображение созданного массива. Кликнув на нём правой кнопкой мыши и войдя в свойства, мы увидим знакомое окно "canvas". На этот раз нас интересует не только размер полотна, но и содержимое полей "X range" и "Y range". Эти поля задают параметры графического отображения содержимого массива. Установим диапазон по оси Х от 0 до 16 (это будет номер ноты в секвенции), а по оси Y от 128 до 12 (это будет высота ноты в стандарте MIDI). Диапазон по Y специально указан в обратном порядке, чтобы более высокие ноты оказались в графике сверху. Поместим наш массив в интерфейсный прямоугольник и подгоним его размеры. В окошке свойств будет примерно такое:

Изображение

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

Теперь создадим ещё один важный элемент – селектор текущего шага. Исполним пункт меню Put->Hradio. В окошке появится горизонтальный многопозиционный переключатель. Установим его под нашим массивом и войдём в его свойства:

Изображение

Здесь мы настроим количество позиций переключения — 16. Также, введём в поля "send-symbol" и "receive-symbol" строку "step_selector". Это будет идентификатор для посылки сообщений селектору и чтения его текущего значения. Он пригодится нам позднее.

Теперь настроим размеры созданных объектов так, чтобы длина селектора совпадала с длиной массива нот, чтобы точно над каждой позицией селектора отображалась высота соответствующей ноты. К этому моменту в окошке sub-patch у нас должна была получиться примерно следующая картина:

Изображение

Основа интерфейса готова. Можно закрыть окно sub-patch и убедиться, что и в основном окне серый прямоугольник превратился в созданный нами интерфейс. Кликнем на нём правой кнопкой и выберем Open, чтобы открыть sub-patch и продолжить создание секвенсора. Основу интерфейса мы сделали. Пришло время приняться за логику.


Логика


Преобразование темпа


Темп в нашем секвенсоре будет задаваться в BPM. Но для организации его работы нам нужно будет знать значение задержки, соответствующей одному шагу. Это время легко сосчитать по формуле 60 / (tempo
  • 4), т.е. количество секунд в минуте поделить на темп и ещё на четыре, так как у нас 4 шага на бит. Поскольку в PureData время принято указывать в миллисекундах, то это значение дополнительно нужно будет умножить на 1000. В итоге имеем формулу 60000 / (tempo
  • 4).

Реализуем эту формулу в виде схемы на Рd. Созадим объект Number, который будет задавать темп, умножим его значение на 4, а затем поделим 60000 на то, что получилось. Поскольку объект [/] производит операцию деления в момент обновления данных на первом входе, а у нас там константа, нам придётся при изменении темпа обновлять значение на входе вручную. Как перепослать сообщение в момент обновления темпа? Очень просто. На вход сообщения мы подадим специальный сигнал-триггер (в Pd принято использовать сигнал "bang"), который вынудит блок сообщения заново послать своё значение. Для этой цели мы используем объект [bang]. Он генерирует сигнал-триггер на выходе при обновлении данных на входе.

Ещё одна важная деталь: нам нужно, чтобы делитель попал на вход объекта [/]раньше делимого, которое инициирует операцию. В Pd не определён порядок выдачи объектами значений на выходе, поэтому мы используем специальный объект [trigger], который гарантированно посылает значение со входа на свои выходы, начиная справа налево. В качестве параметров после имени объекта следуют типы данных на выходах. Два "f" говорят о том, что на оба выхода будут отсылаться значения типа float (т.е. числа).

В конец цепочки повесим Number для контроля. Получилась вот такая схемка:

Изображение

Проверяем работу, меняя темп и наблюдая за тем, что получается на выходе. Работает. Чтобы не усложнять итоговую схему, спрячем реализацию получившегося конвертера темпа в sub-patch. Создадим объект [pd tempo], и в открывшееся окно переносим нашу схему (выделяем мышкой, Ctrl+X, Ctrl+V в окне sub-patch). Удаляем объекты Number, и вставляем вместо них [inlet] и [outlet].

Заодно немного оптимизируем схему. Объект [trigger] умеет преобразовывать тип входного значения. Если мы в качестве его первого параметра укажем "b" вместо "f", то с его первого выхода вместо числа на входе будет отсылаться сигнал "bang". То есть, наш объект [bang] в схеме становится излишним, и его можно убрать. Получилось вот так:

Изображение

А вот так наш конвертер темпа теперь выглядит в окошке секвенсора:

Изображение

На вход он принимает темп в BPM, а на выходе возвращает время задержки, соответствующее одному шагу секвенсора.


Организация цикла


Следующим шагом нам необходимо создать схему, которая будет переключать текущую позицию секвенсора с заданной задержкой, и иметь возможность останавливаться и возобновлять работу по указанию пользователя. Сразу приведём схему:

Изображение

Принцип работы достаточно прост. Зелёная кнопка “play” – это объект типа Toggle. Он может быть в двух состояниях – “вкл” и “выкл”, и, соответственно, меняя состояние, выдаёт на выход 1 или 0. Переключается он просто кликом мышкой.

Объект [metro] – это метроном, выдающий на выход сигнал "bang" с указанным в миллисекундах периодом. Период можно так же задавать, подавая его на второй вход. Именно это свойство объекта [metro] и используется в схеме – на его второй вход подаётся значение задержки после преобразователя темпа.

Сигнал “bang” от метронома служит триггером для объекта [float], который по сигналу выдаёт на выход значение, предварительно записанное в него через его второй вход. Выход же объекта [float] увеличивается на единицу и снова записывается обратно. В результате получается, что значение, выдаваемое объектом [float], увеличивается на единицу каждый раз, когда пришло время переключить шаг в секвенсоре.

Но как сделать, чтобы при достижении конца секвенции счёт начинался сначала? Для этого текущее значение шага анализируется объектом [select]. Параметр, заданный в этом объекте (у нас это 15), сравнивается со значением на входе, и, в случае совпадения, объект генерирует сигнал “bang”. Этот сигнал, в свою очередь, вынуждает сообщение c числом -1 быть посланным на вход объекта [float], где оно тут же инкрементируется, и текущее значение становится равным 0. В итоге, наш шаг меняется циклически от 0 до 15.

В схеме также использован объект [s] (это допустимое сокращение от [send]) – на самом деле он просто осуществляет посылку данных в точку с указанным идентификатором. На схеме это объект [r] (сокращение от [receive]). Использование таких объектов позволяет упростить схему с многочисленными связями. Например, в нашем случае пришлось бы проводить связь по диагонали, пересекая несколько объектов, что явно не сделало бы схему легче воспринимаемой. Кроме того, с помощью объектов [s] и [r] можно удобно передавать данные из одной точки сразу в несколько приёмников (создав несколько объектов [r] c одинаковым идентификатором).

Проверим, что схема работает. Запустим её кликом по “play”, и удостоверимся, что число в контрольном Number меняется от 0 до 15 и опять сбрасывается в ноль. Покрутим темп, и проверим, что скорость соответствует изменению темпа.

Теперь оформим наш цикл в виде sub-patch. Создадим объект [pd loop], и перенесём в него нашу схему, заодно заменив управляющие объекты на входы и выходы. Также, отправим значение задержки по адресу "duration". Оно нам пригодится позже. Получим такую схему:

Изображение

А в окне секвенсора вот такой объект:

Изображение

Первый его вход – переключатель play/stop, второй вход – темп, и на выходе имеем текущую позицию проигрывания.


Механизм проигрывания


Вот мы и создали основные логические блоки, из которых будет построен наш секвенсор. Теперь дело за малым – соединить всё это воедино и заставить играть мелодии. В этом деле нам помогут объекты [t], [tabread] и [makenote].

Логика проигрывания проста:
*Получаем текущую позицию;
*Устанавливаем её в селекторе шагов;
*Читаем номер ноты из элемента массива, соответствующего текущей позиции;
*Формируем MIDI-совместимые сообщения NoteOn и NoteOff;

Всё это реализуется вот такой несложной схемой:

Изображение

Текущая позиция проигрывания попадает с выхода нашего объекта-цикла на вход объекта [t] (сокращение от [trigger]). Он посылает значение со входа на свои выходы, начиная справа налево. Два "f" в качестве параметров говорят о том, что на оба выхода будут отсылаться значения типа float.

Таким образом, при переключении позиции в секвенсоре первым делом новое значение будет отослано объекту с приёмным идентификатором "step_selector". Помните, мы задавали этот идентификатор в окне свойств нашего шагового селектора? В нашем случае именно он первым получит новое значение и переключит своё состояние.

Далее, номер позиции получит объект [tabread]. Этот объект выдаёт на выход значение из таблицы/массива, имя которой указано в качестве параметра. Мы указали имя нашего массива с нотами. Это значит, что при каждом изменении позиции секвенсора объект [tabread] прочитает элемент из массива с номером, указанным на входе, и выдаст значение этого элемента на выходе. Это значение, как мы помним – номер ноты в стандарте MIDI.

Далее, мы подадим номер MIDI ноты на первый вход объекта [makenote]. Этот объект никак не изменяет входящие данные, и пропустит нашу ноту сквозь себя без изменения. Но вот далее он сам, через время, заданное на своём третьем входе, сгенерирует эту же ноту, но с нулевой громкостью (что в стандарте MIDI означает Note Off, конец ноты).
Для этого на третий вход этого объекта мы подадим длительность, ту самую, что выдаёт нам преобразователь темпа. Ведь в нашем секвенсоре длительность ноты всегда соответствует одному шагу.


Механизм записи


Осталось совсем ничего – добавить возможность записи нот с MIDI входа. Добавим к нашему секвенсору вот такую схему:

Изображение

Тут всё совсем просто. Число, пришедшее на вход (номер MIDI ноты), просто записывается объектом [tabwrite] в элемент массива с индексом, прочитанным из селектора шагов. То есть, в текущую ячейку секвенсора.

Ну всё, почти готово. Переместим кнопку “play” и регулятор темпа внутрь красного интерфейсного прямоугольника. Вот так теперь выглядит наш секвенсор изнутри:

Изображение

Закрываем окошко sub-patch, и вот он, наш секвенсор, снаружи. Один вход, два выхода, кнопка “play”, регулятор тембра, селектор шага, и возможность мышкой менять высоту нот:

Изображение


Использование


Проверим секвенсор в деле. Подключим к его MIDI-входу объект [notein], выдающий команды с MIDI порта, установленного в настройках PureData. Если у вас есть MIDI-клавиатура, можно будет запустить секвенсор и записать ноты прямо с неё. Если нет – не беда, можно установить нужный шаг селектором, и вручную установить номер ноты в объекте Number на входе секвенсора. Ну и, наконец, можно просто менять высоту нот мышкой.

Изображение

На выход секвенсора мы подключили объект [noteout]. В качестве параметра он принимает номер MIDI-канала. Этот объект выдаёт MIDI-команды в порт, указанный как выход в настройках PureData. Да, наш секвенсор может играть на любом "железном" синтезаторе. А для проверки работы можно указать в качестве выхода стандартный MIDI-синтезатор Windows, и услышать свою мелодию. А можно и просто повесить на выход секвенсора объект [mtof], преобразующий номер ноты в соответствующую ей частоту, а к нему прицепить [osc~], или один из генераторов звука, созданных нами в предыдущей статье.


Оформление в виде универсального объекта (abstraction)


Ну и напоследок. Чтобы сделать совсем отлично, перенесём содержимое из нашего sub-patch окна секвенсора в основное окно и сохраним в файл под коротким понятным именем, например “stepseq.pd”. Теперь в любом проекте, создаваемом в Pd, вы можете просто добавить объект с именем [stepseq], и будет добавлен наш секвенсор. Такая реализация в терминологии Pd называется abstraction ("абстракция"). Единственным условием является то, чтобы файл “stepseq.pd” или лежал в текущей директории (той же, где создаётся проект), или путь к нему был указан в настройках PureData.

Важное замечание: всем идентификаторам, использованным в патче (в нашем случае это идентификаторы в объектах [r] и [s], а также имя массива и send/receive symbol в свойствах Hradio), очень желательно добавить префикс "$0-", например, "duration" станет "$0-duration". Такой метод именования позволит без проблем использовать в патче несколько одинаковых объектов. Фишка в том, что при загрузке патча Pd заменит $0 на уникальное число, разное у каждой копии объекта. Таким образом, работе патча не будет мешать такая неприятность, как конфликт имён.

P.S. И ещё, в Pd просто отличный хэлп, написанный на Pd. Кликаем на любом объекте, и в контекстном меню выбираем Help. Тот факт, что хэлп написан на Pd, позволяет тут же запустить и проверить в работе схему из примера, или скопировать её к себе в проект.

Rio FX    17.02.2012 20:10
Оценить статью   10  /  0   Пожаловаться  

Похожие статьи:


- Введение в PureData
- Гитара в ваших треках. Бесплатно
- Вокодеры: практика и ассортимент
- Раритетный синтез
- BEEP cjclub edition
Опрос: Нужен ли Форум в новой версии сайта?
Нет
63
Да
252
Зарегистрируйтесь, чтобы проголосовать

Новости

Обсуждаемые треки

Обсуждаемые блоги

 

Статус - ПРОФИ
Статусы - уровень профессионализма Автора в рамках проекта CjClub.ru.
Подробнее...
Rio FX

Рейтинг - уровень активности пользователей сайта, выраженный в виде порядкового номера на сайте.
Подробнее...
89 в Топе музыкантов
23433 по активности
Пол: Мужчина
Дислокация: Россия, Санкт-Петербург
Регистрация: 01.01.2011г.
Последний раз был на сайте: 13.09.2017г. Основной стиль: Trance
Любимые стили: Instrumental, Electronic, Melodic Trance, Chillout, Technotrance, House, Drum & Bass, Instrumental Rock, Rave, Chiptune, !Live Mix!, Hardcore




PiT
- Popularity index of Track
(Индекс популярности трека)
Подробнее...



Attribution
cc by
Лицензия «С указанием авторства»
Подробнее...


Attribution Share Alike
cc by-sa
Лицензия «Распространение на тех же условиях - Копилефт»
Подробнее...


Attribution No Derivatives
cc by-nd
Лицензия «С указанием авторства – Без производных»
Подробнее...


Attribution Non-Commercial
cc by-nc
Лицензия «С указанием авторства – Некоммерческая»
Подробнее...

Attribution Non-Commercial Share Alike
cc by-nc-sa
Лицензия «С указанием авторства – Некоммерческая – Копилефт»
Подробнее...

Attribution Non-Commercial No Derivatives
cc by-nc-nd
Лицензия «С указанием авторства – Некоммерческая – Без производных»
Подробнее...