; //
; // ASMCrusher.asm - VST эффект биткрашер
; // © Кривоус Анатолий Анатольевич (The trick), 2016
; //
format PE GUI 4.0 DLL at 11000000h
include 'include\win32wx.inc'
; // Базовый интерфейс VST эффекта
struct AEEffect
magic dd ?; // Сигнатура 'VstP'
dispatcher dd ?; // Процедура диспечеризации
process dd ?
setParameter dd ?; // Установка параметра
getParameter dd ?; // Получение параметра
numPrograms dd ?
numParams dd ?
numInputs dd ?; // Количество входных каналов
numOutputs dd ?; // Количество выходных каналов
flags dd ?; // Флаги
resvd1 dd ?
resvd2 dd ?
initialDelay dd ?
realQualities dd ?
offQualities dd ?
ioRatio dd ?
object dd ?; // Указатель на объект эффекта
user dd ?
uniqueId dd ?; // Уникальный ИД эффекта
version dd ?; // Версия эффекта
processReplacing dd ?; // Процедура обработки звука
processDoubleReplacing dd ?
future db 56 dup (?)
ends
; // Объект эффекта ASMCrusher
struct ASMCrusher
ae AEEffect ?; // Базовый интерфейс AEEffect
sampleRate dd ?; // Частота дискретизации
volume dd ?; // Громкость 0..1 (0..100%)
downsampling dd ?; // Частота среза 0..1 (0..SampleRate)
quantize dd ?; // Битность 0..1 (2 ^ (value * 15 + 1))
lValue dd ?; // Текущее значение семпла левого канала
rValue dd ?; // Текущее значение семпла правого канала
sampleCounter dd ?; // Счетчик семплов фильтра
ends
NUMBER_OF_PARAMETERS= 3; // Количество параметров эффекта
UNIQUE_ID= 1234567; // Уникальный ИД эффекта
VERSION = 1; // Версия эффекта
PAR_VOLUME= 0; // Индексы параметров ...
PAR_DOWNSAMPLING= 1
PAR_QUANTIZE= 2
kEffectMagic= 0x56737450; // Сигнатура AEEffect
audioMasterVersion= 1; // Версия хоста
; // Максимальные размеры строк
kVstMaxParamStrLen= 8
kVstMaxVendorStrLen= 64
kVstMaxProductStrLen= 64
kVstMaxEffectNameLen= 32
effClose= 1; // Событие вызывается когда эффект уничтожается
effSetSampleRate= 10; // Событие установки частоты дискретизации
effGetParamName = 8; // Событие получения имени параметра
effGetParamLabel= 6; // Событие получения метки единиц измерения параметра
effGetParamDisplay= 7; // Событие получения метки значения параметра
effGetEffectName= 45; // Событие получения имени эффекта
effGetVendorString= 47; // Событие получения имени производителя
effGetProductString= 48; // Событие получения имени продукта
effGetVendorVersion= 49; // Событие получения версии
effFlagsCanReplacing= 16
effFlagsNoSoundInStop= 512
section '.idata' import data readable writeable
library kernel, 'kernel32.dll', \
msvcrt, 'msvcrt.dll'
import kernel,\
GetProcessHeap, 'GetProcessHeap', \
HeapAlloc, 'HeapAlloc', \
HeapFree, 'HeapFree', \
lstrcpynA, 'lstrcpynA'
import msvcrt, \
sprintf, 'sprintf'
data export
export 'AsmCrusher.DLL', Main, 'VSTPluginMain'
end data
section '.reloc' data readable discardable fixups
section '.text' code readable executable
EFFECT_NAME db 'ASMCrusher', 0
VENDOR_NAME db 'Кривоус Анатолий Анатольевич (The trick)', 0
PRODUCT_NAME db 'ASMCrusher', 0
PARAM_NAME_1 db 'Volume', 0
PARAM_NAME_2 db 'Frequency', 0
PARAM_NAME_3 db 'Quantize', 0
PARAM_LABEL_1 db '%', 0
PARAM_LABEL_2 db 'Hz', 0
PARAM_LABEL_3 db 'Levels', 0
PARAM_FORMAT_1 db '%d%%', 0
PARAM_FORMAT_2 db '%dHz', 0
PARAM_FORMAT_3 db '%d', 0
PARAMS_LIST dd PARAM_NAME_1, PARAM_NAME_2, PARAM_NAME_3; // Имена параметров
LABELS_LIST dd PARAM_LABEL_1, PARAM_LABEL_2, PARAM_LABEL_3; // Метки единиц измерения параметров
FORMATS_LIST dd PARAM_FORMAT_1, PARAM_FORMAT_2, PARAM_FORMAT_3 ; // Форматы параметров
entry EntryPoint
; // Точка входа DLL
proc EntryPoint, hinstDLL, fdwReason, lpvReserved
mov eax, 1
ret
endp
; // Выделить память
proc MemAlloc, size
invoke HeapAlloc, <invoke GetProcessHeap>, HEAP_NO_SERIALIZE OR HEAP_ZERO_MEMORY, [size]
ret
endp
; // Освободить память
proc MemFree, pMem
invoke HeapFree, <invoke GetProcessHeap>, [pMem], HEAP_NO_SERIALIZE
ret
endp
; // Вызывается при создании нового экземпляра VST эффекта
proc Main c audioMaster
; // Проверяем версию
cinvoke audioMaster, 0, audioMasterVersion, 0, 0, 0, 0
.if eax = 0
ret
.endif
stdcall CreateASMCrusher
ret
endp
; // Создать объект ASMCrusher
proc CreateASMCrusher uses ebx
stdcall MemAlloc, sizeof.ASMCrusher
.if eax = 0
ret
.endif
mov ebx, eax
lea eax, [ebx + ASMCrusher.ae]
mov [eax + ASMCrusher.ae.magic], kEffectMagic
mov [eax + ASMCrusher.ae.dispatcher], Dispatcher
mov [eax + ASMCrusher.ae.setParameter], SetParameter
mov [eax + ASMCrusher.ae.getParameter], GetParameter
mov [eax + ASMCrusher.ae.processReplacing], ProcessReplacing
mov [eax + ASMCrusher.ae.numInputs], 2
mov [eax + ASMCrusher.ae.numOutputs], 2
mov [eax + ASMCrusher.ae.numParams], NUMBER_OF_PARAMETERS
mov [eax + ASMCrusher.ae.flags], effFlagsCanReplacing OR effFlagsNoSoundInStop
mov [eax + ASMCrusher.ae.uniqueId], UNIQUE_ID
mov [eax + ASMCrusher.ae.version], VERSION
mov [eax + ASMCrusher.ae.object], ebx
; // Загрузка значений по умолчанию
mov [eax + ASMCrusher.sampleRate], 44100
mov [eax + ASMCrusher.volume], 1.0
mov [eax + ASMCrusher.downsampling], 1.0
mov [eax + ASMCrusher.quantize], 1.0
mov [eax + ASMCrusher.lValue], 0.0
mov [eax + ASMCrusher.rValue], 0.0
mov [eax + ASMCrusher.sampleCounter], 0.0
mov eax, ebx
ret
endp
; // Процедура диспетчеризации
proc Dispatcher c, pEffect, uOpcode, uIndex, value, lpPtr, opt
mov ecx, [pEffect]
mov ecx, [ecx + AEEffect.object]
.if [uOpcode] = effClose
; // Удалить VST эффект
stdcall MemFree, ecx
xor eax, eax
.elseif [uOpcode] = effSetSampleRate
; // Установить частоту дискретизации
mov eax, [opt]
mov [ecx + ASMCrusher.sampleRate], eax
xor eax, eax
.elseif [uOpcode] = effGetParamName
; // Получить имя параметра
mov eax, [uIndex]
invoke lstrcpynA, [lpPtr], [PARAMS_LIST + eax * 4], kVstMaxParamStrLen
xor eax, eax
.elseif [uOpcode] = effGetParamLabel
; // Получить имя параметра в окне (надпись)
mov eax, [uIndex]
invoke lstrcpynA, [lpPtr], [PARAMS_LIST + eax * 4], kVstMaxParamStrLen
xor eax, eax
.elseif [uOpcode] = effGetEffectName
; // Получить имя эффекта
invoke lstrcpynA, [lpPtr], EFFECT_NAME, kVstMaxEffectNameLen
xor eax, eax
.elseif [uOpcode] = effGetVendorString
; // Получить имя производителя
invoke lstrcpynA, [lpPtr], VENDOR_NAME, kVstMaxVendorStrLen
xor eax, eax
.elseif [uOpcode] = effGetProductString
; // Получить имя продукта
invoke lstrcpynA, [lpPtr], PRODUCT_NAME, kVstMaxProductStrLen
xor eax, eax
.elseif [uOpcode] = effGetVendorVersion
; // Получить версию
mov eax, VERSION
.elseif [uOpcode] = effGetParamDisplay
; // Получить значение параметра (надпись)
.if [uIndex] = PAR_VOLUME
; // volume * 100
mov eax, 100.0
movd xmm0, eax
mulss xmm0, [ecx + ASMCrusher.volume]
cvtss2si eax, xmm0
cinvoke sprintf, [lpPtr], [FORMATS_LIST + PAR_VOLUME * 4], eax
.elseif [uIndex] = PAR_DOWNSAMPLING
stdcall CalcDownsamplingFreq, [ecx + ASMCrusher.sampleRate], [ecx + ASMCrusher.downsampling]
cinvoke sprintf, [lpPtr], [FORMATS_LIST + PAR_DOWNSAMPLING * 4], eax
.elseif [uIndex] = PAR_QUANTIZE
stdcall CalcLevels, [ecx + ASMCrusher.quantize]
cinvoke sprintf, [lpPtr], [FORMATS_LIST + PAR_QUANTIZE * 4], eax
.endif
xor eax, eax
.else
xor eax, eax
.endif
ret
endp
; // Получить параметр
proc GetParameter c, pEffect, uIndex
mov ecx, [pEffect]
mov ecx, [ecx + AEEffect.object]
.if [uIndex] = PAR_VOLUME
fld [ecx + ASMCrusher.volume]
.elseif [uIndex] = PAR_DOWNSAMPLING
fld [ecx + ASMCrusher.downsampling]
.elseif [uIndex] = PAR_QUANTIZE
fld [ecx + ASMCrusher.quantize]
.else
fldz
.endif
ret
endp
; // Установить параметр
proc SetParameter c, pEffect, uIndex, fValue
mov ecx, [pEffect]
mov ecx, [ecx + AEEffect.object]
mov eax, dword [fValue]
.if [uIndex] = PAR_VOLUME
mov [ecx + ASMCrusher.volume], eax
.elseif [uIndex] = PAR_DOWNSAMPLING
mov [ecx + ASMCrusher.downsampling], eax
.elseif [uIndex] = PAR_QUANTIZE
mov [ecx + ASMCrusher.quantize], eax
.endif
ret
endp
; // Процедура обработки звука, вызывается хостом
proc ProcessReplacing c uses esi edi ebx, pEffect, pInputs, pOutputs, sampleFrames
mov esi, [pInputs]
mov edi, [pOutputs]
mov ebx, [pEffect]
mov ebx, [ebx + AEEffect.object]
; // Обрабатываем левый канал
stdcall ApplyEffectToChannel, ebx, dword [esi], dword [edi], dword [sampleFrames], dword [ebx + ASMCrusher.lValue]
mov [ebx + ASMCrusher.lValue], eax
; // Обрабатываем правый канал
stdcall ApplyEffectToChannel, ebx, dword [esi + 4], dword [edi + 4], dword [sampleFrames], dword [ebx + ASMCrusher.rValue]
mov [ebx + ASMCrusher.rValue], eax
ret
endp
; // Применить эффект к буферу
; // Возвращает значение семпла
proc ApplyEffectToChannel uses esi edi ebx, pObject, pInput, pOutput, nCount, fValue
mov ebx, [pObject]
mov esi, [pInput]
mov edi, [pOutput]
; // Вычисляем количество уровней
stdcall CalcLevels, [ebx + ASMCrusher.quantize]
cvtsi2ss xmm1, eax
; // Вычисляем количество семплов для частоты среза
mov eax, 2.0
movd xmm0, eax
divss xmm0, [ebx + ASMCrusher.downsampling]
; // Восстанавливаем регистр счетчика фильтра
movss xmm2, [ebx + ASMCrusher.sampleCounter]
; // Загружаем граничные значения
mov eax, 1.0
movd xmm3, eax
mov eax, -1.0
movd xmm4, eax
; // Загружаем значение громкости в регистр
movss xmm6, [ebx + ASMCrusher.volume]
; // Загружаем сохраненное значение семпла и применяем уровень громкости
movd xmm5, [fValue]
mulss xmm5, xmm6
; // Задаем количество семплов
mov ecx, [nCount]
; // Проход по семплам
.PROCESS_SAMLE:
; // Увеличиваем счетчик регистра фильтра
addss xmm2, xmm3
comiss xmm2, xmm0
; // Если количество семлов превышает порог, загружаем новый
jb .STORE_SAMPLE
; // Сравниваем с 1
comiss xmm3, dword [esi]
jb .SET_MAX
; // Сравниваем с -1
comiss xmm4, dword [esi]
ja .SET_MIN
movss xmm5, dword [esi]
.CALC_SAMPLE:
; // Сохраняем семп в регистр edx
movd edx, xmm5
; // Вычисляем семпл по формуле int(sample * levels) / levels
mulss xmm5, xmm1
cvtss2si eax, xmm5
cvtsi2ss xmm5, eax
divss xmm5, xmm1
; // Изменяем громкость
mulss xmm5, xmm6
; // Обновляем downsampling регистр
subss xmm2, xmm0
jmp .STORE_SAMPLE
.SET_MAX:
movss xmm5, xmm3
jmp .CALC_SAMPLE
.SET_MIN:
movss xmm5, xmm4
jmp .CALC_SAMPLE
.STORE_SAMPLE:
; // Сохраняем текущий семпл
movss dword [edi], xmm5
add edi, 4
add esi, 4
loop .PROCESS_SAMLE
; // Сохраняем значения
movd [ebx + ASMCrusher.sampleCounter], xmm2
; // Возвращаем значение семпла
mov eax, edx
ret
endp
; // Получить реальную частоту ресемплинга на основании значения downsampling
; // Вычисляем по формуле int(downsampling * samplerate * 0.5)
proc CalcDownsamplingFreq, sampleRate, downsampling
mov eax, 0.5
movd xmm0, eax
mulss xmm0, [downsampling]
mulss xmm0, [sampleRate]
cvtss2si eax, xmm0
ret
endp
; // Посчитать количество уровней сигнала на основании значения quantize
; // Вычисляем по формуле int(2 ^ (quantize * 15 + 1)))
proc CalcLevels, quantize
mov eax, 2
mov ecx, 15.0
movss xmm0, [quantize]
movd xmm1, ecx
mulss xmm0, xmm1
cvtss2si ecx, xmm0
shl eax, cl
ret
endp