LINUX.ORG.RU

EasyEffects не нужно, или PipeWire для продвинутых: часть 3

 , ,


5

3

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

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

Модуль filter-chain, помимо встроенных (builtin) эффектов, поддерживает два самых распространенных в Linux стандарта DSP плагинов - LADSPA и LV2. Прежде всего, эти плагины предназначены для использования в DAW системе (цифровая звуковая рабочая станция, в которой «делают музыку»), такой как Ardour, Qtractor, LMMS. Они реализуют множество эффектов, таких как эквалайзер, компрессор, лимитер и тому подобное.

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

Реализуем очень нужный многим эффект – нормализацию громкости, или компрессию. То есть, нам нужно, чтобы тихие звуки стали громче, и были слышны в зашумлённой обстановке (на работе, к примеру) из плохих динамиков ноутбука. Также эта проблема очень актуальна при просмотре фильмов, часто есть огромная разница в громкости между диалогами и активными сценами (когда начинают бить морды). Надо эту разницу уменьшить.

Для этого мы используем замечательный набор плагинов LSP. Эти плагины доступны во всех основных форматах, мы же используем LV2 версию. В составе набора есть плагин Compressor Mono, который хорошо подходит для нашей задачи – там предусмотрен «обратный» режим работы, когда тихие звуки делаются более громкими (а не только громкие – более тихими, как в большинстве аналогичных плагинов). Это именно то, что нужно.

Добавим этот плагин в цепочку фильтров filter-chain. Для этого, сначала надо узнать такую вещь, как URI плагина. Это такая форма его названия, в виде веб-адреса. Но это не настоящая веб-ссылка, просто такая форма ))). В общем, это тяжело объяснить.

Чтобы узнать URI плагина, выполним команду lv2ls. Команда выдаст URI всех плагинов, которые установлены в системе. Среди них должен быть нужный нам плагин http://lsp-plug.in/plugins/lv2/compressor_mono. Если такого нет, надо установить набор плагинов LSP, при помощи пакетного менеджера дистрибутива.

Теперь добавим в конфиг-файл из предыдущих частей вот такой кусок, в массив nodes (полная версия конфига будет в конце статьи):

{
  type = lv2
  name = compressor
  label = compressor
  plugin = "http://lsp-plug.in/plugins/lv2/compressor_mono"
  control = { "cm" = 1 "cr" = 4.0}
}

Как видно, это очень похоже на добавление builtin эффекта. Только теперь тип будет lv2, plugin - указываем URI плагина. name, label – название, которое мы сами даём этому эффекту, может быть любое. По нему потом всё будет доступно через pw-cli для изменения параметров. Добавляем два параметра - "cm" = 1 это режим работы, который делает тихие звуки громче (а не громкие – тише). "cr" = 4.0 – это степень компрессии, во сколько раз. Чем больше это значение, тем больше будет выравниваться громкость.

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

Очень тяжелый вопрос: откуда же брать названия параметров плагина, допустимые значения этих параметров, чтобы прописать сюда? Как узнать, что есть вот именно два этих параметра, и они называются вот так?

Для этого используем утилиту lv2info. Выполним команду

lv2info http://lsp-plug.in/plugins/lv2/compressor_mono

В выводе команды видим всю информацию о плагине, нас интересуют блоки вида Port N:. Порты у плагина, упрощенно, есть двух видов - это входы/выходы, через которые передается звук, и параметры самого плагина, которые можно задавать и изменять. Находим среди портов те, которые соответствуют нужным нам параметрам. Ищем, ориентируясь на поле Name, оно говорящее, сразу понятно что это.

Port 17:
Type:
  http://lv2plug.in/ns/lv2core#ControlPort
  http://lv2plug.in/ns/lv2core#InputPort
  Scale Points:
    0 = "Down"
    1 = "Up"
    2 = "Boot"

  Symbol:      cm
  Name:        Compression mode
  Minimum:     0.000000
  Maximum:     2.000000
  Default:     0.000000
  Properties:  http://lv2plug.in/ns/ext/port-props#hasStrictBounds
  http://lv2plug.in/ns/lv2core#integer                           
  http://lv2plug.in/ns/lv2core#enumeration

