Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Dynamic Libraries (Windows)
#1
Hello everyone.

The following dynamic libraries are written in collaboration with AI.

I solved some things, but unfortunately only for Windows users. Yes, I was doing research on the laboriousness of Linux... I ran away. Fast. Very fast.

First, one thing that started to crash for some unknown reason - InputBox. The ZIP file contains the source code in C, the BAS program, compiled DLLs for IDE 32bit and IDE64bit.


So for users who want a different inputbox than the one available in QB64PE, there is this option.

32-bit version compiled by QB64PE compiler 4.0.0, 64-bit version compiled by QB64PE compiler 4.1.0

Code: (Select All)
// qb_inputbox_ex.c  (PURE C, build as DLL x86/x64 to match QB64PE)
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <commdlg.h>
#include <wchar.h>
#include <wctype.h>
#include <stdlib.h>
#include <strsafe.h>

#define IDC_PROMPT  1001
#define IDC_EDIT    1002
#define IDC_CHECK  1003
#define IDC_BROWSE  1004

// Flags
#define IB_PASSWORD    0x0001
#define IB_MULTILINE  0x0002
#define IB_NUMBERS    0x0004
#define IB_ALLOWEMPTY  0x0008
#define IB_CHECKBOX    0x0010
#define IB_BROWSE      0x0020

// Built-in UI text (English)
#define CAPTION_BOX        L"InputBox"
#define BTN_OK_TEXT        L"OK"
#define BTN_CANCEL_TEXT    L"Cancel"
#define BTN_BROWSE_TEXT    L"Browse..."
#define DEFAULT_CHECK_TEXT L"Remember"
#define OFN_TITLE_TEXT    L"Select a file"
#define OFN_FILTER_TEXT    L"All files (*.*)\0*.*\0\0"

typedef struct INPUTBOX_CTX {
    const wchar_t* title;
    const wchar_t* prompt;
    const wchar_t* defText;

    wchar_t* outBuf;
    int outBufChars;

    int flags;
    int minLen;
    int maxLen;
    double minVal;
    double maxVal;

    const wchar_t* checkText;
    int checkDefault;
    int* checkValueOut;

    WNDPROC oldEditProc;
} INPUTBOX_CTX;

static BYTE* AlignDword(BYTE* p) {
    ULONG_PTR u = (ULONG_PTR)p;
    u = (u + 3) & ~((ULONG_PTR)3);
    return (BYTE*)u;
}

static void WriteWideString(WORD** pp, const wchar_t* s) {
    while (*s) { **pp = (WORD)*s; (*pp)++; s++; }
    **pp = 0; (*pp)++;
}

static void ReplaceCommaWithDot(wchar_t* s) {
    for (; *s; ++s) if (*s == L',') *s = L'.';
}

static void UpdateOkButton(HWND hDlg) {
    INPUTBOX_CTX* ctx = (INPUTBOX_CTX*)GetWindowLongPtrW(hDlg, DWLP_USER);
    if (!ctx) return;

    wchar_t tmp[4096];
    GetDlgItemTextW(hDlg, IDC_EDIT, tmp, (int)(sizeof(tmp)/sizeof(tmp[0])));
    int len = (int)wcslen(tmp);

    int okEnabled = 1;

    if (!(ctx->flags & IB_ALLOWEMPTY)) {
        int need = (ctx->minLen > 0) ? ctx->minLen : 1;
        if (len < need) okEnabled = 0;
    } else {
        if (ctx->minLen > 0 && len < ctx->minLen) okEnabled = 0;
    }

    EnableWindow(GetDlgItem(hDlg, IDOK), okEnabled ? TRUE : FALSE);
}

static int ValidateOnOk(HWND hDlg, INPUTBOX_CTX* ctx) {
    wchar_t tmp[4096];
    GetDlgItemTextW(hDlg, IDC_EDIT, tmp, (int)(sizeof(tmp)/sizeof(tmp[0])));
    int len = (int)wcslen(tmp);

    if (!(ctx->flags & IB_ALLOWEMPTY)) {
        if (len == 0) {
            MessageBoxW(hDlg, L"Please enter a value (cannot be empty).", CAPTION_BOX, MB_ICONWARNING);
            SetFocus(GetDlgItem(hDlg, IDC_EDIT));
            return 0;
        }
    }

    if (ctx->minLen > 0 && len < ctx->minLen) {
        wchar_t msg[256];
        wsprintfW(msg, L"Text is too short. Minimum: %d characters.", ctx->minLen);
        MessageBoxW(hDlg, msg, CAPTION_BOX, MB_ICONWARNING);
        SetFocus(GetDlgItem(hDlg, IDC_EDIT));
        return 0;
    }

    if (ctx->maxLen > 0 && len > ctx->maxLen) {
        wchar_t msg[256];
        wsprintfW(msg, L"Text is too long. Maximum: %d characters.", ctx->maxLen);
        MessageBoxW(hDlg, msg, CAPTION_BOX, MB_ICONWARNING);
        SetFocus(GetDlgItem(hDlg, IDC_EDIT));
        return 0;
    }

    // numeric validation only for single-line
    if ((ctx->flags & IB_NUMBERS) && !(ctx->flags & IB_MULTILINE)) {
        wchar_t numBuf[4096];
        wcsncpy(numBuf, tmp, (int)(sizeof(numBuf)/sizeof(numBuf[0])) - 1);
        numBuf[(sizeof(numBuf)/sizeof(numBuf[0])) - 1] = 0;
        ReplaceCommaWithDot(numBuf);

        wchar_t* p = numBuf;
        while (*p && iswspace(*p)) p++;

        wchar_t* end = NULL;
        double v = wcstod(p, &end);
        if (end == p) {
            MessageBoxW(hDlg, L"Invalid number.", CAPTION_BOX, MB_ICONWARNING);
            SetFocus(GetDlgItem(hDlg, IDC_EDIT));
            return 0;
        }
        while (*end && iswspace(*end)) end++;
        if (*end != 0) {
            MessageBoxW(hDlg, L"Invalid number (contains unsupported characters).", CAPTION_BOX, MB_ICONWARNING);
            SetFocus(GetDlgItem(hDlg, IDC_EDIT));
            return 0;
        }

        if (ctx->maxVal > ctx->minVal) {
            if (v < ctx->minVal || v > ctx->maxVal) {
                wchar_t msg[256];
                // wsprintfW(msg, L"Number must be in the range %.12g to %.12g.", ctx->minVal, ctx->maxVal);
                StringCchPrintfW(msg, 256, L"Number must be in the range %.12g to %.12g.", ctx->minVal, ctx->maxVal);
                MessageBoxW(hDlg, msg, CAPTION_BOX, MB_ICONWARNING);
                SetFocus(GetDlgItem(hDlg, IDC_EDIT));
                return 0;
            }
        }
    }

    return 1;
}

static int DoBrowseFile(HWND hDlg) {
    wchar_t fileBuf[4096] = {0};

    OPENFILENAMEW ofn;
    ZeroMemory(&ofn, sizeof(ofn));
    ofn.lStructSize = sizeof(ofn);
    ofn.hwndOwner = hDlg;
    ofn.lpstrFile = fileBuf;
    ofn.nMaxFile = (DWORD)(sizeof(fileBuf)/sizeof(fileBuf[0]));
    ofn.lpstrFilter = OFN_FILTER_TEXT;
    ofn.nFilterIndex = 1;
    ofn.lpstrTitle = OFN_TITLE_TEXT;
    ofn.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY;

    if (GetOpenFileNameW(&ofn)) {
        SetDlgItemTextW(hDlg, IDC_EDIT, fileBuf);
        return 1;
    }
    return 0;
}

static LRESULT CALLBACK EditSubclassProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    INPUTBOX_CTX* ctx = (INPUTBOX_CTX*)GetWindowLongPtrW(hWnd, GWLP_USERDATA);
    if (!ctx || !ctx->oldEditProc) return DefWindowProcW(hWnd, msg, wParam, lParam);

    if ((ctx->flags & IB_NUMBERS) && !(ctx->flags & IB_MULTILINE)) {
        if (msg == WM_CHAR) {
            wchar_t ch = (wchar_t)wParam;

            if (ch < 32) return CallWindowProcW(ctx->oldEditProc, hWnd, msg, wParam, lParam);

            if (ch >= L'0' && ch <= L'9')
                return CallWindowProcW(ctx->oldEditProc, hWnd, msg, wParam, lParam);

            if (ch == L'.' || ch == L',') {
                wchar_t t[4096];
                GetWindowTextW(hWnd, t, (int)(sizeof(t)/sizeof(t[0])));
                if (wcschr(t, L'.') || wcschr(t, L',')) return 0;
                return CallWindowProcW(ctx->oldEditProc, hWnd, msg, wParam, lParam);
            }

            if (ch == L'-') {
                wchar_t t[4096];
                GetWindowTextW(hWnd, t, (int)(sizeof(t)/sizeof(t[0])));
                if (wcschr(t, L'-')) return 0;

                DWORD selStart = 0, selEnd = 0;
                SendMessageW(hWnd, EM_GETSEL, (WPARAM)&selStart, (LPARAM)&selEnd);
                if (selStart != 0) return 0;

                return CallWindowProcW(ctx->oldEditProc, hWnd, msg, wParam, lParam);
            }

            return 0;
        }
    }

    return CallWindowProcW(ctx->oldEditProc, hWnd, msg, wParam, lParam);
}

static void AddItem(BYTE** pp, DWORD style, DWORD exStyle,
                    short x, short y, short cx, short cy,
                    WORD id, WORD classAtom, const wchar_t* title)
{
    BYTE* p = AlignDword(*pp);
    DLGITEMTEMPLATE* it = (DLGITEMTEMPLATE*)p;
    it->style = style;
    it->dwExtendedStyle = exStyle;
    it->x = x; it->y = y; it->cx = cx; it->cy = cy;
    it->id = id;
    p += sizeof(DLGITEMTEMPLATE);

    WORD* pw = (WORD*)p;
    *pw++ = 0xFFFF;
    *pw++ = classAtom;

    if (title && *title) WriteWideString(&pw, title);
    else *pw++ = 0;

    *pw++ = 0;
    *pp = (BYTE*)pw;
}

static INT_PTR CALLBACK InputDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
    INPUTBOX_CTX* ctx;

    if (uMsg == WM_INITDIALOG) {
        ctx = (INPUTBOX_CTX*)lParam;
        SetWindowLongPtrW(hDlg, DWLP_USER, (LONG_PTR)ctx);

        if (ctx->title)  SetWindowTextW(hDlg, ctx->title);
        if (ctx->prompt) SetDlgItemTextW(hDlg, IDC_PROMPT, ctx->prompt);
        if (ctx->defText) SetDlgItemTextW(hDlg, IDC_EDIT, ctx->defText);

        if ((ctx->flags & IB_CHECKBOX) && ctx->checkDefault)
            CheckDlgButton(hDlg, IDC_CHECK, BST_CHECKED);

        {
            int limit = 0;
            if (ctx->maxLen > 0) limit = ctx->maxLen;
            else limit = ctx->outBufChars - 1;
            if (limit < 0) limit = 0;
            if (limit > ctx->outBufChars - 1) limit = ctx->outBufChars - 1;
            SendDlgItemMessageW(hDlg, IDC_EDIT, EM_LIMITTEXT, (WPARAM)limit, 0);
        }

        {
            HWND hEdit = GetDlgItem(hDlg, IDC_EDIT);
            SetWindowLongPtrW(hEdit, GWLP_USERDATA, (LONG_PTR)ctx);
            ctx->oldEditProc = (WNDPROC)SetWindowLongPtrW(hEdit, GWLP_WNDPROC, (LONG_PTR)EditSubclassProc);
        }

        SendDlgItemMessageW(hDlg, IDC_EDIT, EM_SETSEL, 0, -1);
        UpdateOkButton(hDlg);
        SetFocus(GetDlgItem(hDlg, IDC_EDIT));
        return FALSE;
    }

    ctx = (INPUTBOX_CTX*)GetWindowLongPtrW(hDlg, DWLP_USER);

    switch (uMsg) {
    case WM_COMMAND: {
        WORD id = LOWORD(wParam);
        WORD code = HIWORD(wParam);

        if (id == IDC_EDIT && code == EN_CHANGE) {
            UpdateOkButton(hDlg);
            return TRUE;
        }

        if (id == IDC_BROWSE) {
            if (DoBrowseFile(hDlg)) UpdateOkButton(hDlg);
            return TRUE;
        }

        if (id == IDOK) {
            if (!ctx) { EndDialog(hDlg, IDCANCEL); return TRUE; }

            if (!ValidateOnOk(hDlg, ctx)) return TRUE;

            if (ctx->outBuf && ctx->outBufChars > 0) {
                GetDlgItemTextW(hDlg, IDC_EDIT, ctx->outBuf, ctx->outBufChars);
                ctx->outBuf[ctx->outBufChars - 1] = 0;
            }
            if ((ctx->flags & IB_CHECKBOX) && ctx->checkValueOut) {
                *ctx->checkValueOut = (IsDlgButtonChecked(hDlg, IDC_CHECK) == BST_CHECKED) ? 1 : 0;
            }
            EndDialog(hDlg, IDOK);
            return TRUE;
        }

        if (id == IDCANCEL) {
            if (ctx && (ctx->flags & IB_CHECKBOX) && ctx->checkValueOut) {
                *ctx->checkValueOut = (IsDlgButtonChecked(hDlg, IDC_CHECK) == BST_CHECKED) ? 1 : 0;
            }
            EndDialog(hDlg, IDCANCEL);
            return TRUE;
        }
        break;
    }
    case WM_CLOSE:
        EndDialog(hDlg, IDCANCEL);
        return TRUE;
    }
    return FALSE;
}

static int InputBoxExW(INPUTBOX_CTX* ctx, HWND parent) {
    if (!ctx || !ctx->outBuf || ctx->outBufChars <= 0) return 0;
    ctx->outBuf[0] = 0;

    int multiline = (ctx->flags & IB_MULTILINE) ? 1 : 0;
    int browse = ((ctx->flags & IB_BROWSE) && !multiline) ? 1 : 0;
    int checkbox = (ctx->flags & IB_CHECKBOX) ? 1 : 0;

    short dlgCx = 240;
    short margin = 7;
    short promptY = 7, promptH = 12;

    short editY = 22;
    short editH = multiline ? 60 : 14;

    short belowEditY = (short)(editY + editH + 6);
    short checkY = belowEditY;
    short buttonsY = checkbox ? (short)(checkY + 16) : belowEditY;

    short dlgCy = (short)(buttonsY + 22);

    short btnW = 50, btnH = 14, gap = 6;
    short btnCancelX = (short)(dlgCx - margin - btnW);
    short btnOkX = (short)(btnCancelX - gap - btnW);

    short editX = margin;
    short browseW = 50;
    short browseX = (short)(dlgCx - margin - browseW);
    short editW = (short)(dlgCx - margin - editX - (browse ? (browseW + gap) : 0));

    int cdit = 4 + (checkbox ? 1 : 0) + (browse ? 1 : 0);

    HGLOBAL hMem = GlobalAlloc(GPTR, 8192);
    if (!hMem) return 0;
    BYTE* mem = (BYTE*)hMem;
    BYTE* p = mem;

    DLGTEMPLATE* dlg = (DLGTEMPLATE*)p;
    dlg->style = WS_POPUP | WS_CAPTION | WS_SYSMENU | DS_MODALFRAME | DS_SETFONT | DS_CENTER;
    dlg->dwExtendedStyle = 0;
    dlg->cdit = (WORD)cdit;
    dlg->x = 10; dlg->y = 10; dlg->cx = dlgCx; dlg->cy = dlgCy;
    p += sizeof(DLGTEMPLATE);

    WORD* pw = (WORD*)p;
    *pw++ = 0;
    *pw++ = 0;
    *pw++ = 0;
    *pw++ = 9;
    WriteWideString(&pw, L"MS Shell Dlg");
    p = (BYTE*)pw;

    AddItem(&p, WS_CHILD | WS_VISIBLE | SS_LEFT, 0,
            margin, promptY, (short)(dlgCx - 2*margin), promptH,
            IDC_PROMPT, 0x0082, L"");

    DWORD editStyle = WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL;
    DWORD editEx = WS_EX_CLIENTEDGE;

    if (multiline)
        editStyle = WS_CHILD | WS_VISIBLE | WS_BORDER | ES_LEFT | ES_MULTILINE | ES_WANTRETURN | WS_VSCROLL;

    if (ctx->flags & IB_PASSWORD)
        editStyle |= ES_PASSWORD;

    AddItem(&p, editStyle, editEx,
            editX, editY, editW, editH,
            IDC_EDIT, 0x0081, L"");

    if (browse) {
        AddItem(&p, WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 0,
                browseX, editY, browseW, 14,
                IDC_BROWSE, 0x0080, BTN_BROWSE_TEXT);
    }

    if (checkbox) {
        AddItem(&p, WS_CHILD | WS_VISIBLE | BS_AUTOCHECKBOX, 0,
                margin, checkY, (short)(dlgCx - 2*margin), 12,
                IDC_CHECK, 0x0080,
                (ctx->checkText && *ctx->checkText) ? ctx->checkText : DEFAULT_CHECK_TEXT);
    }

    AddItem(&p, WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON, 0,
            btnOkX, buttonsY, btnW, btnH,
            IDOK, 0x0080, BTN_OK_TEXT);

    AddItem(&p, WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 0,
            btnCancelX, buttonsY, btnW, btnH,
            IDCANCEL, 0x0080, BTN_CANCEL_TEXT);

    INT_PTR rr = DialogBoxIndirectParamW(GetModuleHandleW(NULL), (LPCDLGTEMPLATEW)mem,
                                        parent, InputDlgProc, (LPARAM)ctx);

    GlobalFree(hMem);
    return (rr == IDOK) ? 1 : 0;
}

