Создаем VST эффект на ассемблере.

Тема в разделе "Разработка плагинов", создана пользователем Thetrik, 9 дек 2016.

  1. Thetrik

    Thetrik UA6527P

    Публикаций:
    0
    Регистрация:
    25 июл 2011
    Сообщения:
    875
    Всем привет, сегодня я хотел бы рассказать как написать простейший VST плагин на ассемблере. Те кто создает музыку на компьютере, или занимается обработкой звука хорошо знакомы с этими плагинами и часто используют их как для генерации звука так и для обработки. Основное достоинство таких плагинов - это простота подключения к большинству аудио или музыкальных редакторов. Существуют два типа плагинов VST эффекты и VST инструменты (которые также называют VSTi).

    В данной статье мы рассмотрим создание VST эффекта, на основе стандарта VST 2.4 который поддерживают большинство редакторов. Программировать будем на FASM'е.
    Итак, для начала нужно определится с самим эффектом и для простоты я решил использовать биткрашер. Суть эффекта состоит в понижении разрешения звука как по частоте так и по амплитуде без всякой фильтрации, что дает характерное звучание из-за шумов квантования. Такой эффект нередко можно встретить в электронной музыке, я и сам его очень часто использую. Наглядно эффект продемонстрирован на рисунке:
    giphy.gif
    Для начала определимся с параметрами эффекта - это частота среза, количество уровней громкости (битность) и дополнительно добавим регулировку выходной громкости. Частота среза у нас может регулироваться от половины частоты дискретизации до нуля, битность от 1 до 16 бит (от 2 до 65536 уровней соответственно), громкость от 0 до 100%. Звук в VST стандарте представляет из себя буффер с семплами, где каждый семпл представлен либо 32-битным числом с плавающей точкой, либо 64 битным числом с плавающей точкой. Частота дискретизации задает количество таких семплов в секунду; максимальная частота которая может быть воспроизведена равна половине частоты дискретизации (обычно 22050Гц). Амплитуда варьируется от -1 до 1, но может также выходить за пределы, что влечет за собой перегруз и клиппинг. Также следует учитывать количество каналов звука, для стерео звука это два канала и каждый обрабатывается независимо. Для того чтобы понизить разрядность звука нужно применить простую формулу:
    newValue = int(oldValue * levels) / levels
    В итоге из-за округления мы получим дискретный шаг который зависит от величины levels. С ограничением частоты также все просто, для этого найдем сначала количество семплов которое следует пропустить для получения нужной частоты по формуле:
    numSamples = sampleRate / downSamplingFreq / 2
    Нужно отметить что это число должно быть вещественное для плавной регулировки. Далее нужно просто завести счетчик семлов и периодически сравнивать его значение с numSamples, если оно больше или равно то следует прогрузить следующий семпл в выходной буфер иначе прогружать семпл из предыдущего такого прогруза. Т.к. мы будем использовать стерео обработку, то нужно иметь 2 независимых канала обработки. Из всего этого можно уже примерно накидать структуру эффекта:
    Код (ASM):
    1. struct
    2.     sampleRate              dd ?                ; // Частота дискретизации
    3.     volume                  dd ?                ; // Громкость 0..1 (0..100%)
    4.     downsampling            dd ?                ; // Частота среза 0..1 (0..SampleRate)
    5.     quantize                dd ?                ; // Битность 0..1 (2 ^ (value * 15 + 1))
    6.     lValue                  dd ?                ; // Текущее значение семпла левого канала
    7.     rValue                  dd ?                ; // Текущее значение семпла правого канала
    8.     sampleCounter           dd ?                ; // Счетчик семплов фильтра
    9. ends    
    Теперь, если просмотреть VST SDK, то можно увидеть что VST плагин представляет собой обычную DLL которая экспортирует функцию VSTPluginMain или main (Main, MAIN, и т.д.). Хост вызывает эту функцию когда создается новый экземпляр VST эффекта. Эта функция должна при успехе возвратить указатель на объект дескриптора эффекта AEEffect, который имеет следующую структуру:
    Код (ASM):
    1. struct AEEffect
    2.     magic                   dd ?                ; // Сигнатура 'VstP'
    3.     dispatcher              dd ?                ; // Процедура диспечеризации
    4.     process                 dd ?
    5.     setParameter            dd ?                ; // Установка параметра
    6.     getParameter            dd ?                ; // Получение параметра
    7.     numPrograms             dd ?
    8.     numParams               dd ?
    9.     numInputs               dd ?                ; // Количество входных каналов
    10.     numOutputs              dd ?                ; // Количество выходных каналов
    11.     flags                   dd ?                ; // Флаги
    12.     resvd1                  dd ?
    13.     resvd2                  dd ?
    14.     initialDelay            dd ?
    15.     realQualities           dd ?
    16.     offQualities            dd ?
    17.     ioRatio                 dd ?          
    18.     object                  dd ?                ; // Указатель на объект эффекта
    19.     user                    dd ?
    20.     uniqueId                dd ?                ; // Уникальный ИД эффекта
    21.     version                 dd ?                ; // Версия эффекта
    22.     processReplacing        dd ?                ; // Процедура обработки звука
    23.     processDoubleReplacing  dd ?
    24.     future                  db 56 dup (?)
    25. ends
    Как видно структура содержит множество полей, но нас интересуют только некоторые. Самое важное поле это dispatcher - указатель на функцию которая принимает различные запросы от хоста (чем-то похоже на WindowProc); setParameter/getParameter - задают указатели на функции установки/получения параметров от элементов управления или автоматизации. В numInputs/numOutputs мы задаем количество поддерживаемых каналов, в нашем случае 2. Поле object - содержит указатель на связанный пользовательский объект эффекта, т.е. там мы будем хранить указатель на структуру объекта что мы привели ранее. processReplacing и processDoubleReplacing содержат процедуры обработки звуковых данных для 32-float и 64-double соответственно. Для нашего примера мы будем использовать только 32-float Обработку. Флаги задают некоторые характеристики эффекта, мы будем использовать два значения: effFlagsCanReplacing и effFlagsNoSoundInStop. Первый говорит нам что плагин имеет функцию processReplacing и должен быть всегда установлен в VST 2.4 эффекте, а effFlagsNoSoundInStop что плагин ничего не делает если нет входного звука или там тишина. Итак чтобы связать AEEffect и наш эффект соберем их в одну структуру ASMCrusher которая будет олецетворять наш эффект:
    Код (ASM):
    1. ; // Объект эффекта ASMCrusher
    2. struct ASMCrusher
    3.     ae                      AEEffect ?          ; // Базовый интерфейс AEEffect
    4.     sampleRate              dd ?                ; // Частота дискретизации
    5.     volume                  dd ?                ; // Громкость 0..1 (0..100%)
    6.     downsampling            dd ?                ; // Частота среза 0..1 (0..SampleRate)
    7.     quantize                dd ?                ; // Битность 0..1 (2 ^ (value * 15 + 1))
    8.     lValue                  dd ?                ; // Текущее значение семпла левого канала
    9.     rValue                  dd ?                ; // Текущее значение семпла правого канала
    10.     sampleCounter           dd ?                ; // Счетчик семплов фильтра
    11. ends
     
    Последнее редактирование модератором: 23 фев 2021
    alex_dz и Mikl___ нравится это.
  2. Thetrik

    Thetrik UA6527P

    Публикаций:
    0
    Регистрация:
    25 июл 2011
    Сообщения:
    875
    Итак для начала зададим формат файла, декларации типов и констант, импорт, экспорт и зададим релокации:
    Код (ASM):
    1. format PE GUI 4.0 DLL at 11000000h
    2.  
    3. include 'win32wx.inc'
    4.  
    5. ; // Базовый интерфейс VST эффекта
    6. struct AEEffect
    7.     magic                   dd ?                ; // Сигнатура 'VstP'
    8.     dispatcher              dd ?                ; // Процедура диспечеризации
    9.     process                 dd ?
    10.     setParameter            dd ?                ; // Установка параметра
    11.     getParameter            dd ?                ; // Получение параметра
    12.     numPrograms             dd ?
    13.     numParams               dd ?
    14.     numInputs               dd ?                ; // Количество входных каналов
    15.     numOutputs              dd ?                ; // Количество выходных каналов
    16.     flags                   dd ?                ; // Флаги
    17.     resvd1                  dd ?
    18.     resvd2                  dd ?
    19.     initialDelay            dd ?
    20.     realQualities           dd ?
    21.     offQualities            dd ?
    22.     ioRatio                 dd ?
    23.     object                  dd ?                ; // Указатель на объект эффекта
    24.     user                    dd ?
    25.     uniqueId                dd ?                ; // Уникальный ИД эффекта
    26.     version                 dd ?                ; // Версия эффекта
    27.     processReplacing        dd ?                ; // Процедура обработки звука
    28.     processDoubleReplacing  dd ?
    29.     future                  db 56 dup (?)
    30. ends
    31.  
    32. ; // Объект эффекта ASMCrusher
    33. struct ASMCrusher
    34.     ae                      AEEffect ?          ; // Базовый интерфейс AEEffect
    35.     sampleRate              dd ?                ; // Частота дискретизации
    36.     volume                  dd ?                ; // Громкость 0..1 (0..100%)
    37.     downsampling            dd ?                ; // Частота среза 0..1 (0..SampleRate)
    38.     quantize                dd ?                ; // Битность 0..1 (2 ^ (value * 15 + 1))
    39.     lValue                  dd ?                ; // Текущее значение семпла левого канала
    40.     rValue                  dd ?                ; // Текущее значение семпла правого канала
    41.     sampleCounter           dd ?                ; // Счетчик семплов фильтра
    42. ends
    43.  
    44. NUMBER_OF_PARAMETERS    = 3                     ; // Количество параметров эффекта
    45. UNIQUE_ID               = 1234567               ; // Уникальный ИД эффекта
    46. VERSION                 = 1                     ; // Версия эффекта
    47. PAR_VOLUME              = 0                     ; // Индексы параметров ...
    48. PAR_DOWNSAMPLING        = 1
    49. PAR_QUANTIZE            = 2
    50.  
    51. kEffectMagic            = 0x56737450            ; // Сигнатура AEEffect
    52. audioMasterVersion      = 1                     ; // Версия хоста
    53.  
    54. ; // Максимальные размеры строк
    55. kVstMaxParamStrLen      = 8
    56. kVstMaxVendorStrLen     = 64
    57. kVstMaxProductStrLen    = 64
    58. kVstMaxEffectNameLen    = 32
    59.  
    60. effClose                = 1                     ; // Событие вызывается когда эффект уничтожается
    61. effSetSampleRate        = 10                    ; // Событие установки частоты дискретизации
    62. effGetParamName         = 8                     ; // Событие получения имени параметра
    63. effGetParamLabel        = 6                     ; // Событие получения метки параметра
    64. effGetParamDisplay      = 7                     ; // Событие получения метки значения параметра
    65. effGetEffectName        = 45                    ; // Событие получения имени эффекта
    66. effGetVendorString      = 47                    ; // Событие получения имени производителя
    67. effGetProductString     = 48                    ; // Событие получения имени продукта
    68. effGetVendorVersion     = 49                    ; // Событие получения версии
    69.  
    70. effFlagsCanReplacing    = 16
    71. effFlagsNoSoundInStop   = 512
    72.  
    73. section '.idata' import data readable writeable
    74.  
    75. library kernel, 'kernel32.dll', \
    76.         msvcrt, 'msvcrt.dll'
    77.  
    78. import kernel,\
    79.        GetProcessHeap, 'GetProcessHeap', \
    80.        HeapAlloc, 'HeapAlloc', \
    81.        HeapFree, 'HeapFree', \
    82.        lstrcpynA, 'lstrcpynA'
    83.  
    84. import msvcrt, \
    85.        sprintf, 'sprintf'
    86.  
    87. data export
    88. export 'AsmCrusher.DLL', Main, 'Main'
    89. end data
    90.  
    91. section '.reloc' data readable discardable fixups  
    Одной замечательной особенностью VST стандарта является то что можно вообще не реализовывать пользовательский интерфейс, нужно лишь сообщить хосту количество параметров и их свойства и каждый хост сам предоставит нужные регуляторы и свяжет их с параметрами эффекта. Поэтому далее задаем таблицу строк и список указателей на необходимые строки для каждого параметра, а также точку входа DLL. Таблицу разместим в секции .text:
    Код (ASM):
    1. section '.text' code readable executable
    2.  
    3. EFFECT_NAME      db      'ASMCrusher', 0
    4. VENDOR_NAME      db      'Кривоус Анатолий Анатольевич (The trick)', 0
    5. PRODUCT_NAME     db      'ASMCrusher', 0
    6. PARAM_NAME_1     db      'Volume', 0
    7. PARAM_NAME_2     db      'Frequency', 0
    8. PARAM_NAME_3     db      'Quantize', 0
    9. PARAM_LABEL_1    db      '%', 0
    10. PARAM_LABEL_2    db      'Hz', 0
    11. PARAM_LABEL_3    db      'Levels', 0
    12. PARAM_FORMAT_1   db      '%d%%', 0
    13. PARAM_FORMAT_2   db      '%dHz', 0
    14. PARAM_FORMAT_3   db      '%d', 0
    15.  
    16. PARAMS_LIST      dd      PARAM_NAME_1, PARAM_NAME_2, PARAM_NAME_3       ; // Имена параметров
    17. LABELS_LIST      dd      PARAM_LABEL_1, PARAM_LABEL_2, PARAM_LABEL_3    ; // Метки единиц измерения параметров
    18. FORMATS_LIST     dd      PARAM_FORMAT_1, PARAM_FORMAT_2, PARAM_FORMAT_3 ; // Форматы параметров
    19.  
    20. entry EntryPoint
    21.  
    22. ; // Точка входа DLL
    23. proc EntryPoint, hinstDLL, fdwReason, lpvReserved
    24.     mov eax, 1
    25.     ret
    26. endp    
    В PARAMS_LIST мы храним указатели на строки имен параметров, в LABELS_LIST на соответствующие единицы измерений для них, а в FORMATS_LIST строки формата для функции sprintf. Каждый экземпляр объекта мы будем хранить в куче процесса, для выделения и освобождения памяти в ней создадим две процедуры:
    Код (ASM):
    1. ; // Выделить память
    2. proc MemAlloc, size
    3.     invoke HeapAlloc, <invoke GetProcessHeap>, HEAP_NO_SERIALIZE OR HEAP_ZERO_MEMORY, [size]
    4.     ret
    5. endp
    6.  
    7. ; // Освободить память
    8. proc MemFree, pMem
    9.     invoke HeapFree, <invoke GetProcessHeap>, [pMem], HEAP_NO_SERIALIZE
    10.     ret
    11. endp  
    Теперь можно приступать к непосредственно к реализации стандартных функций VST формата. Первая самая важная функция которую мы также будем экспортировать из DLL будет Main. В ней мы сначала проверяем версию VST хоста, и если она не равна нулю то переходим к созданию эффекта. Создание эффекта - это просто выделение памяти под структуру ASMCrusher и заполнение некоторых ее полей, а также установка свойств по умолчанию:
    Код (ASM):
    1. ; // Вызывается при создании нового экземпляра VST эффекта
    2. proc Main c audioMaster
    3.  
    4.     ; // Проверяем версию
    5.     cinvoke audioMaster, 0, audioMasterVersion, 0, 0, 0, 0
    6.     .if eax = 0
    7.         ret
    8.     .endif
    9.  
    10.     stdcall CreateASMCrusher
    11.  
    12.     ret
    13.  
    14. endp
    15.  
    16. ; // Создать объект ASMCrusher
    17. proc CreateASMCrusher uses ebx
    18.  
    19.     stdcall MemAlloc, sizeof.ASMCrusher
    20.  
    21.     .if eax = 0
    22.         ret
    23.     .endif
    24.  
    25.     mov ebx, eax
    26.     lea eax, [ebx + ASMCrusher.ae]
    27.  
    28.     mov [eax + ASMCrusher.ae.magic], kEffectMagic
    29.     mov [eax + ASMCrusher.ae.dispatcher], Dispatcher
    30.     mov [eax + ASMCrusher.ae.setParameter], SetParameter
    31.     mov [eax + ASMCrusher.ae.getParameter], GetParameter
    32.     mov [eax + ASMCrusher.ae.processReplacing], ProcessReplacing
    33.     mov [eax + ASMCrusher.ae.numInputs], 2
    34.     mov [eax + ASMCrusher.ae.numOutputs], 2
    35.     mov [eax + ASMCrusher.ae.numParams], NUMBER_OF_PARAMETERS
    36.     mov [eax + ASMCrusher.ae.flags], effFlagsCanReplacing OR effFlagsNoSoundInStop
    37.     mov [eax + ASMCrusher.ae.uniqueId], UNIQUE_ID
    38.     mov [eax + ASMCrusher.ae.version], VERSION
    39.     mov [eax + ASMCrusher.ae.object], ebx
    40.  
    41.     ; // Загрузка значений по умолчанию
    42.     mov [eax + ASMCrusher.sampleRate], 44100
    43.     mov [eax + ASMCrusher.volume], 1.0
    44.     mov [eax + ASMCrusher.downsampling], 1.0
    45.     mov [eax + ASMCrusher.quantize], 1.0
    46.     mov [eax + ASMCrusher.lValue], 0.0
    47.     mov [eax + ASMCrusher.rValue], 0.0
    48.     mov [eax + ASMCrusher.sampleCounter], 0.0
    49.  
    50.     mov eax, ebx
    51.  
    52.     ret
    53.  
    54. endp
    В качестве параметра функция Main принимает указатель на функцию обратного вызова audioMaster, которую мы вызываем для того чтобы определить версию хоста. При создании объекта сначала выделяется память и заполняется члены базового интерфейса AEEffect, затем заполняются поля значений по умолчанию. Dispatcher, SetParameter, GetParameter, ProcessReplacing являются указателями на функции которые будут рассмотрены далее.
     
    Mikl___ нравится это.
  3. Thetrik

    Thetrik UA6527P

    Публикаций:
    0
    Регистрация:
    25 июл 2011
    Сообщения:
    875
    Следующей важной функцией является функция диспетчеризации - Dispatcher, которая принимает различные события от хоста:
    Код (ASM):
    1. ; // Процедура диспетчеризации
    2. proc Dispatcher c, pEffect, uOpcode, uIndex, value, lpPtr, opt
    3.  
    4.     mov ecx, [pEffect]
    5.     mov ecx, [ecx + AEEffect.object]
    6.  
    7.     .if [uOpcode] = effClose
    8.         ; // Удалить VST эффект
    9.         stdcall MemFree, ecx
    10.         xor eax, eax
    11.     .elseif [uOpcode] = effSetSampleRate
    12.         ; // Установить частоту дискретизации
    13.         mov eax, [opt]
    14.         mov [ecx + ASMCrusher.sampleRate], eax
    15.         xor eax, eax
    16.     .elseif [uOpcode] = effGetParamName
    17.         ; // Получить имя параметра
    18.         mov eax, [uIndex]
    19.         invoke lstrcpynA, [lpPtr], [PARAMS_LIST + eax * 4], kVstMaxParamStrLen
    20.         xor eax, eax
    21.     .elseif [uOpcode] = effGetParamLabel
    22.         ; // Получить имя параметра в окне (надпись)
    23.         mov eax, [uIndex]
    24.         invoke lstrcpynA, [lpPtr], [PARAMS_LIST + eax * 4], kVstMaxParamStrLen
    25.         xor eax, eax
    26.     .elseif [uOpcode] = effGetEffectName
    27.         ; // Получить имя эффекта
    28.         invoke lstrcpynA, [lpPtr], EFFECT_NAME, kVstMaxEffectNameLen
    29.         xor eax, eax
    30.     .elseif [uOpcode] = effGetVendorString
    31.         ; // Получить имя производителя
    32.         invoke lstrcpynA, [lpPtr], VENDOR_NAME, kVstMaxVendorStrLen
    33.         xor eax, eax
    34.     .elseif [uOpcode] = effGetProductString
    35.         ; // Получить имя продукта
    36.         invoke lstrcpynA, [lpPtr], PRODUCT_NAME, kVstMaxProductStrLen
    37.         xor eax, eax
    38.     .elseif [uOpcode] = effGetVendorVersion
    39.         ; // Получить версию
    40.         mov eax, VERSION
    41.     .elseif [uOpcode] = effGetParamDisplay
    42.         ; // Получить значение параметра (надпись)
    43.         .if [uIndex] = PAR_VOLUME
    44.  
    45.             ; // volume * 100
    46.             mov eax, 100.0
    47.             movd xmm0, eax
    48.             mulss xmm0, [ecx + ASMCrusher.volume]
    49.             cvtss2si eax, xmm0
    50.  
    51.             cinvoke sprintf, [lpPtr], [FORMATS_LIST + PAR_VOLUME * 4], eax
    52.  
    53.         .elseif [uIndex] = PAR_DOWNSAMPLING
    54.  
    55.             stdcall CalcDownsamplingFreq, [ecx + ASMCrusher.sampleRate], [ecx + ASMCrusher.downsampling]
    56.             cinvoke sprintf, [lpPtr], [FORMATS_LIST + PAR_DOWNSAMPLING * 4], eax
    57.  
    58.         .elseif [uIndex] = PAR_QUANTIZE
    59.  
    60.             stdcall CalcLevels, [ecx + ASMCrusher.quantize]
    61.             cinvoke sprintf, [lpPtr], [FORMATS_LIST + PAR_QUANTIZE * 4], eax
    62.  
    63.         .endif
    64.  
    65.         xor eax, eax
    66.     .else
    67.         xor eax, eax
    68.     .endif
    69.  
    70.     ret
    71.  
    72. endp                
    Процедура диспетчеризации принимает несколько параметров, в качестве pEffect передается указатель на AEEffect нашего VST эффекта. В параметре uOpcode передается идентификатор события. uIndex содержит индексный параметр, в нашем случае здесь содержится индекс параметра о котором хост желает получить те или иные сведения. Параметр value и opt содержат целочисленные значения специфичные для события, в параметре lpPtr передается указатель на данные также специфичные для события. Анализируя исходный код видим что процедура состоит из большого switch в котором перебираются идентификаторы события. При получении события effClose мы просто освобождаем память выделенную для нашего объекта. При получении события effSetSampleRate мы устанавливаем частоту дискретизации, которая используется в расчетах; параметр opt содержит float значение частоты дискретизации. События effGetParamName и effGetParamLabel извлекают данные из таблицы строк и записывают данные в выходной параметр lpPtr. Стоит отметить что длина строки ограничена kVstMaxParamStrLen символами. Аналогично effGetEffectName, effGetVendorString, effGetProductString извлекают соответствующие данные из таблиц строк. effGetVendorVersion просто возвращает версию. При получении события effGetParamDisplay мы уже анализируем индекс эффекта, для того чтобы привести значения из логического диапазона 0..1 в реальный текстового вида, который используется в качестве надписи на элементах управления VST. Если это регулятор громкости то мы просто умножаем это число на 100 и добавляем знак процента; если это частота то мы вызываем функцию CalcDownsamplingFreq которая преобразует частоту из диапазона 0..1 в диапазон 0Гц..SampleRate/2, далее формируется строка с добавлением смволов Hz; наконец если это регулятор квантования то вызывается функция CalcLevels которая возвращает количество уровней исходя из диапазона 0..1 (2..65536). Давайте рассмотрим исходный код этих функций:
    Код (ASM):
    1. ; // Получить реальную частоту ресемплинга на основании значения downsampling
    2. ; // Вычисляем по формуле int(downsampling * samplerate * 0.5)
    3. proc CalcDownsamplingFreq, sampleRate, downsampling
    4.  
    5.     mov eax, 0.5
    6.     movd xmm0, eax
    7.     mulss xmm0, [downsampling]
    8.     mulss xmm0, [sampleRate]
    9.     cvtss2si eax, xmm0
    10.  
    11.     ret
    12.  
    13. endp
    14.  
    15. ; // Посчитать количество уровней сигнала на основании значения quantize
    16. ; // Вычисляем по формуле int(2 ^ (quantize * 15 + 1)))
    17. proc CalcLevels, quantize
    18.  
    19.     mov eax, 2
    20.     mov ecx, 15.0
    21.     movss xmm0, [quantize]
    22.     movd xmm1, ecx
    23.     mulss xmm0, xmm1
    24.     cvtss2si ecx, xmm0
    25.     shl eax, cl
    26.  
    27.     ret
    28.  
    29. endp  
    Первая функция вычисляет частоту по формуле int(downsampling * samplerate * 0.5), где downsampling находится в диапазоне [0..1]. Вторая функция получает количество уровней сигнала по формуле int(2 ^ (quantize * 15 + 1))), где quantize также располагается в диапазоне [0..1]. Эта функция оперирует 16 битными значениями, т.е. максимум получается 65536, а минимум 2. Далее рассмотрим функцию установки и получения параметров:
    Код (ASM):
    1. ; // Получить параметр
    2. proc GetParameter c, pEffect, uIndex
    3.  
    4.     mov ecx, [pEffect]
    5.     mov ecx, [ecx + AEEffect.object]
    6.  
    7.     .if [uIndex] = PAR_VOLUME
    8.         fld [ecx + ASMCrusher.volume]
    9.     .elseif [uIndex] = PAR_DOWNSAMPLING
    10.         fld [ecx + ASMCrusher.downsampling]
    11.     .elseif [uIndex] = PAR_QUANTIZE
    12.         fld [ecx + ASMCrusher.quantize]
    13.     .else
    14.         fldz
    15.     .endif
    16.  
    17.     ret
    18. endp
    19.  
    20. ; // Установить параметр
    21. proc SetParameter c, pEffect, uIndex, fValue
    22.  
    23.     mov ecx, [pEffect]
    24.     mov ecx, [ecx + AEEffect.object]
    25.     mov eax, dword [fValue]
    26.  
    27.     .if [uIndex] = PAR_VOLUME
    28.         mov [ecx + ASMCrusher.volume], eax
    29.     .elseif [uIndex] = PAR_DOWNSAMPLING
    30.         mov [ecx + ASMCrusher.downsampling], eax
    31.     .elseif [uIndex] = PAR_QUANTIZE
    32.         mov [ecx + ASMCrusher.quantize], eax
    33.     .endif
    34.  
    35.     ret
    36.  
    37. endp
    Каждый параметр в VST кодируется 32 bit - float значением в диапазоне от 0 до 1. Здесь все просто, нужно только отметить что возвращаемое значение возвращается на вершине стека FPU.
    При обработке звука вызывается функция ProcessReplacing которая принимает указатель на объект, два указателя на указатели семплов и количество семплов:
    Код (ASM):
    1. proc ProcessReplacing c uses esi edi ebx, pEffect, pInputs, pOutputs, sampleFrames
    2.  
    3.     mov esi, [pInputs]
    4.     mov edi, [pOutputs]
    5.     mov ebx, [pEffect]
    6.     mov ebx, [ebx + AEEffect.object]
    7.  
    8.     ; // Обрабатываем левый канал
    9.     stdcall ApplyEffectToChannel, ebx, dword [esi], dword [edi], dword [sampleFrames], dword [ebx + ASMCrusher.lValue]
    10.     mov [ebx + ASMCrusher.lValue], eax
    11.  
    12.     ; // Обрабатываем правый канал
    13.     stdcall ApplyEffectToChannel, ebx, dword [esi + 4], dword [edi + 4], dword [sampleFrames], dword [ebx + ASMCrusher.rValue]
    14.     mov [ebx + ASMCrusher.rValue], eax
    15.  
    16.     ret
    17.  
    18. endp
     
    rococo795 и Mikl___ нравится это.
  4. Thetrik

    Thetrik UA6527P

    Публикаций:
    0
    Регистрация:
    25 июл 2011
    Сообщения:
    875
    pInputs в нашем случае содержит указатель на два указателя (правый и левый каналы) на звуковые семплы в формате 32 bit float, pOutputs - тоже самое только на выходной буфер. sampleFrames содержит количество семплов в канале. В качестве процедуры обработки служит процедура ApplyEffectToChannel:
    Код (ASM):
    1. ; // Применить эффект к буферу
    2. ; // Возвращает значение семпла
    3. proc ApplyEffectToChannel uses esi edi ebx, pObject, pInput, pOutput, nCount, fValue
    4.  
    5.     mov ebx, [pObject]
    6.     mov esi, [pInput]
    7.     mov edi, [pOutput]
    8.  
    9.     ; // Вычисляем количество уровней
    10.     stdcall CalcLevels, [ebx + ASMCrusher.quantize]
    11.     cvtsi2ss xmm1, eax
    12.  
    13.     ; // Вычисляем количество семплов для частоты среза
    14.     mov eax, 2.0
    15.     movd xmm0, eax
    16.     divss xmm0, [ebx + ASMCrusher.downsampling]
    17.  
    18.     ; // Восстанавливаем регистр счетчика фильтра
    19.     movss xmm2, [ebx + ASMCrusher.sampleCounter]
    20.  
    21.     ; // Загружаем граничные значения
    22.     mov eax, 1.0
    23.     movd xmm3, eax
    24.     mov eax, -1.0
    25.     movd xmm4, eax
    26.  
    27.     ; // Загружаем значение громкости в регистр
    28.     movss xmm6, [ebx + ASMCrusher.volume]
    29.  
    30.     ; // Загружаем сохраненное значение семпла и применяем уровень громкости
    31.     movd xmm5, [fValue]
    32.     mulss xmm5, xmm6
    33.  
    34.     ; // Задаем количество семплов
    35.     mov ecx, [nCount]
    36.  
    37.     ; // Проход по семплам
    38.     .PROCESS_SAMLE:
    39.  
    40.         ; // Увеличиваем счетчик регистра фильтра
    41.         addss xmm2, xmm3
    42.         comiss xmm2, xmm0
    43.         ; // Если количество семлов превышает порог, загружаем новый
    44.         jb .STORE_SAMPLE
    45.  
    46.         ; // Сравниваем с 1
    47.         comiss xmm3, dword [esi]
    48.         jb .SET_MAX
    49.         ; // Сравниваем с -1
    50.         comiss xmm4, dword [esi]
    51.         ja .SET_MIN
    52.         movss xmm5, dword [esi]
    53.  
    54.         .CALC_SAMPLE:
    55.         ; // Сохраняем семп в регистр edx
    56.         movd edx, xmm5
    57.         ; // Вычисляем семпл по формуле int(sample * levels) / levels
    58.         mulss xmm5, xmm1
    59.         cvtss2si eax, xmm5
    60.         cvtsi2ss xmm5, eax
    61.         divss xmm5, xmm1
    62.  
    63.         ; // Изменяем громкость
    64.         mulss xmm5, xmm6
    65.         ; // Обновляем downsampling регистр
    66.         subss xmm2, xmm0
    67.  
    68.         jmp .STORE_SAMPLE
    69.  
    70.         .SET_MAX:
    71.         movss xmm5, xmm3
    72.         jmp .CALC_SAMPLE
    73.         .SET_MIN:
    74.         movss xmm5, xmm4
    75.         jmp .CALC_SAMPLE
    76.  
    77.         .STORE_SAMPLE:
    78.  
    79.         ; // Сохраняем текущий семпл
    80.         movss dword [edi], xmm5
    81.         add edi, 4
    82.         add esi, 4
    83.  
    84.     loop .PROCESS_SAMLE
    85.  
    86.     ; // Сохраняем значения
    87.     movd [ebx + ASMCrusher.sampleCounter], xmm2
    88.  
    89.     ; // Возвращаем значение семпла
    90.     mov eax, edx
    91.  
    92.     ret
    93.  
    94. endp      
    Эта процедура работает по алгоритмам описаным выше. Стоит отметить что для ускорения большинство действий выполняются в регистрах, на выходе тоолько значения сохраняются в объект для последующего восстановления состояния. Регистр xmm0 содержит количество семплов которые необходимо повторять (удержать) чтобы получить необходимую частоту среза. xmm1 содержит количество уровней квантования. xmm3 и xmm4 содержат константы 1 и -1 которые нужны для проверки выхода за диапазон допустимых значений. xmm6 содержит текуще значение громкости, xmm5 содержит текущее значение семпла умноженное на громкость. xmm2 - счетчик семплов. edx содержит текущее значение семпла без применения умножения громкости. Остальное все понятно из кода и пиведенного в начале описания алгоритма.
    Все, пробуем компилировать, и если все выполнено без ошибок в папке с исходником появится DLL. Эту DLL можно теперь подключать к любому хосту. Здесь я приведу несколько примеров GUI хостов:
    test (1).png
    Исходник прикреплен к сообщению. Всем спасибо за внимание!
    С уважением,
    Кривоус Анатолий (The trick).
     

    Вложения:

    • ASMCrusher.zip
      Размер файла:
      5,1 КБ
      Просмотров:
      626
    Последнее редактирование модератором: 23 фев 2021
    SadKo, calidus и Mikl___ нравится это.
  5. GRAFik

    GRAFik Active Member

    Публикаций:
    0
    Регистрация:
    14 мар 2020
    Сообщения:
    352
    Thetrik, я скачивал ваш VST-плагин на FASM-е в году, наверное, 2019 или даже раньше и он у меня компилировался, но хост его почему-то не видел (CUBASE 5.1). У меня тогда был другой ник и я у вас помню, спрашивал про это. Вы мне что-то посоветовали, ну я подумал, что нужно попробовать воспользоваться вашим советом и что-то закрутился, дела, дела... Да и опыта у меня с ассемблером на тот момент было маловато...

    Я тогда ваш плагин для FASM сейчас скачаю и еще раз проверю. Вдруг хост его увидит и все заработает? Если что, то я позже отпишусь.
     
  6. Thetrik

    Thetrik UA6527P

    Публикаций:
    0
    Регистрация:
    25 июл 2011
    Сообщения:
    875
    GRAFik, там нужно экспорт пофиксить еще:
    Код (ASM):
    1. export 'AsmCrusher.DLL', Main, 'Main', Main, 'main', Main, 'VSTPluginMain'
     
  7. GRAFik

    GRAFik Active Member

    Публикаций:
    0
    Регистрация:
    14 мар 2020
    Сообщения:
    352
    Thetrik, УРА!!! Все пофиксил как вы написали и все работает. Попался под руку какой-то семпл-wav скрипки из библиотеки Крис Хейна. Это надо слышать. Звучит гораздо лучше, чем Спиваков играет вживую на скрипке Страдивари. :) Но самое-то главное, что работает. Для нас скрипки Страдивари не показатель - у нас свои критерии качества. :)
     
    Thetrik нравится это.
  8. GRAFik

    GRAFik Active Member

    Публикаций:
    0
    Регистрация:
    14 мар 2020
    Сообщения:
    352
    Thetrik, извиняюсь, что немного не в тему, но вопрос все же родственный, ну или подобный этой теме.

    Если задаться целью создать VST-эффект на FASM, но в формате VST3. Много ли там работы по сравнению с VST2 и можно ли вас как-то мотивировать совершить такой подвиг? :)
     
  9. Thetrik

    Thetrik UA6527P

    Публикаций:
    0
    Регистрация:
    25 июл 2011
    Сообщения:
    875
    Да думаю побольше работы будет, но не сильно. В этом вст самый минимум использован, так для полноценного вст2.4 там тоже много чего нужно делать.