Это описание параметра cm, который мы меняем в конфиг-файле. Из этого куска мы узнаем, что есть параметр Name: Compression mode, он имеет имя Symbol: cm, может принимать значения 0, 1 или 2.

  Scale Points:
    0 = "Down"
    1 = "Up"
    2 = "Boot"

Нам нужен режим Up (делать громче тихий звук), значит мы должны установить этот параметр в значение 1.

Далее, надо «залинковать» этот дополнительный эффект к уже добавленному ранее эквалайзеру. Для этого добавляем в конфиг-файле, в массив links:

{ output = "eq_band_15:Out" input = "compressor:in" }

то есть подключить вход компрессора к выходу 15 полосы эквалайзера. И последний штрих: надо сделать выходом всей цепочки эффектов – выход компрессора.

outputs = [ "compressor:out" ]

Теперь, помимо эквалайзера, у нас в цепочке обработки будет компрессор. Какими параметрами может потребоваться управлять? Можно включать/отключать компрессор

pw-cli s 36 Props '{params = ["compressor:enabled" 0]}'
pw-cli s 36 Props '{params = ["compressor:enabled" 1]}'

Не забываем, что 36 – это номер ноды Equalizer Sink, у вас он может быть совсем другим. Как его определить - см. Часть 2.

Ещё можно подрегулировать степень компрессии, параметр cr.

 pw-cli s 36 Props '{params = ["compressor:cr" 10.0]}'

Вот и всё, два основных эффекта мы реализовали )). Полная версия получившегося конфиг-файла будет выглядеть так:

context.modules = [
    { name = libpipewire-module-filter-chain
        args = {
            node.description = "Equalizer Sink"
            media.name       = "Equalizer Sink"
            filter.graph = {
                nodes = [
                    {
                        type  = builtin
                        name  = eq_preamp
                        label = bq_highshelf
                        control = { "Freq" = 0 "Q" = 1.0 "Gain" = 5.0 }
                    }
                    {
                        type  = builtin
                        name  = eq_band_1
                        label = bq_peaking
                        control = { "Freq" = 25.0 "Q" = 1.7 "Gain" = -10.0 }
                    }
                    {
                        type  = builtin
                        name  = eq_band_2
                        label = bq_peaking
                        control = { "Freq" = 40.0 "Q" = 1.7 "Gain" = -10.0 }
                    }
                    {
                        type  = builtin
                        name  = eq_band_3
                        label = bq_peaking
                        control = { "Freq" = 63.0 "Q" = 1.7 "Gain" = -9.0}
                    }
                    {
                        type  = builtin
                        name  = eq_band_4
                        label = bq_peaking
                        control = { "Freq" = 100.0 "Q" = 1.7 "Gain" = 3.0 }
                    }
                    {
                        type  = builtin
                        name  = eq_band_5
                        label = bq_peaking
                        control = { "Freq" = 160.0 "Q" = 1.7 "Gain" = 3.0 }
                    }
                    {
                        type  = builtin
                        name  = eq_band_6
                        label = bq_peaking
                        control = { "Freq" = 250.0 "Q" = 1.7 "Gain" = 3.0 }
                    }
                    {
                        type  = builtin
                        name  = eq_band_7
                        label = bq_peaking
                        control = { "Freq" = 400.0 "Q" = 1.7 "Gain" = -5.0 }
                    }
                    {
                        type  = builtin
                        name  = eq_band_8
                        label = bq_peaking
                        control = { "Freq" = 630.0 "Q" = 1.7 "Gain" = -5.0 }
                    }
                    {
                        type  = builtin
                        name  = eq_band_9
                        label = bq_peaking
                        control = { "Freq" = 1000.0 "Q" = 1.7 "Gain" = -3.0 }
                    }
                    {
                        type  = builtin
                        name  = eq_band_10
                        label = bq_peaking
                        control = { "Freq" = 1600.0 "Q" = 1.7 "Gain" = -3.0 }
                    }
                    {
                        type  = builtin
                        name  = eq_band_11
                        label = bq_peaking
                        control = { "Freq" = 2500.0 "Q" = 1.7 "Gain" = -3.0 }
                    }
                    {
                        type  = builtin
                        name  = eq_band_12
                        label = bq_peaking
                        control = { "Freq" = 4000.0 "Q" = 1.7 "Gain" = -5.0 }
                    }
                    {
                        type  = builtin
                        name  = eq_band_13
                        label = bq_peaking
                        control = { "Freq" = 6300.0 "Q" = 1.7 "Gain" = -3.0 }
                    }
                    {
                        type  = builtin
                        name  = eq_band_14
                        label = bq_peaking
                        control = { "Freq" = 10000.0 "Q" = 1.7 "Gain" = 0.0 }
                    }
                    {
                        type  = builtin
                        name  = eq_band_15
                        label = bq_peaking
                        control = { "Freq" = 16000.0 "Q" = 1.7 "Gain" = 0.0 }
                    }
                    {
                        type = lv2
                        name = compressor
                        label = compressor
                        plugin = "http://lsp-plug.in/plugins/lv2/compressor_mono"
                        control = { "cm" = 1 "cr" = 4.0}
                    }
                ]
                links = [
                    { output = "eq_preamp:Out" input = "eq_band_1:In" }
                    { output = "eq_band_1:Out" input = "eq_band_2:In" }
                    { output = "eq_band_2:Out" input = "eq_band_3:In" }
                    { output = "eq_band_3:Out" input = "eq_band_4:In" }
                    { output = "eq_band_4:Out" input = "eq_band_5:In" }
                    { output = "eq_band_5:Out" input = "eq_band_6:In" }
                    { output = "eq_band_6:Out" input = "eq_band_7:In" }
                    { output = "eq_band_7:Out" input = "eq_band_8:In" }
                    { output = "eq_band_8:Out" input = "eq_band_9:In" }
                    { output = "eq_band_9:Out" input = "eq_band_10:In" }
                    { output = "eq_band_10:Out" input = "eq_band_11:In" }
                    { output = "eq_band_11:Out" input = "eq_band_12:In" }
                    { output = "eq_band_12:Out" input = "eq_band_13:In" }
                    { output = "eq_band_13:Out" input = "eq_band_14:In" }
                    { output = "eq_band_14:Out" input = "eq_band_15:In" }
                    { output = "eq_band_15:Out" input = "compressor:in" }
                ]
                inputs = [ "eq_preamp:In" ]
                outputs = [ "compressor:out" ]
            }
            capture.props = {
                node.name   = "effect_input.eq6"
                media.class = Audio/Sink
                audio.channels = 2
                audio.position=[FL FR]
            }
            playback.props = {
                node.name   = "effect_output.eq6"
                node.passive = true
                audio.channels = 2
                audio.position=[FL FR]
            }
        }
    }
]
★★★★

Проверено: dataman ()
Последнее исправление: James_Holden (всего исправлений: 3)

Так. Вопрос про стерео/моно - это делается для одного канала, да? Тоесть для каждого канала свой экземпляр создаётся в pipewire, так? Я ужо забыл как оно работает.

Хм. А почему ты не пользуешься lv2info? Ты просто смотришь в ttl? Но это же не трушно же? :)

Я в предвкушении рождения морды управления всем этим. Неужели моя мечта сбывается?

R_He_Po6oT ★★★★★
()

Спасибо за статьи. Очень интересный цикл. Ему бы тег придумать, чтоб можно было подписаться и не пропустить следующие статьи.

Beewek ★★★
()
Ответ на: комментарий от dataman

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

James_Holden ★★★★
() автор топика
Ответ на: комментарий от R_He_Po6oT

Создается два экземпляра, на каждый канал. Там есть и стерео вариант, но так проще, пусть пока так будет. Результат меня удовлетворяет.

lv2info

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

James_Holden ★★★★
() автор топика

плюсую, очень полезный цикл получился
спасибо!

z0idator
()
Ответ на: комментарий от James_Holden

если можно

Вряд ли. Предполагаю, что только @maxcom сможет наадминить.

dataman ★★★★★
()
Ответ на: комментарий от James_Holden

Ну не менять же ник из-за тега:) Пусть будет тег без подчёркивания, не страшно. Я уже подписался. Ещё раз спасибо за цикл.