// ACP <-> UTF-16
static int ToWideACP(const char* s, wchar_t* out, int outChars) {
    if (!out || outChars <= 0) return 0;
    out[0] = 0;
    if (!s) return 0;
    {
        int n = MultiByteToWideChar(CP_ACP, 0, s, -1, out, outChars);
        if (n <= 0) out[0] = 0;
        return n;
    }
}
static int FromWideACP(const wchar_t* s, char* out, int outBytes) {
    if (!out || outBytes <= 0) return 0;
    out[0] = 0;
    if (!s) return 0;
    {
        int n = WideCharToMultiByte(CP_ACP, 0, s, -1, out, outBytes, NULL, NULL);
        if (n <= 0) out[0] = 0;
        return n;
    }
}

// Export
__declspec(dllexport) int __stdcall QB_InputBoxExA(
    const char* title,
    const char* prompt,
    const char* defText,
    char* outBuf, int outBufBytes,
    int flags,
    int minLen, int maxLen,
    double minVal, double maxVal,
    const char* checkboxText,
    int checkboxDefault,
    int* checkboxValueOut
) {
    if (!outBuf || outBufBytes <= 0) return 0;
    outBuf[0] = 0;

    wchar_t wTitle[256], wPrompt[1024], wDef[2048], wCheck[256];
    wchar_t wOut[4096] = {0};

    ToWideACP(title, wTitle, (int)(sizeof(wTitle)/sizeof(wTitle[0])));
    ToWideACP(prompt, wPrompt, (int)(sizeof(wPrompt)/sizeof(wPrompt[0])));
    ToWideACP(defText, wDef, (int)(sizeof(wDef)/sizeof(wDef[0])));
    ToWideACP(checkboxText, wCheck, (int)(sizeof(wCheck)/sizeof(wCheck[0])));

    INPUTBOX_CTX ctx;
    ZeroMemory(&ctx, sizeof(ctx));
    ctx.title = wTitle;
    ctx.prompt = wPrompt;
    ctx.defText = wDef;
    ctx.outBuf = wOut;
    ctx.outBufChars = (int)(sizeof(wOut)/sizeof(wOut[0]));
    ctx.flags = flags;
    ctx.minLen = minLen;
    ctx.maxLen = maxLen;
    ctx.minVal = minVal;
    ctx.maxVal = maxVal;
    ctx.checkText = (wCheck[0] ? wCheck : NULL);
    ctx.checkDefault = checkboxDefault ? 1 : 0;
    ctx.checkValueOut = checkboxValueOut;

    {
        HWND parent = GetForegroundWindow();
        int ok = InputBoxExW(&ctx, parent);
        if (!ok) { outBuf[0] = 0; return 0; }
    }

    FromWideACP(wOut, outBuf, outBufBytes);
    outBuf[outBufBytes - 1] = 0;
    return 1;
}
Code: (Select All)

' ===== flags =====
Const IB_PASSWORD = &H1
Const IB_MULTILINE = &H2
Const IB_NUMBERS = &H4
Const IB_ALLOWEMPTY = &H8
Const IB_CHECKBOX = &H10
Const IB_BROWSE = &H20

Declare Dynamic Library "qb_inputbox_2en"
    FUNCTION QB_InputBoxExA% (BYVAL title AS _OFFSET, BYVAL prompt AS _OFFSET, BYVAL defText AS _OFFSET, _
                              BYVAL outBuf AS _OFFSET, BYVAL outBufBytes AS LONG, _
                              BYVAL flags AS LONG, BYVAL minLen AS LONG, BYVAL maxLen AS LONG, _
                              BYVAL minVal AS DOUBLE, BYVAL maxVal AS DOUBLE, _
                              BYVAL cbText AS _OFFSET, BYVAL cbDefault AS LONG, _
                              BYVAL cbValueOut AS _OFFSET)
End Declare



' ===== demo =====
Dim ok As Long, chk As Long
Dim flags As Long

