flat assembler
Message board for the users of flat assembler.
Index
> Projects and Ideas > VST with fasm |
| Author |
|
|
macgub 24 Aug 2025, 16:48
MenuetOS has MIDI application wihch allows basic instuments synth and possibility to set them into music song...
.. I attach screenshot, if I broke MeOS licence - let me know, I will delete it.
|
||||||||||
|
||||||||||
|
edfed 13 Sep 2026, 22:52
i am playing with grok about this subject, and claude also.
vst specs are not easy task. and DAW use them a lot, then it should be possible to make a hello world vst using fasm. for the moment, the basic vst i try using flstudio5 (yes it's old) to get is just a gain button. but it don't work. the verify part blocks. and som versions have the loading part that crashes fl. what is frustrating is that a long time ago, someone shared a asm basic vst that worked very fine, but i lost the source code. now all the work have to be made again from nothing. and dealing with vst specs is a mess (and i'm lasy) Code: ; ============================================================================= ; simple_vst.asm — Minimal VST2.4 gain plugin for FL Studio (x86 FASM) ; ; AEffect layout verified against VST SDK 2.4 aeffect.h (144 bytes, x86): ; [ 0] magic dd ; [ 4] dispatcher dd (proc ptr) ; [ 8] _process dd (deprecated, NULL) ; [ 12] setParameter dd (proc ptr) ; [ 16] getParameter dd (proc ptr) ; [ 20] numPrograms dd ; [ 24] numParams dd ; [ 28] numInputs dd ; [ 32] numOutputs dd ; [ 36] flags dd ; [ 40] reserved1 dd (must be 0) ; [ 44] reserved2 dd (must be 0) ; [ 48] initialDelay dd ; [ 52] _realQualities dd (deprecated, 0) ; [ 56] _offQualities dd (deprecated, 0) ; [ 60] _ioRatio dd (deprecated, 0) ; [ 64] object dd (back-ptr to MyPlugin) ; [ 68] user dd (host sets this, we leave 0) ; [ 72] uniqueID dd ; [ 76] version dd ; [ 80] processReplacing dd (proc ptr) ; [ 84] processDouble dd (NULL, we don't support F64) ; [ 88] future 56 bytes (all zero) ; -------------------- Total: 144 bytes ; ============================================================================= format PE GUI 4.0 DLL entry DllEntryPoint include 'win32a.inc' ; --------------------------------------------------------------------------- ; VST2 constants ; --------------------------------------------------------------------------- VST_MAGIC = 0x56537450 ; 'VstP' little-endian effOpen = 0 effClose = 1 effEditGetRect = 5 effEditOpen = 6 effEditClose = 7 effGetEffectName = 9 effMainsChanged = 12 effGetParamLabel = 44 effGetParamName = 45 effGetPlugCategory = 35 effGetVendorString = 47 effGetProductString = 48 effGetVstVersion = 58 effFlagsHasEditor = 1 ; bit 0 effFlagsCanReplacing = 16 ; bit 4 kPlugCategEffect = 2 ; --------------------------------------------------------------------------- ; AEffect struct — exact VST2.4 layout, 144 bytes on x86 ; Fields named _xxx are deprecated but must occupy their slot. ; --------------------------------------------------------------------------- struct AEffect magic dd ? ; +0 dispatcher dd ? ; +4 _process dd ? ; +8 deprecated, keep NULL setParameter dd ? ; +12 getParameter dd ? ; +16 numPrograms dd ? ; +20 numParams dd ? ; +24 numInputs dd ? ; +28 numOutputs dd ? ; +32 flags dd ? ; +36 reserved1 dd ? ; +40 must be 0 reserved2 dd ? ; +44 must be 0 initialDelay dd ? ; +48 _realQualities dd ? ; +52 deprecated, keep 0 _offQualities dd ? ; +56 deprecated, keep 0 _ioRatio dd ? ; +60 deprecated, keep 0 object dd ? ; +64 back-ptr to MyPlugin user dd ? ; +68 host-owned, leave 0 uniqueID dd ? ; +72 version dd ? ; +76 processReplacing dd ? ; +80 processDouble dd ? ; +84 NULL (no F64 support) future db 56 dup ? ; +88 must be 56 bytes = 144 total ends ; --------------------------------------------------------------------------- ; ERect — VST2 editor rectangle, fields are WORDs (16-bit), not DWORDs ; effEditGetRect receives ERect** — host passes pointer to its ERect* variable; ; we write our ERect* into that slot. ; --------------------------------------------------------------------------- struct ERect top dw ? left dw ? bottom dw ? right dw ? ends ; --------------------------------------------------------------------------- ; Private plugin state — AEffect must be the FIRST field (offset 0) so that ; &MyPlugin == &MyPlugin.effect and the host's AEffect* is also a MyPlugin*. ; --------------------------------------------------------------------------- struct MyPlugin effect AEffect gain dd ? ; float 0.0-1.0, default 1.0 hwnd dd ? ; editor child window ends ; --------------------------------------------------------------------------- ; Read-only / read-write data ; --------------------------------------------------------------------------- section '.data' data readable writeable gain_default dd 1.0 ; Static ERect for effEditGetRect — host reads through its own pointer plugin_rect ERect dw 0 ; top dw 0 ; left dw 200 ; bottom dw 320 ; right ; ANSI strings — use db, never TCHAR (which emits UTF-16 on Unicode builds) sz_class db 'STATIC', 0 sz_winname db 'Simple Gain', 0 sz_effectname db 'Simple Gain', 0 sz_vendor db 'MyVendor', 0 sz_product db 'GainVU', 0 sz_param_gain db 'Gain', 0 sz_label_gain db '%', 0 ; param unit label ; --------------------------------------------------------------------------- ; Code ; --------------------------------------------------------------------------- section '.text' code readable executable ; --- DLL entry: nothing to do --- proc DllEntryPoint hinstDLL, fdwReason, lpvReserved mov eax, TRUE ret endp ; =========================================================================== ; VSTPluginMain ; Exported entry called by FL Studio to create a plugin instance. ; Returns: eax = AEffect* (0 on failure) ; =========================================================================== proc VSTPluginMain invoke GlobalAlloc, GMEM_ZEROINIT, sizeof.MyPlugin test eax, eax jz .fail mov ebx, eax ; ebx = MyPlugin* ; --- Proc pointers --- mov [ebx + MyPlugin.effect.magic], VST_MAGIC mov [ebx + MyPlugin.effect.dispatcher], dispatcher mov [ebx + MyPlugin.effect._process], 0 mov [ebx + MyPlugin.effect.setParameter], setParameter mov [ebx + MyPlugin.effect.getParameter], getParameter mov [ebx + MyPlugin.effect.processReplacing], processReplacing mov [ebx + MyPlugin.effect.processDouble], 0 ; --- Counts / meta --- mov [ebx + MyPlugin.effect.numPrograms], 1 mov [ebx + MyPlugin.effect.numParams], 1 mov [ebx + MyPlugin.effect.numInputs], 2 mov [ebx + MyPlugin.effect.numOutputs], 2 mov [ebx + MyPlugin.effect.flags], effFlagsHasEditor + effFlagsCanReplacing mov [ebx + MyPlugin.effect.uniqueID], 'smpl' mov [ebx + MyPlugin.effect.version], 1000 ; --- Back-pointer (object field at +64) --- mov [ebx + MyPlugin.effect.object], ebx ; --- Default gain = 1.0 --- mov eax, [gain_default] mov [ebx + MyPlugin.gain], eax ; Return AEffect* (== MyPlugin* because effect is first at offset 0) lea eax, [ebx + MyPlugin.effect] ret .fail: xor eax, eax ret endp ; =========================================================================== ; dispatcher ; Called by the host for all non-audio operations. ; Signature (cdecl): ; VstIntPtr dispatcher(AEffect*, VstInt32 opcode, VstInt32 index, ; VstIntPtr value, void* ptr, float opt) ; =========================================================================== proc dispatcher effect, opcode, index, value, ptr, opt push ebx esi edi ; AEffect* is at offset 0 of MyPlugin, so cast is free mov ebx, [effect] ; ebx = MyPlugin* mov eax, [opcode] cmp eax, effOpen ; 0 je .ret1 cmp eax, effClose ; 1 je .close cmp eax, effEditGetRect ; 5 je .getrect cmp eax, effEditOpen ; 6 je .editopen cmp eax, effEditClose ; 7 je .editclose cmp eax, effGetEffectName ; 9 je .effectname cmp eax, effMainsChanged ; 12 je .ret1 cmp eax, effGetPlugCategory ; 35 je .category cmp eax, effGetParamLabel ; 44 je .paramlabel cmp eax, effGetParamName ; 45 je .paramname cmp eax, effGetVendorString ; 47 je .vendor cmp eax, effGetProductString; 48 je .product cmp eax, effGetVstVersion ; 58 je .vstversion xor eax, eax jmp .done ; --- effOpen / effMainsChanged: just return 1 --- .ret1: mov eax, 1 jmp .done ; --- effClose: free plugin memory --- .close: invoke GlobalFree, ebx xor eax, eax jmp .done ; --- effEditGetRect --- ; ptr is ERect** — host passes the address of its own ERect* variable. ; We write the address of our static plugin_rect into *ptr. .getrect: mov eax, [ptr] ; eax = ERect** test eax, eax jz .getrect_fail mov dword [eax], plugin_rect ; *ptr = &plugin_rect mov eax, 1 jmp .done .getrect_fail: xor eax, eax jmp .done ; --- effEditOpen --- ; ptr (void*) holds the parent HWND — NOT value. .editopen: mov eax, [ptr] ; parent HWND invoke CreateWindowEx, 0, sz_class, sz_winname, \ WS_CHILD or WS_VISIBLE, \ 0, 0, 320, 200, \ eax, 0, 0, 0 mov [ebx + MyPlugin.hwnd], eax mov eax, 1 jmp .done ; --- effEditClose --- .editclose: invoke DestroyWindow, [ebx + MyPlugin.hwnd] mov dword [ebx + MyPlugin.hwnd], 0 mov eax, 1 jmp .done ; --- String responses: copy bytes into host's 64-byte buffer (ptr) --- ; Each opcode gets its own source pointer. .effectname: lea esi, [sz_effectname] jmp .copystr .vendor: lea esi, [sz_vendor] jmp .copystr .product: lea esi, [sz_product] jmp .copystr .paramname: lea esi, [sz_param_gain] jmp .copystr .paramlabel: lea esi, [sz_label_gain] ; fall through to .copystr .copystr: mov edi, [ptr] ; destination buffer (host-allocated, >= 64 bytes) test edi, edi jz .copystr_fail .copystr_loop: mov al, [esi] mov [edi], al inc esi inc edi test al, al jnz .copystr_loop mov eax, 1 jmp .done .copystr_fail: xor eax, eax jmp .done ; --- effGetPlugCategory: 2 = kPlugCategEffect --- .category: mov eax, kPlugCategEffect jmp .done ; --- effGetVstVersion: report VST 2.4 --- .vstversion: mov eax, 2400 jmp .done .done: pop edi esi ebx ret endp ; =========================================================================== ; processReplacing ; Called per audio block. inputs/outputs are float** (array of channel ptrs). ; ; inputs[0] -> float* left input buffer ; inputs[1] -> float* right input buffer ; outputs[0] -> float* left output buffer ; outputs[1] -> float* right output buffer ; =========================================================================== proc processReplacing effect, inputs, outputs, sampleframes push ebx esi edi ebp mov ebx, [effect] movss xmm2, [ebx + MyPlugin.gain] ; gain scalar into xmm2 ; --- Left channel --- mov eax, [inputs] mov esi, [eax] ; esi = inputs[0] (left float*) mov eax, [outputs] mov edi, [eax] ; edi = outputs[0] (left float*) mov ecx, [sampleframes] test ecx, ecx jz .right .left_loop: movss xmm0, [esi] mulss xmm0, xmm2 movss [edi], xmm0 add esi, 4 add edi, 4 dec ecx jnz .left_loop ; --- Right channel --- .right: mov eax, [inputs] mov esi, [eax + 4] ; esi = inputs[1] (right float*) mov eax, [outputs] mov edi, [eax + 4] ; edi = outputs[1] (right float*) mov ecx, [sampleframes] test ecx, ecx jz .done .right_loop: movss xmm0, [esi] mulss xmm0, xmm2 movss [edi], xmm0 add esi, 4 add edi, 4 dec ecx jnz .right_loop .done: pop ebp edi esi ebx ret endp ; =========================================================================== ; setParameter (AEffect*, VstInt32 index, float value) ; float is passed as a raw 4-byte stack value in MSVC cdecl. ; =========================================================================== proc setParameter effect, index, value mov ebx, [effect] cmp dword [index], 0 jne .done mov eax, [value] ; reinterpret float bits mov [ebx + MyPlugin.gain], eax .done: ret endp ; =========================================================================== ; getParameter (AEffect*, VstInt32 index) -> float in ST(0) ; =========================================================================== proc getParameter effect, index mov ebx, [effect] cmp dword [index], 0 jne .zero fld dword [ebx + MyPlugin.gain] ret .zero: fldz ret endp ; --------------------------------------------------------------------------- ; Imports ; --------------------------------------------------------------------------- section '.idata' import data readable writeable library kernel, 'KERNEL32.DLL', \ user, 'USER32.DLL' import kernel, \ GlobalAlloc, 'GlobalAlloc', \ GlobalFree, 'GlobalFree' import user, \ CreateWindowEx, 'CreateWindowExA', \ DestroyWindow, 'DestroyWindow' ; --------------------------------------------------------------------------- ; Exports — FL Studio looks for VSTPluginMain; export 'main' as alias too ; --------------------------------------------------------------------------- section '.edata' export data readable export 'simple_vst.dll', \ VSTPluginMain, 'VSTPluginMain', \ VSTPluginMain, 'main' there, the vst doesn't initialize, load or register. words that means nothing for me. a basic hello world vst is just a wire, with an input and an output. |
|||
|
< Last Thread | Next Thread > |
Forum Rules:
|
Copyright © 1999-2026, Tomasz Grysztar. Also on GitHub, YouTube.
Website powered by rwasa.