Будучи потенциальным программистом и любителем электрогитар, я не мог остаться в стороне от разработки музыкального ПО. Все кто когда-либо пытался подключить электрогитару к компьютеру на ОС Windows, используя какой-либо гитарный процессор, знают, что зачастую для этих целей требуется наличие интерфейса ASIO (Возможно и другие, но ASIO наиболее популярен). Это связано с тем, что для обработки звукового ввода и вывода тратится значительное время, и, как следствие, при игре на инструменте слышно неприятную задержку, которая весьма сильно затрудняет игру. ASIO позволяет пропустить этап микширования звука при его выводе и, соответственно, значительно уменьшить задержку. Подробнее об этом здесь.

В чем проблема


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

Библиотека Bass


Код с официального сайта Stainberg с использованием SDK, который просто проигрывает тишину в течении 20 секунд, занимает 500 строк. Однако существует прекрасная библиотека Bass. Любители поработать со звуком знают, как с ее помощью можно создавать звуковые потоки и каналы, как накладывать FX эффекты, микшировать… я на это горячо надеюсь. У Bass есть аддон Bassasio, который, по сути, за несколько строк кода позволяет настроить драйвер ASIO и выполнить идентичную примеру выше задачу.

Установка и настройка библиотек


  1. Качаем библиотеку Bass и ее аддон Bassasio. Это официальный сайт
  2. Скидываем .lib файлы в отдельную папку. В данном случае, файлы bass.lib и bassasio.lib я скинул в папку Libs
  3. .dll файлы переносим в папки Release и Debug. (bass.dll и bassasio.dll) Если этого не сделать, то при запуске кода вашего приложения, программа выдаст ошибку вроде этой:



  4. Далее в настройках проекта прописываем компоновщику путь к папке с .lib файлами. То же самое делаем для с/с++.





  5. Скидываем .h файлы в папку проекта и инклюдим их в проект, а именно файлы bass.h и bassasio.h.

    #include "bass.h"
    #include "bassasio.h"
    


Пример использования


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

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

BASS_Init() инициализирует устройство звукового вывода, и принимает на вход следующие параметры:

Параметры
int device — идентификатор устройства вывода. Если установить параметр -1, то будет использовано стандартное устройство вывода, если 0 – звук не будет проигрываться вовсе, т.е. не будет предоставлен ни одному устройству вывода.

DWORD freq – частота дискретизации в Гц

DWORD flags – флаги… Их много, прочитать подробнее можно в официальной документации www.un4seen.com/doc/#bass/BASS_Init.html. Если кратко, с помощью флагов можно выбрать глубину кодировния звука, моно или стерео, воспроизведение звука более чем двумя динамиками и т.д.

HWND win – устанавливает основное окно приложения. Для консольных приложений следует установить 0.

GUID *clsid – класс для инициализации объекта, который будет использован для инициализации DirectSound. В остальных случаях устанавливаем Null.

С инициализацией ASIO драйвера все немного попроще. В функцию BASSASIO_Init передаем только 2 параметра:

Параметры
int device – идентификатор «девайса»… технологии, с которой будет работать библиотека. Все дело в том, что если у вас установлено такое ПО, как guitar rig или у вас есть звуковая карта со своим драйвером ASIO, в списке доступных «девайсов» вы увидите несколько пунктов. Список можно получить вызывая для каждого идентификатора (перебирая их) функцию BASS_ASIO_GetDeviceInfo(). Как правило, 0 – наш драйвер ASIO4ALL, который мы и будем использовать в дальнейшем. Значение -1 установит устройство по-умолчанию, об этом уже сказано в документации.

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. Однако стоит отметить пару моментов:

  1. Можно осуществить работу драйвера ASIO на одном устройстве и вывод звука стандартными средствами на другом (скажем… будет играть музыка). Для этого достаточно выбрать разные устройства при инициализации BASS и настройке драйвера ASIO (Во время работы будет его иконка в области уведомлений).
  2. Чтобы получить список доступных устройств используйте 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 абсолютно все равно на то, было ли отключено устройство операционной системой или нет. В настройках драйвера выбирайте нужные вам устройства звукового ввода и вывода.
  3. Нет смысла пытаться выводить звук с помощью 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)


  1. nikbond
    23.08.2019 20:48
    +4

    Сначала подумал про boost::asio, и не мог понять, при чем тут гитары :)


    1. pal666
      24.08.2019 06:53

      я споткнулся до гитар, на потенциальном программисте


  1. Sabubu
    23.08.2019 21:02

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


    1. phantom-code
      23.08.2019 21:37

      На моей ASUS Xonar Essence STX II можно получить задержку около 10 мс с использованием WASAPI (на ASIO в принципе так же, хотя у моей карты кривой драйвер). ASIO это все же про еще меньшие задержки. Говорят, на нормальных аудиоинтерфейсах доистижима задержка в 2-5 мс.


      1. Hidon
        24.08.2019 12:24

        Говорят, на нормальных аудиоинтерфейсах доистижима задержка в 2-5 мс.

        такие задержки достижимы, но избыточны. опытным путём давно установлен «стандарт» — 7 миллисекунд. да, можно меньше, но тут либо страдает качество сэмплов, либо стоимость… проще самолёт купить, получить лётное свидетельство, улететь куда-нибудь на море, жить там пол года и вернуться обратно.


      1. ALF_Zetas
        24.08.2019 20:27

        от комбика до уха гитариста звук по воздуху дольше идет ;) — поэтому нет практического смысла в таких маленьких задержках


    1. Hidon
      24.08.2019 12:18

      ASIO в первую очередь рассчитан на пользователей звуковых карт и DAW от steinberg, которая этот «стандарт» и разработала.
      они так же делают и профессиональные звуковые… картами их назвать уже не получится, интерфесы наверное.

      про WASAPI что-то слышал, но ничего про это не знаю, предпочитаю работать с простыми, но проверенными вещами. и кстати я не один такой, это как в авиации — работает десятилетиями, но машина сама себя не полетит, ей лётчик нужен.


  1. phantom-code
    23.08.2019 21:34
    +3

    Читая заголовок подумал, что будет про разработку «под ASIO», оказалось, статья про работу со сторонней библиотекой, умеющей работать с ASIO. Так бы и писали, «использование библиотеки Bassasio».
    В качестве примеров работы с ASIO можно посмотреть исходники проектов rtaudio и portaudio.


    1. AlexPelmen Автор
      24.08.2019 00:38

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


  1. 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


  1. Daddy_Cool
    23.08.2019 23:41

    Очень интересно!
    Я так понимаю, ASIO это довольно стандартная вещь.
    У меня есть хорошая звукокарточкакоробочка Focusrite с собственными драйверами, которые видимо много чего хорошего позволяют в т.ч. и задержку до 3-5 мс. Все программы — включая Гитар Риг видят карту с её драйверами и нормально работают. Как это программируется? Никогда не возился со звуком в программном смысле, но хочется попробовать.


    1. AlexPelmen Автор
      24.08.2019 00:55

      ASIO — это не конкретный драйвер, а технология, на основе которой пишется софт для железяк отдельно, учитывая особенности каждой — это как раз те самые собственные драйвера звукокарточек. Именно поэтому весь софт, о котором идет речь, корректно воспринимает карту и ее дровишки, ведь для софта это устройство ASIO. Я это так понимаю, поправьте, если ошибаюсь.
      Про программирование Рига ничего сказать не могу. Большой проект, большой компании с большой аудиторией программируется штабом опытных разработчиков в течение долгого времени. Поддерживает как ASIO, так и WASAPI.
      Если для вас Guitar Rig — это только «комбик с эффектами», то простое подобие этого можно написать и самому, используя средства, перечисленные в статье.


  1. DarkWolfer
    24.08.2019 00:39

    Круто!) Очень полезная статья!


  1. dporollo
    24.08.2019 08:40

    Steinberg. А так вроде железо должно поддерживать ASIO, если нет, то это странные эмуляции, вроде вывших когда-то KX драйверов.


    1. AlexPelmen Автор
      24.08.2019 08:50

      Если нет подходящего железа, то ставят ASIO4ALL. Это по сути тот же WASAPI… более того, это просто мост, между выходом ASIO и KS/WASAPI, но с дополнительными настройками буфера, задержек и т.д. Тык
      Просто если железо поддерживает — это будет работать. Нет — с ASIO4ALL также будет.


  1. sadko4u
    24.08.2019 15:01

    Ещё одна статья уровня "Я нашёл библиотеку, которая делает всю работу за меня" вместо того, чтобы разобраться в реальных принципах работы с ASIO напрямую и на примерах их доходчиво здесь изложить. Заголовок не соответствует содержанию.


  1. monah_tuk
    25.08.2019 02:08

    Только прочитав про электрогитару, я понял, что речь не про тот ASIO :-D


    Но в тему, а есть годные доки по ASIO, но с другой стороны, со стороны драйверописателя? Какие API можно вытянуть, какие кастомные контроллы (просто есть на чём пощупать, но не совсем Windows погромист).


    1. AlexPelmen Автор
      25.08.2019 09:40

      Изначально я пробовал общаться с ASIO через SDK. У Stainberg с сайта можно скачать, там еще и примерчик есть и мануальчик. Но инфы было мало. По запросу "...asio… " то и дело я получал «boost.asio» или просто инфу про технологию. Потом узнал, что BASS может работать с asio и передумал использовать SDK.

      В основном я пользовался только документацией к bass и тем, что смог найти где-то на сторонних форумах.


      1. Matisumi
        25.08.2019 12:03
        +1

        Прям даже интересно, почему вы так упрямо пишете «Stainberg», хотя даже ссылка в статье у вас «steinberg»


        1. AlexPelmen Автор
          25.08.2019 12:52

          Невнимательность