Будучи потенциальным программистом и любителем электрогитар, я не мог остаться в стороне от разработки музыкального ПО. Все кто когда-либо пытался подключить электрогитару к компьютеру на ОС Windows, используя какой-либо гитарный процессор, знают, что зачастую для этих целей требуется наличие интерфейса ASIO (Возможно и другие, но ASIO наиболее популярен). Это связано с тем, что для обработки звукового ввода и вывода тратится значительное время, и, как следствие, при игре на инструменте слышно неприятную задержку, которая весьма сильно затрудняет игру. ASIO позволяет пропустить этап микширования звука при его выводе и, соответственно, значительно уменьшить задержку. Подробнее об этом здесь.
В чем проблема
Я решил использовать ASIO, так как драйвер популярен и удобен, но здесь я столкнулся с проблемой: очень мало статей по теме разработки под данную технологию. По сути, все, чем я мог руководствоваться в самом начале – официальная документация к SDK и несколько англоязычных форумов. Я надеюсь, что если вы имеете подобную проблему, то статья будет хоть немножко полезной для вас.
Библиотека Bass
Код с официального сайта Stainberg с использованием SDK, который просто проигрывает тишину в течении 20 секунд, занимает 500 строк. Однако существует прекрасная библиотека Bass. Любители поработать со звуком знают, как с ее помощью можно создавать звуковые потоки и каналы, как накладывать FX эффекты, микшировать… я на это горячо надеюсь. У Bass есть аддон Bassasio, который, по сути, за несколько строк кода позволяет настроить драйвер ASIO и выполнить идентичную примеру выше задачу.
Установка и настройка библиотек
- Качаем библиотеку Bass и ее аддон Bassasio. Это официальный сайт
- Скидываем .lib файлы в отдельную папку. В данном случае, файлы bass.lib и bassasio.lib я скинул в папку Libs
- .dll файлы переносим в папки Release и Debug. (bass.dll и bassasio.dll) Если этого не сделать, то при запуске кода вашего приложения, программа выдаст ошибку вроде этой:
- Далее в настройках проекта прописываем компоновщику путь к папке с .lib файлами. То же самое делаем для с/с++.
- Скидываем .h файлы в папку проекта и инклюдим их в проект, а именно файлы bass.h и bassasio.h.
#include "bass.h" #include "bassasio.h"
Пример использования
Для примера, создадим небольшое консольное приложение, которое будет проигрывать звук гитары, т.е. программа будет перенаправлять поток из звукового входа на звуковой вывод. Код приложения доступен на гитхабе.Вроде бы все просто. Должно получиться подобие гитарного комбоусилителя (воткнул и играй!), но звук пока что будет чистым (без эффектов).
Библиотеки мы подключили в предыдущем пункте, теперь нужно инициализировать устройства звукового ввода и вывода. Начнем!
BASS_Init() инициализирует устройство звукового вывода, и принимает на вход следующие параметры:
DWORD freq – частота дискретизации в Гц
DWORD flags – флаги… Их много, прочитать подробнее можно в официальной документации www.un4seen.com/doc/#bass/BASS_Init.html. Если кратко, с помощью флагов можно выбрать глубину кодировния звука, моно или стерео, воспроизведение звука более чем двумя динамиками и т.д.
HWND win – устанавливает основное окно приложения. Для консольных приложений следует установить 0.
GUID *clsid – класс для инициализации объекта, который будет использован для инициализации DirectSound. В остальных случаях устанавливаем Null.
С инициализацией ASIO драйвера все немного попроще. В функцию BASSASIO_Init передаем только 2 параметра:
std::cout << "ASIO Devices info:" << std::endl;
a = 0; count = 0;
BASS_ASIO_DEVICEINFO asio_info;
for (a = 0; BASS_ASIO_GetDeviceInfo(a, &asio_info); a++)
std::cout << "Device " << a << ") " << asio_info.name << std::endl;
std::cout << " ________ " << std::endl;
Список драйверов
DWORD flags – флаги. Их всего 2: BASS_ASIO_THREAD – запускать драйвер в отдельном потоке и BASS_ASIO_JOINORDER – отвечает за порядок работы каналов channels.
Код, инициализации устройства звукового вывода и драйвера ASIO:
try {
if ( ! BASS_Init(0, 44100, 0, 0, NULL) )
throw BASS_ErrorGetCode();
if ( ! BASS_ASIO_Init( 0, NULL ) )
throw BASS_ASIO_ErrorGetCode();
}
catch ( int err ) {
std::cout << "Err no - " << err << std::endl;
system("pause");
return;
}
Может возникнуть вопрос: почему в BASS_Init устройство вывода не используется (0). Дело в том, что звуковой вывод будет производиться через ASIO с помощью аддона BASSASIO. Стандартный звуковой вывод с помощью библиотеки BASS нам для реализации поставленной задачи совершенно не нужен — поэтому 0. Однако стоит отметить пару моментов:
- Можно осуществить работу драйвера ASIO на одном устройстве и вывод звука стандартными средствами на другом (скажем… будет играть музыка). Для этого достаточно выбрать разные устройства при инициализации BASS и настройке драйвера ASIO (Во время работы будет его иконка в области уведомлений).
- Чтобы получить список доступных устройств используйте BASS_GetDeviceInfo.
setlocale(LC_ALL, "Rus"); std::cout << "Devices info:" << std::endl; int a, count = 0; BASS_DEVICEINFO info; for (a = 1; BASS_GetDeviceInfo(a, &info); a++) { if (info.flags&BASS_DEVICE_ENABLED) // device is enabled std::cout << "Device " << a << ") " << info.name << " is availible" << std::endl; else std::cout << "Device " << a << ") " << info.name << " is unable" << std::endl; } std::cout << " ________ " << std::endl;
BASS работает только с устройствами, активными на данный момент. Отключенные устройства попросту не будут видны.
Активные устройства звукового вывода
BASSASIO абсолютно все равно на то, было ли отключено устройство операционной системой или нет. В настройках драйвера выбирайте нужные вам устройства звукового ввода и вывода.
- Нет смысла пытаться выводить звук с помощью BASS и BASSASIO одновременно в одно устройство – технология ASIO расчитана именно на то, что звук не будет микшироваться операционкой и пойдет сразу на звуковую карту для последующего вывода. Т.е. вы услышите только звук от приложения, использующего ASIO.
Далее, нам необходимо звуковой поток с микрофона направить на устройство звукового вывода. Это можно сделать несколькими способами. Самый просто – «отзеркалить» канал.
Мы имеем 3 канала: микрофон, левый наушник и правый наушник. Чтобы сигнал с микрофона перенести на каналы вывода, воспользуемся функцией
BOOL BASS_ASIO_ChannelEnableMirror(
DWORD channel - идентификатор канала
BOOL input2 - входной или выходной? (0 или 1)
DWORD channel2 – идентификатор исходного канала (с сигналом)
);
Здесь все просто. Нам нужно передать сигнал в output-каналы 0 и 1 из input-канала 0:
BASS_ASIO_ChannelEnableMirror( 0, 1, 0 );
BASS_ASIO_ChannelEnableMirror( 1, 1, 0 );
Тогда возникает вопрос: откуда я узнал в какие каналы мне нужно передавать сигнал. Ответ – информацию по каналам можно узнать с помощью функции BASS_ASIO_ ChannelGetInfo.
a = 0; BASS_ASIO_CHANNELINFO channel_info;
std::cout << "inputs: " << std::endl;
for (a = 0; BASS_ASIO_ChannelGetInfo(0, a, &channel_info ); a++ )
std::cout << a << ") " << channel_info.name << " format: " << channel_info.format << std::endl;
std::cout << "Outputs: " << std::endl;
for (a = 0; BASS_ASIO_ChannelGetInfo(1, a, &channel_info); a++)
std::cout << a << ") " << channel_info.name << " format: " << channel_info.format << std::endl;
std::cout << "__________" << std::endl;
Все настройки готовы. Старт — BASS_ASIO_Start. Можно дать на вход параметры для максимальной длины сэмпла и количества потоков, но для нашей задачи можно оставить эти параметры по-умолчанию (заполним нулями).
BASS_ASIO_Start( 0, 0 );
Фактически, это все. Однако, это консольное приложение, не забываем про system( “pause” ), чтобы оно не закрывалось сразу после запуска, и сразу после этого, освобождаем память и прекращаем работу с устройствами и драйвером.
BASS_ASIO_Stop();
BASS_ASIO_Free();
BASS_Stop();
BASS_Free();
return;
Итоги
Исходный код оставлю на гитхабе.
Вот мы и получили простое консольное приложение, использующее технологию ASIO. Подключая инструмент к звуковой карте, мы не услышим той задержки, что могла быть при стандартном подключении с помощью стандартных средств Windows. Конечно, возможности как библиотеки BASS, так и аддона BASSASIO шире представленных выше, однако не хватит ни формата статьи, ни моих знаний, чтобы изложить абсолютно все.
Если эта тема была для вас хоть немного интересной — был рад стараться. В свою очередь постараюсь украсть немного времени для написания продолжения, в котором мы сможем работать со стандартными FX-эффектами из DX8 и получим подобие простенького гитарного процессора.
Спасибо за внимание!
Комментарии (20)
Sabubu
23.08.2019 21:02ASIO рассчитан в первую очередь на профессиональные карты, с которыми идет драйвер для ASIO. В новых WIndows есть WASAPI, который также позволяет эксклюзивно работать с устройством в обход микшера. Не знаю, впрочем, как там с задержками.
phantom-code
23.08.2019 21:37На моей ASUS Xonar Essence STX II можно получить задержку около 10 мс с использованием WASAPI (на ASIO в принципе так же, хотя у моей карты кривой драйвер). ASIO это все же про еще меньшие задержки. Говорят, на нормальных аудиоинтерфейсах доистижима задержка в 2-5 мс.
Hidon
24.08.2019 12:24Говорят, на нормальных аудиоинтерфейсах доистижима задержка в 2-5 мс.
такие задержки достижимы, но избыточны. опытным путём давно установлен «стандарт» — 7 миллисекунд. да, можно меньше, но тут либо страдает качество сэмплов, либо стоимость… проще самолёт купить, получить лётное свидетельство, улететь куда-нибудь на море, жить там пол года и вернуться обратно.
ALF_Zetas
24.08.2019 20:27от комбика до уха гитариста звук по воздуху дольше идет ;) — поэтому нет практического смысла в таких маленьких задержках
Hidon
24.08.2019 12:18ASIO в первую очередь рассчитан на пользователей звуковых карт и DAW от steinberg, которая этот «стандарт» и разработала.
они так же делают и профессиональные звуковые… картами их назвать уже не получится, интерфесы наверное.
про WASAPI что-то слышал, но ничего про это не знаю, предпочитаю работать с простыми, но проверенными вещами. и кстати я не один такой, это как в авиации — работает десятилетиями, но машина сама себя не полетит, ей лётчик нужен.
phantom-code
23.08.2019 21:34+3Читая заголовок подумал, что будет про разработку «под ASIO», оказалось, статья про работу со сторонней библиотекой, умеющей работать с ASIO. Так бы и писали, «использование библиотеки Bassasio».
В качестве примеров работы с ASIO можно посмотреть исходники проектов rtaudio и portaudio.AlexPelmen Автор
24.08.2019 00:38Прошу прощения за то, что ввел в заблуждение. В будущем буду ясней излагать мысли) Спасибо за ссылки на исходники, обязательно гляну позже.
maksqwe
23.08.2019 22:35А тем временем люди готовят понемногу предложение «Standard Audio API for C++» в стандарт:
www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1386r0.pdf
Если что гитхаб страничка базовой реализации, от одного из авторов:
github.com/stdcpp-audio
Daddy_Cool
23.08.2019 23:41Очень интересно!
Я так понимаю, ASIO это довольно стандартная вещь.
У меня есть хорошая звукокарточкакоробочка Focusrite с собственными драйверами, которые видимо много чего хорошего позволяют в т.ч. и задержку до 3-5 мс. Все программы — включая Гитар Риг видят карту с её драйверами и нормально работают. Как это программируется? Никогда не возился со звуком в программном смысле, но хочется попробовать.AlexPelmen Автор
24.08.2019 00:55ASIO — это не конкретный драйвер, а технология, на основе которой пишется софт для железяк отдельно, учитывая особенности каждой — это как раз те самые собственные драйвера звукокарточек. Именно поэтому весь софт, о котором идет речь, корректно воспринимает карту и ее дровишки, ведь для софта это устройство ASIO. Я это так понимаю, поправьте, если ошибаюсь.
Про программирование Рига ничего сказать не могу. Большой проект, большой компании с большой аудиторией программируется штабом опытных разработчиков в течение долгого времени. Поддерживает как ASIO, так и WASAPI.
Если для вас Guitar Rig — это только «комбик с эффектами», то простое подобие этого можно написать и самому, используя средства, перечисленные в статье.
dporollo
24.08.2019 08:40Steinberg. А так вроде железо должно поддерживать ASIO, если нет, то это странные эмуляции, вроде вывших когда-то KX драйверов.
AlexPelmen Автор
24.08.2019 08:50Если нет подходящего железа, то ставят ASIO4ALL. Это по сути тот же WASAPI… более того, это просто мост, между выходом ASIO и KS/WASAPI, но с дополнительными настройками буфера, задержек и т.д. Тык
Просто если железо поддерживает — это будет работать. Нет — с ASIO4ALL также будет.
sadko4u
24.08.2019 15:01Ещё одна статья уровня "Я нашёл библиотеку, которая делает всю работу за меня" вместо того, чтобы разобраться в реальных принципах работы с ASIO напрямую и на примерах их доходчиво здесь изложить. Заголовок не соответствует содержанию.
monah_tuk
25.08.2019 02:08Только прочитав про электрогитару, я понял, что речь не про тот ASIO :-D
Но в тему, а есть годные доки по ASIO, но с другой стороны, со стороны драйверописателя? Какие API можно вытянуть, какие кастомные контроллы (просто есть на чём пощупать, но не совсем Windows погромист).
AlexPelmen Автор
25.08.2019 09:40Изначально я пробовал общаться с ASIO через SDK. У Stainberg с сайта можно скачать, там еще и примерчик есть и мануальчик. Но инфы было мало. По запросу "...asio… " то и дело я получал «boost.asio» или просто инфу про технологию. Потом узнал, что BASS может работать с asio и передумал использовать SDK.
В основном я пользовался только документацией к bass и тем, что смог найти где-то на сторонних форумах.Matisumi
25.08.2019 12:03+1Прям даже интересно, почему вы так упрямо пишете «Stainberg», хотя даже ссылка в статье у вас «steinberg»
nikbond
Сначала подумал про boost::asio, и не мог понять, при чем тут гитары :)
pal666
я споткнулся до гитар, на потенциальном программисте