Beewek ★★★
()
Ответ на: комментарий от Beewek

Не то что из за тега конкретно здесь, меня в целом напрягает это подчеркивание))

James_Holden ★★★★
() автор топика

А эта твоя предполагаемая штука по управлению общесистемными эффектами будет тащить эффекты в себе, или давать некий способ собрать очередь сторонних плагинов? Т.е. морда типа JDSP, или стойка типа guitarix, без всяких там коммутационных графов?

thesis ★★★★★
()
Ответ на: комментарий от thesis

Я предлагаю вот что - просто морда, в ней самой реализации эффектов не будет, это просто GUI конфигурялка для pipewire. А далее, когда она добавит конфиги для pipewire, все будет работать без нее. То есть ее запускать надо только для настройки/перенастройки. Она максимально легкая получится.

По организации GUI - я сторонник просто фиксированного набора самого основного (как в виндовых тулзах от производителя обычно). Вот эти графы, миллиард крутилок, конструктор в конструкторе - они, во-первых, и так уже есть, а во вторых я сторонник противоположного, чтобы была кнопка «сделать хорошо» и все.

К тому же, тут прицел еще вот на что. Сейчас популярным становится использование одноплатников типа Orange Pi Zero как источников звука (в качестве «WiFi ЦАП/Интернет радиоприемник»). К ним подключают плату усилителя класса D, сейчас это копейки стоит, и колонки от старого музцентра, которые девать некуда, так как тело музцентра давно сдохло. Так вот там эквалайзер тоже не помешает, потому что колонки эти бывают с кривой АЧХ. Ну и комната.

Запускать на Orange Pi Zero, где 512 M оперативы и нет GUI аппаратно, какой-нибудь EasyEffects - это сразу нет. А мой подход решает проблему. Сделать веб-морду для руления эффектами через сеть не сложнее, чем Qt морду.

James_Holden ★★★★
() автор топика
Ответ на: комментарий от James_Holden

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

А для отдельной коробочки такой подход использует MOD Desktop, видел? Может, оттуда возможно натырить каких-то идей.

thesis ★★★★★
()
Ответ на: комментарий от thesis

Может и висеть, этому же ничего не препятствует. Тут главное, что может и не висеть.

James_Holden ★★★★
() автор топика

Спасибо! Цикл статей открывает глаза, я лично не знал, что в PipeWire уже есть свои обработки и поддержка LV2. Лайк и подписка.

Теперь мне хочется сделать свой аналог EasyEffects с более удобным GUI.

zezic ★★★★
()
Ответ на: комментарий от zezic

Скоро будет продолжение, про пространственные эффекты и про GUI

James_Holden ★★★★
() автор топика

Ещё такое дело - если появляется другой выход, usb, или bluetooth, или, там, просто hdmi, как себя вести должна система? Вот эквалайзер надо бы на лаптоповские колонци оставить (кто там хотел), а компрессор надо бы переподключить на новый выход. И приложения чтобы опять в него говорили. Вот. Как-то это по человечески сформулировать.

R_He_Po6oT ★★★★★
()
Ответ на: комментарий от R_He_Po6oT

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

А должно делаться так, как ты описал.

За руление подключениями отвечает wireplumber, логика которого реализована lua скриптами. Можно добавить свой скрипт, который будет переподключать все по той логике, которая нужна пользователю. Это я тоже рассмотрю позже в одной из частей, очень важный момент.

James_Holden ★★★★
() автор топика

периодически вспоминаю с ностальгией твои забористые треды про kpp plugins, а тут вона чо - новая движуха! радостно! :) не бросай!

aol ★★★★★
()

Не знаю про lv2, но компрессор из easy effects стрёмный. Не сразу реагирует на изменение громкости. Я понимаю, что он работает в онлайн режиме и не знает, что будет дальше, но там же программисты, могли бы что-нибудь придумать.

ox55ff ★★★★★
()
Ответ на: комментарий от ox55ff

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

Настройки по дефолту, которые тут, как раз на быстрый вариант.

James_Holden ★★★★
() автор топика
Для того чтобы оставить комментарий войдите или зарегистрируйтесь.