flat assembler
Message board for the users of flat assembler.

Index > Projects and Ideas > VST with fasm

Author
Thread Post new topic Reply to topic
edfed



Joined: 20 Feb 2006
Posts: 4370
Location: Now
edfed 19 Aug 2025, 16:19
I saw a long time ago a code to make a basic vst with asm, but don't remember where, neither with what assembler.

even google don't remember, then, i want to make it with fasm, and then, be able to make really interresting and usefull stuff with fasm.

then, just imagine a DAW made with fasm, and with a set a VST also made with fasm, and all these with a bunch of libs made with fasm. it can be very interesting, and can let low power computers become powerfull synths.
Post 19 Aug 2025, 16:19
View user's profile Send private message Visit poster's website Reply with quote
macgub



Joined: 11 Jan 2006
Posts: 376
Location: Poland
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.


Description:
Filesize: 11.87 KB
Viewed: 5773 Time(s)

meos-synth4.png


Post 24 Aug 2025, 16:48
View user's profile Send private message Visit poster's website Reply with quote
edfed



Joined: 20 Feb 2006
Posts: 4370
Location: Now
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.
Post 13 Sep 2026, 22:52
View user's profile Send private message Visit poster's website Reply with quote
Display posts from previous:
Post new topic Reply to topic

Jump to:  


< Last Thread | Next Thread >
Forum Rules:
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum
You cannot attach files in this forum
You can download files in this forum


Copyright © 1999-2026, Tomasz Grysztar. Also on GitHub, YouTube.

Website powered by rwasa.