flags = IB_PASSWORD ' Or IB_CHECKBOX Or IB_BROWSE
a$ = WinInputBoxEx$("Password", "Input password", "", flags, 1, 300, 0#, 0#, "", 1, chk, ok)
Print "ok="; ok; " chk="; chk; " text=["; a$; "]"

flags = IB_NUMBERS
b$ = WinInputBoxEx$("Number", "Input number in range -10 to 10:", "0", flags, 1, 20, -10#, 10#, "", 0, chk, ok)
Print "ok="; ok; " text=["; b$; "]"


flags = IB_MULTILINE ' Or IB_CHECKBOX Or IB_BROWSE
a$ = WinInputBoxEx$("Multiline", "Input text", "", flags, 1, 300, 0#, 0#, "", 1, chk, ok)
Print "ok="; ok; " chk="; chk; " text=["; a$; "]"


flags = IB_ALLOWEMPTY ' Or IB_CHECKBOX Or IB_BROWSE
a$ = WinInputBoxEx$("Allow Empty", "Input text", "", flags, 1, 300, 0#, 0#, "", 1, chk, ok)
Print "ok="; ok; " chk="; chk; " text=["; a$; "]"

flags = IB_CHECKBOX ' Or IB_CHECKBOX Or IB_BROWSE
a$ = WinInputBoxEx$("Checkbox", "Input text", "", flags, 1, 300, 0#, 0#, "Memorize", 1, chk, ok)
Print "ok="; ok; " chk="; chk; " text=["; a$; "]"

flags = IB_BROWSE ' Or IB_CHECKBOX Or IB_BROWSE
a$ = WinInputBoxEx$("Browse", "Select file", "", flags, 1, 300, 0#, 0#, "", 1, chk, ok)
Print "ok="; ok; " chk="; chk; " text=["; a$; "]"


Sleep







Function WinInputBoxEx$ (title$, prompt$, def$, flags&, minLen&, maxLen&, minVal#, maxVal#, cbText$, cbDefault&, cbValue&, ok&)
    Dim t As String * 256
    Dim p As String * 1024
    Dim d As String * 2048
    Dim c As String * 256
    Dim outs As String * 4096

    t = Left$(title$, Len(t) - 1) + Chr$(0)
    p = Left$(prompt$, Len(p) - 1) + Chr$(0)
    d = Left$(def$, Len(d) - 1) + Chr$(0)
    c = Left$(cbText$, Len(c) - 1) + Chr$(0)

    outs = Chr$(0) + Space$(Len(outs) - 1)

    cbValue& = 0
    ok& = QB_InputBoxExA(_OFFSET(t), _OFFSET(p), _OFFSET(d), _
                        _OFFSET(outs), LEN(outs), _
                        flags&, minLen&, maxLen&, minVal#, maxVal#, _
                        _OFFSET(c), cbDefault&, _OFFSET(cbValue&))

    If ok& Then
        WinInputBoxEx$ = Left$(outs, InStr(outs, Chr$(0)) - 1)
    Else
        WinInputBoxEx$ = ""
    End If
End Function



Attached Files
.zip   INPUTBOX2.ZIP (Size: 71.21 KB / Downloads: 26)


Reply
#2
Extended _ScreenImage (declared with existing dynamic libraries in BAS source)

  The built-in _ScreenImage returns the image of the primary monitor only.

   This version returns the image of the primary monitor, or the secondary monitor, or both.


Code: (Select All)

Option _Explicit


'      API types

Type RECT
    left As Long
    top As Long
    right As Long
    bottom As Long
End Type

Type POINTAPI
    x As Long
    y As Long
End Type

Type MONITORINFOEX
    cbSize As _Unsigned Long
    rcMonitor As RECT
    rcWork As RECT
    dwFlags As _Unsigned Long
    szDevice As String * 32
End Type

Type BITMAPINFOHEADER
    biSize As _Unsigned Long
    biWidth As Long
    biHeight As Long
    biPlanes As _Unsigned Integer
    biBitCount As _Unsigned Integer
    biCompression As _Unsigned Long
    biSizeImage As _Unsigned Long
    biXPelsPerMeter As Long
    biYPelsPerMeter As Long
    biClrUsed As _Unsigned Long
    biClrImportant As _Unsigned Long
End Type

Type BITMAPINFO
    bmiHeader As BITMAPINFOHEADER
    bmiColors0 As _Unsigned Long
End Type


'      API declaring

Declare Dynamic Library "user32"
    Function GetDC%& (ByVal hwnd As _Offset)
    Function ReleaseDC& (ByVal hwnd As _Offset, ByVal hdc As _Offset)
    Function GetSystemMetrics& (ByVal nIndex As Long)
    Function GetCursorPos (lpPoint As POINTAPI)
    Function MonitorFromRect%& (lprc As RECT, ByVal dwFlags As _Unsigned Long)
    Function GetMonitorInfoA& (ByVal hMon As _Offset, lpmi As MONITORINFOEX)
End Declare

Declare Dynamic Library "gdi32"
    Function CreateCompatibleDC%& (ByVal hdc As _Offset)
    Function DeleteDC& (ByVal hdc As _Offset)
    Function CreateCompatibleBitmap%& (ByVal hdc As _Offset, ByVal nWidth As Long, ByVal nHeight As Long)
    Function SelectObject%& (ByVal hdc As _Offset, ByVal hObject As _Offset)
    Function DeleteObject& (ByVal hObject As _Offset)
    Function BitBlt& (ByVal hDestDC As _Offset, ByVal x As Long, ByVal y As Long, ByVal nWidth As Long, ByVal nHeight As Long, ByVal hSrcDC As _Offset, ByVal xSrc As Long, ByVal ySrc As Long, ByVal dwRop As _Unsigned Long)
    Function GetDIBits& (ByVal hdc As _Offset, ByVal hbmp As _Offset, ByVal uStartScan As _Unsigned Long, ByVal cScanLines As _Unsigned Long, ByVal lpvBits As _Offset, bmi As BITMAPINFO, ByVal uUsage As _Unsigned Long)
End Declare

'--------------------------------------------

' 1 = primary monitor
' 2 = secondary monitor
' 3 = both (virtual desktop)
' ESC = end
'--------------------------------------------

Screen _NewImage(1280, 720, 32)
_Title "Extended _ScreenImage! Press 1 for primary monitor, 2 for secondary monitor, 3 for both!"

Dim shot&: shot& = 0

Do
    _Limit 60

    Dim k$: k$ = InKey$
    If k$ <> "" Then
        Select Case k$
            Case Chr$(27)
                Exit Do

            Case "1"
                If shot& Then _FreeImage shot&
                shot& = CapturePrimary32&

            Case "2"
                If shot& Then _FreeImage shot&
                shot& = CaptureMonitorIndex32&(2)

            Case "3"
                If shot& Then _FreeImage shot&
                shot& = CaptureAll32&
        End Select

        Cls
        If shot& Then
            _PutImage (0, 0)-(_Width(0) - 1, _Height(0) - 1), shot&
        Else
            _PrintString (10, 10), "Capture selhalo (shot&=0)."
        End If
        _Display
    End If
Loop

If shot& Then _FreeImage shot&
End








'  Both monitors at once (virtual desktop)

Function CaptureAll32& ()
    Const SM_XVIRTUALSCREEN = 76
    Const SM_YVIRTUALSCREEN = 77
    Const SM_CXVIRTUALSCREEN = 78
    Const SM_CYVIRTUALSCREEN = 79

    Dim x As Long, y As Long, w As Long, h As Long
    x = GetSystemMetrics(SM_XVIRTUALSCREEN)
    y = GetSystemMetrics(SM_YVIRTUALSCREEN)
    w = GetSystemMetrics(SM_CXVIRTUALSCREEN)
    h = GetSystemMetrics(SM_CYVIRTUALSCREEN)

    CaptureAll32& = CaptureRect32&(x, y, w, h)
End Function


'  Primary monitor only

Function CapturePrimary32& ()
    Const SM_CXSCREEN = 0
    Const SM_CYSCREEN = 1

    Dim w As Long, h As Long
    w = GetSystemMetrics(SM_CXSCREEN)
    h = GetSystemMetrics(SM_CYSCREEN)

    CapturePrimary32& = CaptureRect32&(0, 0, w, h)
End Function


'  Monitor according to index: (1 = first,, 2 = second, 3 = third...)

Function CaptureMonitorIndex32& (idx As Long)
    Dim r As RECT
    If GetMonitorRectByIndex%(idx, r) = 0 Then Exit Function
    CaptureMonitorIndex32& = CaptureRect32&(r.left, r.top, r.right - r.left, r.bottom - r.top)
End Function


' Internal capture any rectangle of the virtual surface

Function CaptureRect32& (sx As Long, sy As Long, w As Long, h As Long)
    Const BI_RGB = 0
    Const DIB_RGB_COLORS = 0
    Const SRCCOPY = &H00CC0020~&
    Const CAPTUREBLT = &H40000000~&

    If w <= 0 Or h <= 0 Then Exit Function

    Dim img&: img& = 0
    Dim hScreenDC As _Offset, hMemDC As _Offset, hBmp As _Offset, hOld As _Offset
    Dim bmi As BITMAPINFO
    Dim m As _MEM
    Dim ok As Long

    hScreenDC = GetDC(0)
    If hScreenDC = 0 Then GoTo cleanup

    hMemDC = CreateCompatibleDC(hScreenDC)
    If hMemDC = 0 Then GoTo cleanup

    hBmp = CreateCompatibleBitmap(hScreenDC, w, h)
    If hBmp = 0 Then GoTo cleanup

    hOld = SelectObject(hMemDC, hBmp)
    ok = BitBlt(hMemDC, 0, 0, w, h, hScreenDC, sx, sy, (SRCCOPY Or CAPTUREBLT))
    If ok = 0 Then GoTo cleanup

    img& = _NewImage(w, h, 32)
    If img& = 0 Then GoTo cleanup

    m = _MemImage(img&)

    bmi.bmiHeader.biSize = Len(bmi.bmiHeader)
    bmi.bmiHeader.biWidth = w
    bmi.bmiHeader.biHeight = -h
    bmi.bmiHeader.biPlanes = 1
    bmi.bmiHeader.biBitCount = 32
    bmi.bmiHeader.biCompression = BI_RGB
    bmi.bmiHeader.biSizeImage = w * h * 4

    ok = GetDIBits(hMemDC, hBmp, 0, h, m.OFFSET, bmi, DIB_RGB_COLORS)
    If ok = 0 Then
        _MemFree m
        _FreeImage img&
        img& = 0
        GoTo cleanup
    End If

    ' B,G,R,X -> set alpha to 255
    Dim o As _Offset, oLast As _Offset
    o = m.OFFSET + 3
    oLast = m.OFFSET + (w * h * 4)
    $Checking:Off
    Do While o < oLast
        _MemPut m, o, 255 As _UNSIGNED _BYTE
        o = o + 4
    Loop
    $Checking:On

    _MemFree m

    cleanup:
    If hMemDC <> 0 And hOld <> 0 Then hOld = SelectObject(hMemDC, hOld)
    If hBmp <> 0 Then hBmp = DeleteObject(hBmp)
    If hMemDC <> 0 Then hMemDC = DeleteDC(hMemDC)
    If hScreenDC <> 0 Then hScreenDC = ReleaseDC(0, hScreenDC)

    CaptureRect32& = img&
End Function


' Internal: find monitor rectangle by index
' idx=1 primary; others left to right, top to bottom

Function GetMonitorRectByIndex% (idx As Long, rOut As RECT)
    Const SM_XVIRTUALSCREEN = 76
    Const SM_YVIRTUALSCREEN = 77
    Const SM_CXVIRTUALSCREEN = 78
    Const SM_CYVIRTUALSCREEN = 79
    Const SM_CMONITORS = 80

    Const MONITOR_DEFAULTTONULL = 0
    Const MONITORINFOF_PRIMARY = 1

    Dim need As Long: need = GetSystemMetrics(SM_CMONITORS)
    If need <= 0 Then need = 1

    'scan a unique HMONITOR without a callback: via MonitorFromRect on a grid of points
    Dim vx As Long, vy As Long, vw As Long, vh As Long
    vx = GetSystemMetrics(SM_XVIRTUALSCREEN)
    vy = GetSystemMetrics(SM_YVIRTUALSCREEN)
    vw = GetSystemMetrics(SM_CXVIRTUALSCREEN)
    vh = GetSystemMetrics(SM_CYVIRTUALSCREEN)
    If vw <= 0 Or vh <= 0 Then Exit Function

    Dim hMon As _Offset
    Dim monCount As Long: monCount = 0
    ReDim hMon(1 To 1) As _Offset

    Dim stepPx As Long: stepPx = 256
    If stepPx < 32 Then stepPx = 32

    Dim xx As Long, yy As Long, i As Long, found As Long
    Dim rr As RECT
    Dim hm As _Offset

    For yy = vy To vy + vh - 1 Step stepPx
        For xx = vx To vx + vw - 1 Step stepPx
            rr.left = xx: rr.top = yy: rr.right = xx + 1: rr.bottom = yy + 1
            hm = MonitorFromRect(rr, MONITOR_DEFAULTTONULL)
            If hm <> 0 Then
                found = 0
                For i = 1 To monCount
                    If hMon(i) = hm Then found = 1: Exit For
                Next
                If found = 0 Then
                    monCount = monCount + 1
                    If monCount = 1 Then
                        hMon(1) = hm
                    Else
                        ReDim _Preserve hMon(1 To monCount) As _Offset
                        hMon(monCount) = hm
                    End If
                    If monCount >= need Then Exit For
                End If
            End If
        Next
        If monCount >= need Then Exit For
    Next

    If monCount = 0 Then Exit Function

    ' načíst recty + flagy
    Dim leftA As Long, topA As Long, primA As Long
    ReDim leftA(1 To monCount) As Long
    ReDim topA(1 To monCount) As Long
    ReDim primA(1 To monCount) As Long
    Dim mi As MONITORINFOEX, ok As Long

    For i = 1 To monCount
        mi.cbSize = Len(mi)
        ok = GetMonitorInfoA(hMon(i), mi)
        If ok Then
            leftA(i) = mi.rcMonitor.left
            topA(i) = mi.rcMonitor.top
            primA(i) = ((mi.dwFlags And MONITORINFOF_PRIMARY) <> 0)
        Else
            leftA(i) = 0: topA(i) = 0: primA(i) = 0
        End If
    Next

    ' sort: primary first, rest left/top
    Dim As Long j, swp, tmpL, tmpT, tmpP
    Dim tmpO As _Offset
    For i = 1 To monCount - 1
        For j = i + 1 To monCount
            swp = 0

            ' primary always before non-primary
            If primA(i) = 0 And primA(j) = 1 Then swp = 1

            ' both of the same "primary" type -> left/top
            If swp = 0 And primA(i) = primA(j) Then
                If leftA(j) < leftA(i) Then swp = 1
                If leftA(j) = leftA(i) And topA(j) < topA(i) Then swp = 1
            End If

            If swp Then
                tmpO = hMon(i): hMon(i) = hMon(j): hMon(j) = tmpO
                tmpL = leftA(i): leftA(i) = leftA(j): leftA(j) = tmpL
                tmpT = topA(i): topA(i) = topA(j): topA(j) = tmpT
                tmpP = primA(i): primA(i) = primA(j): primA(j) = tmpP
            End If
        Next
    Next

    If idx < 1 Or idx > monCount Then Exit Function

    mi.cbSize = Len(mi)
    ok = GetMonitorInfoA(hMon(idx), mi)
    If ok = 0 Then Exit Function

    rOut = mi.rcMonitor
    GetMonitorRectByIndex% = 1
End Function


Reply
#3
And here we finally have something that men have been wanting for a long time.

Microphone?
Line - In?
Live sound?

Do you want to visualize it? Do you want to save it?

Well, you can. You can do all of this. We wrote a dynamic library in C for it. The ZIP file includes DLLs for 32bit IDE and 64bit IDE, the source code of the dynamic library in C, and the main BAS program.

DLL source code
Code: (Select All)
// wasapi_loopback.c
// WASAPI: loopback (what-you-hear) + capture (mic/line-in)
// WAV PCM16 stereo zapis + volitelny stream do QB64PE pres ring-buffer
//
// Build (QB64PE gcc / mingw):
//  gcc -shared -O2 -o wasapi_loopback.dll wasapi_loopback.c -lole32 -lavrt

#define COBJMACROS
#define WIN32_LEAN_AND_MEAN
#define INITGUID

#include <windows.h>
#include <initguid.h>

#include <mmdeviceapi.h>
#include <audioclient.h>
#include <avrt.h>
#include <mmreg.h>

#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

// --- GUIDy pro WAVEFORMATEXTENSIBLE (aby linker nehledal ksguid) ---
static const GUID MY_KSDATAFORMAT_SUBTYPE_PCM = {
    0x00000001, 0x0000, 0x0010, {0x80,0x00,0x00,0xAA,0x00,0x38,0x9B,0x71}
};
static const GUID MY_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = {
    0x00000003, 0x0000, 0x0010, {0x80,0x00,0x00,0xAA,0x00,0x38,0x9B,0x71}
};

// --- Flags pro start ---
#define WL_FLAG_WRITE_WAV  1
#define WL_FLAG_STREAM    2

// endpointType: 0=loopback(eRender), 1=capture(eCapture)
#define WL_ENDPOINT_LOOPBACK 0
#define WL_ENDPOINT_CAPTURE  1

// role: 0=eConsole, 1=eCommunications, 2=eMultimedia
#define WL_ROLE_CONSOLE        0
#define WL_ROLE_COMMUNICATIONS  1
#define WL_ROLE_MULTIMEDIA      2

// --- Globals ---
static HANDLE gThread = NULL;
static volatile LONG gStop = 0;
static volatile LONG gRunning = 0;

static char gPath[MAX_PATH];

static volatile LONG gSampleRate = 0;  // pro QB dotaz
static volatile LONG gOutChannels = 2; // vystup DLL je vzdy stereo (L,R)

static volatile LONG gFlags = 0;
static volatile LONG gRingMs = 2000;
static volatile LONG gEndpointType = WL_ENDPOINT_LOOPBACK;
static volatile LONG gRole = WL_ROLE_CONSOLE;

// --- Ring buffer (stereo PCM16): 1 frame = 4 bajty (L16,R16) ---
static CRITICAL_SECTION gCs;
static volatile LONG gCsInit = 0;

static uint8_t*  gRing = NULL;
static uint32_t  gRingBytes = 0;
static uint32_t  gHead = 0; // write
static uint32_t  gTail = 0; // read

#pragma pack(push, 1)
typedef struct {
    char    riff[4];
    uint32_t riffSize;
    char    wave[4];
    char    fmt[4];
    uint32_t fmtSize;
    uint16_t audioFormat;    // 1 PCM
    uint16_t numChannels;    // 2
    uint32_t sampleRate;
    uint32_t byteRate;
    uint16_t blockAlign;    // 4
    uint16_t bitsPerSample;  // 16
    char    data[4];
    uint32_t dataSize;
} WAVHDR;
#pragma pack(pop)

static int write_all(HANDLE h, const void* p, DWORD cb) {
    const uint8_t* b = (const uint8_t*)p;
    while (cb) {
        DWORD w = 0;
        if (!WriteFile(h, b, cb, &w, NULL)) return 0;
        if (w == 0) return 0;
        b += w;
        cb -= w;
    }
    return 1;
}

static void patch_wav_sizes(HANDLE h, uint32_t dataBytes) {
    uint32_t riffSize = 36u + dataBytes;
    SetFilePointer(h, 4, NULL, FILE_BEGIN);
    write_all(h, &riffSize, 4);
    SetFilePointer(h, 40, NULL, FILE_BEGIN);
    write_all(h, &dataBytes, 4);
}

static uint32_t ring_used_nolock(void) {
    if (!gRingBytes) return 0;
    if (gHead >= gTail) return gHead - gTail;
    return gRingBytes - (gTail - gHead);
}

static void ring_reset_nolock(void) {
    gHead = gTail = 0;
}

static void ring_write_bytes(const uint8_t* src, uint32_t n) {
    if (!gRing || gRingBytes == 0 || n == 0) return;

    if (n > gRingBytes) {
        src += (n - gRingBytes);
        n = gRingBytes;
    }

    EnterCriticalSection(&gCs);

    uint32_t used = ring_used_nolock();
    uint32_t freeb = gRingBytes - used;

    if (n > freeb) {
        uint32_t drop = n - freeb;
        gTail = (gTail + drop) % gRingBytes; // zahod nejstarsi
    }

    uint32_t first = gRingBytes - gHead;
    if (first >= n) {
        memcpy(gRing + gHead, src, n);
        gHead = (gHead + n) % gRingBytes;
    } else {
        memcpy(gRing + gHead, src, first);
        memcpy(gRing, src + first, n - first);
        gHead = n - first;
    }

    LeaveCriticalSection(&gCs);
}

static uint32_t ring_read_bytes(uint8_t* dst, uint32_t maxBytes) {
    if (!gRing || gRingBytes == 0 || maxBytes == 0) return 0;

    EnterCriticalSection(&gCs);

    uint32_t used = ring_used_nolock();
    uint32_t n = (used < maxBytes) ? used : maxBytes;

    uint32_t first = gRingBytes - gTail;
    if (first >= n) {
        memcpy(dst, gRing + gTail, n);
        gTail = (gTail + n) % gRingBytes;
    } else {
        memcpy(dst, gRing + gTail, first);
        memcpy(dst + first, gRing, n - first);
        gTail = n - first;
    }

    LeaveCriticalSection(&gCs);
    return n;
}

static int detect_input_format(const WAVEFORMATEX* pwfx, int* isFloat, int* isPCM16) {
    *isFloat = 0;
    *isPCM16 = 0;
    if (!pwfx) return 0;

    if (pwfx->wFormatTag == WAVE_FORMAT_IEEE_FLOAT && pwfx->wBitsPerSample == 32) {
        *isFloat = 1; return 1;
    }
    if (pwfx->wFormatTag == WAVE_FORMAT_PCM && pwfx->wBitsPerSample == 16) {
        *isPCM16 = 1; return 1;
    }
    if (pwfx->wFormatTag == WAVE_FORMAT_EXTENSIBLE) {
        const WAVEFORMATEXTENSIBLE* wfe = (const WAVEFORMATEXTENSIBLE*)pwfx;
        if (IsEqualGUID(&wfe->SubFormat, &MY_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT) && pwfx->wBitsPerSample == 32) {
            *isFloat = 1; return 1;
        }
        if (IsEqualGUID(&wfe->SubFormat, &MY_KSDATAFORMAT_SUBTYPE_PCM) && pwfx->wBitsPerSample == 16) {
            *isPCM16 = 1; return 1;
        }
    }
    return 0;
}

static ERole role_from_long(LONG r) {
    switch (r) {
        case WL_ROLE_COMMUNICATIONS: return eCommunications;
        case WL_ROLE_MULTIMEDIA:    return eMultimedia;
        default:                    return eConsole;
    }
}

static DWORD WINAPI capture_thread(LPVOID lp) {
    (void)lp;

    HANDLE hFile = INVALID_HANDLE_VALUE;
    uint32_t totalData = 0;

    HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
    if (FAILED(hr)) { InterlockedExchange(&gRunning, 0); return 2; }

    IMMDeviceEnumerator* pEnum = NULL;
    IMMDevice* pDev = NULL;
    IAudioClient* pClient = NULL;
    IAudioCaptureClient* pCap = NULL;
    WAVEFORMATEX* pwfx = NULL;

    int16_t* stereo = NULL;
    uint32_t stereoCapFrames = 0;

    hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL,
                          &IID_IMMDeviceEnumerator, (void**)&pEnum);
    if (FAILED(hr)) goto done;

    LONG ep = InterlockedCompareExchange(&gEndpointType, 0, 0);
    EDataFlow flow = (ep == WL_ENDPOINT_CAPTURE) ? eCapture : eRender;
    ERole role = role_from_long(InterlockedCompareExchange(&gRole, 0, 0));

    hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(pEnum, flow, role, &pDev);
    if (FAILED(hr)) goto done;

    hr = IMMDevice_Activate(pDev, &IID_IAudioClient, CLSCTX_ALL, NULL, (void**)&pClient);
    if (FAILED(hr)) goto done;

    hr = IAudioClient_GetMixFormat(pClient, &pwfx);
    if (FAILED(hr) || !pwfx) goto done;

    InterlockedExchange(&gSampleRate, (LONG)pwfx->nSamplesPerSec);
    InterlockedExchange(&gOutChannels, 2);

    // ring buffer alokace (jen kdyz WL_FLAG_STREAM)
    if (InterlockedCompareExchange(&gFlags, 0, 0) & WL_FLAG_STREAM) {
        uint32_t sr = (uint32_t)pwfx->nSamplesPerSec;
        uint32_t bytesPerSec = sr * 4; // stereo 16bit
        uint32_t rb = (uint32_t)(bytesPerSec * (uint32_t)InterlockedCompareExchange(&gRingMs, 0, 0) / 1000u);
        if (rb < bytesPerSec / 4) rb = bytesPerSec / 4; // min ~250ms

        EnterCriticalSection(&gCs);
        free(gRing);
        gRing = (uint8_t*)malloc(rb);
        gRingBytes = gRing ? rb : 0;
        ring_reset_nolock();
        LeaveCriticalSection(&gCs);
    }

    REFERENCE_TIME hns = 1000000; // 100 ms buffer

    DWORD streamFlags = 0;
    if (flow == eRender) streamFlags |= AUDCLNT_STREAMFLAGS_LOOPBACK; // jen loopback

    hr = IAudioClient_Initialize(pClient,
                                AUDCLNT_SHAREMODE_SHARED,
                                streamFlags,
                                hns, 0, pwfx, NULL);
    if (FAILED(hr)) goto done;

    hr = IAudioClient_GetService(pClient, &IID_IAudioCaptureClient, (void**)&pCap);
    if (FAILED(hr)) goto done;

    // otevri WAV jen kdyz WL_FLAG_WRITE_WAV
    if (InterlockedCompareExchange(&gFlags, 0, 0) & WL_FLAG_WRITE_WAV) {
        hFile = CreateFileA(gPath, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS,
                            FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL);
        if (hFile == INVALID_HANDLE_VALUE) goto done;

        WAVHDR hdr;
        memset(&hdr, 0, sizeof(hdr));
        memcpy(hdr.riff, "RIFF", 4);
        memcpy(hdr.wave, "WAVE", 4);
        memcpy(hdr.fmt,  "fmt ", 4);
        memcpy(hdr.data, "data", 4);
        hdr.fmtSize = 16;
        hdr.audioFormat = 1;
        hdr.numChannels = 2;
        hdr.sampleRate = (uint32_t)pwfx->nSamplesPerSec;
        hdr.bitsPerSample = 16;
        hdr.blockAlign = 4;
        hdr.byteRate = hdr.sampleRate * 4;
        hdr.dataSize = 0;
        hdr.riffSize = 36;

        if (!write_all(hFile, &hdr, sizeof(hdr))) goto done;
    }

    DWORD taskIdx = 0;
    HANDLE hTask = AvSetMmThreadCharacteristicsA("Pro Audio", &taskIdx);

    int inIsFloat = 0, inIsPCM16 = 0;
    (void)detect_input_format(pwfx, &inIsFloat, &inIsPCM16);

    hr = IAudioClient_Start(pClient);
    if (FAILED(hr)) goto done;

    while (!InterlockedCompareExchange(&gStop, 0, 0)) {
        UINT32 pkt = 0;
        hr = IAudioCaptureClient_GetNextPacketSize(pCap, &pkt);
        if (FAILED(hr)) break;

        while (pkt) {
            BYTE* pData = NULL;
            UINT32 frames = 0;
            DWORD flags = 0;

            hr = IAudioCaptureClient_GetBuffer(pCap, &pData, &frames, &flags, NULL, NULL);
            if (FAILED(hr)) break;

            if (frames > stereoCapFrames) {
                free(stereo);
                stereo = (int16_t*)malloc((size_t)frames * 2 * sizeof(int16_t));
                stereoCapFrames = stereo ? frames : 0;
                if (!stereo) { IAudioCaptureClient_ReleaseBuffer(pCap, frames); goto done; }
            }

            if ((flags & AUDCLNT_BUFFERFLAGS_SILENT) || !pData) {
                memset(stereo, 0, (size_t)frames * 4);
            } else {
                const int inCh = (int)pwfx->nChannels;

                if (inIsFloat) {
                    const float* in = (const float*)pData;
                    for (UINT32 i = 0; i < frames; i++) {
                        float l = in[i * inCh + 0];
                        float r = (inCh >= 2) ? in[i * inCh + 1] : l;
                        if (l > 1.f) l = 1.f; if (l < -1.f) l = -1.f;
                        if (r > 1.f) r = 1.f; if (r < -1.f) r = -1.f;
                        stereo[i * 2 + 0] = (int16_t)lrintf(l * 32767.f);
                        stereo[i * 2 + 1] = (int16_t)lrintf(r * 32767.f);
                    }
                } else if (inIsPCM16) {
                    const int16_t* in = (const int16_t*)pData;
                    for (UINT32 i = 0; i < frames; i++) {
                        int16_t l = in[i * inCh + 0];
                        int16_t r = (inCh >= 2) ? in[i * inCh + 1] : l;
                        stereo[i * 2 + 0] = l;
                        stereo[i * 2 + 1] = r;
                    }
                } else {
                    memset(stereo, 0, (size_t)frames * 4);
                }
            }

            IAudioCaptureClient_ReleaseBuffer(pCap, frames);

            if (InterlockedCompareExchange(&gFlags, 0, 0) & WL_FLAG_STREAM) {
                ring_write_bytes((const uint8_t*)stereo, (uint32_t)frames * 4);
            }

            if (hFile != INVALID_HANDLE_VALUE) {
                uint32_t outBytes = (uint32_t)frames * 4;
                if (!write_all(hFile, stereo, outBytes)) { hr = E_FAIL; break; }
                totalData += outBytes;
            }

            hr = IAudioCaptureClient_GetNextPacketSize(pCap, &pkt);
            if (FAILED(hr)) break;
        }

        Sleep(5);
    }

    if (pClient) IAudioClient_Stop(pClient);
    if (hFile != INVALID_HANDLE_VALUE) patch_wav_sizes(hFile, totalData);
    if (hTask) AvRevertMmThreadCharacteristics(hTask);

done:
    free(stereo);

    if (hFile != INVALID_HANDLE_VALUE) CloseHandle(hFile);

    if (pwfx) CoTaskMemFree(pwfx);
    if (pCap) IAudioCaptureClient_Release(pCap);
    if (pClient) IAudioClient_Release(pClient);
    if (pDev) IMMDevice_Release(pDev);
    if (pEnum) IMMDeviceEnumerator_Release(pEnum);

    CoUninitialize();

    InterlockedExchange(&gSampleRate, 0);
    InterlockedExchange(&gRunning, 0);
    return 0;
}

// --- Exports ---

__declspec(dllexport) int WL_StartEx2(const char* pathNullTerminated,
                                    LONG flags, LONG ringMs,
                                    LONG endpointType, LONG role)
{
    if (InterlockedCompareExchange(&gRunning, 1, 1)) return 0;

    if (!InterlockedCompareExchange(&gCsInit, 1, 1)) {
        InitializeCriticalSection(&gCs);
        InterlockedExchange(&gCsInit, 1);
    }

    InterlockedExchange(&gEndpointType, endpointType);
    InterlockedExchange(&gRole, role);

    InterlockedExchange(&gFlags, flags);
    InterlockedExchange(&gRingMs, (ringMs <= 0) ? 0 : ringMs);

    if (flags & WL_FLAG_WRITE_WAV) {
        if (!pathNullTerminated || !pathNullTerminated[0]) return 0;
        lstrcpynA(gPath, pathNullTerminated, MAX_PATH);
    } else {
        gPath[0] = 0;
    }

    InterlockedExchange(&gStop, 0);
    InterlockedExchange(&gRunning, 1);

    gThread = CreateThread(NULL, 0, capture_thread, NULL, 0, NULL);
    if (!gThread) {
        InterlockedExchange(&gRunning, 0);
        return 0;
    }
    return 1;
}

// kompatibilni: loopback + WAV + stream, ring 2000ms, role console
__declspec(dllexport) int WL_StartEx(const char* pathNullTerminated, LONG flags, LONG ringMs) {
    return WL_StartEx2(pathNullTerminated, flags, ringMs, WL_ENDPOINT_LOOPBACK, WL_ROLE_CONSOLE);
}

// kompatibilni: loopback + WAV + stream, ring 2000ms
__declspec(dllexport) int WL_Start(const char* pathNullTerminated) {
    return WL_StartEx2(pathNullTerminated,
                      (WL_FLAG_WRITE_WAV | WL_FLAG_STREAM),
                      2000,
                      WL_ENDPOINT_LOOPBACK,
                      WL_ROLE_CONSOLE);
}

__declspec(dllexport) void WL_Stop(void) {
    if (!InterlockedCompareExchange(&gRunning, 1, 1)) return;
    InterlockedExchange(&gStop, 1);
    if (gThread) {
        WaitForSingleObject(gThread, INFINITE);
        CloseHandle(gThread);
        gThread = NULL;
    }
}

__declspec(dllexport) int WL_Running(void) {
    return InterlockedCompareExchange(&gRunning, 0, 0) ? 1 : 0;
}

__declspec(dllexport) void WL_GetFormat(LONG* sampleRate, LONG* channels) {
    if (sampleRate) *sampleRate = InterlockedCompareExchange(&gSampleRate, 0, 0);
    if (channels)  *channels  = 2; // vystup vzdy stereo (L,R)
}

__declspec(dllexport) LONG WL_AvailableFrames(void) {
    if (!gCsInit || !gRing || gRingBytes == 0) return 0;
    LONG frames = 0;
    EnterCriticalSection(&gCs);
    frames = (LONG)(ring_used_nolock() / 4);
    LeaveCriticalSection(&gCs);
    return frames;
}

// dest = pointer na int16_t buffer (interleaved L,R), maxFrames = kolik stereo frames
__declspec(dllexport) LONG WL_ReadFrames(void* dest, LONG maxFrames) {
    if (!gCsInit || !dest || maxFrames <= 0) return 0;
    uint32_t want = (uint32_t)maxFrames * 4;
    uint32_t got = ring_read_bytes((uint8_t*)dest, want);
    return (LONG)(got / 4);
}

My program:

Code: (Select All)

' WASAPI LOOPBACK / CAPTURE demo (QB64PE)
' - saves WAV (PCM16 stereo) and simultaneously reads live samples for visualization
'
' Requires: wasapi_loopback.dll in the same folder as EXE / BAS
'
' Controls:
' 1 = Loopback (what you hear in speakers) -> write to WAV + stream
' 2 = Capture (default mic/line-in) -> write to WAV + stream
' V = on/off visualization (saves CPU)
' ESC = stop and end
'
' Notes:
' - DLL always streams to QB64PE stereo PCM16: L,R (interleaved), 1 frame = 4 bytes
' - WL_ReadFrames() "consumes" data from the ring-buffer (shifts the reading index)
' - Ring buffer holds the last ringMs milliseconds. If you can't read, older data is discarded.

Option _Explicit

'----- Import DLL
Declare Dynamic Library "wasapi_loopback3_64"
    Function WL_StartEx2% (pathZ As String, ByVal flags As Long, ByVal ringMs As Long, ByVal endpointType As Long, ByVal role As Long)
    Sub WL_Stop
    Function WL_Running% ()
    Sub WL_GetFormat (sampleRate As Long, channels As Long)
    Function WL_AvailableFrames& ()
    Function WL_ReadFrames& (ByVal dest As _Offset, ByVal maxFrames As Long)
End Declare

' this constants must be the same as used in DLL
Const WL_FLAG_WRITE_WAV = 1
Const WL_FLAG_STREAM = 2

Const WL_ENDPOINT_LOOPBACK = 0
Const WL_ENDPOINT_CAPTURE = 1

Const WL_ROLE_CONSOLE = 0
Const WL_ROLE_COMMUNICATIONS = 1
Const WL_ROLE_MULTIMEDIA = 2

' How many stereo frames do we pull at once for visualization?
Const BUF_FRAMES = 2048

' Buffer for stereo PCM16 (INTEGER = 16-bit signed)
Dim samples(0 To BUF_FRAMES * 2 - 1) As Integer
Dim memS As _MEM: memS = _Mem(samples(0))

' UI
Screen _NewImage(800, 200, 32)
_Title "WASAPI demo: 1 loopback, 2 mic/line-in, V viz, ESC stop"


Dim vizOn As Long: vizOn = -1
Dim sr As Long, ch As Long
Dim As Long running, gotFrames, i, peakL, peakR, aL, aR, barL, barR, w
Dim As String k

' Start default loopback
StartCapture WL_ENDPOINT_LOOPBACK 'visualize only
'StartCapture WL_ENDPOINT_CAPTURE 'visualize + record sound to WAV 16 bit stereo file

'sound from microphone and Line-In is saved to  file "mic_or_linein.wav"
'livestream (that what is in speakers) is saved to file "what_you_hear.wav"

'try play video on youtube + this program at once, or just this program and play something from qb64 (Play, Sound, Beep, _SndPlay...)

Do
    _Limit 60

    k$ = InKey$
    If k$ <> "" Then
        Select Case UCase$(k$)
            Case "1": StartCapture WL_ENDPOINT_LOOPBACK
            Case "2": StartCapture WL_ENDPOINT_CAPTURE
            Case "V": vizOn = Not vizOn
            Case Chr$(27): Exit Do
        End Select
    End If

    running = WL_Running%
    If running = 0 Then
        'if error just wait
        _Limit 30
        'LOOP
    End If

    ' Visualize
    If vizOn Then
        ' get new chunk, if is nothing, draw nothing
        gotFrames& = WL_ReadFrames(memS.OFFSET, BUF_FRAMES)
        If gotFrames& > 0 Then
            ' simple peak (VU)
            peakL& = 0: peakR& = 0

            For i& = 0 To gotFrames& * 2 - 1 Step 2
                aL& = Abs(samples(i&))
                aR& = Abs(samples(i& + 1))
                If aL& > peakL& Then peakL& = aL&
                If aR& > peakR& Then peakR& = aR&
            Next

            ' vykresli bary
            Cls
            w& = _Width(0) - 40
            barL& = (peakL& * w&) \ 32767
            barR& = (peakR& * w&) \ 32767

            _PrintString (10, 10), "SR=" + LTrim$(Str$(sr)) + "Hz  CH=2  gotFrames=" + LTrim$(Str$(gotFrames&)) + "  avail=" + LTrim$(Str$(WL_AvailableFrames&))
            _PrintString (10, 30), "L peak: " + LTrim$(Str$(peakL&))
            Line (20, 50)-(20 + barL&, 80), -1, BF

            _PrintString (10, 100), "R peak: " + LTrim$(Str$(peakR&))
            Line (20, 120)-(20 + barR&, 150), -1, BF

            _PrintString (10, 170), "1=loopback  2=mic/line-in  V=viz  ESC=stop"
            _Display
        End If
    End If
Loop


' clear ram BUT ALSO (!) WRITE WAV Header if recorded!!!

If WL_Running% Then WL_Stop
_MemFree memS
End




Sub StartCapture (endpointType As Long)
    Dim flags As Long, ok As Integer
    Dim fname$, mode$

    flags = WL_FLAG_WRITE_WAV Or WL_FLAG_STREAM

    If endpointType = WL_ENDPOINT_LOOPBACK Then
        fname$ = "what_you_hear.wav"
        mode$ = "LOOPBACK (what you hear)"
    Else
        fname$ = "mic_or_linein.wav"
        mode$ = "CAPTURE (default input)"
    End If


    If WL_Running% Then WL_Stop

    'ringMs: how many ms of history to keep for visualization (2000 = 2 s)
    ok = WL_StartEx2(fname$ + Chr$(0), flags, 2000, endpointType, WL_ROLE_CONSOLE)
    If ok = 0 Then
        Cls
        _PrintString (10, 10), "WL_StartEx2 selhalo."
        _Display
        Sleep 2
        Exit Sub
    End If

    ' wait until DLL find format
    Dim As Single sr, ch
    sr = 0: ch = 0
    Dim t0 As Double: t0 = Timer
    Do
        _Limit 120
        WL_GetFormat sr, ch
        If sr <> 0 Then Exit Do
    Loop While (Timer - t0) < 1.0

    Cls
    _PrintString (10, 10), "MODE: " + mode$
    _PrintString (10, 30), "WAV: " + fname$
    _PrintString (10, 50), "Format: " + LTrim$(Str$(sr)) + " Hz, " + LTrim$(Str$(ch)) + " ch (DLL output is always stereo)"
    _PrintString (10, 70), "V = toggle viz | ESC = stop+exit"
    _Display
End Sub


Attached Files
.zip   wasapi_loopback3.zip (Size: 77.64 KB / Downloads: 27)


Reply
#4
thanks @Petr  Smile
Reply
#5
This is really bad cooperation. Why didn't someone write to me that the 32-bit version simply doesn't work? I understand. You wanted to give me a moment of joy. You are very kind and I thank you for that Smile

I came to the computer, started the 32-bit version and boom. The program crashed. What's going on? Why? Did it work yesterday? Or did I only try it in the 64-bit version and I didn't run the 32-bit version? I had about 6 windows with the IDE here, so it was a mess...

Result. New C code for the 32-bit version. Reason why it crashed:
The essence of the problem was in the calling convention (the way to call functions) between QB64PE and the DLL.
What exactly was wrong:
In 32-bit Windows there are different calling conventions: mainly cdecl and stdcall (WINAPI).
The original DLL exported functions without WINAPI, so on x86 they were called as cdecl (or at least not as stdcall).
But QB64PE called them as stdcall/WinAPI (typically on Windows).
Result: function return threw the stack --> process "closed" (classic crash without mercy).

Why it worked in 64bit
On 64bit Windows, function calls are unified (practically "one" ABI). The difference between cdecl vs stdcall is not relevant there in this way, so even though it was written "badly for x86", x64 tolerated it.
What I fixed and why it works now:
I switched exports to WINAPI/stdcall.
And at the same time I ensured that the DLL also exported undecorated names (WL_StartEx2) because of QB64PE.
I used the report output for this:
llvm-readobj --coff-exports wasapi_loopback3_32.dll

So now two files are needed to compile into a DLL. C file and DEF file.


New C source code for library:

Code: (Select All)
// wasapi_loopback3_32.c
// WASAPI: loopback (what-you-hear) + capture (mic/line-in)
// WAV PCM16 stereo zapis + volitelny stream do QB64PE pres ring-buffer
//
// 32-bit build (MinGW/GCC):
//  gcc -shared -O2 -static-libgcc -o wasapi_loopback3_32.dll wasapi_loopback3_32.c wasapi_loopback3_32.def -lole32 -lavrt
//
// 64-bit build (no .def needed typically):
//  gcc -shared -O2 -static-libgcc -o wasapi_loopback3_64.dll wasapi_loopback3_32.c -lole32 -lavrt

#define COBJMACROS
#define WIN32_LEAN_AND_MEAN
#define INITGUID

#include <windows.h>
#include <initguid.h>

#include <mmdeviceapi.h>
#include <audioclient.h>
#include <avrt.h>
#include <mmreg.h>

#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <process.h> // _beginthreadex

// --- QB64PE-friendly exports (stdcall on 32-bit) ---
#define WL_EXPORT __declspec(dllexport)
#define WL_CALL  WINAPI

// --- GUIDy pro WAVEFORMATEXTENSIBLE (aby linker nehledal ksguid) ---
static const GUID MY_KSDATAFORMAT_SUBTYPE_PCM = {
    0x00000001, 0x0000, 0x0010, {0x80,0x00,0x00,0xAA,0x00,0x38,0x9B,0x71}
};
static const GUID MY_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = {
    0x00000003, 0x0000, 0x0010, {0x80,0x00,0x00,0xAA,0x00,0x38,0x9B,0x71}
};

// --- Flags pro start ---
#define WL_FLAG_WRITE_WAV  1
#define WL_FLAG_STREAM    2

// endpointType: 0=loopback(eRender), 1=capture(eCapture)
#define WL_ENDPOINT_LOOPBACK 0
#define WL_ENDPOINT_CAPTURE  1

// role: 0=eConsole, 1=eCommunications, 2=eMultimedia
#define WL_ROLE_CONSOLE        0
#define WL_ROLE_COMMUNICATIONS  1
#define WL_ROLE_MULTIMEDIA      2

// --- Globals ---
static HANDLE gThread = NULL;
static volatile LONG gStop = 0;
static volatile LONG gRunning = 0;

static char gPath[MAX_PATH];

static volatile LONG gSampleRate = 0;  // pro QB dotaz
static volatile LONG gOutChannels = 2; // vystup DLL je vzdy stereo (L,R)

static volatile LONG gFlags = 0;
static volatile LONG gRingMs = 2000;
static volatile LONG gEndpointType = WL_ENDPOINT_LOOPBACK;
static volatile LONG gRole = WL_ROLE_CONSOLE;

// --- Ring buffer (stereo PCM16): 1 frame = 4 bajty (L16,R16) ---
static CRITICAL_SECTION gCs;
static volatile LONG gCsInit = 0;

static uint8_t*  gRing = NULL;
static uint32_t  gRingBytes = 0;
static uint32_t  gHead = 0; // write
static uint32_t  gTail = 0; // read

#pragma pack(push, 1)
typedef struct {
    char    riff[4];
    uint32_t riffSize;
    char    wave[4];
    char    fmt[4];
    uint32_t fmtSize;
    uint16_t audioFormat;    // 1 PCM
    uint16_t numChannels;    // 2
    uint32_t sampleRate;
    uint32_t byteRate;
    uint16_t blockAlign;    // 4
    uint16_t bitsPerSample;  // 16
    char    data[4];
    uint32_t dataSize;
} WAVHDR;
#pragma pack(pop)

static int write_all(HANDLE h, const void* p, DWORD cb) {
    const uint8_t* b = (const uint8_t*)p;
    while (cb) {
        DWORD w = 0;
        if (!WriteFile(h, b, cb, &w, NULL)) return 0;
        if (w == 0) return 0;
        b += w;
        cb -= w;
    }
    return 1;
}

static void patch_wav_sizes(HANDLE h, uint32_t dataBytes) {
    uint32_t riffSize = 36u + dataBytes;
    SetFilePointer(h, 4, NULL, FILE_BEGIN);
    write_all(h, &riffSize, 4);
    SetFilePointer(h, 40, NULL, FILE_BEGIN);
    write_all(h, &dataBytes, 4);
}

static uint32_t ring_used_nolock(void) {
    if (!gRingBytes) return 0;
    if (gHead >= gTail) return gHead - gTail;
    return gRingBytes - (gTail - gHead);
}

static void ring_reset_nolock(void) {
    gHead = gTail = 0;
}

static void ring_write_bytes(const uint8_t* src, uint32_t n) {
    if (!gRing || gRingBytes == 0 || n == 0) return;

    if (n > gRingBytes) {
        src += (n - gRingBytes);
        n = gRingBytes;
    }

    EnterCriticalSection(&gCs);

    uint32_t used = ring_used_nolock();
    uint32_t freeb = gRingBytes - used;

    if (n > freeb) {
        uint32_t drop = n - freeb;
        gTail = (gTail + drop) % gRingBytes; // zahod nejstarsi
    }

    uint32_t first = gRingBytes - gHead;
    if (first >= n) {
        memcpy(gRing + gHead, src, n);
        gHead = (gHead + n) % gRingBytes;
    } else {
        memcpy(gRing + gHead, src, first);
        memcpy(gRing, src + first, n - first);
        gHead = n - first;
    }

    LeaveCriticalSection(&gCs);
}

static uint32_t ring_read_bytes(uint8_t* dst, uint32_t maxBytes) {
    if (!gRing || gRingBytes == 0 || maxBytes == 0) return 0;

    EnterCriticalSection(&gCs);

    uint32_t used = ring_used_nolock();
    uint32_t n = (used < maxBytes) ? used : maxBytes;

    uint32_t first = gRingBytes - gTail;
    if (first >= n) {
        memcpy(dst, gRing + gTail, n);
        gTail = (gTail + n) % gRingBytes;
    } else {
        memcpy(dst, gRing + gTail, first);
        memcpy(dst + first, gRing, n - first);
        gTail = n - first;
    }

    LeaveCriticalSection(&gCs);
    return n;
}

static int detect_input_format(const WAVEFORMATEX* pwfx, int* isFloat, int* isPCM16) {
    *isFloat = 0;
    *isPCM16 = 0;
    if (!pwfx) return 0;

    if (pwfx->wFormatTag == WAVE_FORMAT_IEEE_FLOAT && pwfx->wBitsPerSample == 32) {
        *isFloat = 1; return 1;
    }
    if (pwfx->wFormatTag == WAVE_FORMAT_PCM && pwfx->wBitsPerSample == 16) {
        *isPCM16 = 1; return 1;
    }
    if (pwfx->wFormatTag == WAVE_FORMAT_EXTENSIBLE) {
        const WAVEFORMATEXTENSIBLE* wfe = (const WAVEFORMATEXTENSIBLE*)pwfx;
        if (IsEqualGUID(&wfe->SubFormat, &MY_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT) && pwfx->wBitsPerSample == 32) {
            *isFloat = 1; return 1;
        }
        if (IsEqualGUID(&wfe->SubFormat, &MY_KSDATAFORMAT_SUBTYPE_PCM) && pwfx->wBitsPerSample == 16) {
            *isPCM16 = 1; return 1;
        }
    }
    return 0;
}

static ERole role_from_long(LONG r) {
    switch (r) {
        case WL_ROLE_COMMUNICATIONS: return eCommunications;
        case WL_ROLE_MULTIMEDIA:    return eMultimedia;
        default:                    return eConsole;
    }
}

// _beginthreadex thread proc
static unsigned __stdcall capture_thread(void* lp) {
    (void)lp;

    HANDLE hFile = INVALID_HANDLE_VALUE;
    uint32_t totalData = 0;

    HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
    if (FAILED(hr)) { InterlockedExchange(&gRunning, 0); return 2; }

    IMMDeviceEnumerator* pEnum = NULL;
    IMMDevice* pDev = NULL;
    IAudioClient* pClient = NULL;
    IAudioCaptureClient* pCap = NULL;
    WAVEFORMATEX* pwfx = NULL;

    int16_t* stereo = NULL;
    uint32_t stereoCapFrames = 0;

    hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL,
                          &IID_IMMDeviceEnumerator, (void**)&pEnum);
    if (FAILED(hr)) goto done;

    LONG ep = InterlockedCompareExchange(&gEndpointType, 0, 0);
    EDataFlow flow = (ep == WL_ENDPOINT_CAPTURE) ? eCapture : eRender;
    ERole role = role_from_long(InterlockedCompareExchange(&gRole, 0, 0));

    hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(pEnum, flow, role, &pDev);
    if (FAILED(hr)) goto done;

    hr = IMMDevice_Activate(pDev, &IID_IAudioClient, CLSCTX_ALL, NULL, (void**)&pClient);
    if (FAILED(hr)) goto done;

    hr = IAudioClient_GetMixFormat(pClient, &pwfx);
    if (FAILED(hr) || !pwfx) goto done;

    InterlockedExchange(&gSampleRate, (LONG)pwfx->nSamplesPerSec);
    InterlockedExchange(&gOutChannels, 2);

    // ring buffer alokace (jen kdyz WL_FLAG_STREAM)
    if (InterlockedCompareExchange(&gFlags, 0, 0) & WL_FLAG_STREAM) {
        uint32_t sr = (uint32_t)pwfx->nSamplesPerSec;
        uint32_t bytesPerSec = sr * 4; // stereo 16bit
        uint32_t rb = (uint32_t)(bytesPerSec * (uint32_t)InterlockedCompareExchange(&gRingMs, 0, 0) / 1000u);
        if (rb < bytesPerSec / 4) rb = bytesPerSec / 4; // min ~250ms

        EnterCriticalSection(&gCs);
        free(gRing);
        gRing = (uint8_t*)malloc(rb);
        gRingBytes = gRing ? rb : 0;
        ring_reset_nolock();
        LeaveCriticalSection(&gCs);
    }

    REFERENCE_TIME hns = 1000000; // 100 ms buffer

    DWORD streamFlags = 0;
    if (flow == eRender) streamFlags |= AUDCLNT_STREAMFLAGS_LOOPBACK; // jen loopback

    hr = IAudioClient_Initialize(pClient,
                                AUDCLNT_SHAREMODE_SHARED,
                                streamFlags,
                                hns, 0, pwfx, NULL);
    if (FAILED(hr)) goto done;

    hr = IAudioClient_GetService(pClient, &IID_IAudioCaptureClient, (void**)&pCap);
    if (FAILED(hr)) goto done;

    // otevri WAV jen kdyz WL_FLAG_WRITE_WAV
    if (InterlockedCompareExchange(&gFlags, 0, 0) & WL_FLAG_WRITE_WAV) {
        hFile = CreateFileA(gPath, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS,
                            FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL);
        if (hFile == INVALID_HANDLE_VALUE) goto done;

        WAVHDR hdr;
        memset(&hdr, 0, sizeof(hdr));
        memcpy(hdr.riff, "RIFF", 4);
        memcpy(hdr.wave, "WAVE", 4);
        memcpy(hdr.fmt,  "fmt ", 4);
        memcpy(hdr.data, "data", 4);
        hdr.fmtSize = 16;
        hdr.audioFormat = 1;
        hdr.numChannels = 2;
        hdr.sampleRate = (uint32_t)pwfx->nSamplesPerSec;
        hdr.bitsPerSample = 16;
        hdr.blockAlign = 4;
        hdr.byteRate = hdr.sampleRate * 4;
        hdr.dataSize = 0;
        hdr.riffSize = 36;

        if (!write_all(hFile, &hdr, (DWORD)sizeof(hdr))) goto done;
    }

    DWORD taskIdx = 0;
    HANDLE hTask = AvSetMmThreadCharacteristicsA("Pro Audio", &taskIdx);

    int inIsFloat = 0, inIsPCM16 = 0;
    (void)detect_input_format(pwfx, &inIsFloat, &inIsPCM16);

    hr = IAudioClient_Start(pClient);
    if (FAILED(hr)) goto done;

    while (!InterlockedCompareExchange(&gStop, 0, 0)) {
        UINT32 pkt = 0;
        hr = IAudioCaptureClient_GetNextPacketSize(pCap, &pkt);
        if (FAILED(hr)) break;

        while (pkt) {
            BYTE* pData = NULL;
            UINT32 frames = 0;
            DWORD flags = 0;

            hr = IAudioCaptureClient_GetBuffer(pCap, &pData, &frames, &flags, NULL, NULL);
            if (FAILED(hr)) break;

            if (frames > stereoCapFrames) {
                free(stereo);
                stereo = (int16_t*)malloc((size_t)frames * 2 * sizeof(int16_t));
                stereoCapFrames = stereo ? frames : 0;
                if (!stereo) { IAudioCaptureClient_ReleaseBuffer(pCap, frames); goto done; }
            }

            if ((flags & AUDCLNT_BUFFERFLAGS_SILENT) || !pData) {
                memset(stereo, 0, (size_t)frames * 4);
            } else {
                const int inCh = (int)pwfx->nChannels;

                if (inIsFloat) {
                    const float* in = (const float*)pData;
                    for (UINT32 i = 0; i < frames; i++) {
                        float l = in[i * inCh + 0];
                        float r = (inCh >= 2) ? in[i * inCh + 1] : l;
                        if (l > 1.f) l = 1.f; if (l < -1.f) l = -1.f;
                        if (r > 1.f) r = 1.f; if (r < -1.f) r = -1.f;
                        stereo[i * 2 + 0] = (int16_t)lrintf(l * 32767.f);
                        stereo[i * 2 + 1] = (int16_t)lrintf(r * 32767.f);
                    }
                } else if (inIsPCM16) {
                    const int16_t* in = (const int16_t*)pData;
                    for (UINT32 i = 0; i < frames; i++) {
                        int16_t l = in[i * inCh + 0];
                        int16_t r = (inCh >= 2) ? in[i * inCh + 1] : l;
                        stereo[i * 2 + 0] = l;
                        stereo[i * 2 + 1] = r;
                    }
                } else {
                    memset(stereo, 0, (size_t)frames * 4);
                }
            }

            IAudioCaptureClient_ReleaseBuffer(pCap, frames);

            if (InterlockedCompareExchange(&gFlags, 0, 0) & WL_FLAG_STREAM) {
                ring_write_bytes((const uint8_t*)stereo, (uint32_t)frames * 4);
            }

            if (hFile != INVALID_HANDLE_VALUE) {
                uint32_t outBytes = (uint32_t)frames * 4;
                if (!write_all(hFile, stereo, (DWORD)outBytes)) { hr = E_FAIL; break; }
                totalData += outBytes;
            }

            hr = IAudioCaptureClient_GetNextPacketSize(pCap, &pkt);
            if (FAILED(hr)) break;
        }

        Sleep(5);
    }

    if (pClient) IAudioClient_Stop(pClient);
    if (hFile != INVALID_HANDLE_VALUE) patch_wav_sizes(hFile, totalData);
    if (hTask) AvRevertMmThreadCharacteristics(hTask);

done:
    free(stereo);

    if (hFile != INVALID_HANDLE_VALUE) CloseHandle(hFile);

    if (pwfx) CoTaskMemFree(pwfx);
    if (pCap) IAudioCaptureClient_Release(pCap);
    if (pClient) IAudioClient_Release(pClient);
    if (pDev) IMMDevice_Release(pDev);
    if (pEnum) IMMDeviceEnumerator_Release(pEnum);

    CoUninitialize();

    InterlockedExchange(&gSampleRate, 0);
    InterlockedExchange(&gRunning, 0);
    return 0;
}

// --- Exports ---

WL_EXPORT int WL_CALL WL_StartEx2(const char* pathNullTerminated,
                                LONG flags, LONG ringMs,
                                LONG endpointType, LONG role)
{
    if (InterlockedCompareExchange(&gRunning, 1, 1)) return 0;

    // init critical section exactly once
    if (InterlockedCompareExchange(&gCsInit, 1, 0) == 0) {
        InitializeCriticalSection(&gCs);
    }

    InterlockedExchange(&gEndpointType, endpointType);
    InterlockedExchange(&gRole, role);

    InterlockedExchange(&gFlags, flags);
    InterlockedExchange(&gRingMs, (ringMs <= 0) ? 0 : ringMs);

    if (flags & WL_FLAG_WRITE_WAV) {
        if (!pathNullTerminated || !pathNullTerminated[0]) return 0;
        lstrcpynA(gPath, pathNullTerminated, MAX_PATH);
    } else {
        gPath[0] = 0;
    }

    InterlockedExchange(&gStop, 0);
    InterlockedExchange(&gRunning, 1);

    unsigned tid = 0;
    uintptr_t th = _beginthreadex(NULL, 0, capture_thread, NULL, 0, &tid);
    gThread = (HANDLE)th;
    if (!gThread) {
        InterlockedExchange(&gRunning, 0);
        return 0;
    }
    return 1;
}

// kompatibilni: loopback + WAV + stream, ring 2000ms, role console
WL_EXPORT int WL_CALL WL_StartEx(const char* pathNullTerminated, LONG flags, LONG ringMs) {
    return WL_StartEx2(pathNullTerminated, flags, ringMs, WL_ENDPOINT_LOOPBACK, WL_ROLE_CONSOLE);
}

// kompatibilni: loopback + WAV + stream, ring 2000ms
WL_EXPORT int WL_CALL WL_Start(const char* pathNullTerminated) {
    return WL_StartEx2(pathNullTerminated,
                      (WL_FLAG_WRITE_WAV | WL_FLAG_STREAM),
                      2000,
                      WL_ENDPOINT_LOOPBACK,
                      WL_ROLE_CONSOLE);
}

WL_EXPORT void WL_CALL WL_Stop(void) {
    if (!InterlockedCompareExchange(&gRunning, 1, 1)) return;
    InterlockedExchange(&gStop, 1);
    if (gThread) {
        WaitForSingleObject(gThread, INFINITE);
        CloseHandle(gThread);
        gThread = NULL;
    }
}

WL_EXPORT int WL_CALL WL_Running(void) {
    return InterlockedCompareExchange(&gRunning, 0, 0) ? 1 : 0;
}

WL_EXPORT void WL_CALL WL_GetFormat(LONG* sampleRate, LONG* channels) {
    if (sampleRate) *sampleRate = InterlockedCompareExchange(&gSampleRate, 0, 0);
    if (channels)  *channels  = 2; // vystup vzdy stereo (L,R)
}

WL_EXPORT LONG WL_CALL WL_AvailableFrames(void) {
    if (!gCsInit || !gRing || gRingBytes == 0) return 0;
    LONG frames = 0;
    EnterCriticalSection(&gCs);
    frames = (LONG)(ring_used_nolock() / 4);
    LeaveCriticalSection(&gCs);
    return frames;
}

// dest = pointer na int16_t buffer (interleaved L,R), maxFrames = kolik stereo frames
WL_EXPORT LONG WL_CALL WL_ReadFrames(void* dest, LONG maxFrames) {
    if (!gCsInit || !dest || maxFrames <= 0) return 0;
    uint32_t want = (uint32_t)maxFrames * 4;
    uint32_t got = ring_read_bytes((uint8_t*)dest, want);
    return (LONG)(got / 4);
}

and also DEF file is needed:

Code: (Select All)
LIBRARY wasapi_loopback3_32
EXPORTS
  WL_StartEx2=WL_StartEx2@20
  WL_StartEx=WL_StartEx@12
  WL_Start=WL_Start@4
  WL_Stop=WL_Stop@0
  WL_Running=WL_Running@0
  WL_GetFormat=WL_GetFormat@8
  WL_AvailableFrames=WL_AvailableFrames@0
  WL_ReadFrames=WL_ReadFrames@8


and only then compile it all into a DLL like this:
i686-w64-mingw32-gcc -shared -O2 -static-libgcc ^ -o wasapi_loopback3_32.dll wasapi_loopback3.c wasapi_loopback3_32.def ^ -lole32 -lavrt

So here is the new 32bit DLL file, replace the original one (in the 32bit IDE). Tested in the latest IDE 4.2.0 x86 and also in IDE 4.0.0 32bit, it works correctly in both cases. BAS source code remains the same.


Attached Files
.zip   wasapi_loopback3_32.zip (Size: 34.92 KB / Downloads: 20)


Reply
#6
We had a conversation with @madscijr about MP3 as audio in the ADPCM compression thread. I promised to look into it after I was notified by @ahenry3068 that MP3 is no longer covered by licenses. At first I thought I would start writing an encoder in QB64PE.... after researching the complexity I gave up.

Here we have a ready-made solution for saving audio to MP3. I used a program from LAME (in C) and compiled two dynamic libraries for QB64PE from it. I chose the easier way, an incomplete API, only the basics, but it is enough to create MP3. If there is interest, I can also try the full LAME API (but that is the libmp3lame library and it has more difficult declarations).

GoTo row 78, 79 and rewrite source sound file (all which QB64PE support are supported) and output MP3 file name.

ZIP file contains lame_enc32.dll, lame_enc64.dll, BAS source code and s3m music file. 

Code: (Select All)


'Source code for library is 3th party work:

'https://sourceforge.net/projects/lame/files/lame/3.100/

'downloaded from sourceforge.net, then source code compiled to DLL using nMake




Option _Explicit

'========================= CONSTANTS FOR LAME DLL =========================

Const BE_CONFIG_MP3 = 0
Const BE_CONFIG_LAME = 256

Const BE_MP3_MODE_STEREO = 0
Const BE_MP3_MODE_JSTEREO = 1
Const BE_MP3_MODE_DUALCHANNEL = 2
Const BE_MP3_MODE_MONO = 3

Const MPEG1 = 1
Const MPEG2 = 0

Const BE_ERR_SUCCESSFUL = 0

' Preset -1 = LQP_NOPRESET, i.e. no predefined preset, we use plain CBR
Const LQP_NOPRESET = -1

' Structure size BE_CONFIG according to BladeMP3EncDLL.h (CURRENT_STRUCT_SIZE)
Const BE_CONFIG_SIZE = 331

' This buffer is used to copy MP3 data in chunks as a string
Const MP3_CHUNKBUF = 65535
Dim Shared mp3Chunk As String * MP3_CHUNKBUF

'====================== FUNCTION DECLARATIONS FROM lame_enc.dll ===================

Declare Dynamic Library "lame_enc32"
    ' BE_ERR beInitStream(PBE_CONFIG pbeConfig, PDWORD dwSamples, PDWORD dwBufferSize, PHBE_STREAM phbeStream)
    Function beInitStream& (ByVal pCfg As _Offset, ByVal pSamples As _Offset, ByVal pBufSize As _Offset, ByVal pStream As _Offset)

    ' BE_ERR beEncodeChunk(HBE_STREAM hbeStream, DWORD nSamples, PSHORT pSamples, PBYTE pOutput, PDWORD pdwOutput)
    Function beEncodeChunk& (ByVal hStream As _Offset, ByVal nSamples As _Unsigned Long, ByVal pPCM As _Offset, ByVal pOut As _Offset, ByVal pOutBytes As _Offset)

    ' BE_ERR beDeinitStream(HBE_STREAM hbeStream, PBYTE pOutput, PDWORD pdwOutput)
    Function beDeinitStream& (ByVal hStream As _Offset, ByVal pOut As _Offset, ByVal pOutBytes As _Offset)

    ' BE_ERR beCloseStream(HBE_STREAM hbeStream)
    Function beCloseStream& (ByVal hStream As _Offset)

    ' BE_ERR beWriteVBRHeader(LPCSTR lpszFileName)
    Function beWriteVBRHeader& (ByVal pszFile As _Offset)
End Declare

'========================= FORWARD DECLARATIONS OF SUBS ========================

Declare Sub PutU32 (m As _MEM, ofs As _Unsigned Long, v As _Unsigned Long)
Declare Sub PutS32 (m As _MEM, ofs As _Unsigned Long, v As Long)
Declare Function BytesPerSample% (t As Long)
Declare Function MemChannels% (m As _MEM)
Declare Function Clamp16% (v As Long)
Declare Function Long32To16% (x As Long)
Declare Sub WriteBytesToFile (hFile As Long, ou() As _Unsigned _Byte, outLen As _Unsigned Long)


Dim inFile As String, outFile As String
Dim As Long snd, ch, errCode
Dim As _MEM src, cfgM
Dim As _Unsigned Long sampleRate, dwSamples, dwMP3Buf
Dim cfg(0 To BE_CONFIG_SIZE - 1) As _Unsigned _Byte ' 331 bytes
Dim hStream As _Offset




' SET INPUT AND OUTPUT HERE
inFile = "awesome.s3m" ' source audio (WAV/MP3/XM/... anything _SndOpen can load)
outFile = "out.mp3" ' destination MP3 file

'-------------------------------------------------------------
' 1) Load sound via _SndOpen and _MemSound
'-------------------------------------------------------------


snd = _SndOpen(inFile)
If snd = 0 Then
    Print "Unable to open audio file: "; inFile
    End
End If


src = _MemSound(snd, 0)
If src.SIZE = 0 Then
    Print "_MemSound returned SIZE = 0 – no PCM data."
    _SndClose snd
    End
End If

' Determine number of channels from the _MEM block (1 = mono, 2 = stereo)

ch = MemChannels(src)
If ch <> 1 And ch <> 2 Then
    Print "Unknown channel count ("; ch; ") – only mono or stereo is supported."
    _MemFree src
    _SndClose snd
    End
End If

' Sample rate: we use QB64PE mixing rate

sampleRate = _SndRate

Print "Input: "; inFile
Print "Channels: "; ch; "  (1=mono, 2=stereo)"
Print "Sample rate (QB64PE mix): "; sampleRate; " Hz"
Print "Type _MemSound.TYPE: "; src.TYPE
Print

'-------------------------------------------------------------
' 2) Build BE_CONFIG (LHV1) structure into a byte array
'    according to BladeMP3EncDLL.h
'
'    Structure:
'    dwConfig                        (offset 0)
'    format.LHV1.dwStructVersion      (4)
'    format.LHV1.dwStructSize        (8)
'    format.LHV1.dwSampleRate        (12)
'    format.LHV1.dwReSampleRate      (16)
'    format.LHV1.nMode                (20)
'    format.LHV1.dwBitrate            (24)
'    format.LHV1.dwMaxBitrate        (28)
'    format.LHV1.nPreset              (32)
'    format.LHV1.dwMpegVersion        (36)
'    format.LHV1.dwPsyModel          (40)
'    format.LHV1.dwEmphasis          (44)
'    format.LHV1.bPrivate            (48)
'    format.LHV1.bCRC                (52)
'    format.LHV1.bCopyright          (56)
'    format.LHV1.bOriginal            (60)
'    format.LHV1.bWriteVBRHeader      (64)
'    format.LHV1.bEnableVBR          (68)
'    format.LHV1.nVBRQuality          (72)
'    format.LHV1.dwVbrAbr_bps        (76)
'    format.LHV1.bNoRes              (80)
'    format.LHV1.btReserved[...]      (84..)
'-------------------------------------------------------------

cfgM = _Mem(cfg(0)) ' _MEM over the entire array

' whole array is zeroed after DIM, so btReserved[] is implicitly 0

' dwConfig = BE_CONFIG_LAME
PutU32 cfgM, 0, BE_CONFIG_LAME

' dwStructVersion = 1
PutU32 cfgM, 4, 1

' dwStructSize = sizeof(BE_CONFIG) = 331
PutU32 cfgM, 8, BE_CONFIG_SIZE

' dwSampleRate = sample rate of input
PutU32 cfgM, 12, sampleRate

' dwReSampleRate = 0 (encoder keeps same rate; for CBR we leave 1:1)
PutU32 cfgM, 16, 0

' nMode = mono / joint-stereo depending on channel count
If ch = 1 Then
    PutS32 cfgM, 20, BE_MP3_MODE_MONO
Else
    PutS32 cfgM, 20, BE_MP3_MODE_JSTEREO
End If

' CBR bitrate in kbps (classic: 192 kbps)
PutU32 cfgM, 24, 192 '                      <----- quality set here

' dwMaxBitrate = 0 (ignored for CBR)
PutU32 cfgM, 28, 0

' nPreset = LQP_NOPRESET (-1) => no preset, pure CBR based on dwBitrate
PutS32 cfgM, 32, LQP_NOPRESET

' dwMpegVersion: for >= 32000 Hz use MPEG1, otherwise MPEG2
If sampleRate >= 32000 Then
    PutU32 cfgM, 36, MPEG1
Else
    PutU32 cfgM, 36, MPEG2
End If

' dwPsyModel = 0 (future use)
PutU32 cfgM, 40, 0

' dwEmphasis = 0 (no emphasis)
PutU32 cfgM, 44, 0

' bPrivate, bCRC, bCopyright = 0, bOriginal = 1
PutS32 cfgM, 48, 0 ' bPrivate
PutS32 cfgM, 52, 0 ' bCRC
PutS32 cfgM, 56, 0 ' bCopyright
PutS32 cfgM, 60, 1 ' bOriginal

' bWriteVBRHeader = 1 => write XING/Info header (length, bitrate...)
PutS32 cfgM, 64, 1

' bEnableVBR = 0 => CBR mode
PutS32 cfgM, 68, 0

' nVBRQuality and dwVbrAbr_bps = 0 (we don't use VBR)
PutS32 cfgM, 72, 0
PutU32 cfgM, 76, 0

' bNoRes = 0 (bit reservoir enabled)
PutS32 cfgM, 80, 0

'-------------------------------------------------------------
' 3) beInitStream – encoder initialization
'    Returns:
'      dwSamples  = number of 16bit samples (SHORT) to pass to beEncodeChunk
'      dwMP3Buf    = minimum size of output MP3 buffer in bytes
'      hStream    = stream handle for subsequent calls
'-------------------------------------------------------------


errCode = beInitStream(_Offset(cfg(0)), _Offset(dwSamples), _Offset(dwMP3Buf), _Offset(hStream))
If errCode <> BE_ERR_SUCCESSFUL Then
    Print "beInitStream failed, error: "; errCode
    _MemFree cfgM
    _MemFree src
    _SndClose snd
    End
End If

Print "LAME initialized."
Print "Recommended number of 16bit samples per beEncodeChunk: "; dwSamples
Print "Minimum MP3 buffer size in bytes:                      "; dwMP3Buf
Print

'-------------------------------------------------------------
' 4) Prepare buffers:
'    - pcm()  : 16bit PCM samples for LAME (Integer)
'    - outBuf(): byte buffer for MP3 output
'-------------------------------------------------------------


ReDim pcm(0 To dwSamples - 1) As Integer ' number of 16bit samples as reported by LAME
ReDim outBuf(0 To dwMP3Buf - 1) As _Unsigned _Byte
Dim outBytes As _Unsigned Long

'-------------------------------------------------------------
' 5) Open output MP3 file
'-------------------------------------------------------------

If _FileExists(outFile) Then Kill outFile
Open outFile For Binary As #1

'-------------------------------------------------------------
' 6) Loop over the entire _MEM block with audio
'    - src.SIZE        = number of bytes in the memory block
'    - src.ELEMENTSIZE = size of one "frame" (all channels at one time)
'      e.g. 16bit stereo: ELEMENTSIZE = 4 bytes (L 16bit + R 16bit)
'          SINGLE stereo: ELEMENTSIZE = 8 bytes (L float + R float)
'
'    - framesTotal = number of frames = number of samples per channel
'    - in each iteration:
'        * compute how many 16bit samples (wordsThis) we can send
'        * unpack frames into pcm() as 16bit PCM
'        * call beEncodeChunk
'-------------------------------------------------------------

Dim framesTotal As _Integer64
framesTotal = src.SIZE \ src.ELEMENTSIZE ' number of "frames" (1 frame = 1 sample of all channels)

Dim totalWords As _Integer64
totalWords = framesTotal * ch ' number of 16bit words = frames * channels

Dim framePos As _Integer64
framePos = 0

Do While framePos < framesTotal

    Dim wordsLeft As _Integer64
    wordsLeft = (framesTotal - framePos) * ch ' how many 16bit samples remain

    Dim wordsThis As _Unsigned Long
    If wordsLeft > dwSamples Then
        wordsThis = dwSamples ' LAME-recommended chunk
    Else
        wordsThis = wordsLeft ' last, shorter chunk
    End If

    ' From wordsThis compute how many frames (1 frame = 1 sample of all channels)
    Dim framesThis As _Unsigned Long
    framesThis = wordsThis \ ch ' wordsThis is always multiple of number of channels

    ' Clear PCM buffer (entire buffer, for safety)
    Dim i As Long
    For i = 0 To dwSamples - 1
        pcm(i) = 0
    Next

    ' Convert framesThis frames to 16bit PCM stored in pcm()
    Dim f As _Unsigned Long
    Dim ofs As _Offset

    Select Case src.TYPE

        Case 260 ' 32bit SINGLE in range -1.0..+1.0
            Dim sLeft As Single, sRight As Single
            For f = 0 To framesThis - 1
                ' Frame address = base + (frame index) * frame size
                ofs = src.OFFSET + (framePos + f) * src.ELEMENTSIZE

                ' left channel
                sLeft = _MemGet(src, ofs, Single)
                pcm(f * ch) = Clamp16(CLng(sLeft * 32767!))

                If ch = 2 Then
                    ' right channel is 4 bytes further
                    sRight = _MemGet(src, ofs + 4, Single)
                    pcm(f * ch + 1) = Clamp16(CLng(sRight * 32767!))
                End If
            Next

        Case 132 ' 32bit LONG integer (signed)
            Dim lLeft As Long, lRight As Long
            For f = 0 To framesThis - 1
                ofs = src.OFFSET + (framePos + f) * src.ELEMENTSIZE

                lLeft = _MemGet(src, ofs, Long)
                pcm(f * ch) = Long32To16(lLeft)

                If ch = 2 Then
                    lRight = _MemGet(src, ofs + 4, Long)
                    pcm(f * ch + 1) = Long32To16(lRight)
                End If
            Next

        Case 130 ' 16bit INTEGER PCM (already in the desired format)
            Dim iLeft As Integer, iRight As Integer
            For f = 0 To framesThis - 1
                ofs = src.OFFSET + (framePos + f) * src.ELEMENTSIZE

                iLeft = _MemGet(src, ofs, Integer)
                pcm(f * ch) = iLeft

                If ch = 2 Then
                    iRight = _MemGet(src, ofs + 2, Integer)
                    pcm(f * ch + 1) = iRight
                End If
            Next

        Case 1153 ' 8bit UNSIGNED PCM (0..255), convert to signed 16bit
            Dim bLeft As _Unsigned _Byte, bRight As _Unsigned _Byte
            For f = 0 To framesThis - 1
                ofs = src.OFFSET + (framePos + f) * src.ELEMENTSIZE

                bLeft = _MemGet(src, ofs, _Unsigned _Byte)
                pcm(f * ch) = (CLng(bLeft) - 128) * 256 ' shift to -128..+127 and scale to 16bit

                If ch = 2 Then
                    bRight = _MemGet(src, ofs + 1, _Unsigned _Byte)
                    pcm(f * ch + 1) = (CLng(bRight) - 128) * 256
                End If
            Next

        Case Else
            Print "Unsupported _MemSound.TYPE: "; src.TYPE
            Exit Do

    End Select

    ' Encode this chunk pcm() into MP3
    outBytes = 0
    errCode = beEncodeChunk(hStream, wordsThis, _Offset(pcm(0)), _Offset(outBuf(0)), _Offset(outBytes))
    If errCode <> BE_ERR_SUCCESSFUL Then
        Print "beEncodeChunk failed, error: "; errCode
        Exit Do
    End If

    ' If any MP3 bytes were produced, write them to the file
    If outBytes > 0 Then
        WriteBytesToFile 1, outBuf(), outBytes
    End If

    ' Advance frame position
    framePos = framePos + framesThis

Loop

'-------------------------------------------------------------
' 7) Flush – finish internal LAME buffers and write remaining MP3 data
'-------------------------------------------------------------

outBytes = 0
errCode = beDeinitStream(hStream, _Offset(outBuf(0)), _Offset(outBytes))
If errCode <> BE_ERR_SUCCESSFUL Then
    Print "beDeinitStream failed, error: "; errCode
End If

If outBytes > 0 Then
    WriteBytesToFile 1, outBuf(), outBytes
End If

' Close the LAME stream
Dim closeRes As Long
closeRes = beCloseStream(hStream)

Close #1

'-------------------------------------------------------------
' 8) beWriteVBRHeader – update MP3 file with XING/Info header
'    (length, possibly VBR info, CRC, etc.)
'-------------------------------------------------------------

Dim zPath As String
zPath = outFile + Chr$(0) ' C-style null-terminated string
closeRes = beWriteVBRHeader(_Offset(zPath))

'-------------------------------------------------------------
' 9) Clean up and exit
'-------------------------------------------------------------

_MemFree cfgM
_MemFree src
_SndClose snd

Print
Print "Done, saved as: "; outFile
End

' PutU32:
' Write 32bit UNSIGNED value to a _MEM block
'---------------------------------------------
Sub PutU32 (m As _MEM, ofs As _Unsigned Long, v As _Unsigned Long)
    _MemPut m, m.OFFSET + ofs, v
End Sub

Sub PutS32 (m As _MEM, ofs As _Unsigned Long, v As Long)
    _MemPut m, m.OFFSET + ofs, v
End Sub


' BytesPerSample:
' How many bytes one sample per channel takes, based on _MemSound.TYPE
' 260  = SINGLE (32bit float)
' 132  = LONG  (32bit signed)
' 130  = INTEGER(16bit signed)
' 1153 = _Unsigned _Byte (8bit unsigned)

Function BytesPerSample% (t As Long)
    Select Case t
        Case 260, 132
            BytesPerSample = 4
        Case 130
            BytesPerSample = 2
        Case 1153
            BytesPerSample = 1
        Case Else
            BytesPerSample = 0
    End Select
End Function


' MemChannels:
' Number of channels = ELEMENTSIZE / BytesPerSample
' Examples:
'  16bit mono:  TYPE=130, ELEMENTSIZE=2  => 2/2 = 1
'  16bit stereo: TYPE=130, ELEMENTSIZE=4  => 4/2 = 2
'  SINGLE stereo:TYPE=260, ELEMENTSIZE=8  => 8/4 = 2

Function MemChannels% (m As _MEM)
    Dim bps As Integer
    bps = BytesPerSample(m.TYPE)
    If bps = 0 Then
        MemChannels = 0
    Else
        MemChannels = m.ELEMENTSIZE \ bps
    End If
End Function


' Clamp16:
' Clamp value to 16bit signed PCM range
Function Clamp16% (v As Long)
    If v > 32767 Then
        Clamp16 = 32767
    ElseIf v < -32768 Then
        Clamp16 = -32768
    Else
        Clamp16 = v
    End If
End Function


' Long32To16:
' Convert 32bit signed PCM (LONG) to 16bit
' by shifting 16 bits (divide by 65536) and preserving sign
Function Long32To16% (x As Long)
    If x < 0 Then
        Long32To16 = -((-x) \ 65536)
    Else
        Long32To16 = x \ 65536
    End If
End Function

' WriteBytesToFile:
' From byte array ou() (filled by LAME) create string chunks
' and write them to file hFile.
'
' - outLen is the number of valid bytes in ou()
' - mp3Chunk$ is fixed-length string * 65535, used as copy buffer via _MEMCOPY

Sub WriteBytesToFile (hFile As Long, ou() As _Unsigned _Byte, outLen As _Unsigned Long)
    If outLen = 0 Then Exit Sub

    Dim mAry As _MEM, mStr As _MEM
    mAry = _Mem(ou(0))
    mStr = _Mem(mp3Chunk)

    Dim i As _Unsigned Long, n As _Unsigned Long
    Dim chunk As String

    i = 0
    Do While i < outLen
        n = outLen - i
        If n > Len(mp3Chunk) Then n = Len(mp3Chunk)

        ' Copy n bytes from ou() starting at offset i into mp3Chunk$
        _MemCopy mAry, mAry.OFFSET + i, n To mStr, mStr.OFFSET

        ' Create string of exact length n and write it to file
        chunk = Left$(mp3Chunk, n)
        Put #hFile, , chunk

        i = i + n
    Loop

    _MemFree mStr
    _MemFree mAry
End Sub



Attached Files
.zip   MakeMp3.zip (Size: 519.66 KB / Downloads: 17)


Reply
#7
Here’s something for the audiophiles out there. I personally use this format because it offers the absolute best sound quality. FLAC is a lossless compression format widely used in professional studios. Since QB64PE can now play it, you also have the option to save audio in this format. It’s open source and license-free.
In terms of storage, FLAC is about 60% the size of a WAV file, yet it offers significantly better sound quality than ADPCM.

Zip file contains libFLAC32.DLL file, libFLAC64.DLL file, bas file, MOD music file and txt info file.


Code: (Select All)

Option _Explicit

Const FLAC__STREAM_ENCODER_INIT_STATUS_OK = 0
Const FLAC_COMPRESSION_LEVEL = 6 ' 0 = fastest, 8/12 = highest compression (depending on libFLAC version)
Const FLAC_CHUNK_FRAMES = 4096 ' number of sample frames per encode call


$If 64BIT Then
    Declare Dynamic Library "libFLAC64"
        ' FLAC__StreamEncoder* FLAC__stream_encoder_new(void);
        Function FLAC_New%& Alias "FLAC__stream_encoder_new" ()

        ' void FLAC__stream_encoder_delete(FLAC__StreamEncoder *encoder);
        Sub FLAC_Del Alias "FLAC__stream_encoder_delete" (ByVal enc As _Offset)

        ' FLAC__bool FLAC__stream_encoder_set_channels(FLAC__StreamEncoder *encoder, unsigned channels);
        Function FLAC_SetChannels& Alias "FLAC__stream_encoder_set_channels" (ByVal enc As _Offset, ByVal channels As _Unsigned Long)

        ' FLAC__bool FLAC__stream_encoder_set_sample_rate(FLAC__StreamEncoder *encoder, unsigned sample_rate);
        Function FLAC_SetSampleRate& Alias "FLAC__stream_encoder_set_sample_rate" (ByVal enc As _Offset, ByVal sample_rate As _Unsigned Long)

        ' FLAC__bool FLAC__stream_encoder_set_bits_per_sample(FLAC__StreamEncoder *encoder, unsigned bits_per_sample);
        Function FLAC_SetBitsPerSample& Alias "FLAC__stream_encoder_set_bits_per_sample" (ByVal enc As _Offset, ByVal bits As _Unsigned Long)

        ' FLAC__bool FLAC__stream_encoder_set_compression_level(FLAC__StreamEncoder *encoder, unsigned level);
        Function FLAC_SetCompressionLevel& Alias "FLAC__stream_encoder_set_compression_level" (ByVal enc As _Offset, ByVal level As _Unsigned Long)

        ' FLAC__StreamEncoderInitStatus FLAC__stream_encoder_init_file(...);
        Function FLAC_InitFile& Alias "FLAC__stream_encoder_init_file" (ByVal enc As _Offset, ByVal filename As _Offset, ByVal progress_cb As _Offset, ByVal client_data As _Offset)

        ' FLAC__bool FLAC__stream_encoder_process_interleaved(..., const FLAC__int32 buffer[], unsigned samples);
        Function FLAC_ProcessInterleaved& Alias "FLAC__stream_encoder_process_interleaved" (ByVal enc As _Offset, ByVal buffer As _Offset, ByVal samples As _Unsigned Long)

        ' FLAC__bool FLAC__stream_encoder_finish(FLAC__StreamEncoder *encoder);
        Function FLAC_Finish& Alias "FLAC__stream_encoder_finish" (ByVal enc As _Offset)
    End Declare
$Else
    Declare Dynamic Library "libFLAC32"
        ' FLAC__StreamEncoder* FLAC__stream_encoder_new(void);
        Function FLAC_New%& Alias "FLAC__stream_encoder_new" ()

        ' void FLAC__stream_encoder_delete(FLAC__StreamEncoder *encoder);
        Sub FLAC_Del Alias "FLAC__stream_encoder_delete" (ByVal enc As _Offset)

        ' FLAC__bool FLAC__stream_encoder_set_channels(FLAC__StreamEncoder *encoder, unsigned channels);
        Function FLAC_SetChannels& Alias "FLAC__stream_encoder_set_channels" (ByVal enc As _Offset, ByVal channels As _Unsigned Long)

        ' FLAC__bool FLAC__stream_encoder_set_sample_rate(FLAC__StreamEncoder *encoder, unsigned sample_rate);
        Function FLAC_SetSampleRate& Alias "FLAC__stream_encoder_set_sample_rate" (ByVal enc As _Offset, ByVal sample_rate As _Unsigned Long)

        ' FLAC__bool FLAC__stream_encoder_set_bits_per_sample(FLAC__StreamEncoder *encoder, unsigned bits_per_sample);
        Function FLAC_SetBitsPerSample& Alias "FLAC__stream_encoder_set_bits_per_sample" (ByVal enc As _Offset, ByVal bits As _Unsigned Long)

        ' FLAC__bool FLAC__stream_encoder_set_compression_level(FLAC__StreamEncoder *encoder, unsigned level);
        Function FLAC_SetCompressionLevel& Alias "FLAC__stream_encoder_set_compression_level" (ByVal enc As _Offset, ByVal level As _Unsigned Long)

        ' FLAC__StreamEncoderInitStatus FLAC__stream_encoder_init_file(...);
        Function FLAC_InitFile& Alias "FLAC__stream_encoder_init_file" (ByVal enc As _Offset, ByVal filename As _Offset, ByVal progress_cb As _Offset, ByVal client_data As _Offset)

        ' FLAC__bool FLAC__stream_encoder_process_interleaved(..., const FLAC__int32 buffer[], unsigned samples);
        Function FLAC_ProcessInterleaved& Alias "FLAC__stream_encoder_process_interleaved" (ByVal enc As _Offset, ByVal buffer As _Offset, ByVal samples As _Unsigned Long)

        ' FLAC__bool FLAC__stream_encoder_finish(FLAC__StreamEncoder *encoder);
        Function FLAC_Finish& Alias "FLAC__stream_encoder_finish" (ByVal enc As _Offset)
    End Declare
$End If


Dim inFile$, outFile$
inFile$ = "g7.mod" ' input audio file (any format _SndOpen can load)
outFile$ = "out.flac" ' output FLAC filename

Dim snd As Long
snd = _SndOpen(inFile$)
If snd = 0 Then
    Print "Cannot open input sound file: "; inFile$
    End
End If

' Get raw sample data from QB64PE mixer
Dim src As _MEM
src = _MemSound(snd, 0)
If src.SIZE = 0 Then
    Print "_MemSound returned SIZE = 0 (no audio data)."
    _SndClose snd
    End
End If

Dim ch%: ch% = MemChannels(src)
If ch% < 1 Then
    Print "Unable to detect channel count from _MEM (TYPE="; src.TYPE; ", ELEMENTSIZE="; src.ELEMENTSIZE; ")."
    _MemFree src
    _SndClose snd
    End
End If

Dim sr As _Unsigned Long
sr = _SndRate ' QB64PE global mixer rate; _MemSound data are at this rate

Print "Input file: "; inFile$
Print "Channels : "; ch%
Print "SampleRate: "; sr


Dim enc As _Offset
Dim encInited%: encInited% = 0
Dim ok%: ok% = -1 ' assume success until some step fails

enc = FLAC_New
If enc = 0 Then
    Print "FLAC__stream_encoder_new returned NULL."
    ok% = 0
    GoTo EncodeCleanup
End If

' Required basic parameters: channels, bits per sample, sample rate
If FLAC_SetChannels(enc, ch%) = 0 Then
    Print "FLAC__stream_encoder_set_channels failed."
    ok% = 0
    GoTo EncodeCleanup
End If

' We always encode as 16-bit FLAC, regardless of source format
If FLAC_SetBitsPerSample(enc, 16) = 0 Then
    Print "FLAC__stream_encoder_set_bits_per_sample(16) failed."
    ok% = 0
    GoTo EncodeCleanup
End If

If FLAC_SetSampleRate(enc, sr) = 0 Then
    Print "FLAC__stream_encoder_set_sample_rate("; sr; ") failed."
    ok% = 0
    GoTo EncodeCleanup
End If

' Optional: compression level (0 = fastest, higher = better compression, slower)
If FLAC_SetCompressionLevel(enc, FLAC_COMPRESSION_LEVEL) = 0 Then
    Print "FLAC__stream_encoder_set_compression_level("; FLAC_COMPRESSION_LEVEL; ") failed."
    ok% = 0
    GoTo EncodeCleanup
End If


' Initialize encoder with output filename
Dim outNameZ As String
outNameZ = outFile$ + Chr$(0) ' C-style zero-terminated filename string

Dim initStatus&: initStatus& = FLAC_InitFile(enc, _Offset(outNameZ), 0, 0)
If initStatus& <> FLAC__STREAM_ENCODER_INIT_STATUS_OK Then
    Print "FLAC__stream_encoder_init_file failed, status="; initStatus&
    ok% = 0
    GoTo EncodeCleanup
End If

encInited% = -1

Dim framesTotal&&
framesTotal&& = src.SIZE \ src.ELEMENTSIZE ' number of sample frames (all channels together)

Dim framesLeft&&: framesLeft&& = framesTotal&&
Dim posFrame&&: posFrame&& = 0

' Buffer for interleaved FLAC__int32 samples (16-bit data stored in 32-bit container)
Dim flacBuf As Long
ReDim flacBuf(0 To FLAC_CHUNK_FRAMES * ch% - 1) As Long

Print "Total frames: "; framesTotal&&


Dim As Long framesThis, frameIdx
Dim BaseOfs As _Offset

Do While framesLeft&& > 0
    framesThis& = FLAC_CHUNK_FRAMES
    If framesThis& > framesLeft&& Then framesThis& = framesLeft&&



    ' Convert QB64PE internal format to signed 16-bit samples in 32-bit container (FLAC__int32)
    For frameIdx& = 0 To framesThis& - 1
        BaseOfs = src.OFFSET + (posFrame&& + frameIdx&) * src.ELEMENTSIZE
        Select Case src.TYPE
            Case 260
                ' 32-bit float samples, range roughly [-1.0, +1.0]
                Dim s As Single
                Dim chan%
                For chan% = 0 To ch% - 1
                    s = _MemGet(src, BaseOfs + chan% * 4, Single)
                    flacBuf(frameIdx& * ch% + chan%) = ClampTo16(CLng(s * 32767!))
                Next

            Case 132
                ' 32-bit signed PCM
                Dim l As Long
                Dim chan2%
                For chan2% = 0 To ch% - 1
                    l = _MemGet(src, BaseOfs + chan2% * 4, Long)
                    flacBuf(frameIdx& * ch% + chan2%) = Long32To16(l)
                Next

            Case 130
                ' 16-bit signed PCM
                Dim iSS As Integer
                Dim chan3%
                For chan3% = 0 To ch% - 1
                    iSS = _MemGet(src, BaseOfs + chan3% * 2, Integer)
                    flacBuf(frameIdx& * ch% + chan3%) = iSS
                Next

            Case 1153
                ' 8-bit unsigned PCM (0..255) -> convert to signed 16-bit
                Dim b As _Unsigned _Byte
                Dim chan4%
                For chan4% = 0 To ch% - 1
                    b = _MemGet(src, BaseOfs + chan4%, _Unsigned _Byte)
                    flacBuf(frameIdx& * ch% + chan4%) = (CLng(b) - 128) * 256
                Next

            Case Else
                Print "Unsupported _MemSound TYPE: "; src.TYPE
                ok% = 0
                GoTo EncodeCleanup
        End Select
    Next

    ' Send converted samples to FLAC encoder
    If FLAC_ProcessInterleaved(enc, _Offset(flacBuf(0)), framesThis&) = 0 Then
        Print "FLAC__stream_encoder_process_interleaved failed."
        ok% = 0
        GoTo EncodeCleanup
    End If

    posFrame&& = posFrame&& + framesThis&
    framesLeft&& = framesLeft&& - framesThis&
Loop

EncodeCleanup:

If encInited% Then
    ' Finish encoder; this also finalizes and closes the output file internally
    Dim finRes&: finRes& = FLAC_Finish(enc)
    If finRes& = 0 Then
        Print "Warning: FLAC__stream_encoder_finish reported failure."
        ok% = 0
    End If
End If

If enc <> 0 Then FLAC_Del enc

_MemFree src
_SndClose snd

If ok% Then
    Print "FLAC encoding completed successfully: "; outFile$
Else
    Print "FLAC encoding ended with an error."
End If

End

Function BytesPerSample% (t As Long)
    ' Returns number of bytes for a single channel sample
    Select Case t
        Case 260, 132: BytesPerSample = 4 ' SINGLE, LONG
        Case 130: BytesPerSample = 2 ' 16-bit INTEGER
        Case 1153: BytesPerSample = 1 ' 8-bit unsigned
        Case Else: BytesPerSample = 0
    End Select
End Function

Function MemChannels% (m As _MEM)
    ' Derive channel count from TYPE and ELEMENTSIZE
    Dim bps%: bps% = BytesPerSample(m.TYPE)
    If bps% = 0 Then MemChannels = 0: Exit Function
    MemChannels = m.ELEMENTSIZE \ bps%
End Function

Function ClampTo16& (v As Long)
    ' Clamp a value into 16-bit signed range and keep it as 32-bit container (FLAC__int32)
    If v > 32767 Then
        ClampTo16 = 32767
    ElseIf v < -32768 Then
        ClampTo16 = -32768
    Else
        ClampTo16 = v
    End If
End Function

Function Long32To16& (x As Long)
    ' Convert 32-bit signed PCM to 16-bit range, with truncating toward zero
    If x < 0 Then
        Long32To16 = -((-x) \ 65536)
    Else
        Long32To16 = x \ 65536
    End If
End Function



Attached Files
.zip   makeFLAC.zip (Size: 563.35 KB / Downloads: 22)


Reply
#8
MakeOGG

The program uses the Ogg Vorbis libraries from the Xiph.Org Foundation.

These Ogg libraries were compiled from:

libogg-1.3.6 and libvorbis-1.3.7 C source code, downloaded from:

 https://downloads.xiph.org/releases/ogg/...3.6.tar.xz
 https://downloads.xiph.org/releases/vorb...3.7.tar.xz

New Feature: Saving to OGG 

Here is a new update: I have added support for saving to OGG format. This was a challenging project, but the results are interesting.

Important Notes:

32-bit Version: This includes 5 DLL files. One is compiled but unused (related to playback, seeking and metadata), so you don't need it because QB64PE handles that natively.

The qboggvorbis.dll file acts as a wrapper. This is necessary because the 32-bit DLL calling conventions differ from those used by QB64PE. 
This wrapper resolves that compatibility issue (similar to the live visualization tool discussed earlier).

Note: vorbisfile.dll is included but not used here (it is strictly for playback, seeking, and metadata).

64-bit Version: This includes 4 DLL files. No wrapper is needed here; it works out of the box. Again, vorbisfile.dll is not used.

WARNING: Do NOT rename any of the DLL files. The internal dependencies are hardcoded in the source code. Renaming even a single DLL will break the linkage and cause "dynamic library not found" errors.

Folder Structure: I attempted to organize the 32-bit and 64-bit versions into separate subfolders within the program, but I couldn't get the path referencing to work properly. If anyone knows how to achieve this, I’d appreciate the tip. For now, the 32-bit and 64-bit versions are in separate folders. Since the filenames cannot be changed, please be careful not to mix them up.

Future Plans & Feedback: Finally, I have a question: What else would you be interested in seeing? I'm open to suggestions.

Thanks to this thread, I plan to rework my SaveSound library to consolidate everything nicely. There are a few more formats QB64PE can write directly (like AIFF, AIFC). Some others might require a full tracker implementation to be useful, or I need to research them further.

Zip file contains: 
32bit version: qboggvorbis.dll warapper, wrapper source code in C, wrapper DEF file, mod music file, BAS file, ogg.dll, vorbis.dll, vorbisenc.dll, vorbisfile.dll.
64bit version: the same BAS source code, ogg.dll, vorbis.dll, vorbisenc.dll, vorbisfile.dll, s3m music file. 



Code: (Select All)

Option _Explicit

' ============================================================
' QB64PE OGG/Vorbis encoder (DLL): _MemSound() -> .OGG
'
' 64-bit: calls original DLLs directly:
'  ogg.dll, vorbis.dll, vorbisenc.dll  (DO NOT RENAME)
'
' 32-bit: use stdcall bridge wrapper:
'  qboggvorbis.dll  (exports same symbol names)
'  + original ogg.dll, vorbis.dll, vorbisenc.dll next to EXE
' ============================================================

' ---------------- USER SETTINGS ----------------
Const INPUT_FILE$ = "07.mp3" ' Any format _SndOpen can load
Const OUTPUT_OGG$ = "out.ogg"
Const VORBIS_QUALITY! = 0.30! ' VBR quality ~0.3..0.6 typical
Const CHUNK_FRAMES& = 1024
Const MAX_CHANNELS& = 16



$If 32BIT Then
    Const PTR_BYTES& = 4
    Const OGG_PAGE_BYTES& = 16
$Else
    Const PTR_BYTES& = 8
    Const OGG_PAGE_BYTES& = 32
$End If

' ---------------- DLL DECLARES ----------------
$If 32BIT Then
    ' x86: stdcall bridge (wrapper)
    Declare Dynamic Library "qboggvorbis"
        Function ogg_stream_init& Alias "ogg_stream_init" (ByVal os As _Offset, ByVal serialno As Long)
        Function ogg_stream_packetin& Alias "ogg_stream_packetin" (ByVal os As _Offset, ByVal op As _Offset)
        Function ogg_stream_pageout& Alias "ogg_stream_pageout" (ByVal os As _Offset, ByVal og As _Offset)
        Function ogg_stream_flush& Alias "ogg_stream_flush" (ByVal os As _Offset, ByVal og As _Offset)
        Function ogg_stream_clear& Alias "ogg_stream_clear" (ByVal os As _Offset)

        Sub vorbis_info_init Alias "vorbis_info_init" (ByVal vi As _Offset)
        Sub vorbis_info_clear Alias "vorbis_info_clear" (ByVal vi As _Offset)

        Sub vorbis_comment_init Alias "vorbis_comment_init" (ByVal vc As _Offset)
        Sub vorbis_comment_add_tag Alias "vorbis_comment_add_tag" (ByVal vc As _Offset, ByVal tag As _Offset, ByVal contents As _Offset)
        Sub vorbis_comment_clear Alias "vorbis_comment_clear" (ByVal vc As _Offset)

        Function vorbis_analysis_init& Alias "vorbis_analysis_init" (ByVal vd As _Offset, ByVal vi As _Offset)
        Function vorbis_block_init& Alias "vorbis_block_init" (ByVal vd As _Offset, ByVal vb As _Offset)
        Function vorbis_analysis_headerout& Alias "vorbis_analysis_headerout" (ByVal vd As _Offset, ByVal vc As _Offset, ByVal op As _Offset, ByVal op_comm As _Offset, ByVal op_code As _Offset)

        Function vorbis_analysis_buffer%& Alias "vorbis_analysis_buffer" (ByVal vd As _Offset, ByVal vals As Long) ' returns float** as pointer
        Function vorbis_analysis_wrote& Alias "vorbis_analysis_wrote" (ByVal vd As _Offset, ByVal vals As Long)
        Function vorbis_analysis_blockout& Alias "vorbis_analysis_blockout" (ByVal vd As _Offset, ByVal vb As _Offset)
        Function vorbis_analysis& Alias "vorbis_analysis" (ByVal vb As _Offset, ByVal op As _Offset) ' op may be NULL
        Function vorbis_bitrate_addblock& Alias "vorbis_bitrate_addblock" (ByVal vb As _Offset)
        Function vorbis_bitrate_flushpacket& Alias "vorbis_bitrate_flushpacket" (ByVal vd As _Offset, ByVal op As _Offset)

        Function vorbis_block_clear& Alias "vorbis_block_clear" (ByVal vb As _Offset)
        Sub vorbis_dsp_clear Alias "vorbis_dsp_clear" (ByVal vd As _Offset)

        Function vorbis_encode_init_vbr& Alias "vorbis_encode_init_vbr" (ByVal vi As _Offset, ByVal channels As Long, ByVal rate As Long, ByVal base_quality As Single)
    End Declare
$Else
    ' x64: direct calls to original DLLs
    Declare Dynamic Library "ogg"
        Function ogg_stream_init& Alias "ogg_stream_init" (ByVal os As _Offset, ByVal serialno As Long)
        Function ogg_stream_packetin& Alias "ogg_stream_packetin" (ByVal os As _Offset, ByVal op As _Offset)
        Function ogg_stream_pageout& Alias "ogg_stream_pageout" (ByVal os As _Offset, ByVal og As _Offset)
        Function ogg_stream_flush& Alias "ogg_stream_flush" (ByVal os As _Offset, ByVal og As _Offset)
        Function ogg_stream_clear& Alias "ogg_stream_clear" (ByVal os As _Offset)
    End Declare

    Declare Dynamic Library "vorbis"
        Sub vorbis_info_init Alias "vorbis_info_init" (ByVal vi As _Offset)
        Sub vorbis_info_clear Alias "vorbis_info_clear" (ByVal vi As _Offset)

        Sub vorbis_comment_init Alias "vorbis_comment_init" (ByVal vc As _Offset)
        Sub vorbis_comment_add_tag Alias "vorbis_comment_add_tag" (ByVal vc As _Offset, ByVal tag As _Offset, ByVal contents As _Offset)
        Sub vorbis_comment_clear Alias "vorbis_comment_clear" (ByVal vc As _Offset)

        Function vorbis_analysis_init& Alias "vorbis_analysis_init" (ByVal vd As _Offset, ByVal vi As _Offset)
        Function vorbis_block_init& Alias "vorbis_block_init" (ByVal vd As _Offset, ByVal vb As _Offset)
        Function vorbis_analysis_headerout& Alias "vorbis_analysis_headerout" (ByVal vd As _Offset, ByVal vc As _Offset, ByVal op As _Offset, ByVal op_comm As _Offset, ByVal op_code As _Offset)

        Function vorbis_analysis_buffer%& Alias "vorbis_analysis_buffer" (ByVal vd As _Offset, ByVal vals As Long)
        Function vorbis_analysis_wrote& Alias "vorbis_analysis_wrote" (ByVal vd As _Offset, ByVal vals As Long)
        Function vorbis_analysis_blockout& Alias "vorbis_analysis_blockout" (ByVal vd As _Offset, ByVal vb As _Offset)
        Function vorbis_analysis& Alias "vorbis_analysis" (ByVal vb As _Offset, ByVal op As _Offset)
        Function vorbis_bitrate_addblock& Alias "vorbis_bitrate_addblock" (ByVal vb As _Offset)
        Function vorbis_bitrate_flushpacket& Alias "vorbis_bitrate_flushpacket" (ByVal vd As _Offset, ByVal op As _Offset)

        Function vorbis_block_clear& Alias "vorbis_block_clear" (ByVal vb As _Offset)
        Sub vorbis_dsp_clear Alias "vorbis_dsp_clear" (ByVal vd As _Offset)
    End Declare

    Declare Dynamic Library "vorbisenc"
        Function vorbis_encode_init_vbr& Alias "vorbis_encode_init_vbr" (ByVal vi As _Offset, ByVal channels As Long, ByVal rate As Long, ByVal base_quality As Single)
    End Declare
$End If


' ============================================================
' MAIN
' ============================================================
Dim snd As Long
snd = _SndOpen(INPUT_FILE$)
If snd = 0 Then
    Print "Cannot open input: "; INPUT_FILE$
    End
End If

Dim src As _MEM
src = _MemSound(snd, 0)
If src.SIZE = 0 Then
    Print "_MemSound returned SIZE=0."
    _SndClose snd
    End
End If

Dim ch As Long: ch = MemChannels(src)
If ch < 1 Or ch > MAX_CHANNELS& Then
    Print "Bad channel count: "; ch; " (TYPE="; src.TYPE; " ELEMENTSIZE="; src.ELEMENTSIZE; ")"
    _MemFree src
    _SndClose snd
    End
End If

Dim sr As Long: sr = _SndRate

Print "Input    : "; INPUT_FILE$
Print "Output    : "; OUTPUT_OGG$
Print "Channels  : "; ch
Print "SampleRate: "; sr
Print "Quality  : "; VORBIS_QUALITY!

If SaveMemSoundAsOggVorbis(src, ch, sr, OUTPUT_OGG$, VORBIS_QUALITY!) Then
    Print "OK."
Else
    Print "FAILED."
End If

_MemFree src
_SndClose snd
End


' ============================================================
' Core encoder: uses opaque memory blocks for lib structs
' ============================================================
Function SaveMemSoundAsOggVorbis% (src As _MEM, ch As Long, sr As Long, outFile$, quality As Single)
    SaveMemSoundAsOggVorbis = 0

    ' Oversized opaque blocks (must be >= real struct sizes)
    Dim vi As _MEM: vi = _MemNew(256)
    Dim vc As _MEM: vc = _MemNew(2048)
    Dim vd As _MEM: vd = _MemNew(16384)
    Dim vb As _MEM: vb = _MemNew(8192)
    Dim os As _MEM: os = _MemNew(8192)

    Dim op As _MEM: op = _MemNew(512)
    Dim op_comm As _MEM: op_comm = _MemNew(512)
    Dim op_code As _MEM: op_code = _MemNew(512)

    Dim og As _MEM: og = _MemNew(OGG_PAGE_BYTES&)

    ' Zero-fill blocks (safe)
    _MemFill vi, vi.OFFSET, vi.SIZE, 0 As _Unsigned _Byte
    _MemFill vc, vc.OFFSET, vc.SIZE, 0 As _Unsigned _Byte
    _MemFill vd, vd.OFFSET, vd.SIZE, 0 As _Unsigned _Byte
    _MemFill vb, vb.OFFSET, vb.SIZE, 0 As _Unsigned _Byte
    _MemFill os, os.OFFSET, os.SIZE, 0 As _Unsigned _Byte
    _MemFill op, op.OFFSET, op.SIZE, 0 As _Unsigned _Byte
    _MemFill op_comm, op_comm.OFFSET, op_comm.SIZE, 0 As _Unsigned _Byte
    _MemFill op_code, op_code.OFFSET, op_code.SIZE, 0 As _Unsigned _Byte
    _MemFill og, og.OFFSET, og.SIZE, 0 As _Unsigned _Byte

    Dim fh As Integer
    fh = FreeFile
    Open outFile$ For Binary As #fh

    Dim ok As Integer: ok = -1
    Dim serial As Long
    Randomize Timer
    serial = Int(Rnd * 2147483647)

    ' --- Vorbis init ---
    vorbis_info_init vi.OFFSET

    If vorbis_encode_init_vbr(vi.OFFSET, ch, sr, quality) <> 0 Then
        Print "vorbis_encode_init_vbr failed (this is where x86 calling-convention mismatch usually kills the process)."
        ok = 0
        GoTo Cleanup
    End If

    vorbis_comment_init vc.OFFSET
    AddTag vc, "ENCODER", "QB64PE"
    AddTag vc, "TITLE", INPUT_FILE$

    If vorbis_analysis_init(vd.OFFSET, vi.OFFSET) <> 0 Then
        Print "vorbis_analysis_init failed."
        ok = 0
        GoTo Cleanup
    End If

    If vorbis_block_init(vd.OFFSET, vb.OFFSET) <> 0 Then
        Print "vorbis_block_init failed."
        ok = 0
        GoTo Cleanup
    End If

    ' --- Ogg stream init ---
    If ogg_stream_init(os.OFFSET, serial) <> 0 Then
        Print "ogg_stream_init failed."
        ok = 0
        GoTo Cleanup
    End If

    ' --- Header packets ---
    If vorbis_analysis_headerout(vd.OFFSET, vc.OFFSET, op.OFFSET, op_comm.OFFSET, op_code.OFFSET) <> 0 Then
        Print "vorbis_analysis_headerout failed."
        ok = 0
        GoTo Cleanup
    End If

    If ogg_stream_packetin(os.OFFSET, op.OFFSET) <> 0 Then ok = 0: GoTo Cleanup
    If ogg_stream_packetin(os.OFFSET, op_comm.OFFSET) <> 0 Then ok = 0: GoTo Cleanup
    If ogg_stream_packetin(os.OFFSET, op_code.OFFSET) <> 0 Then ok = 0: GoTo Cleanup

    Do While ogg_stream_flush(os.OFFSET, og.OFFSET) <> 0
        WriteOggPageFromMem fh, og
    Loop

    ' --- Audio frames ---
    Dim framesTotal As _Integer64
    framesTotal = src.SIZE \ src.ELEMENTSIZE

    Dim poss As _Integer64: poss = 0
    Dim framesThis As Long
    Dim bufPP As _Offset
    Dim mp As _MEM
    Dim c As Long, f As Long
    Dim N As Long

    Dim chanPtr As _Offset
    ReDim chanPtr(0 To ch - 1) As _Offset
    Dim mchan As _MEM
    Dim baseOfs As _Offset
    Dim s As Single

    Do While poss < framesTotal
        framesThis = CHUNK_FRAMES&
        If poss + framesThis > framesTotal Then framesThis = framesTotal - poss

        bufPP = vorbis_analysis_buffer(vd.OFFSET, framesThis)
        If bufPP = 0 Then
            Print "vorbis_analysis_buffer returned NULL."
            ok = 0
            GoTo Cleanup
        End If
        mp = _Mem(bufPP, ch * PTR_BYTES&)
        For c = 0 To ch - 1
            chanPtr(c) = _MemGet(mp, mp.OFFSET + c * PTR_BYTES&, _Offset)
        Next
        _MemFree mp
        For c = 0 To ch - 1
            mchan = _Mem(chanPtr(c), framesThis * 4)
            For f = 0 To framesThis - 1
                baseOfs = src.OFFSET + (poss + f) * src.ELEMENTSIZE
                s = SampleToFloat!(src, baseOfs, c)
                If s > 1! Then s = 1!
                If s < -1! Then s = -1!
                _MemPut mchan, mchan.OFFSET + f * 4, s
            Next
            _MemFree mchan
        Next

        If vorbis_analysis_wrote(vd.OFFSET, framesThis) <> 0 Then
            Print "vorbis_analysis_wrote failed."
            ok = 0
            GoTo Cleanup
        End If

        Do While vorbis_analysis_blockout(vd.OFFSET, vb.OFFSET) = 1
            N = vorbis_analysis(vb.OFFSET, 0)
            N = vorbis_bitrate_addblock(vb.OFFSET)

            Do While vorbis_bitrate_flushpacket(vd.OFFSET, op.OFFSET) = 1
                N = ogg_stream_packetin(os.OFFSET, op.OFFSET)
                Do While ogg_stream_pageout(os.OFFSET, og.OFFSET) <> 0
                    WriteOggPageFromMem fh, og
                Loop
            Loop
        Loop

        poss = poss + framesThis
    Loop

    ' End-of-stream
    N = vorbis_analysis_wrote(vd.OFFSET, 0)
    Do While vorbis_analysis_blockout(vd.OFFSET, vb.OFFSET) = 1
        N = vorbis_analysis(vb.OFFSET, 0)
        N = vorbis_bitrate_addblock(vb.OFFSET)

        Do While vorbis_bitrate_flushpacket(vd.OFFSET, op.OFFSET) = 1
            N = ogg_stream_packetin(os.OFFSET, op.OFFSET)
            Do While ogg_stream_flush(os.OFFSET, og.OFFSET) <> 0
                WriteOggPageFromMem fh, og
            Loop
        Loop
    Loop

    Cleanup:
    Close #fh

    N = ogg_stream_clear(os.OFFSET)
    N = vorbis_block_clear(vb.OFFSET)
    Call vorbis_dsp_clear(vd.OFFSET)
    Call vorbis_comment_clear(vc.OFFSET)
    Call vorbis_info_clear(vi.OFFSET)

    _MemFree og
    _MemFree op_code
    _MemFree op_comm
    _MemFree op
    _MemFree os
    _MemFree vb
    _MemFree vd
    _MemFree vc
    _MemFree vi

    If ok Then SaveMemSoundAsOggVorbis = -1
End Function



' Add Vorbis comment tag (ASCIIZ)
Sub AddTag (vc As _MEM, tag$, value$)
    Dim tZ$, vZ$
    tZ$ = tag$ + Chr$(0)
    vZ$ = value$ + Chr$(0)
    vorbis_comment_add_tag vc.OFFSET, _Offset(tZ$), _Offset(vZ$)
End Sub



' Convert one interleaved sample to float [-1..+1]
Function SampleToFloat! (src As _MEM, baseOfs As _Offset, c As Long)
    Select Case src.TYPE
        Case 260
            SampleToFloat! = _MemGet(src, baseOfs + c * 4, Single)

        Case 132
            Dim l As Long
            l = _MemGet(src, baseOfs + c * 4, Long)
            SampleToFloat! = l / 2147483648!

        Case 130
            Dim i16 As Integer
            i16 = _MemGet(src, baseOfs + c * 2, Integer)
            SampleToFloat! = i16 / 32768!

        Case 1153
            Dim b As _Unsigned _Byte
            b = _MemGet(src, baseOfs + c, _Unsigned _Byte)
            SampleToFloat! = (CSng(b) - 128!) / 128!

        Case Else
            SampleToFloat! = 0!
    End Select
End Function



' Write ogg_page (read fields from opaque ogg_page memory)
Sub WriteOggPageFromMem (fh As Integer, og As _MEM)
    Dim headerPtr As _Offset, bodyPtr As _Offset
    Dim headerLen As Long, bodyLen As Long

    $If 32BIT Then
        headerPtr = _MemGet(og, og.OFFSET + 0, _Offset)
        headerLen = _MemGet(og, og.OFFSET + 4, Long)
        bodyPtr = _MemGet(og, og.OFFSET + 8, _Offset)
        bodyLen = _MemGet(og, og.OFFSET + 12, Long)
    $Else
    headerPtr = _MemGet(og, og.OFFSET + 0, _Offset)
    headerLen = _MemGet(og, og.OFFSET + 8, Long)
    bodyPtr = _MemGet(og, og.OFFSET + 16, _Offset)
    bodyLen = _MemGet(og, og.OFFSET + 24, Long)
    $End If

    If headerLen > 0 Then
        Dim s$
        s$ = Space$(headerLen)
        Dim mh As _MEM
        mh = _Mem(headerPtr, headerLen)
        _MemGet mh, mh.OFFSET, s$
        _MemFree mh
        Put #fh, , s$
    End If

    If bodyLen > 0 Then
        Dim b$
        b$ = Space$(bodyLen)
        Dim mb As _MEM
        mb = _Mem(bodyPtr, bodyLen)
        _MemGet mb, mb.OFFSET, b$
        _MemFree mb
        Put #fh, , b$
    End If
End Sub



' Detect channels from _MEMSOUND format (interleaved)
Function BytesPerSample% (t As Long)
    Select Case t
        Case 260, 132: BytesPerSample% = 4
        Case 130: BytesPerSample% = 2
        Case 1153: BytesPerSample% = 1
        Case Else: BytesPerSample% = 0
    End Select
End Function

Function MemChannels% (m As _MEM)
    Dim bps As Long: bps = BytesPerSample%(m.TYPE)
    If bps = 0 Then MemChannels% = 0: Exit Function
    MemChannels% = m.ELEMENTSIZE \ bps
End Function


Attached Files
.zip   MakeOGG.zip (Size: 739.94 KB / Downloads: 13)


Reply
#9
Very nice work Petr,
I tried to play an avi video in qb64
but I couldn't find a stable code.
I found the code below but it doesn't
show the video properly (flickering).
The only way to avoid flickering is to play
the avi in another window.
This happens in QB64, in QB64PE it doesn't
show anyhting.
I don't need avi playing, it's just for learning,
not something important. I have windows7-64
Does anyone know something?

Code: (Select All)

Const ACS_TRANSPARENT = 2
Const ACM_OPEN = 1124
Const ACM_PLAY = 1125
Const ACM_STOP = 1126
Const WS_VISIBLE = &H10000000
Const WS_CHILD = &H40000000

handle& = _NewImage(800, 600, 32)
Screen handle&

_Title "QB64 Video"
hwnd& = _WindowHandle 'FindWindow(0, "QB64 Video" + CHR$(0))

Dim CommandString As String, Filename As String, vbNullString As String
Dim RetVal As Long
Filename = "1.avi"

ReturnString$ = Space$(255)
ErrorString$ = Space$(255)

Declare Dynamic Library "WINMM"
    Function mciSendStringA% (lpstrCommand As String, lpstrReturnString As String, ByVal uReturnLength As Integer, ByVal hwndCallback As Integer)
    Function mciGetErrorStringA% (ByVal dwError As Integer, lpstrBuffer As String, ByVal uLength As Integer)
End Declare

'a% = mciSendStringA%("open " + Filename + " alias avifile parent " + Str$(hwnd&) + " style child", ReturnString$, Len(ReturnString$), 0) ' ==== OPENS ANOTHER WINDOW IN QB64, ==== DOES NOTHING IN QB64PE
a% = mciSendStringA%("open " + Filename + " style popup", ReturnString$, Len(ReturnString$), 0) '==== FLICKERING IN QB64, =====SHOWS NOTHING IN QB64PE

b% = mciSendStringA%("window " + Filename$ + " handle " + Str$(hwnd&), ReturnString$, Len(ReturnString$), 0)

c% = mciSendStringA%("play " + Filename, ReturnString$, Len(ReturnString$), 0)

Do
    _Delay .1
Loop Until InKey$ <> ""
Reply
#10
Hi 2112. Bad data types, bad callings.

This is reapired version, it play in QB64PE 4.2.0 and also in QB64 1.4....

Code: (Select All)

Option _Explicit

Declare Dynamic Library "winmm"
    Function mciSendStringA& (lpstrCommand As String, lpstrReturnString As String, ByVal uReturnLength As Long, ByVal hwndCallback As _Offset)
    Function mciGetErrorStringA& (ByVal dwerrsor As Long, lpstrBuffer As String, ByVal uLength As Long)
End Declare


Dim handle&: handle& = _NewImage(800, 600, 32)
Screen handle&
_Title "QB64 Video"

Dim hwnd As _Offset
hwnd = _WindowHandle

Dim filename As String, aliasName As String
filename = "1.avi"
aliasName = "vid"

Dim cmd As String
Dim errs As Long, N As Long
Dim dummy As String: dummy = Space$(1)

' Open AVI as avivideo, place it as CHILD to QB64PE window
cmd = "open " + Chr$(34) + filename + Chr$(34) + " type avivideo alias " + aliasName + _
      " parent " + LTrim$(Str$(hwnd)) + " style child"
errs = mciSendStringA&(cmd, dummy, 0, 0): MCI_Check errs, cmd

' Set window size and window postition
cmd = "put " + aliasName + " window at 0 0 800 600"
errs = mciSendStringA&(cmd, dummy, 0, 0): MCI_Check errs, cmd

' Play it
cmd = "play " + aliasName
errs = mciSendStringA&(cmd, dummy, 0, 0): MCI_Check errs, cmd

Do
    _Limit 60
Loop Until InKey$ <> ""

N = mciSendStringA&("stop " + aliasName, dummy, 0, 0)
N = mciSendStringA&("close " + aliasName, dummy, 0, 0)
End


Sub MCI_Check (errsCode As Long, where As String) 'error code handler if error occur
    Dim N As Long
    If errsCode <> 0 Then
        Dim msg As String
        msg = Space$(256)
        N = mciGetErrorStringA&(errsCode, msg, Len(msg))
        Print "MCI errsOR ("; errsCode; "): "; where
        Print RTrim$(msg)
        Sleep
        End
    End If
End Sub


Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  Dynamic Libraries (Linux) Petr 19 1,162 12-29-2025, 09:52 PM
Last Post: Petr

Forum Jump:


Users browsing this thread: 1 Guest(s)