From 607ad0daf7a26c0c16023d628bfe1cd130bbf337 Mon Sep 17 00:00:00 2001 From: gng Date: Wed, 14 Jan 2026 22:08:43 +0100 Subject: [PATCH] Add Wine 11 support with PE/Unix split architecture (v1.4.0) New Features: - Full Wine 11 support with new PE/Unix split architecture - Makefile.wine11 for building with Wine 10.2+/11 - Separate PE DLL and Unix SO builds - Support for __wine_unix_call interface - Full 32-bit and 64-bit WoW64 support - Settings GUI integration - launch from DAW's ASIO control panel - JACK MIDI ports (WineASIO:midi_in, WineASIO:midi_out) - Windows launcher executables for GUI New Files: - asio_pe.c - Windows-side ASIO/COM implementation - asio_unix.c - Unix-side JACK/MIDI implementation - unixlib.h - Shared interface definitions - Makefile.wine11 - Wine 11+ build system - ntdll_wine.def, ntdll_wine32.def - NT DLL imports - WINE11_PORTING.md - Technical porting guide - RELEASE_NOTES.md - Release documentation - CONTRIBUTING.md - Contribution guidelines - .github/workflows/build.yml - CI/CD pipeline - gui/wineasio-settings-launcher.c - Windows GUI launcher Updated: - README.md - Full Wine 11 documentation - gui/settings.py - PyQt5/6 compatibility - gui/wineasio-settings - Native launcher script --- .github/workflows/build.yml | 186 +++++ .gitignore | 58 +- CONTRIBUTING.md | 127 +++ Makefile.wine11 | 280 +++++++ README.md | 449 ++++++++--- RELEASE_NOTES.md | 174 +++++ WINE11_PORTING.md | 566 ++++++++++++++ asio_pe.c | 1027 ++++++++++++++++++++++++ asio_unix.c | 1248 ++++++++++++++++++++++++++++++ gui/Makefile | 43 +- gui/wineasio-settings-launcher.c | 90 +++ ntdll_wine.def | 6 + ntdll_wine32.def | 7 + package.sh | 334 ++++++++ unixlib.h | 319 ++++++++ wineasio.def | 9 + 16 files changed, 4792 insertions(+), 131 deletions(-) create mode 100644 .github/workflows/build.yml create mode 100644 CONTRIBUTING.md create mode 100644 Makefile.wine11 create mode 100644 RELEASE_NOTES.md create mode 100644 WINE11_PORTING.md create mode 100644 asio_pe.c create mode 100644 asio_unix.c create mode 100644 gui/wineasio-settings-launcher.c create mode 100644 ntdll_wine.def create mode 100644 ntdll_wine32.def create mode 100755 package.sh create mode 100644 unixlib.h create mode 100644 wineasio.def diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..bf544ea --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,186 @@ +# WineASIO GitHub Actions Build Workflow +# Builds WineASIO for Wine 11+ on Ubuntu + +name: Build WineASIO + +on: + push: + branches: [ main, master, develop ] + tags: [ 'v*' ] + pull_request: + branches: [ main, master ] + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install dependencies + run: | + sudo dpkg --add-architecture i386 + sudo apt-get update + sudo apt-get install -y \ + gcc-mingw-w64-x86-64 \ + gcc-mingw-w64-i686 \ + gcc-multilib \ + libjack-jackd2-dev \ + libjack-jackd2-dev:i386 \ + wine-stable \ + wine-stable-dev \ + python3-pyqt5 + + - name: Build 64-bit + run: make -f Makefile.wine11 64 + + - name: Build 32-bit + run: make -f Makefile.wine11 32 + + - name: Build GUI launchers + run: | + cd gui + make || true + cd .. + + - name: List build artifacts + run: | + echo "=== Build directory ===" + ls -la build_wine11/ + echo "=== GUI directory ===" + ls -la gui/ || true + + - name: Upload 64-bit artifacts + uses: actions/upload-artifact@v4 + with: + name: wineasio-64bit + path: | + build_wine11/wineasio64.dll + build_wine11/wineasio64.so + + - name: Upload 32-bit artifacts + uses: actions/upload-artifact@v4 + with: + name: wineasio-32bit + path: | + build_wine11/wineasio.dll + build_wine11/wineasio.so + + - name: Upload GUI artifacts + uses: actions/upload-artifact@v4 + with: + name: wineasio-gui + path: | + gui/wineasio-settings + gui/settings.py + gui/ui_settings.py + gui/wineasio-settings.exe + gui/wineasio-settings64.exe + if-no-files-found: warn + + package: + runs-on: ubuntu-latest + needs: build + if: startsWith(github.ref, 'refs/tags/v') + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts + + - name: Create release package + run: | + VERSION="${GITHUB_REF#refs/tags/v}" + PACKAGE_NAME="wineasio-${VERSION}" + + # Create binary package structure + mkdir -p "${PACKAGE_NAME}/64bit" + mkdir -p "${PACKAGE_NAME}/32bit" + mkdir -p "${PACKAGE_NAME}/gui" + + # Copy binaries + cp artifacts/wineasio-64bit/* "${PACKAGE_NAME}/64bit/" || true + cp artifacts/wineasio-32bit/* "${PACKAGE_NAME}/32bit/" || true + cp artifacts/wineasio-gui/* "${PACKAGE_NAME}/gui/" || true + + # Copy documentation + cp README.md "${PACKAGE_NAME}/" + cp RELEASE_NOTES.md "${PACKAGE_NAME}/" || true + cp COPYING.LIB "${PACKAGE_NAME}/" + cp COPYING.GUI "${PACKAGE_NAME}/" + + # Create tarball + tar -czvf "${PACKAGE_NAME}-binaries.tar.gz" "${PACKAGE_NAME}" + + # Create source tarball + git archive --format=tar.gz --prefix="${PACKAGE_NAME}-source/" -o "${PACKAGE_NAME}-source.tar.gz" HEAD + + # Create checksums + sha256sum *.tar.gz > SHA256SUMS.txt + + - name: Upload release packages + uses: actions/upload-artifact@v4 + with: + name: release-packages + path: | + *.tar.gz + SHA256SUMS.txt + + release: + runs-on: ubuntu-latest + needs: package + if: startsWith(github.ref, 'refs/tags/v') + permissions: + contents: write + + steps: + - name: Download release packages + uses: actions/download-artifact@v4 + with: + name: release-packages + + - name: Create GitHub Release + uses: softprops/action-gh-release@v1 + with: + files: | + *.tar.gz + SHA256SUMS.txt + body: | + # WineASIO ${{ github.ref_name }} 🍷🎵 + + ## What's New + + See RELEASE_NOTES.md for full details. + + ## Downloads + + | File | Description | + |------|-------------| + | `wineasio-*-source.tar.gz` | Complete source code | + | `wineasio-*-binaries.tar.gz` | Pre-built binaries | + + ## Quick Install + + ```bash + tar -xzf wineasio-*-binaries.tar.gz + cd wineasio-*/ + sudo cp 64bit/* /opt/wine-stable/lib/wine/x86_64-{windows,unix}/ + sudo cp 32bit/* /opt/wine-stable/lib/wine/i386-{windows,unix}/ + wine regsvr32 wineasio64.dll + ``` + + ## Requirements + + - Wine 10.2+ or Wine 11 + - JACK Audio Connection Kit + - PyQt5 or PyQt6 (for settings GUI) + draft: false + prerelease: false + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 1b46db5..201cf2e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,56 @@ -*~ -*.dll -*.o -*.so +# Build directories +build_wine11/ build32/ build64/ -debian/ +dist/ + +# Object files +*.o +*.a +*.obj + +# Libraries and executables +*.dll +*.so +*.exe +*.dll.so + +# Editor and IDE files +*.swp +*.swo +*~ +.vscode/ +.idea/ +*.sublime-project +*.sublime-workspace + +# OS files +.DS_Store +Thumbs.db + +# Python __pycache__/ +*.pyc +*.pyo +*.pyd +.Python +*.egg-info/ +.eggs/ + +# Wine/Windows temp files +*.log + +# Temporary files +*.tmp +*.temp +*.bak +*.backup + +# Archives (don't commit release tarballs) +*.tar.gz +*.zip +*.7z + +# Keep these specific files even if they match patterns above +!wineasio-register +!gui/wineasio-settings diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..c0ce322 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,127 @@ +# Contributing to WineASIO + +Thank you for your interest in contributing to WineASIO! 🎵🍷 + +## Ways to Contribute + +### Bug Reports + +1. Check existing issues first to avoid duplicates +2. Include your system information: + - Wine version (`wine --version`) + - Linux distribution and version + - JACK version (`jackd --version`) + - DAW name and version +3. Describe steps to reproduce the issue +4. Include relevant logs (see Debugging section below) + +### Feature Requests + +Open an issue describing: +- What you'd like to see +- Why it would be useful +- Any implementation ideas you have + +### Code Contributions + +1. Fork the repository +2. Create a feature branch (`git checkout -b feature/my-feature`) +3. Make your changes +4. Test with multiple DAWs if possible +5. Submit a pull request + +## Development Setup + +### Prerequisites + +```bash +# Ubuntu/Debian +sudo apt install gcc-mingw-w64 wine-stable wine-stable-dev libjack-jackd2-dev python3-pyqt5 + +# Fedora +sudo dnf install mingw64-gcc mingw32-gcc wine-devel jack-audio-connection-kit-devel python3-qt5 + +# Arch Linux +sudo pacman -S mingw-w64-gcc wine wine-staging jack2 python-pyqt5 +``` + +### Build + +```bash +# Build both architectures +make -f Makefile.wine11 all + +# Install locally +sudo make -f Makefile.wine11 install + +# Register +make -f Makefile.wine11 register +``` + +### Testing + +Please test your changes with: + +1. **Multiple Wine versions** - Wine 10.2+, Wine 11 +2. **Both architectures** - 32-bit and 64-bit DAWs +3. **Multiple DAWs** - FL Studio, Reaper, Ableton, etc. +4. **Settings GUI** - Verify control panel opens from DAW +5. **JACK MIDI** - Check MIDI ports appear in `jack_lsp` + +## Debugging + +Enable Wine debug output: + +```bash +WINEDEBUG=+loaddll,+module wine your_daw.exe +``` + +Check JACK connections: + +```bash +jack_lsp -c | grep -i wine +``` + +Verify WineASIO is loaded: + +```bash +WINEDEBUG=+loaddll wine your_daw.exe 2>&1 | grep -i asio +``` + +## Code Style + +- Use 4 spaces for indentation (no tabs) +- Follow existing code formatting +- Comment complex logic +- Keep functions focused and small + +## Architecture Overview + +WineASIO uses Wine 11's split architecture: + +| Component | Language | Purpose | +|-----------|----------|---------| +| `asio_pe.c` | C (mingw) | Windows ASIO/COM interface | +| `asio_unix.c` | C (gcc) | Linux JACK interface | +| `unixlib.h` | C header | Shared structures | +| `gui/settings.py` | Python | Settings GUI | + +Communication between PE and Unix uses `__wine_unix_call`. + +## Pull Request Guidelines + +1. **One feature per PR** - Keep changes focused +2. **Update documentation** - README, WINE11_PORTING.md if needed +3. **Test thoroughly** - List what you tested in PR description +4. **Descriptive commits** - Explain what and why + +## License + +- Library code: LGPL v2.1 (COPYING.LIB) +- GUI code: GPL v2+ (COPYING.GUI) + +By contributing, you agree your code will be licensed under these terms. + +## Questions? + +Open an issue or discussion if you need help! \ No newline at end of file diff --git a/Makefile.wine11 b/Makefile.wine11 new file mode 100644 index 0000000..9457d31 --- /dev/null +++ b/Makefile.wine11 @@ -0,0 +1,280 @@ +#!/usr/bin/make -f +# Makefile for WineASIO - Wine 11+ Architecture +# --------------------------------------------- +# This builds WineASIO with the new PE/Unix split architecture +# required for Wine 10.2+ and Wine 11 +# +# Usage: +# make -f Makefile.wine11 64 # Build 64-bit only +# make -f Makefile.wine11 32 # Build 32-bit only +# make -f Makefile.wine11 all # Build both +# make -f Makefile.wine11 install # Install to Wine directories +# make -f Makefile.wine11 clean # Clean build files +# + +# Wine installation prefix +WINE_PREFIX ?= /opt/wine-stable +# Alternative: /usr + +# Build directory +BUILD_DIR = build_wine11 + +# Compilers +MINGW32 = i686-w64-mingw32-gcc +MINGW64 = x86_64-w64-mingw32-gcc +DLLTOOL32 = i686-w64-mingw32-dlltool +DLLTOOL64 = x86_64-w64-mingw32-dlltool +GCC = gcc + +# Wine tools +WINEBUILD = winebuild + +# Flags for PE DLL (Windows side - mingw) +PE_CFLAGS = -Wall -O2 -fvisibility=hidden +PE_CFLAGS += -DWIN32 -D_WIN32 + +PE_LDFLAGS = -shared +PE_LIBS = -lole32 -loleaut32 -luuid -ladvapi32 -lkernel32 + +# Flags for Unix .so (Linux side - gcc) +UNIX_CFLAGS = -Wall -O2 -fPIC -fvisibility=hidden +UNIX_CFLAGS += -DWINE_UNIX_LIB -D__WINESRC__ +UNIX_CFLAGS += -I$(WINE_PREFIX)/include +UNIX_CFLAGS += -I$(WINE_PREFIX)/include/wine +UNIX_CFLAGS += -I$(WINE_PREFIX)/include/wine/windows + +UNIX_LDFLAGS = -shared -fPIC +UNIX_LIBS = -ldl -lpthread + +# Source files +PE_SOURCES = asio_pe.c +UNIX_SOURCES = asio_unix.c + +# Output files +# Note: 32-bit uses "wineasio.dll" (not wineasio32) to match Wine's expected naming +DLL32 = wineasio.dll +DLL64 = wineasio64.dll +SO32 = wineasio.so +SO64 = wineasio64.so + +# Import library for ntdll Wine-specific symbols +NTDLL_DEF64 = ntdll_wine.def +NTDLL_DEF32 = ntdll_wine32.def +NTDLL_LIB32 = $(BUILD_DIR)/libntdll_wine32.a +NTDLL_LIB64 = $(BUILD_DIR)/libntdll_wine64.a + +# Installation directories +INSTALL_DIR32 = $(WINE_PREFIX)/lib/wine/i386-windows +INSTALL_DIR64 = $(WINE_PREFIX)/lib/wine/x86_64-windows +INSTALL_UNIX32 = $(WINE_PREFIX)/lib/wine/i386-unix +INSTALL_UNIX64 = $(WINE_PREFIX)/lib/wine/x86_64-unix + +# GUI installation +PREFIX ?= /usr +GUI_BIN_DIR = $(PREFIX)/bin +GUI_SHARE_DIR = $(PREFIX)/share/wineasio + +.PHONY: all 32 64 clean install install32 install64 install-gui help ntdll-libs + +help: + @echo "WineASIO Wine 11+ Build System" + @echo "" + @echo "Usage:" + @echo " make -f Makefile.wine11 64 Build 64-bit" + @echo " make -f Makefile.wine11 32 Build 32-bit" + @echo " make -f Makefile.wine11 all Build both" + @echo " make -f Makefile.wine11 install Install to Wine" + @echo " make -f Makefile.wine11 clean Clean build files" + @echo "" + @echo "Variables:" + @echo " WINE_PREFIX=$(WINE_PREFIX)" + @echo "" + +all: 64 32 + +64: $(BUILD_DIR)/$(DLL64) $(BUILD_DIR)/$(SO64) + @echo "64-bit build complete" + @echo " PE DLL: $(BUILD_DIR)/$(DLL64)" + @echo " Unix SO: $(BUILD_DIR)/$(SO64)" + +32: $(BUILD_DIR)/$(DLL32) $(BUILD_DIR)/$(SO32) + @echo "32-bit build complete" + @echo " PE DLL: $(BUILD_DIR)/$(DLL32)" + @echo " Unix SO: $(BUILD_DIR)/$(SO32)" + @echo " (Note: 32-bit files use 'wineasio' name, not 'wineasio32')" + +$(BUILD_DIR): + mkdir -p $(BUILD_DIR) + +# Create ntdll import library for 64-bit +$(NTDLL_LIB64): $(NTDLL_DEF64) | $(BUILD_DIR) + @echo "Creating 64-bit ntdll import library..." + $(DLLTOOL64) -d $(NTDLL_DEF64) -l $@ + +# Create ntdll import library for 32-bit (uses different def file for stdcall) +$(NTDLL_LIB32): $(NTDLL_DEF32) | $(BUILD_DIR) + @echo "Creating 32-bit ntdll import library..." + $(DLLTOOL32) -d $(NTDLL_DEF32) -l $@ + +# 64-bit PE DLL +$(BUILD_DIR)/$(DLL64): $(PE_SOURCES) unixlib.h $(NTDLL_LIB64) | $(BUILD_DIR) + @echo "Building 64-bit PE DLL..." + $(MINGW64) $(PE_CFLAGS) -m64 \ + -o $@ $(PE_SOURCES) \ + $(NTDLL_LIB64) \ + $(PE_LDFLAGS) $(PE_LIBS) \ + -Wl,--out-implib,$(BUILD_DIR)/libwineasio64.a + @echo "Marking as Wine builtin..." + -$(WINEBUILD) --builtin $@ 2>/dev/null || true + +# 32-bit PE DLL +# Note: We use wineasio.def to export functions without stdcall decoration +# because Wine's LdrGetProcedureAddress looks for undecorated names +$(BUILD_DIR)/$(DLL32): $(PE_SOURCES) unixlib.h $(NTDLL_LIB32) wineasio.def | $(BUILD_DIR) + @echo "Building 32-bit PE DLL..." + $(MINGW32) $(PE_CFLAGS) -m32 \ + -o $@ $(PE_SOURCES) \ + $(NTDLL_LIB32) \ + $(PE_LDFLAGS) $(PE_LIBS) \ + wineasio.def \ + -Wl,--out-implib,$(BUILD_DIR)/libwineasio.a + @echo "Marking as Wine builtin..." + -$(WINEBUILD) --builtin $@ 2>/dev/null || true + +# 64-bit Unix .so +$(BUILD_DIR)/$(SO64): $(UNIX_SOURCES) unixlib.h | $(BUILD_DIR) + @echo "Building 64-bit Unix .so..." + $(GCC) $(UNIX_CFLAGS) -m64 \ + -o $@ $(UNIX_SOURCES) \ + $(UNIX_LDFLAGS) $(UNIX_LIBS) + +# 32-bit Unix .so +$(BUILD_DIR)/$(SO32): $(UNIX_SOURCES) unixlib.h | $(BUILD_DIR) + @echo "Building 32-bit Unix .so..." + $(GCC) $(UNIX_CFLAGS) -m32 \ + -o $@ $(UNIX_SOURCES) \ + $(UNIX_LDFLAGS) $(UNIX_LIBS) + +install: install64 install32 install-gui register + @echo "" + @echo "Installation complete!" + @echo "" + @echo "WineASIO Settings GUI installed to $(GUI_BIN_DIR)/wineasio-settings" + @echo "You can launch it from FL Studio's ASIO control panel button," + @echo "or run 'wineasio-settings' from the command line." + +install64: $(BUILD_DIR)/$(DLL64) $(BUILD_DIR)/$(SO64) + @echo "Installing 64-bit WineASIO..." + sudo mkdir -p $(INSTALL_DIR64) $(INSTALL_UNIX64) + sudo cp $(BUILD_DIR)/$(DLL64) $(INSTALL_DIR64)/ + sudo cp $(BUILD_DIR)/$(SO64) $(INSTALL_UNIX64)/ + -sudo $(WINEBUILD) --builtin $(INSTALL_DIR64)/$(DLL64) 2>/dev/null || true + @echo "64-bit installation complete" + +install32: $(BUILD_DIR)/$(DLL32) $(BUILD_DIR)/$(SO32) + @echo "Installing 32-bit WineASIO..." + sudo mkdir -p $(INSTALL_DIR32) $(INSTALL_UNIX32) + sudo cp $(BUILD_DIR)/$(DLL32) $(INSTALL_DIR32)/ + sudo cp $(BUILD_DIR)/$(SO32) $(INSTALL_UNIX32)/ + -sudo $(WINEBUILD) --builtin $(INSTALL_DIR32)/$(DLL32) 2>/dev/null || true + @echo "32-bit installation complete" + +install-gui: build-gui-launchers + @echo "Installing WineASIO Settings GUI..." + sudo mkdir -p $(DESTDIR)$(GUI_BIN_DIR) + sudo mkdir -p $(DESTDIR)$(GUI_SHARE_DIR) + # Install Python files + sudo install -m 644 gui/settings.py $(DESTDIR)$(GUI_SHARE_DIR)/ + sudo install -m 644 gui/ui_settings.py $(DESTDIR)$(GUI_SHARE_DIR)/ + # Install launcher script + sudo install -m 755 gui/wineasio-settings $(DESTDIR)$(GUI_BIN_DIR)/ + # Adjust PREFIX in launcher script + sudo sed -i "s?X-PREFIX-X?$(PREFIX)?" $(DESTDIR)$(GUI_BIN_DIR)/wineasio-settings + # Install Windows launcher executables (for use from FL Studio etc.) + sudo install -m 755 gui/wineasio-settings.exe $(DESTDIR)$(GUI_SHARE_DIR)/ 2>/dev/null || true + sudo install -m 755 gui/wineasio-settings64.exe $(DESTDIR)$(GUI_SHARE_DIR)/ 2>/dev/null || true + @echo "GUI installation complete" + @echo "" + @echo "To use the ASIO Control Panel from FL Studio or other DAWs:" + @echo " 1. The native Linux tool: $(GUI_BIN_DIR)/wineasio-settings" + @echo " 2. Windows launchers (copy to your DAW folder or run directly):" + @echo " $(GUI_SHARE_DIR)/wineasio-settings.exe (32-bit)" + @echo " $(GUI_SHARE_DIR)/wineasio-settings64.exe (64-bit)" + +build-gui-launchers: + @echo "Building Windows launcher executables..." + $(MAKE) -C gui launchers || echo " (launcher build skipped - mingw not available)" + +uninstall-gui: + @echo "Removing WineASIO Settings GUI..." + sudo rm -f $(DESTDIR)$(GUI_BIN_DIR)/wineasio-settings + sudo rm -rf $(DESTDIR)$(GUI_SHARE_DIR) + @echo "GUI removed" + +register: + @echo "" + @echo "Registering WineASIO..." + @echo " 64-bit registration..." + -wine regsvr32 wineasio64.dll 2>/dev/null || true + @echo " 32-bit registration (using syswow64 regsvr32 for WoW64)..." + -wine ~/.wine/drive_c/windows/syswow64/regsvr32.exe wineasio.dll 2>/dev/null || true + @echo "" + @echo "Registration complete. You can verify with:" + @echo " wine reg query 'HKLM\\Software\\ASIO\\WineASIO'" + +unregister: + @echo "Unregistering WineASIO..." + -wine regsvr32 /u wineasio64.dll 2>/dev/null || true + -wine ~/.wine/drive_c/windows/syswow64/regsvr32.exe /u wineasio.dll 2>/dev/null || true + +clean: + rm -rf $(BUILD_DIR) + @echo "Build directory cleaned" + +# Debug target - show paths and check tools +debug: + @echo "=== WineASIO Wine 11 Build Configuration ===" + @echo "" + @echo "WINE_PREFIX = $(WINE_PREFIX)" + @echo "" + @echo "Compilers:" + @echo " MINGW32 = $(MINGW32)" + @echo " MINGW64 = $(MINGW64)" + @echo " GCC = $(GCC)" + @echo "" + @echo "PE Install paths:" + @echo " 32-bit: $(INSTALL_DIR32)" + @echo " 64-bit: $(INSTALL_DIR64)" + @echo "" + @echo "Unix Install paths:" + @echo " 32-bit: $(INSTALL_UNIX32)" + @echo " 64-bit: $(INSTALL_UNIX64)" + @echo "" + @echo "Checking tools..." + @which $(MINGW64) > /dev/null 2>&1 && echo " [OK] $(MINGW64)" || echo " [MISSING] $(MINGW64)" + @which $(MINGW32) > /dev/null 2>&1 && echo " [OK] $(MINGW32)" || echo " [MISSING] $(MINGW32)" + @which $(GCC) > /dev/null 2>&1 && echo " [OK] $(GCC)" || echo " [MISSING] $(GCC)" + @which $(WINEBUILD) > /dev/null 2>&1 && echo " [OK] $(WINEBUILD)" || echo " [MISSING] $(WINEBUILD)" + @echo "" + @echo "Checking Wine headers..." + @test -f $(WINE_PREFIX)/include/wine/unixlib.h && echo " [OK] Wine unixlib.h found" || echo " [MISSING] Wine unixlib.h" + @echo "" + @echo "Wine version:" + @wine --version 2>/dev/null || echo " Wine not found in PATH" + +# Verify installation +verify: + @echo "Checking installed files..." + @echo "" + @echo "64-bit PE DLL:" + @test -f $(INSTALL_DIR64)/$(DLL64) && echo " [OK] $(INSTALL_DIR64)/$(DLL64)" || echo " [MISSING]" + @echo "64-bit Unix SO:" + @test -f $(INSTALL_UNIX64)/$(SO64) && echo " [OK] $(INSTALL_UNIX64)/$(SO64)" || echo " [MISSING]" + @echo "" + @echo "32-bit PE DLL:" + @test -f $(INSTALL_DIR32)/$(DLL32) && echo " [OK] $(INSTALL_DIR32)/$(DLL32)" || echo " [MISSING]" + @echo "32-bit Unix SO:" + @test -f $(INSTALL_UNIX32)/$(SO32) && echo " [OK] $(INSTALL_UNIX32)/$(SO32)" || echo " [MISSING]" + @echo "" + @echo "Registry entries:" + @wine reg query "HKLM\\Software\\ASIO\\WineASIO" 2>/dev/null | grep -v "^[0-9a-f]*:" || echo " [MISSING] ASIO driver not registered" diff --git a/README.md b/README.md index ab01256..275217f 100644 --- a/README.md +++ b/README.md @@ -3,204 +3,406 @@ WineASIO provides an ASIO to JACK driver for WINE. ASIO is the most common Windows low-latency driver, so is commonly used in audio workstation programs. -You can, for example, use with FLStudio under GNU/Linux systems (together with JACK). +You can, for example, use with FL Studio, Ableton Live, Reaper, and other DAWs under GNU/Linux systems (together with JACK). ![Screenshot](screenshot.png) -For best results with Debian-based distributions, -enable the [KXStudio repositories](https://kx.studio/Repositories) and install WineASIO from there. +--- -### BUILDING +## 🎉 Wine 11 Support (NEW!) -Do the following to build for 32-bit Wine. +**WineASIO now supports Wine 11!** + +Wine 11 (released January 13, 2025) introduced a new DLL architecture that separates PE (Windows) code from Unix code. This required a complete rewrite of WineASIO's build system and internal architecture. + +### Wine Version Compatibility + +| Wine Version | Build Method | Status | +|--------------|--------------|--------| +| Wine 11.x | `make -f Makefile.wine11` | ✅ Fully Supported | +| Wine 10.2+ | `make -f Makefile.wine11` | ✅ Fully Supported | +| Wine 10.0-10.1 | `make` (legacy) | ✅ Supported | +| Wine 6.x-9.x | `make` (legacy) | ✅ Supported | + +--- + +## Building for Wine 11+ + +### Prerequisites ```sh -make 32 +# Ubuntu/Debian +sudo apt install gcc-mingw-w64 wine-stable wine-stable-dev libjack-jackd2-dev + +# Fedora +sudo dnf install mingw64-gcc mingw32-gcc wine-devel jack-audio-connection-kit-devel + +# Arch Linux +sudo pacman -S mingw-w64-gcc wine wine-staging jack2 ``` -Do the following to build for 64-bit Wine. +### Build Commands (Wine 11+) + +Build both 32-bit and 64-bit versions: ```sh -make 64 +make -f Makefile.wine11 all ``` -### INSTALLING +Build only 64-bit: -To install 32-bit WineASIO (substitute with the path to the 32-bit wine libs for your distro). +```sh +make -f Makefile.wine11 64 +``` + +Build only 32-bit: ```sh -sudo cp build32/wineasio32.dll /usr/lib/i386-linux-gnu/wine/i386-windows/ -sudo cp build32/wineasio32.dll.so /usr/lib/i386-linux-gnu/wine/i386-unix/ +make -f Makefile.wine11 32 +``` + +### Installation (Wine 11+) + +```sh +sudo make -f Makefile.wine11 install +``` + +This will: +- Copy `wineasio64.dll` to Wine's 64-bit PE directory +- Copy `wineasio64.so` to Wine's 64-bit Unix directory +- Copy `wineasio.dll` to Wine's 32-bit PE directory +- Copy `wineasio.so` to Wine's 32-bit Unix directory +- Register the driver with Wine + +### Manual Installation (Wine 11+) + +For custom Wine installations, specify the Wine prefix: + +```sh +sudo make -f Makefile.wine11 install WINE_PREFIX=/path/to/wine +``` + +Default paths for Wine 11: + +```sh +# 64-bit +sudo cp build_wine11/wineasio64.dll /opt/wine-stable/lib/wine/x86_64-windows/ +sudo cp build_wine11/wineasio64.so /opt/wine-stable/lib/wine/x86_64-unix/ + +# 32-bit +sudo cp build_wine11/wineasio.dll /opt/wine-stable/lib/wine/i386-windows/ +sudo cp build_wine11/wineasio.so /opt/wine-stable/lib/wine/i386-unix/ +``` + +### Registration (Wine 11+) + +After installation, register the driver: + +```sh +# 64-bit +wine regsvr32 wineasio64.dll + +# 32-bit (use syswow64 regsvr32 for WoW64) +wine ~/.wine/drive_c/windows/syswow64/regsvr32.exe wineasio.dll ``` -To install 64bit WineASIO (substitute with the path to the 64-bit wine libs for your distro). +Or use the Makefile: ```sh +make -f Makefile.wine11 register +``` + +### Verify Installation + +```sh +make -f Makefile.wine11 verify +``` + +This checks: +- All DLL and SO files are in place +- Registry entries are correct + +--- + +## Building for Wine 10.1 and Earlier (Legacy) + +### Build Commands + +```sh +# 32-bit +make 32 + +# 64-bit +make 64 +``` + +### Installation (Legacy) + +```sh +# 32-bit +sudo cp build32/wineasio32.dll /usr/lib/i386-linux-gnu/wine/i386-windows/ +sudo cp build32/wineasio32.dll.so /usr/lib/i386-linux-gnu/wine/i386-unix/ + +# 64-bit sudo cp build64/wineasio64.dll /usr/lib/x86_64-linux-gnu/wine/x86_64-windows/ sudo cp build64/wineasio64.dll.so /usr/lib/x86_64-linux-gnu/wine/x86_64-unix/ ``` -**NOTE:** -**Wine does not have consistent paths between different Linux distributions, these paths are only a hint and likely not what will work for you.** -**New versions of wine might also need to use `wineasio64.so` as name instead of `wineasio64.dll.so`.** -**It is up to the packager to figure out what works for the Wine version used on their specific distro.** +**Note:** Wine paths vary between distributions. Adjust paths accordingly. -#### EXTRAS +--- -For user convenience a `wineasio-register` script is included in this repo, if you are packaging WineASIO consider installing it as part of WineASIO. +## Registering WineASIO -Additionally a control panel GUI is provided in this repository's `gui` subdir, which requires PyQt6 or PyQt5 to build and run. -The WineASIO driver will use this GUI as the ASIO control panel. +After installing, register WineASIO in your Wine prefix: -### REGISTERING +```sh +wineasio-register +``` -After building and installing WineASIO, we still need to register it on each Wine prefix. -For your convenience a script is provided on this repository, so you can simply run: +### Custom Wine Prefix ```sh -wineasio-register +env WINEPREFIX=~/my-daw-prefix wineasio-register ``` -to activate WineASIO for the current Wine prefix. +--- + +## Configuration + +WineASIO is configured via the Windows registry (`HKEY_CURRENT_USER\Software\Wine\WineASIO`). +All options can be overridden by environment variables. + +### Available Options + +| Registry Key | Default | Environment Variable | Description | +|--------------|---------|---------------------|-------------| +| Number of inputs | 16 | `WINEASIO_NUMBER_INPUTS` | Number of JACK input ports | +| Number of outputs | 16 | `WINEASIO_NUMBER_OUTPUTS` | Number of JACK output ports | +| Autostart server | 0 (off) | `WINEASIO_AUTOSTART_SERVER` | Start JACK automatically | +| Connect to hardware | 1 (on) | `WINEASIO_CONNECT_TO_HARDWARE` | Auto-connect to physical ports | +| Fixed buffersize | 1 (on) | `WINEASIO_FIXED_BUFFERSIZE` | Buffer size controlled by JACK | +| Preferred buffersize | 1024 | `WINEASIO_PREFERRED_BUFFERSIZE` | Preferred buffer size (power of 2) | +| Client name | (auto) | `WINEASIO_CLIENT_NAME` | JACK client name | -#### CUSTOM WINEPREFIX +### GUI Control Panel (Wine 11) -The `wineasio-register` script will register the WineASIO driver in the default Wine prefix `~/.wine`. -You can specify another prefix like so: +A PyQt5/PyQt6 control panel is included for configuring WineASIO settings. When you click "Show ASIO Panel" in your DAW (e.g., FL Studio, Reaper), WineASIO launches the native Linux settings GUI. + +**How it works:** +1. Your DAW calls the ASIO `ControlPanel()` function +2. WineASIO's Unix-side code uses `fork()/exec()` to launch `wineasio-settings` +3. The Python/PyQt GUI reads and writes settings to the Wine registry + +**Launch manually:** ```sh -env WINEPREFIX=~/asioapp wineasio-register +# From Linux terminal +wineasio-settings + +# From Wine (using Windows launcher) +wine /usr/share/wineasio/wineasio-settings64.exe ``` -### GENERAL INFORMATION +**Requirements:** +- PyQt5 or PyQt6: `pip install PyQt5` or `pip install PyQt6` -ASIO apps get notified if the jack buffersize changes. +--- -WineASIO can slave to the jack transport. +## JACK MIDI Support (Wine 11) -WineASIO can change jack's buffersize if so desired. Must be enabled in the registry, see below. +WineASIO 1.4.0+ creates JACK MIDI ports for routing MIDI between your DAW and other JACK applications. -The configuration of WineASIO is done with Windows registry (`HKEY_CURRENT_USER\Software\Wine\WineASIO`). -All these options can be overridden by environment variables. -There is also a GUI for changing these settings, which WineASIO will try to launch when the ASIO "panel" is clicked. +### MIDI Ports -The registry keys are automatically created with default values if they doesn't exist when the driver initializes. -The available options are: +When WineASIO connects to JACK, it registers: +- `WineASIO:midi_in` - MIDI input (receives MIDI from other JACK clients) +- `WineASIO:midi_out` - MIDI output (sends MIDI to other JACK clients) -#### [Number of inputs] & [Number of outputs] -These two settings control the number of jack ports that WineASIO will try to open. -Defaults are 16 in and 16 out. Environment variables are `WINEASIO_NUMBER_INPUTS` and `WINEASIO_NUMBER_OUTPUTS`. +### Verify MIDI Ports -#### [Autostart server] +```sh +jack_lsp | grep -i midi +``` -Defaults to off (0), setting it to 1 enables WineASIO to launch the jack server. -See the jack documentation for further details. -The environment variable is `WINEASIO_AUTOSTART_SERVER`, and it can be set to on or off. +You should see: +``` +WineASIO:midi_in +WineASIO:midi_out +``` -#### [Connect to hardware] -Defaults to on (1), makes WineASIO try to connect the ASIO channels to the physical I/O ports on your hardware. -Setting it to 0 disables it. -The environment variable is `WINEASIO_CONNECT_TO_HARDWARE`, and it can be set to on or off. +### Connecting MIDI Devices -#### [Fixed buffersize] -Defaults to on (1) which means the buffer size is controlled by jack and WineASIO has no say in the matter. -When set to 0, an ASIO app will be able to change the jack buffer size when calling CreateBuffers(). -The environment variable is `WINEASIO_FIXED_BUFFERSIZE` and it can be set to on or off. +Use `jack_connect`, QjackCtl, or Carla to route MIDI: -#### [Preferred buffersize] -Defaults to 1024, and is one of the sizes returned by `GetBufferSize()`, see the ASIO documentation for details. -Must be a power of 2. +```sh +# Connect a hardware MIDI input to WineASIO +jack_connect "system:midi_capture_1" "WineASIO:midi_in" -The other values returned by the driver are hardcoded in the source, -see `ASIO_MINIMUM_BUFFERSIZE` which is set at 16, and `ASIO_MAXIMUM_BUFFERSIZE` which is set to 8192. -The environment variable is `WINEASIO_PREFERRED_BUFFERSIZE`. +# Connect WineASIO MIDI output to a synth +jack_connect "WineASIO:midi_out" "Yoshimi:midi_in" +``` -Be careful, if you set a size that isn't supported by the backend, the jack server will most likely shut down, -might be a good idea to change `ASIO_MINIMUM_BUFFERSIZE` and `ASIO_MAXIMUM_BUFFERSIZE` to values you know work on your system before building. +### Using with a2jmidid -In addition there is a `WINEASIO_CLIENT_NAME` environment variable, -that overrides the JACK client name derived from the program name. +If your MIDI devices appear as ALSA MIDI (not JACK), use `a2jmidid` to bridge them: -### CHANGE LOG +```sh +# Start the ALSA-to-JACK MIDI bridge +a2jmidid -e & -#### 1.3.0 -* 24-JUL-2025: Make GUI settings panel compatible with PyQt6 or PyQt5 -* 17-JUL-2025: Load libjack.so.0 dynamically at runtime, removing build dep -* 17-JUL-2025: Remove useless -mnocygwin flag -* 28-JUN-2025: Remove dependency on asio headers +# Now ALSA MIDI devices appear as JACK MIDI ports +jack_lsp | grep a2j +``` -#### 1.2.0 -* 29-SEP-2023: Fix compatibility with Wine > 8 -* 29-SEP-2023: Add wineasio-register script for simplifying driver registration +### Relationship with Wine ALSA MIDI -#### 1.1.0 -* 18-FEB-2022: Various bug fixes (falkTX) -* 24-NOV-2021: Fix compatibility with Wine > 6.5 +Wine uses `winealsa.drv` for Windows MIDI API support (separate from WineASIO). The WineASIO JACK MIDI ports provide an additional routing option specifically for JACK-based workflows. -#### 1.0.0 -* 14-JUL-2020: Add packaging script -* 12-MAR-2020: Fix control panel startup -* 08-FEB-2020: Fix code to work with latest Wine -* 08-FEB-2020: Add custom GUI for WineASIO settings, made in PyQt5 (taken from Cadence project code) +--- -#### 0.9.2 -* 28-OCT-2013: Add 64-bit support and some small fixes +## Troubleshooting -#### 0.9.1 -* 15-OCT-2013: Various bug fixes (JH) +### Settings GUI doesn't open -#### 0.9.0 -* 19-FEB-2011: Nearly complete refactoring of the WineASIO codebase (asio.c) (JH) +1. Ensure `wineasio-settings` is in PATH: `which wineasio-settings` +2. Check PyQt is installed: `python3 -c "from PyQt5.QtWidgets import QApplication"` +3. Try launching manually: `wineasio-settings` -#### 0.8.1 -* 05-OCT-2010: Code from Win32 callback thread moved to JACK process callback, except for bufferSwitch() call. -* 05-OCT-2010: Switch from int to float for samples. +### JACK MIDI ports not visible -#### 0.8.0 -* 08-AUG-2010: Forward port JackWASIO changes... needs testing hard. (PLJ) +1. Ensure JACK is running: `jack_lsp` +2. Verify WineASIO is connected: `jack_lsp | grep WineASIO` +3. For ALSA MIDI devices, use `a2jmidid -e` to bridge to JACK -#### 0.7.6 -* 27-DEC-2009: Fixes for compilation on 64-bit platform. (PLJ) +### Wine 11: "Unix library not found" -#### 0.7.5 -* 29-Oct-2009: Added fork with call to qjackctl from ASIOControlPanel(). (JH) -* 29-Oct-2009: Changed the SCHED_FIFO priority of the win32 callback thread. (JH) -* 28-Oct-2009: Fixed wrongly reported output latency. (JH) +Ensure both PE and Unix libraries are installed: -#### 0.7.4 -* 08-APR-2008: Updates to the README.TXT (PLJ) -* 02-APR-2008: Move update to "toggle" to hopefully better place (PLJ) -* 24-MCH-2008: Don't trace in win32_callback. Set patch-level to 4. (PLJ) -* 09-JAN-2008: Nedko Arnaudov supplied a fix for Nuendo under WINE. +```sh +# Check files exist +ls -la /opt/wine-stable/lib/wine/x86_64-windows/wineasio64.dll +ls -la /opt/wine-stable/lib/wine/x86_64-unix/wineasio64.so +``` -#### 0.7.3 -* 27-DEC-2007: Make slaving to jack transport work, correct port allocation bug. (RB) +### Wine 11: DLL not loading -#### 0.7 -* 01-DEC-2007: In a fit of insanity, I merged JackLab and Robert Reif code bases. (PLJ) +Make sure the DLL is marked as a Wine builtin: -#### 0.6 -* 21-NOV-2007: add dynamic client naming (PLJ) +```sh +sudo winebuild --builtin /opt/wine-stable/lib/wine/x86_64-windows/wineasio64.dll +``` + +### JACK not connecting + +1. Ensure JACK is running: `jack_lsp` +2. Check WineASIO JACK client: `jack_lsp | grep -i wine` +3. Use QjackCtl or Carla to manage connections + +### 32-bit apps not finding WineASIO + +32-bit Windows apps use WoW64. Ensure: +- `wineasio.dll` is in `i386-windows/` +- `wineasio.so` is in `i386-unix/` +- Register with 32-bit regsvr32: `wine ~/.wine/drive_c/windows/syswow64/regsvr32.exe wineasio.dll` + +--- + +## Technical Details (Wine 11 Architecture) + +Wine 11 requires a split architecture: + +| Component | Description | Built With | +|-----------|-------------|------------| +| `wineasio64.dll` | 64-bit PE DLL (Windows code) | mingw-w64 | +| `wineasio64.so` | 64-bit Unix library (JACK interface) | gcc | +| `wineasio.dll` | 32-bit PE DLL (Windows code) | mingw-w64 | +| `wineasio.so` | 32-bit Unix library (JACK interface) | gcc | + +The PE DLL handles: +- COM/ASIO interface +- Registry configuration +- Host application callbacks + +The Unix SO handles: +- JACK connection and audio processing +- Real-time audio callbacks +- Buffer management + +Communication between PE and Unix uses Wine's `__wine_unix_call` interface. + +--- -#### 0.0.3 -* 17-NOV-2007: Unique port name code (RR) +## File Structure -#### 0.5 -* 03-SEP-2007: port mapping and config file (PLJ) +``` +wineasio-1.3.0/ +├── asio_pe.c # PE-side code (Wine 11) +├── asio_unix.c # Unix-side code (Wine 11) +├── unixlib.h # Shared interface definitions +├── Makefile.wine11 # Wine 11+ build system +├── Makefile # Legacy build system +├── asio.c # Legacy combined code +├── wineasio.def # Export definitions +├── ntdll_wine.def # 64-bit ntdll imports +├── ntdll_wine32.def # 32-bit ntdll imports +├── gui/ # PyQt control panel +│ ├── settings.py # Main settings GUI +│ ├── ui_settings.py # UI definitions +│ └── launcher/ # Windows launcher sources +└── docker/ # Docker build environment +``` + +--- + +## Change Log + +### 1.4.0 (Wine 11 Port) - January 2025 +* **NEW:** Full Wine 11 support with new PE/Unix split architecture +* **NEW:** `Makefile.wine11` for building with Wine 10.2+/11 +* **NEW:** Separate PE DLL and Unix SO builds +* **NEW:** Support for `__wine_unix_call` interface +* **NEW:** 32-bit and 64-bit builds with proper WoW64 support +* **NEW:** Settings GUI integration - launch from DAW's ASIO control panel +* **NEW:** JACK MIDI ports (`WineASIO:midi_in`, `WineASIO:midi_out`) +* **NEW:** Windows launcher executables for GUI (`wineasio-settings*.exe`) +* Added `asio_pe.c` - Windows-side ASIO implementation +* Added `asio_unix.c` - Unix-side JACK implementation (with MIDI support) +* Added `unixlib.h` - Shared interface definitions +* Added export definition files for Wine compatibility + +### 1.3.0 +* 24-JUL-2025: Make GUI settings panel compatible with PyQt6 or PyQt5 +* 17-JUL-2025: Load libjack.so.0 dynamically at runtime +* 17-JUL-2025: Remove useless -mnocygwin flag +* 28-JUN-2025: Remove dependency on asio headers + +### 1.2.0 +* 29-SEP-2023: Fix compatibility with Wine > 8 +* 29-SEP-2023: Add wineasio-register script + +### 1.1.0 +* 18-FEB-2022: Various bug fixes +* 24-NOV-2021: Fix compatibility with Wine > 6.5 + +### 1.0.0 +* 14-JUL-2020: Add packaging script +* 12-MAR-2020: Fix control panel startup +* 08-FEB-2020: Fix code to work with latest Wine +* 08-FEB-2020: Add custom GUI for WineASIO settings -#### 0.3 -* 30-APR-2007: corrected connection of in/outputs (RB) +--- -#### 0.1 -* ???????????: Initial RB release (RB) +## Contributing -#### 0.0.2 -* 12-SEP-2006: Fix thread bug, tidy up code (RR) +Contributions are welcome! Please test with: +- Wine 11.x (new architecture) +- Various DAWs (FL Studio, Reaper, Ableton, Bitwig, etc.) +- Both 32-bit and 64-bit applications -#### 0.0.1 -* 31-AUG-2006: Initial version (RR) +--- -### LEGAL STUFF +## Legal Copyright (C) 2006 Robert Reif Portions copyright (C) 2007 Ralf Beck @@ -213,6 +415,7 @@ Portions copyright (C) 2010 Nedko Arnaudov Portions copyright (C) 2011 Christian Schoenebeck Portions copyright (C) 2013 Joakim Hernberg Portions copyright (C) 2020-2023 Filipe Coelho +Portions copyright (C) 2025 Wine 11 Port Contributors The WineASIO library code is licensed under LGPL v2.1, see COPYING.LIB for more details. -The WineASIO settings UI code is licensed under GPL v2+, see COPYING.GUI for more details. +The WineASIO settings UI code is licensed under GPL v2+, see COPYING.GUI for more details. \ No newline at end of file diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md new file mode 100644 index 0000000..2423085 --- /dev/null +++ b/RELEASE_NOTES.md @@ -0,0 +1,174 @@ +# WineASIO 1.4.0 Release Notes + +**Release Date:** January 2025 +**Codename:** Wine 11 Edition + +--- + +## 🎉 Overview + +WineASIO 1.4.0 is a major release that brings full compatibility with **Wine 11** and its new DLL architecture. This release represents a complete rewrite of the build system and internal architecture to support Wine's separation of PE (Windows) and Unix code. + +--- + +## ✨ New Features + +### Wine 11 Support +- **New PE/Unix split architecture** - Complete rewrite to support Wine 11's builtin DLL system +- **Separate PE DLL and Unix SO builds** - Clean separation between Windows and Linux code +- **`__wine_unix_call` interface** - Modern Wine communication between PE and Unix layers +- **Full 32-bit and 64-bit support** - Proper WoW64 compatibility for legacy DAWs + +### Settings GUI Integration +- **Launch from DAW** - Click "Show ASIO Panel" in FL Studio, Reaper, etc. to open settings +- **Native Linux GUI** - PyQt5/PyQt6 settings panel runs natively for best performance +- **Windows launcher executables** - `wineasio-settings.exe` and `wineasio-settings64.exe` included +- **Real-time configuration** - Modify settings without restarting your DAW + +### JACK MIDI Support +- **JACK MIDI ports** - `WineASIO:midi_in` and `WineASIO:midi_out` ports +- **Flexible routing** - Connect hardware MIDI or software synths via JACK +- **a2jmidid compatible** - Works with ALSA-to-JACK MIDI bridge + +--- + +## 📦 What's Included + +### Core Files (Wine 11) +| File | Description | +|------|-------------| +| `asio_pe.c` | PE-side ASIO/COM implementation | +| `asio_unix.c` | Unix-side JACK/MIDI implementation | +| `unixlib.h` | Shared interface definitions | +| `Makefile.wine11` | Wine 11+ build system | + +### Built Libraries +| File | Architecture | Description | +|------|--------------|-------------| +| `wineasio64.dll` | 64-bit | PE DLL for Wine x86_64 | +| `wineasio64.so` | 64-bit | Unix library for JACK interface | +| `wineasio.dll` | 32-bit | PE DLL for Wine i386 (WoW64) | +| `wineasio.so` | 32-bit | Unix library for JACK interface | + +### GUI Components +| File | Description | +|------|-------------| +| `wineasio-settings` | Linux launcher script | +| `settings.py` | PyQt settings application | +| `wineasio-settings.exe` | 32-bit Windows launcher | +| `wineasio-settings64.exe` | 64-bit Windows launcher | + +--- + +## 🔧 Installation + +### Quick Install (Wine 11+) + +```bash +# Build +make -f Makefile.wine11 all + +# Install (requires sudo) +sudo make -f Makefile.wine11 install + +# Register +make -f Makefile.wine11 register + +# Verify +make -f Makefile.wine11 verify +``` + +### Manual Installation + +```bash +# 64-bit +sudo cp build_wine11/wineasio64.dll /opt/wine-stable/lib/wine/x86_64-windows/ +sudo cp build_wine11/wineasio64.so /opt/wine-stable/lib/wine/x86_64-unix/ + +# 32-bit +sudo cp build_wine11/wineasio.dll /opt/wine-stable/lib/wine/i386-windows/ +sudo cp build_wine11/wineasio.so /opt/wine-stable/lib/wine/i386-unix/ + +# Register +wine regsvr32 wineasio64.dll +wine ~/.wine/drive_c/windows/syswow64/regsvr32.exe wineasio.dll +``` + +--- + +## 💻 System Requirements + +### Build Requirements +- GCC with 32-bit and 64-bit support +- MinGW-w64 cross-compiler (i686 and x86_64) +- Wine 10.2+ or Wine 11 development headers +- JACK Audio Connection Kit development files +- Python 3 with PyQt5 or PyQt6 (for GUI) + +### Runtime Requirements +- Wine 10.2+ or Wine 11 +- JACK Audio Connection Kit (jack2 recommended) +- Python 3 with PyQt5 or PyQt6 (for settings GUI) + +--- + +## 🐛 Known Issues + +1. **32-bit registration** - Must use the WoW64 regsvr32 (`syswow64/regsvr32.exe`) +2. **JACK must be running** - Start JACK before launching your DAW +3. **PyQt dependency** - Settings GUI requires PyQt5 or PyQt6 + +--- + +## 🔄 Migration from 1.3.0 + +If you're upgrading from WineASIO 1.3.0: + +1. **Remove old libraries** - Delete old `wineasio*.dll.so` files +2. **Use new Makefile** - Switch to `Makefile.wine11` for Wine 11+ +3. **Re-register** - Run `make -f Makefile.wine11 register` + +The registry settings are compatible and will be preserved. + +--- + +## 🙏 Acknowledgments + +This release was made possible by: +- The Wine development team for the excellent documentation on the new DLL architecture +- All WineASIO contributors and maintainers over the years +- The JACK and Linux audio community + +--- + +## 📄 License + +- **WineASIO library**: LGPL v2.1 (see COPYING.LIB) +- **Settings GUI**: GPL v2+ (see COPYING.GUI) + +--- + +## 🔗 Links + +- **GitHub**: https://github.com/giang17/wineasio +- **Original Project**: https://github.com/wineasio/wineasio +- **Wine**: https://www.winehq.org/ +- **JACK**: https://jackaudio.org/ + +--- + +## 📊 Compatibility Matrix + +| DAW | 32-bit | 64-bit | Control Panel | Notes | +|-----|--------|--------|---------------|-------| +| FL Studio | ✅ | ✅ | ✅ | Fully tested | +| Reaper | ✅ | ✅ | ✅ | Fully tested | +| Ableton Live | ⚠️ | ✅ | ✅ | 64-bit recommended | +| Bitwig Studio | - | ✅ | ✅ | Linux native preferred | +| Cubase | ⚠️ | ✅ | ✅ | Some versions may need tweaks | + +Legend: ✅ Working | ⚠️ Partially tested | - Not applicable + +--- + +**Happy music making with Wine 11! 🎵🍷** \ No newline at end of file diff --git a/WINE11_PORTING.md b/WINE11_PORTING.md new file mode 100644 index 0000000..c114de7 --- /dev/null +++ b/WINE11_PORTING.md @@ -0,0 +1,566 @@ +# WineASIO Wine 11 Porting Guide + +This document describes the technical changes required to port WineASIO from the legacy Wine architecture to Wine 11's new PE/Unix split architecture. + +## Background + +### Wine's New DLL Architecture (Wine 10.2+/11) + +Starting with Wine 10.2, Wine moved to a new builtin DLL architecture: + +**Old Architecture (Wine ≤ 10.1):** +- Single `.dll.so` file containing both PE and Unix code +- Mixed Windows and Linux code in one binary +- Built with `winegcc` + +**New Architecture (Wine 10.2+/11):** +- Separate PE DLL (pure Windows binary) built with `mingw-w64` +- Separate Unix `.so` (pure Linux binary) built with `gcc` +- Communication via `__wine_unix_call` interface + +## Architecture Overview + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Windows Application │ +│ (FL Studio, Reaper, etc.) │ +└─────────────────────────────────────────────────────────────────┘ + │ + │ ASIO API calls + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ wineasio64.dll (PE) │ +│ ┌───────────────────────────────────────────────────────────┐ │ +│ │ • COM/ASIO Interface implementation │ │ +│ │ • Registry configuration reading │ │ +│ │ • Host callback management │ │ +│ │ • Callback polling thread │ │ +│ └───────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────┘ + │ + │ __wine_unix_call() + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ wineasio64.so (Unix) │ +│ ┌───────────────────────────────────────────────────────────┐ │ +│ │ • JACK client connection │ │ +│ │ • Audio buffer management │ │ +│ │ • JACK callbacks (process, buffer size, sample rate) │ │ +│ │ • Real-time audio processing │ │ +│ └───────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────┘ + │ + │ libjack API + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ JACK Server │ +└─────────────────────────────────────────────────────────────────┘ +``` + +## File Structure + +``` +wineasio-1.3.0/ +├── asio_pe.c # PE-side implementation (Windows code) +├── asio_unix.c # Unix-side implementation (JACK code) +├── unixlib.h # Shared interface between PE and Unix +├── wineasio.def # DLL export definitions (for 32-bit) +├── ntdll_wine.def # ntdll import definitions (64-bit) +├── ntdll_wine32.def # ntdll import definitions (32-bit) +├── Makefile.wine11 # New build system for Wine 11 +├── asio.c # Legacy combined code (Wine ≤ 10.1) +└── gui/ + ├── settings.py # PyQt5/6 Settings GUI + ├── settings.ui # Qt Designer UI file + ├── ui_settings.py # Generated UI code + ├── wineasio-settings # Linux launcher script + ├── wineasio-settings-launcher.c # Windows launcher source + ├── wineasio-settings.exe # 32-bit Windows launcher + └── wineasio-settings64.exe # 64-bit Windows launcher +``` + +## Key Components + +### 1. unixlib.h - Shared Interface + +This header defines: +- Parameter structures for each Unix function call +- Function ID enumeration (`enum unix_funcs`) +- Shared type definitions +- ASIO error codes and constants + +```c +enum unix_funcs { + unix_asio_init, + unix_asio_exit, + unix_asio_start, + unix_asio_stop, + unix_asio_get_channels, + // ... etc + unix_funcs_count +}; + +struct asio_init_params { + struct asio_config config; + HRESULT result; + asio_handle handle; + // ... etc +}; +``` + +### 2. asio_pe.c - PE Side (Windows) + +Responsibilities: +- Implements the IASIO COM interface +- Reads configuration from Windows registry +- Manages the ASIO host callbacks +- Runs a polling thread to check for buffer switch notifications +- Calls Unix functions via `__wine_unix_call()` + +Key functions: +- `DllMain()` - Initializes Wine Unix call interface +- `Init()` - Calls Unix side to connect to JACK +- `Start()` / `Stop()` - Controls audio streaming +- `CreateBuffers()` - Sets up audio buffers +- `callback_thread_proc()` - Polls Unix side for callbacks + +### 3. asio_unix.c - Unix Side (Linux) + +Responsibilities: +- Loads libjack dynamically +- Creates JACK client and ports +- Handles JACK callbacks (process, buffer size, sample rate) +- Manages audio buffers +- Signals PE side when buffer switch is needed + +Key functions: +- `asio_init()` - Connects to JACK server +- `asio_start()` / `asio_stop()` - Activates/deactivates JACK client +- `jack_process_callback()` - Real-time audio processing +- `asio_get_callback()` - Returns pending callback information to PE side + +## Wine Unix Call Interface + +### Initialization (PE Side) + +```c +// Import from ntdll +extern unixlib_handle_t __wine_unixlib_handle; +extern NTSTATUS (WINAPI *__wine_unix_call_dispatcher)(...); + +// Get Unix library handle in DllMain +static BOOL init_wine_unix_call(HINSTANCE hInstDLL) +{ + // Get NtQueryVirtualMemory dynamically (avoids stdcall issues on 32-bit) + pNtQueryVirtualMemory = GetProcAddress(GetModuleHandleA("ntdll.dll"), + "NtQueryVirtualMemory"); + + // Query for Unix library handle (MemoryWineUnixFuncs = 1000) + pNtQueryVirtualMemory(GetCurrentProcess(), hInstDLL, + 1000, &wineasio_unix_handle, ...); +} + +// Call Unix function +#define UNIX_CALL(func, params) \ + wine_unix_call(wineasio_unix_handle, unix_##func, params) +``` + +### Unix Function Table (Unix Side) + +```c +// Each function takes a void* args parameter +NTSTATUS asio_init(void *args) +{ + struct asio_init_params *params = args; + // ... implementation + return STATUS_SUCCESS; +} + +// Export the function table +const unixlib_entry_t __wine_unix_call_funcs[] = +{ + asio_init, + asio_exit, + asio_start, + // ... etc +}; +``` + +## Callback Handling + +ASIO requires the host to be called back from the audio thread. Since we can't call Windows code directly from Unix threads, we use a polling mechanism: + +1. **Unix side** (in JACK process callback): + - Copies audio data to shared buffers + - Sets `callback_pending` flag + - Stores timing information + +2. **PE side** (in polling thread): + - Regularly calls `UNIX_CALL(asio_get_callback, ¶ms)` + - If callback is pending, calls host's `bufferSwitch()` or `bufferSwitchTimeInfo()` + - Acknowledges callback with `UNIX_CALL(asio_callback_done, ¶ms)` + +```c +// PE side polling thread +DWORD callback_thread_proc(LPVOID param) +{ + while (!This->stop_callback_thread) { + UNIX_CALL(asio_get_callback, &cb_params); + + if (cb_params.buffer_switch_ready) { + if (This->time_info_mode) + This->callbacks->bufferSwitchTimeInfo(...); + else + This->callbacks->bufferSwitch(...); + + UNIX_CALL(asio_callback_done, &done_params); + } + Sleep(1); // Polling interval + } +} +``` + +## Build System + +### Makefile.wine11 + +Key targets: +- `64` - Builds 64-bit PE DLL and Unix SO +- `32` - Builds 32-bit PE DLL and Unix SO +- `install` - Installs to Wine directories +- `register` - Registers with Wine + +### 32-bit Specific Issues + +1. **stdcall decoration**: mingw-w64 uses `@N` suffix for stdcall functions, but Wine expects undecorated names + - Solution: Use a `.def` file to export undecorated names + +2. **NtQueryVirtualMemory**: Same decoration issue + - Solution: Load dynamically via `GetProcAddress()` + +```c +// wineasio.def - exports without decoration +EXPORTS + DllCanUnloadNow + DllGetClassObject + DllRegisterServer + DllUnregisterServer +``` + +### Naming Convention + +| Architecture | PE DLL | Unix SO | +|--------------|--------|---------| +| 64-bit | `wineasio64.dll` | `wineasio64.so` | +| 32-bit | `wineasio.dll` | `wineasio.so` | + +Note: 32-bit uses `wineasio` (not `wineasio32`) to match Wine's internal expectations. + +## Installation Paths + +Wine 11 uses separate directories for PE and Unix libraries: + +``` +/opt/wine-stable/lib/wine/ +├── x86_64-windows/ # 64-bit PE DLLs +│ └── wineasio64.dll +├── x86_64-unix/ # 64-bit Unix SOs +│ └── wineasio64.so +├── i386-windows/ # 32-bit PE DLLs +│ └── wineasio.dll +└── i386-unix/ # 32-bit Unix SOs + └── wineasio.so +``` + +## Debugging + +### Enable Wine Debug Output + +```bash +WINEDEBUG=+asio,+module wine your_app.exe +``` + +### Check Library Loading + +```bash +WINEDEBUG=+loaddll wine regsvr32 wineasio64.dll 2>&1 | grep wineasio +``` + +### Verify Exports + +```bash +# 64-bit +x86_64-w64-mingw32-objdump -p wineasio64.dll | grep -i dllregister + +# 32-bit (should NOT have @0 suffix) +i686-w64-mingw32-objdump -p wineasio.dll | grep -i dllregister +``` + +### Check Unix Library Symbols + +```bash +nm -D wineasio64.so | grep wine +# Should show: __wine_unix_call_funcs +``` + +## 32-bit Registration (WoW64) + +### Important: Use the Correct regsvr32 + +On a 64-bit Wine prefix, there are two `regsvr32.exe` binaries: + +| Binary | Location | Purpose | +|--------|----------|---------| +| 64-bit | `C:\windows\system32\regsvr32.exe` | Registers 64-bit DLLs | +| 32-bit | `C:\windows\syswow64\regsvr32.exe` | Registers 32-bit DLLs | + +**The default `wine regsvr32` uses the 64-bit version!** + +To register the 32-bit WineASIO DLL, you must explicitly use the 32-bit regsvr32: + +```bash +# 64-bit registration (works with default regsvr32) +wine regsvr32 wineasio64.dll + +# 32-bit registration (MUST use syswow64 regsvr32) +wine ~/.wine/drive_c/windows/syswow64/regsvr32.exe wineasio.dll +``` + +### WoW64 Registry Entries + +After successful 32-bit registration, you should see these registry entries: + +``` +HKCR\WOW6432Node\CLSID\{48D0C522-BFCC-45CC-8B84-17F25F33E6E8} +HKLM\Software\WOW6432Node\ASIO\WineASIO +``` + +Verify with: +```bash +wine reg query "HKCR\WOW6432Node\CLSID\{48D0C522-BFCC-45CC-8B84-17F25F33E6E8}" +wine reg query "HKLM\Software\WOW6432Node\ASIO\WineASIO" +``` + +## JACK MIDI Support + +WineASIO includes optional JACK MIDI ports for direct MIDI routing through the JACK audio graph. + +### JACK MIDI Ports + +When WineASIO initializes, it registers two JACK MIDI ports: + +| Port | Type | Description | +|------|------|-------------| +| `WineASIO:midi_in` | Input | Receives MIDI from JACK graph | +| `WineASIO:midi_out` | Output | Sends MIDI to JACK graph | + +### How It Works + +The MIDI implementation uses a ringbuffer for thread-safe communication: + +```c +/* JACK process callback handles MIDI */ +static int jack_process_callback(jack_nframes_t nframes, void *arg) +{ + /* Read MIDI input events */ + if (stream->midi_enabled && stream->midi_input.port) { + void *midi_buf = pjack_port_get_buffer(stream->midi_input.port, nframes); + jack_nframes_t event_count = pjack_midi_get_event_count(midi_buf); + for (jack_nframes_t j = 0; j < event_count; j++) { + jack_midi_event_t event; + pjack_midi_event_get(&event, midi_buf, j); + midi_ringbuffer_write(&stream->midi_input.ringbuffer, + event.buffer, event.size, event.time); + } + } + + /* Write MIDI output events */ + if (stream->midi_enabled && stream->midi_output.port) { + void *midi_buf = pjack_port_get_buffer(stream->midi_output.port, nframes); + pjack_midi_clear_buffer(midi_buf); + /* ... write pending events from ringbuffer ... */ + } +} +``` + +### Verify MIDI Ports + +After starting a WineASIO application (like FL Studio): + +```bash +# List JACK MIDI ports +jack_lsp -t | grep -E 'WineASIO.*midi' + +# Expected output: +# WineASIO:midi_in +# 8 bit raw midi +# WineASIO:midi_out +# 8 bit raw midi +``` + +### Connecting MIDI Devices + +Use `jack_connect` or QjackCtl to route MIDI: + +```bash +# Connect MIDI controller to WineASIO input +jack_connect "a2j:Your Controller [X] (capture): [0] MIDI 1" "WineASIO:midi_in" + +# Connect WineASIO output to synthesizer +jack_connect "WineASIO:midi_out" "a2j:Your Synth [X] (playback): [0] MIDI 1" +``` + +### Relationship with Wine ALSA MIDI + +WineASIO's JACK MIDI ports are **additional** to Wine's built-in ALSA MIDI support: + +| Feature | Source | Use Case | +|---------|--------|----------| +| Wine ALSA MIDI | `winealsa.drv` | Direct hardware access (e.g., "M4 - M4 MIDI 1") | +| WineASIO JACK MIDI | `wineasio.so` | JACK graph routing, software connections | + +**Note:** Windows applications (like FL Studio) use Wine's ALSA MIDI driver for the Windows MIDI API. The JACK MIDI ports are useful for: +- Direct routing in the JACK graph without a2jmidid +- Connecting to JACK-native applications (Ardour, Carla, etc.) +- Advanced MIDI routing scenarios + +### Troubleshooting MIDI + +**MIDI ports don't appear:** +- Ensure WineASIO is actively being used by an application +- Check if JACK is running: `jack_lsp` +- Verify JACK MIDI functions are loaded (check Wine debug output) + +**"device already open" error in Wine:** +- This usually means you're trying to open an a2jmidid bridge port +- Use hardware ports directly (e.g., "M4 - M4 MIDI 1") instead of "a2jmidid - port" + +## Control Panel / Settings GUI + +WineASIO includes a settings GUI that can be launched from DAWs like FL Studio. + +### How It Works + +1. **ASIO Control Panel Button**: When you click "Show ASIO Panel" in FL Studio (or similar in other DAWs), the ASIO driver's `ControlPanel()` function is called. + +2. **Unix-side Implementation**: The `asio_control_panel()` function in `asio_unix.c` uses `fork()/exec()` to launch the native Linux `wineasio-settings` tool: + +```c +static NTSTATUS asio_control_panel(void *args) +{ + pid_t pid = fork(); + if (pid == 0) { + execvp("wineasio-settings", arg_list); + execl("/usr/bin/wineasio-settings", "wineasio-settings", NULL); + _exit(1); + } + return STATUS_SUCCESS; +} +``` + +3. **Native Linux GUI**: The `wineasio-settings` Python/PyQt application reads and writes WineASIO settings to the Wine registry. + +### Installation + +The GUI is installed automatically with `make -f Makefile.wine11 install`: + +- **Linux launcher**: `/usr/bin/wineasio-settings` +- **Python code**: `/usr/share/wineasio/settings.py` +- **Windows launchers**: `/usr/share/wineasio/wineasio-settings*.exe` + +### Manual Launch + +```bash +# From Linux terminal +wineasio-settings + +# From Wine (using Windows launcher) +wine /usr/share/wineasio/wineasio-settings64.exe +``` + +### Settings Stored + +The GUI modifies these registry values in `HKCU\Software\Wine\WineASIO`: + +| Setting | Type | Default | Description | +|---------|------|---------|-------------| +| Number of inputs | DWORD | 16 | JACK input ports | +| Number of outputs | DWORD | 16 | JACK output ports | +| Connect to hardware | DWORD | 1 | Auto-connect to physical ports | +| Autostart server | DWORD | 0 | Start JACK if not running | +| Fixed buffersize | DWORD | 1 | Prevent apps from changing buffer | +| Preferred buffersize | DWORD | 1024 | Default buffer size | + +## Common Issues + +### "Unix library not found" + +- Ensure `.so` file is in the correct `*-unix/` directory +- Ensure `.so` exports `__wine_unix_call_funcs` +- Check for unresolved symbols: `nm -D wineasio64.so | grep " U "` + +### "DllRegisterServer not found" (32-bit) + +- Ensure `wineasio.def` is included in the build +- Verify exports don't have stdcall decoration + +### "NtQueryVirtualMemory not found" (32-bit) + +- Load via `GetProcAddress()` instead of static import +- Don't use `@24` decoration in def file + +### "cannot find builtin library" (32-bit registration) + +- You're using the wrong regsvr32! Use the 32-bit version: + ```bash + wine ~/.wine/drive_c/windows/syswow64/regsvr32.exe wineasio.dll + ``` + +### Control Panel doesn't open + +- Ensure `wineasio-settings` is in PATH (`/usr/bin/`) +- Check if PyQt5 or PyQt6 is installed: `python3 -c "from PyQt5.QtWidgets import QApplication"` +- Try launching manually: `wineasio-settings` + +## References + +- Wine GitLab: https://gitlab.winehq.org/wine/wine +- Wine Unix Call Interface: `include/wine/unixlib.h` +- Example builtin DLLs: `dlls/winealsa.drv/`, `dlls/winepulse.drv/` + +## Quick Reference + +### Build Commands + +```bash +make -f Makefile.wine11 64 # Build 64-bit only +make -f Makefile.wine11 32 # Build 32-bit only +make -f Makefile.wine11 all # Build both +make -f Makefile.wine11 install # Install everything (requires sudo) +make -f Makefile.wine11 register # Register with Wine +``` + +### Verify Installation + +```bash +# Check files +ls -la /opt/wine-stable/lib/wine/x86_64-windows/wineasio64.dll +ls -la /opt/wine-stable/lib/wine/x86_64-unix/wineasio64.so +ls -la /opt/wine-stable/lib/wine/i386-windows/wineasio.dll +ls -la /opt/wine-stable/lib/wine/i386-unix/wineasio.so + +# Check registration +wine reg query "HKLM\Software\ASIO\WineASIO" +wine reg query "HKLM\Software\WOW6432Node\ASIO\WineASIO" + +# Check GUI +which wineasio-settings +``` + +## Authors + +Wine 11 port implemented January 2025, one day after Wine 11's official release (January 13, 2025). + +Control Panel GUI integration added January 2025. + +JACK MIDI support added January 2025. \ No newline at end of file diff --git a/asio_pe.c b/asio_pe.c new file mode 100644 index 0000000..26ac4a7 --- /dev/null +++ b/asio_pe.c @@ -0,0 +1,1027 @@ +/* + * WineASIO PE Side - Windows ASIO Interface + * Copyright (C) 2006 Robert Reif + * Portions copyright (C) 2007-2013 Various contributors + * Copyright (C) 2024 WineASIO contributors + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include +#include +#include + +#define COBJMACROS +#include +#include +#include +#include +#include + +/* NtQueryVirtualMemory will be loaded dynamically to avoid stdcall decoration issues on 32-bit */ +typedef NTSTATUS (WINAPI *NtQueryVirtualMemory_t)(HANDLE, LPCVOID, ULONG, PVOID, SIZE_T, SIZE_T*); +static NtQueryVirtualMemory_t pNtQueryVirtualMemory = NULL; + +#include "unixlib.h" + +/* NTSTATUS definitions for mingw if not available */ +#ifndef STATUS_SUCCESS +#define STATUS_SUCCESS ((NTSTATUS)0x00000000L) +#endif +#ifndef STATUS_UNSUCCESSFUL +#define STATUS_UNSUCCESSFUL ((NTSTATUS)0xC0000001L) +#endif + +/* Debug macros - replace Wine's debug system with simple printf for PE side */ +#ifdef WINEASIO_DEBUG +#define TRACE(fmt, ...) do { fprintf(stderr, "wineasio:trace: " fmt, ##__VA_ARGS__); } while(0) +#define WARN(fmt, ...) do { fprintf(stderr, "wineasio:warn: " fmt, ##__VA_ARGS__); } while(0) +#define ERR(fmt, ...) do { fprintf(stderr, "wineasio:err: " fmt, ##__VA_ARGS__); } while(0) +#else +#define TRACE(fmt, ...) do { } while(0) +#define WARN(fmt, ...) do { } while(0) +#define ERR(fmt, ...) do { fprintf(stderr, "wineasio:err: " fmt, ##__VA_ARGS__); } while(0) +#endif + +/* + * Wine Unix Call interface for mingw + * These symbols are imported directly from ntdll.dll using dllimport + */ +typedef UINT64 unixlib_handle_t; + +/* Import Wine's unix call interface directly from ntdll + * These are set up by Wine's loader for builtin DLLs */ +extern unixlib_handle_t __wine_unixlib_handle; +extern NTSTATUS (WINAPI *__wine_unix_call_dispatcher)(unixlib_handle_t, unsigned int, void *); + +/* Our handle - set during initialization */ +static unixlib_handle_t wineasio_unix_handle = 0; + +/* Wrapper for unix calls */ +static inline NTSTATUS wine_unix_call(unixlib_handle_t handle, unsigned int code, void *args) +{ + if (!__wine_unix_call_dispatcher) { + ERR("Unix call dispatcher not available!\n"); + return STATUS_UNSUCCESSFUL; + } + return __wine_unix_call_dispatcher(handle, code, args); +} + +/* GUID to string helper - replacement for wine_dbgstr_guid */ +static const char *debugstr_guid(const GUID *guid) +{ + static char buf[64]; + if (!guid) return "(null)"; + snprintf(buf, sizeof(buf), "{%08lx-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}", + (unsigned long)guid->Data1, guid->Data2, guid->Data3, + guid->Data4[0], guid->Data4[1], guid->Data4[2], guid->Data4[3], + guid->Data4[4], guid->Data4[5], guid->Data4[6], guid->Data4[7]); + return buf; +} +#define wine_dbgstr_guid debugstr_guid + +/* Initialize Wine unix call interface - must be called from DllMain */ +static BOOL init_wine_unix_call(HINSTANCE hInstDLL) +{ + NTSTATUS status; + HMODULE hNtdll; + + /* Check if unix call dispatcher is available */ + if (!__wine_unix_call_dispatcher) { + ERR("Wine unix call dispatcher not available - not running under Wine?\n"); + return FALSE; + } + + /* Load NtQueryVirtualMemory dynamically to avoid stdcall decoration issues on 32-bit */ + hNtdll = GetModuleHandleA("ntdll.dll"); + if (!hNtdll) { + ERR("Failed to get ntdll.dll handle\n"); + return FALSE; + } + + pNtQueryVirtualMemory = (NtQueryVirtualMemory_t)GetProcAddress(hNtdll, "NtQueryVirtualMemory"); + if (!pNtQueryVirtualMemory) { + ERR("Failed to get NtQueryVirtualMemory from ntdll\n"); + return FALSE; + } + + /* Get our unix library handle using NtQueryVirtualMemory with MemoryWineUnixFuncs = 1000 + * Wine's loader should have already loaded the unix .so for this builtin DLL */ + status = pNtQueryVirtualMemory(GetCurrentProcess(), hInstDLL, + 1000 /* MemoryWineUnixFuncs */, + &wineasio_unix_handle, + sizeof(wineasio_unix_handle), NULL); + if (status) { + ERR("Failed to get unix library handle, status 0x%lx\n", (unsigned long)status); +#ifdef _WIN64 + ERR("Make sure wineasio64.so is in the Wine unix library path\n"); +#else + ERR("Make sure wineasio.so is in the Wine unix library path\n"); +#endif + return FALSE; + } + + TRACE("Wine unix call interface initialized, handle=%llx\n", + (unsigned long long)wineasio_unix_handle); + return TRUE; +} + +#define WINEASIO_VERSION 13 /* 1.3 */ + +/* ASIO type definitions */ +typedef struct { + LONG hi; + LONG lo; +} ASIOSamples; + +typedef struct { + LONG hi; + LONG lo; +} ASIOTimeStamp; + +typedef struct { + double speed; + ASIOTimeStamp systemTime; + ASIOSamples samples; + ASIOTimeStamp tcTimeCode; + LONG flags; + char future[64]; +} ASIOTimeCode; + +typedef struct { + LONG isInput; + LONG channelNum; + void *buffers[2]; +} ASIOBufferInfo; + +typedef struct { + LONG channel; + LONG isInput; + LONG isActive; + LONG channelGroup; + LONG type; + char name[32]; +} ASIOChannelInfo; + +typedef struct { + ASIOTimeCode timeCode; + ASIOSamples timeInfo; + ASIOTimeStamp systemTime; + double sampleRate; + LONG flags; + char reserved[12]; +} ASIOTime; + +typedef struct { + void (*bufferSwitch)(LONG bufferIndex, LONG directProcess); + void (*sampleRateDidChange)(double sRate); + LONG (*asioMessage)(LONG selector, LONG value, void *message, double *opt); + ASIOTime* (*bufferSwitchTimeInfo)(ASIOTime *params, LONG bufferIndex, LONG directProcess); +} ASIOCallbacks; + +/* Forward declarations */ +struct IWineASIO; +typedef struct IWineASIO IWineASIO; +typedef IWineASIO *LPWINEASIO; + +/* ASIO interface - uses COM-like structure */ +typedef struct IWineASIOVtbl { + /* IUnknown */ + HRESULT (STDMETHODCALLTYPE *QueryInterface)(LPWINEASIO iface, REFIID riid, void **ppvObject); + ULONG (STDMETHODCALLTYPE *AddRef)(LPWINEASIO iface); + ULONG (STDMETHODCALLTYPE *Release)(LPWINEASIO iface); + /* IASIO */ + LONG (STDMETHODCALLTYPE *Init)(LPWINEASIO iface, void *sysRef); + void (STDMETHODCALLTYPE *GetDriverName)(LPWINEASIO iface, char *name); + LONG (STDMETHODCALLTYPE *GetDriverVersion)(LPWINEASIO iface); + void (STDMETHODCALLTYPE *GetErrorMessage)(LPWINEASIO iface, char *string); + LONG (STDMETHODCALLTYPE *Start)(LPWINEASIO iface); + LONG (STDMETHODCALLTYPE *Stop)(LPWINEASIO iface); + LONG (STDMETHODCALLTYPE *GetChannels)(LPWINEASIO iface, LONG *numInputChannels, LONG *numOutputChannels); + LONG (STDMETHODCALLTYPE *GetLatencies)(LPWINEASIO iface, LONG *inputLatency, LONG *outputLatency); + LONG (STDMETHODCALLTYPE *GetBufferSize)(LPWINEASIO iface, LONG *minSize, LONG *maxSize, LONG *preferredSize, LONG *granularity); + LONG (STDMETHODCALLTYPE *CanSampleRate)(LPWINEASIO iface, double sampleRate); + LONG (STDMETHODCALLTYPE *GetSampleRate)(LPWINEASIO iface, double *currentRate); + LONG (STDMETHODCALLTYPE *SetSampleRate)(LPWINEASIO iface, double sampleRate); + LONG (STDMETHODCALLTYPE *GetClockSources)(LPWINEASIO iface, void *clocks, LONG *numSources); + LONG (STDMETHODCALLTYPE *SetClockSource)(LPWINEASIO iface, LONG reference); + LONG (STDMETHODCALLTYPE *GetSamplePosition)(LPWINEASIO iface, ASIOSamples *sPos, ASIOTimeStamp *tStamp); + LONG (STDMETHODCALLTYPE *GetChannelInfo)(LPWINEASIO iface, ASIOChannelInfo *info); + LONG (STDMETHODCALLTYPE *CreateBuffers)(LPWINEASIO iface, ASIOBufferInfo *bufferInfos, LONG numChannels, LONG bufferSize, ASIOCallbacks *callbacks); + LONG (STDMETHODCALLTYPE *DisposeBuffers)(LPWINEASIO iface); + LONG (STDMETHODCALLTYPE *ControlPanel)(LPWINEASIO iface); + LONG (STDMETHODCALLTYPE *Future)(LPWINEASIO iface, LONG selector, void *params); + LONG (STDMETHODCALLTYPE *OutputReady)(LPWINEASIO iface); +} IWineASIOVtbl; + +struct IWineASIO { + const IWineASIOVtbl *lpVtbl; + LONG ref; + + /* Unix handle */ + asio_handle handle; + + /* Host callbacks */ + ASIOCallbacks *callbacks; + BOOL time_info_mode; + BOOL can_time_code; + + /* State */ + LONG num_inputs; + LONG num_outputs; + double sample_rate; + LONG buffer_size; + + /* Callback thread */ + HANDLE callback_thread; + BOOL stop_callback_thread; + ASIOTime host_time; + + /* Configuration */ + struct asio_config config; +}; + +/* {48D0C522-BFCC-45cc-8B84-17F25F33E6E8} */ +static const GUID CLSID_WineASIO = { + 0x48d0c522, 0xbfcc, 0x45cc, { 0x8b, 0x84, 0x17, 0xf2, 0x5f, 0x33, 0xe6, 0xe8 } +}; + +#define UNIX_CALL(func, params) wine_unix_call(wineasio_unix_handle, unix_##func, params) + +/* Read configuration from registry */ +static void read_config(IWineASIO *This) +{ + HKEY hkey; + DWORD type, size, value; + char str_value[256]; + + /* Set defaults */ + This->config.num_inputs = 16; + This->config.num_outputs = 16; + This->config.preferred_bufsize = 1024; + This->config.fixed_bufsize = FALSE; + This->config.autoconnect = TRUE; + strcpy(This->config.client_name, "WineASIO"); + + if (RegOpenKeyExA(HKEY_CURRENT_USER, "Software\\Wine\\WineASIO", 0, KEY_READ, &hkey) == ERROR_SUCCESS) { + size = sizeof(value); + if (RegQueryValueExA(hkey, "Number of inputs", NULL, &type, (BYTE*)&value, &size) == ERROR_SUCCESS && type == REG_DWORD) + This->config.num_inputs = value; + + size = sizeof(value); + if (RegQueryValueExA(hkey, "Number of outputs", NULL, &type, (BYTE*)&value, &size) == ERROR_SUCCESS && type == REG_DWORD) + This->config.num_outputs = value; + + size = sizeof(value); + if (RegQueryValueExA(hkey, "Preferred buffersize", NULL, &type, (BYTE*)&value, &size) == ERROR_SUCCESS && type == REG_DWORD) + This->config.preferred_bufsize = value; + + size = sizeof(value); + if (RegQueryValueExA(hkey, "Fixed buffersize", NULL, &type, (BYTE*)&value, &size) == ERROR_SUCCESS && type == REG_DWORD) + This->config.fixed_bufsize = value ? TRUE : FALSE; + + size = sizeof(value); + if (RegQueryValueExA(hkey, "Connect to hardware", NULL, &type, (BYTE*)&value, &size) == ERROR_SUCCESS && type == REG_DWORD) + This->config.autoconnect = value ? TRUE : FALSE; + + size = sizeof(str_value); + if (RegQueryValueExA(hkey, "Client name", NULL, &type, (BYTE*)str_value, &size) == ERROR_SUCCESS && type == REG_SZ) + strncpy(This->config.client_name, str_value, 63); + + RegCloseKey(hkey); + } + + TRACE("Config: inputs=%d outputs=%d bufsize=%d fixed=%d autoconnect=%d name=%s\n", + This->config.num_inputs, This->config.num_outputs, This->config.preferred_bufsize, + This->config.fixed_bufsize, This->config.autoconnect, This->config.client_name); +} + +/* Callback polling thread - polls Unix side for buffer switches */ +static DWORD WINAPI callback_thread_proc(LPVOID arg) +{ + IWineASIO *This = (IWineASIO *)arg; + struct asio_get_callback_params params; + + TRACE("Callback thread started\n"); + + while (!This->stop_callback_thread) { + params.handle = This->handle; + UNIX_CALL(asio_get_callback, ¶ms); + + if (params.result == ASE_OK && params.buffer_switch_ready && This->callbacks) { + /* Handle sample rate change */ + if (params.sample_rate_changed) { + TRACE("Sample rate changed to %f\n", params.new_sample_rate); + This->sample_rate = params.new_sample_rate; + This->callbacks->sampleRateDidChange(params.new_sample_rate); + } + + /* Handle reset request */ + if (params.reset_request) { + TRACE("Reset requested\n"); + This->callbacks->asioMessage(1 /* kAsioSelectorSupported */, 3 /* kAsioResetRequest */, NULL, NULL); + This->callbacks->asioMessage(3 /* kAsioResetRequest */, 0, NULL, NULL); + } + + /* Handle latency change */ + if (params.latency_changed) { + TRACE("Latency changed\n"); + This->callbacks->asioMessage(1, 6 /* kAsioLatenciesChanged */, NULL, NULL); + This->callbacks->asioMessage(6, 0, NULL, NULL); + } + + /* Buffer switch */ + if (This->time_info_mode) { + /* Use time info mode */ + This->host_time.timeInfo.hi = (LONG)(params.time_info.sample_position >> 32); + This->host_time.timeInfo.lo = (LONG)(params.time_info.sample_position & 0xFFFFFFFF); + This->host_time.systemTime.hi = (LONG)(params.time_info.system_time >> 32); + This->host_time.systemTime.lo = (LONG)(params.time_info.system_time & 0xFFFFFFFF); + This->host_time.sampleRate = params.time_info.sample_rate; + This->host_time.flags = params.time_info.flags; + + This->callbacks->bufferSwitchTimeInfo(&This->host_time, params.buffer_index, params.direct_process); + } else { + /* Use simple buffer switch */ + This->callbacks->bufferSwitch(params.buffer_index, params.direct_process); + } + } + + /* Small sleep to avoid busy waiting - 1ms */ + Sleep(1); + } + + TRACE("Callback thread stopped\n"); + return 0; +} + +/* IUnknown methods */ +static HRESULT STDMETHODCALLTYPE QueryInterface(LPWINEASIO iface, REFIID riid, void **ppvObject) +{ + TRACE("iface=%p riid=%s\n", iface, wine_dbgstr_guid(riid)); + + if (!ppvObject) + return E_POINTER; + + if (IsEqualGUID(riid, &IID_IUnknown)) { + *ppvObject = iface; + iface->lpVtbl->AddRef(iface); + return S_OK; + } + + *ppvObject = NULL; + return E_NOINTERFACE; +} + +static ULONG STDMETHODCALLTYPE AddRef(LPWINEASIO iface) +{ + IWineASIO *This = (IWineASIO *)iface; + ULONG ref = InterlockedIncrement(&This->ref); + TRACE("iface=%p ref=%u\n", iface, ref); + return ref; +} + +static ULONG STDMETHODCALLTYPE Release(LPWINEASIO iface) +{ + IWineASIO *This = (IWineASIO *)iface; + ULONG ref = InterlockedDecrement(&This->ref); + + TRACE("iface=%p ref=%u\n", iface, ref); + + if (ref == 0) { + /* Stop callback thread */ + if (This->callback_thread) { + This->stop_callback_thread = TRUE; + WaitForSingleObject(This->callback_thread, 5000); + CloseHandle(This->callback_thread); + } + + /* Close Unix side */ + if (This->handle) { + struct asio_exit_params params = { .handle = This->handle }; + UNIX_CALL(asio_exit, ¶ms); + } + + HeapFree(GetProcessHeap(), 0, This); + } + + return ref; +} + +/* IASIO methods */ +static LONG STDMETHODCALLTYPE Init(LPWINEASIO iface, void *sysRef) +{ + IWineASIO *This = (IWineASIO *)iface; + struct asio_init_params params; + + TRACE("iface=%p sysRef=%p\n", iface, sysRef); + + /* Read config from registry */ + read_config(This); + + /* Initialize Unix side */ + memset(¶ms, 0, sizeof(params)); + params.config = This->config; + + UNIX_CALL(asio_init, ¶ms); + + if (params.result != ASE_OK) { + WARN("Unix init failed: %d\n", params.result); + return 0; /* ASIO Init returns 0 on failure */ + } + + This->handle = params.handle; + This->num_inputs = params.input_channels; + This->num_outputs = params.output_channels; + This->sample_rate = params.sample_rate; + + TRACE("Initialized: handle=%llu inputs=%d outputs=%d rate=%f\n", + (unsigned long long)This->handle, This->num_inputs, This->num_outputs, This->sample_rate); + + return 1; /* Success */ +} + +static void STDMETHODCALLTYPE GetDriverName(LPWINEASIO iface, char *name) +{ + TRACE("iface=%p name=%p\n", iface, name); + strcpy(name, "WineASIO"); +} + +static LONG STDMETHODCALLTYPE GetDriverVersion(LPWINEASIO iface) +{ + TRACE("iface=%p\n", iface); + return WINEASIO_VERSION; +} + +static void STDMETHODCALLTYPE GetErrorMessage(LPWINEASIO iface, char *string) +{ + TRACE("iface=%p string=%p\n", iface, string); + strcpy(string, "No error"); +} + +static LONG STDMETHODCALLTYPE Start(LPWINEASIO iface) +{ + IWineASIO *This = (IWineASIO *)iface; + struct asio_start_params params = { .handle = This->handle }; + + TRACE("iface=%p\n", iface); + + UNIX_CALL(asio_start, ¶ms); + + if (params.result != ASE_OK) { + WARN("Start failed: %d\n", params.result); + return params.result; + } + + /* Start callback polling thread */ + This->stop_callback_thread = FALSE; + This->callback_thread = CreateThread(NULL, 0, callback_thread_proc, This, 0, NULL); + + /* Prime the first buffer */ + if (This->callbacks) { + if (This->time_info_mode) { + memset(&This->host_time, 0, sizeof(This->host_time)); + This->host_time.sampleRate = This->sample_rate; + This->host_time.flags = 0x7; + This->callbacks->bufferSwitchTimeInfo(&This->host_time, 0, TRUE); + } else { + This->callbacks->bufferSwitch(0, TRUE); + } + } + + return ASE_OK; +} + +static LONG STDMETHODCALLTYPE Stop(LPWINEASIO iface) +{ + IWineASIO *This = (IWineASIO *)iface; + struct asio_stop_params params = { .handle = This->handle }; + + TRACE("iface=%p\n", iface); + + /* Stop callback thread */ + if (This->callback_thread) { + This->stop_callback_thread = TRUE; + WaitForSingleObject(This->callback_thread, 5000); + CloseHandle(This->callback_thread); + This->callback_thread = NULL; + } + + UNIX_CALL(asio_stop, ¶ms); + + return params.result; +} + +static LONG STDMETHODCALLTYPE GetChannels(LPWINEASIO iface, LONG *numInputChannels, LONG *numOutputChannels) +{ + IWineASIO *This = (IWineASIO *)iface; + struct asio_get_channels_params params = { .handle = This->handle }; + + TRACE("iface=%p\n", iface); + + if (!numInputChannels || !numOutputChannels) + return ASE_InvalidParameter; + + UNIX_CALL(asio_get_channels, ¶ms); + + *numInputChannels = params.num_inputs; + *numOutputChannels = params.num_outputs; + + return params.result; +} + +static LONG STDMETHODCALLTYPE GetLatencies(LPWINEASIO iface, LONG *inputLatency, LONG *outputLatency) +{ + IWineASIO *This = (IWineASIO *)iface; + struct asio_get_latencies_params params = { .handle = This->handle }; + + TRACE("iface=%p\n", iface); + + if (!inputLatency || !outputLatency) + return ASE_InvalidParameter; + + UNIX_CALL(asio_get_latencies, ¶ms); + + *inputLatency = params.input_latency; + *outputLatency = params.output_latency; + + return params.result; +} + +static LONG STDMETHODCALLTYPE GetBufferSize(LPWINEASIO iface, LONG *minSize, LONG *maxSize, LONG *preferredSize, LONG *granularity) +{ + IWineASIO *This = (IWineASIO *)iface; + struct asio_get_buffer_size_params params = { .handle = This->handle }; + + TRACE("iface=%p\n", iface); + + UNIX_CALL(asio_get_buffer_size, ¶ms); + + if (minSize) *minSize = params.min_size; + if (maxSize) *maxSize = params.max_size; + if (preferredSize) *preferredSize = params.preferred_size; + if (granularity) *granularity = params.granularity; + + return params.result; +} + +static LONG STDMETHODCALLTYPE CanSampleRate(LPWINEASIO iface, double sampleRate) +{ + IWineASIO *This = (IWineASIO *)iface; + struct asio_can_sample_rate_params params = { .handle = This->handle, .sample_rate = sampleRate }; + + TRACE("iface=%p rate=%f\n", iface, sampleRate); + + UNIX_CALL(asio_can_sample_rate, ¶ms); + + return params.result; +} + +static LONG STDMETHODCALLTYPE GetSampleRate(LPWINEASIO iface, double *currentRate) +{ + IWineASIO *This = (IWineASIO *)iface; + struct asio_get_sample_rate_params params = { .handle = This->handle }; + + TRACE("iface=%p\n", iface); + + if (!currentRate) + return ASE_InvalidParameter; + + UNIX_CALL(asio_get_sample_rate, ¶ms); + + *currentRate = params.sample_rate; + This->sample_rate = params.sample_rate; + + return params.result; +} + +static LONG STDMETHODCALLTYPE SetSampleRate(LPWINEASIO iface, double sampleRate) +{ + IWineASIO *This = (IWineASIO *)iface; + struct asio_set_sample_rate_params params = { .handle = This->handle, .sample_rate = sampleRate }; + + TRACE("iface=%p rate=%f\n", iface, sampleRate); + + UNIX_CALL(asio_set_sample_rate, ¶ms); + + if (params.result == ASE_OK) + This->sample_rate = sampleRate; + + return params.result; +} + +static LONG STDMETHODCALLTYPE GetClockSources(LPWINEASIO iface, void *clocks, LONG *numSources) +{ + TRACE("iface=%p\n", iface); + + /* We only have one clock source - JACK */ + if (numSources) + *numSources = 0; + + return ASE_OK; +} + +static LONG STDMETHODCALLTYPE SetClockSource(LPWINEASIO iface, LONG reference) +{ + TRACE("iface=%p ref=%d\n", iface, reference); + + /* Only one clock source, ignore */ + return ASE_OK; +} + +static LONG STDMETHODCALLTYPE GetSamplePosition(LPWINEASIO iface, ASIOSamples *sPos, ASIOTimeStamp *tStamp) +{ + IWineASIO *This = (IWineASIO *)iface; + struct asio_get_sample_position_params params = { .handle = This->handle }; + + if (!sPos || !tStamp) + return ASE_InvalidParameter; + + UNIX_CALL(asio_get_sample_position, ¶ms); + + sPos->hi = (LONG)(params.sample_position >> 32); + sPos->lo = (LONG)(params.sample_position & 0xFFFFFFFF); + tStamp->hi = (LONG)(params.system_time >> 32); + tStamp->lo = (LONG)(params.system_time & 0xFFFFFFFF); + + return params.result; +} + +static LONG STDMETHODCALLTYPE GetChannelInfo(LPWINEASIO iface, ASIOChannelInfo *info) +{ + IWineASIO *This = (IWineASIO *)iface; + struct asio_get_channel_info_params params; + + TRACE("iface=%p channel=%d isInput=%d\n", iface, info->channel, info->isInput); + + if (!info) + return ASE_InvalidParameter; + + memset(¶ms, 0, sizeof(params)); + params.handle = This->handle; + params.info.channel = info->channel; + params.info.is_input = info->isInput; + + UNIX_CALL(asio_get_channel_info, ¶ms); + + info->isActive = params.info.is_active; + info->channelGroup = params.info.channel_group; + info->type = params.info.sample_type; + strncpy(info->name, params.info.name, 31); + info->name[31] = 0; + + return params.result; +} + +static LONG STDMETHODCALLTYPE CreateBuffers(LPWINEASIO iface, ASIOBufferInfo *bufferInfos, LONG numChannels, LONG bufferSize, ASIOCallbacks *callbacks) +{ + IWineASIO *This = (IWineASIO *)iface; + struct asio_create_buffers_params params; + struct asio_buffer_info *unix_infos; + int i; + + TRACE("iface=%p numChannels=%d bufferSize=%d\n", iface, numChannels, bufferSize); + + if (!bufferInfos || !callbacks || numChannels <= 0) + return ASE_InvalidParameter; + + /* Store callbacks */ + This->callbacks = callbacks; + This->buffer_size = bufferSize; + + /* Check for time info support */ + This->time_info_mode = FALSE; + This->can_time_code = FALSE; + if (callbacks->asioMessage) { + if (callbacks->asioMessage(1 /* kAsioSelectorSupported */, 14 /* kAsioSupportsTimeInfo */, NULL, NULL) == 1) + This->time_info_mode = TRUE; + if (callbacks->asioMessage(1, 15 /* kAsioSupportsTimeCode */, NULL, NULL) == 1) + This->can_time_code = TRUE; + } + + TRACE("time_info_mode=%d can_time_code=%d\n", This->time_info_mode, This->can_time_code); + + /* Prepare Unix call */ + unix_infos = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, numChannels * sizeof(*unix_infos)); + if (!unix_infos) + return ASE_NoMemory; + + for (i = 0; i < numChannels; i++) { + unix_infos[i].is_input = bufferInfos[i].isInput; + unix_infos[i].channel_num = bufferInfos[i].channelNum; + } + + memset(¶ms, 0, sizeof(params)); + params.handle = This->handle; + params.num_channels = numChannels; + params.buffer_size = bufferSize; + params.buffer_infos = unix_infos; + + UNIX_CALL(asio_create_buffers, ¶ms); + + /* Copy buffer pointers back */ + if (params.result == ASE_OK) { + for (i = 0; i < numChannels; i++) { + bufferInfos[i].buffers[0] = (void *)(UINT_PTR)unix_infos[i].buffer_ptr[0]; + bufferInfos[i].buffers[1] = (void *)(UINT_PTR)unix_infos[i].buffer_ptr[1]; + } + } + + HeapFree(GetProcessHeap(), 0, unix_infos); + + return params.result; +} + +static LONG STDMETHODCALLTYPE DisposeBuffers(LPWINEASIO iface) +{ + IWineASIO *This = (IWineASIO *)iface; + struct asio_dispose_buffers_params params = { .handle = This->handle }; + + TRACE("iface=%p\n", iface); + + UNIX_CALL(asio_dispose_buffers, ¶ms); + + This->callbacks = NULL; + + return params.result; +} + +static LONG STDMETHODCALLTYPE ControlPanel(LPWINEASIO iface) +{ + IWineASIO *This = (IWineASIO *)iface; + struct asio_control_panel_params params = { .handle = This->handle }; + + TRACE("iface=%p\n", iface); + + UNIX_CALL(asio_control_panel, ¶ms); + + return params.result; +} + +static LONG STDMETHODCALLTYPE Future(LPWINEASIO iface, LONG selector, void *opt) +{ + IWineASIO *This = (IWineASIO *)iface; + struct asio_future_params params; + + TRACE("iface=%p selector=%d\n", iface, selector); + + memset(¶ms, 0, sizeof(params)); + params.handle = This->handle; + params.selector = selector; + params.opt = (UINT64)(UINT_PTR)opt; + + UNIX_CALL(asio_future, ¶ms); + + return params.result; +} + +static LONG STDMETHODCALLTYPE OutputReady(LPWINEASIO iface) +{ + IWineASIO *This = (IWineASIO *)iface; + struct asio_output_ready_params params = { .handle = This->handle }; + + UNIX_CALL(asio_output_ready, ¶ms); + + return params.result; +} + +/* VTable */ +static const IWineASIOVtbl WineASIO_Vtbl = { + QueryInterface, + AddRef, + Release, + Init, + GetDriverName, + GetDriverVersion, + GetErrorMessage, + Start, + Stop, + GetChannels, + GetLatencies, + GetBufferSize, + CanSampleRate, + GetSampleRate, + SetSampleRate, + GetClockSources, + SetClockSource, + GetSamplePosition, + GetChannelInfo, + CreateBuffers, + DisposeBuffers, + ControlPanel, + Future, + OutputReady +}; + +/* Create WineASIO instance */ +HRESULT WINAPI WineASIOCreateInstance(REFIID riid, LPVOID *ppobj) +{ + IWineASIO *pAsio; + + TRACE("riid=%s ppobj=%p\n", wine_dbgstr_guid(riid), ppobj); + + if (!ppobj) + return E_POINTER; + + *ppobj = NULL; + + pAsio = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*pAsio)); + if (!pAsio) + return E_OUTOFMEMORY; + + pAsio->lpVtbl = &WineASIO_Vtbl; + pAsio->ref = 1; + + *ppobj = pAsio; + return S_OK; +} + +/* + * ClassFactory + */ +typedef struct { + const IClassFactoryVtbl *lpVtbl; + LONG ref; +} IClassFactoryImpl; + +static HRESULT WINAPI CF_QueryInterface(LPCLASSFACTORY iface, REFIID riid, LPVOID *ppobj) +{ + if (ppobj == NULL) + return E_POINTER; + *ppobj = NULL; + return E_NOINTERFACE; +} + +static ULONG WINAPI CF_AddRef(LPCLASSFACTORY iface) +{ + IClassFactoryImpl *This = (IClassFactoryImpl *)iface; + return InterlockedIncrement(&This->ref); +} + +static ULONG WINAPI CF_Release(LPCLASSFACTORY iface) +{ + IClassFactoryImpl *This = (IClassFactoryImpl *)iface; + return InterlockedDecrement(&This->ref); +} + +static HRESULT WINAPI CF_CreateInstance(LPCLASSFACTORY iface, LPUNKNOWN pOuter, REFIID riid, LPVOID *ppobj) +{ + if (pOuter) + return CLASS_E_NOAGGREGATION; + + if (ppobj == NULL) + return E_INVALIDARG; + + *ppobj = NULL; + return WineASIOCreateInstance(riid, ppobj); +} + +static HRESULT WINAPI CF_LockServer(LPCLASSFACTORY iface, BOOL dolock) +{ + return S_OK; +} + +static const IClassFactoryVtbl CF_Vtbl = { + CF_QueryInterface, + CF_AddRef, + CF_Release, + CF_CreateInstance, + CF_LockServer +}; + +static IClassFactoryImpl WINEASIO_CF = { &CF_Vtbl, 1 }; + +/* + * DLL exports + */ +HRESULT WINAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID *ppv) +{ + TRACE("rclsid=%s riid=%s ppv=%p\n", wine_dbgstr_guid(rclsid), wine_dbgstr_guid(riid), ppv); + + if (ppv == NULL) + return E_INVALIDARG; + + *ppv = NULL; + + if (!IsEqualIID(riid, &IID_IClassFactory) && !IsEqualIID(riid, &IID_IUnknown)) + return E_NOINTERFACE; + + if (IsEqualGUID(rclsid, &CLSID_WineASIO)) { + CF_AddRef((IClassFactory *)&WINEASIO_CF); + *ppv = &WINEASIO_CF; + return S_OK; + } + + return CLASS_E_CLASSNOTAVAILABLE; +} + +HRESULT WINAPI DllCanUnloadNow(void) +{ + return S_FALSE; +} + +/* + * DllRegisterServer - Register the COM class and ASIO driver + */ +HRESULT WINAPI DllRegisterServer(void) +{ + HKEY hkey, hsubkey; + LONG rc; + char clsid_str[] = "{48D0C522-BFCC-45CC-8B84-17F25F33E6E8}"; + char module_path[MAX_PATH]; + +#ifdef _WIN64 + const char *dll_name = "wineasio64.dll"; +#else + const char *dll_name = "wineasio.dll"; +#endif + + TRACE("Registering WineASIO\n"); + + /* Register COM class under HKEY_CLASSES_ROOT\CLSID\{...} */ + rc = RegCreateKeyExA(HKEY_CLASSES_ROOT, + "CLSID\\{48D0C522-BFCC-45CC-8B84-17F25F33E6E8}", + 0, NULL, 0, KEY_WRITE, NULL, &hkey, NULL); + if (rc != ERROR_SUCCESS) { + ERR("Failed to create CLSID key: %ld\n", rc); + return SELFREG_E_CLASS; + } + + /* Set default value to class name */ + RegSetValueExA(hkey, NULL, 0, REG_SZ, (const BYTE*)"WineASIO Driver", 16); + + /* Create InprocServer32 subkey */ + rc = RegCreateKeyExA(hkey, "InprocServer32", 0, NULL, 0, KEY_WRITE, NULL, &hsubkey, NULL); + if (rc == ERROR_SUCCESS) { + /* Set default value to DLL path */ + GetSystemDirectoryA(module_path, MAX_PATH); + strcat(module_path, "\\"); + strcat(module_path, dll_name); + RegSetValueExA(hsubkey, NULL, 0, REG_SZ, (const BYTE*)module_path, strlen(module_path) + 1); + + /* Set threading model */ + RegSetValueExA(hsubkey, "ThreadingModel", 0, REG_SZ, (const BYTE*)"Apartment", 10); + RegCloseKey(hsubkey); + } + RegCloseKey(hkey); + + /* Register ASIO driver under HKEY_LOCAL_MACHINE\Software\ASIO\WineASIO */ + rc = RegCreateKeyExA(HKEY_LOCAL_MACHINE, "Software\\ASIO\\WineASIO", + 0, NULL, 0, KEY_WRITE, NULL, &hkey, NULL); + if (rc == ERROR_SUCCESS) { + RegSetValueExA(hkey, "CLSID", 0, REG_SZ, (const BYTE*)clsid_str, strlen(clsid_str) + 1); + RegSetValueExA(hkey, "Description", 0, REG_SZ, (const BYTE*)"WineASIO Driver", 16); + RegCloseKey(hkey); + } + + TRACE("WineASIO registered successfully\n"); + return S_OK; +} + +/* + * DllUnregisterServer - Unregister the COM class and ASIO driver + */ +HRESULT WINAPI DllUnregisterServer(void) +{ + TRACE("Unregistering WineASIO\n"); + + /* Delete ASIO driver key */ + RegDeleteKeyA(HKEY_LOCAL_MACHINE, "Software\\ASIO\\WineASIO"); + + /* Delete COM class registration */ + RegDeleteKeyA(HKEY_CLASSES_ROOT, "CLSID\\{48D0C522-BFCC-45CC-8B84-17F25F33E6E8}\\InprocServer32"); + RegDeleteKeyA(HKEY_CLASSES_ROOT, "CLSID\\{48D0C522-BFCC-45CC-8B84-17F25F33E6E8}"); + + TRACE("WineASIO unregistered\n"); + return S_OK; +} + +BOOL WINAPI DllMain(HINSTANCE hInstDLL, DWORD fdwReason, LPVOID lpvReserved) +{ + TRACE("hInstDLL=%p fdwReason=%lx lpvReserved=%p\n", hInstDLL, fdwReason, lpvReserved); + + switch (fdwReason) { + case DLL_PROCESS_ATTACH: + DisableThreadLibraryCalls(hInstDLL); + if (!init_wine_unix_call(hInstDLL)) { + ERR("Failed to load Unix library\n"); + return FALSE; + } + break; + case DLL_PROCESS_DETACH: + break; + } + return TRUE; +} \ No newline at end of file diff --git a/asio_unix.c b/asio_unix.c new file mode 100644 index 0000000..e014461 --- /dev/null +++ b/asio_unix.c @@ -0,0 +1,1248 @@ +/* + * WineASIO Unix Library - JACK Audio Connection + * Copyright (C) 2006 Robert Reif + * Portions copyright (C) 2007-2013 Various contributors + * Copyright (C) 2024 WineASIO contributors + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#if 0 +#pragma makedep unix +#endif + +/* config.h is not needed - we define what we need directly */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ntstatus.h" +#define WIN32_NO_STATUS +#include "windef.h" +#include "winbase.h" +#include "winternl.h" + +/* Don't use Wine's debug.h - it requires symbols that may not be available + * when the unix library is loaded. Use our own debug macros instead. */ +#ifdef WINEASIO_DEBUG +#define TRACE(fmt, ...) do { fprintf(stderr, "wineasio:trace: " fmt, ##__VA_ARGS__); } while(0) +#define WARN(fmt, ...) do { fprintf(stderr, "wineasio:warn: " fmt, ##__VA_ARGS__); } while(0) +#define ERR(fmt, ...) do { fprintf(stderr, "wineasio:err: " fmt, ##__VA_ARGS__); } while(0) +#else +#define TRACE(fmt, ...) do { } while(0) +#define WARN(fmt, ...) do { } while(0) +#define ERR(fmt, ...) do { fprintf(stderr, "wineasio:err: " fmt, ##__VA_ARGS__); } while(0) +#endif + +#include "wine/unixlib.h" + +#include "unixlib.h" + +/* Define C_ASSERT if not available */ +#ifndef C_ASSERT +#define C_ASSERT(e) typedef char __C_ASSERT__[(e)?1:-1] +#endif + +/* Define ARRAYSIZE if not available */ +#ifndef ARRAYSIZE +#define ARRAYSIZE(a) (sizeof(a)/sizeof((a)[0])) +#endif + +/* JACK types and definitions */ +typedef uint32_t jack_nframes_t; +typedef struct _jack_client jack_client_t; +typedef struct _jack_port jack_port_t; +typedef float jack_default_audio_sample_t; +typedef uint64_t jack_uuid_t; +typedef uint32_t jack_port_id_t; + +typedef enum { + JackTransportStopped = 0, + JackTransportRolling = 1, + JackTransportStarting = 3, +} jack_transport_state_t; + +typedef enum { + JackNullOption = 0x00, + JackNoStartServer = 0x01, +} jack_options_t; + +typedef enum { + JackCaptureLatency, + JackPlaybackLatency +} jack_latency_callback_mode_t; + +typedef struct { + jack_nframes_t min; + jack_nframes_t max; +} jack_latency_range_t; + +typedef int jack_status_t; + +typedef struct { + jack_nframes_t frame; + uint32_t valid; + /* ... more fields we don't use */ +} jack_position_t; + +/* JACK function pointers */ +static void *jack_handle = NULL; + +static jack_client_t* (*pjack_client_open)(const char*, jack_options_t, jack_status_t*, ...); +static int (*pjack_client_close)(jack_client_t*); +static const char* (*pjack_get_client_name)(jack_client_t*); +static jack_nframes_t (*pjack_get_sample_rate)(jack_client_t*); +static jack_nframes_t (*pjack_get_buffer_size)(jack_client_t*); +static int (*pjack_set_buffer_size)(jack_client_t*, jack_nframes_t); +static jack_port_t* (*pjack_port_register)(jack_client_t*, const char*, const char*, unsigned long, unsigned long); +static int (*pjack_port_unregister)(jack_client_t*, jack_port_t*); +static void* (*pjack_port_get_buffer)(jack_port_t*, jack_nframes_t); +static const char* (*pjack_port_name)(const jack_port_t*); +static int (*pjack_connect)(jack_client_t*, const char*, const char*); +static int (*pjack_disconnect)(jack_client_t*, const char*, const char*); +static const char** (*pjack_get_ports)(jack_client_t*, const char*, const char*, unsigned long); +static void (*pjack_free)(void*); +static int (*pjack_activate)(jack_client_t*); +static int (*pjack_deactivate)(jack_client_t*); +static int (*pjack_set_process_callback)(jack_client_t*, int (*)(jack_nframes_t, void*), void*); +static int (*pjack_set_buffer_size_callback)(jack_client_t*, int (*)(jack_nframes_t, void*), void*); +static int (*pjack_set_sample_rate_callback)(jack_client_t*, int (*)(jack_nframes_t, void*), void*); +static int (*pjack_set_latency_callback)(jack_client_t*, void (*)(jack_latency_callback_mode_t, void*), void*); +static void (*pjack_port_get_latency_range)(jack_port_t*, jack_latency_callback_mode_t, jack_latency_range_t*); +static jack_transport_state_t (*pjack_transport_query)(const jack_client_t*, jack_position_t*); + +#define JACK_DEFAULT_AUDIO_TYPE "32 bit float mono audio" +#define JACK_DEFAULT_MIDI_TYPE "8 bit raw midi" +#define JackPortIsInput 0x1 +#define JackPortIsOutput 0x2 +#define JackPortIsPhysical 0x4 + +#define MAX_CHANNELS 128 +#define MAX_NAME_LENGTH 64 + +/* JACK MIDI event structure */ +typedef struct { + jack_nframes_t time; + size_t size; + unsigned char *buffer; +} jack_midi_event_t; + +/* JACK MIDI function pointers */ +static jack_nframes_t (*pjack_midi_get_event_count)(void*); +static int (*pjack_midi_event_get)(jack_midi_event_t*, void*, jack_nframes_t); +static void (*pjack_midi_clear_buffer)(void*); +static int (*pjack_midi_event_write)(void*, jack_nframes_t, const unsigned char*, size_t); + +/* MIDI ringbuffer for thread-safe MIDI event passing */ +#define MIDI_RINGBUFFER_SIZE 256 +#define MAX_MIDI_EVENT_SIZE 256 + +typedef struct { + unsigned char data[MAX_MIDI_EVENT_SIZE]; + size_t size; + jack_nframes_t time; +} MidiEvent; + +typedef struct { + MidiEvent events[MIDI_RINGBUFFER_SIZE]; + volatile int read_pos; + volatile int write_pos; +} MidiRingBuffer; + +/* MIDI port state */ +typedef struct { + jack_port_t *port; + char name[MAX_NAME_LENGTH]; + BOOL active; + MidiRingBuffer ringbuffer; +} MidiChannel; + +/* Channel state */ +typedef struct { + jack_port_t *port; + char name[MAX_NAME_LENGTH]; + BOOL active; + jack_default_audio_sample_t *audio_buffer; /* Double buffer */ +} IOChannel; + +/* Stream state - lives on Unix side */ +typedef struct { + jack_client_t *client; + char client_name[MAX_NAME_LENGTH]; + + /* JACK info */ + double sample_rate; + LONG buffer_size; + LONG input_latency; + LONG output_latency; + + /* Channels */ + int num_inputs; + int num_outputs; + IOChannel inputs[MAX_CHANNELS]; + IOChannel outputs[MAX_CHANNELS]; + + /* Physical port counts */ + int jack_num_input_ports; + int jack_num_output_ports; + const char **jack_input_ports; + const char **jack_output_ports; + + /* State */ + int state; /* 0=Loaded, 1=Initialized, 2=Prepared, 3=Running */ + BOOL active_inputs; + BOOL active_outputs; + + /* Double buffering */ + LONG buffer_index; + jack_default_audio_sample_t *callback_audio_buffer; + + /* Callback notification (polled by PE side) */ + pthread_mutex_t callback_lock; + BOOL buffer_switch_pending; + LONG pending_buffer_index; + INT64 sample_position; + INT64 system_time; + BOOL sample_rate_changed; + double new_sample_rate; + BOOL reset_request; + BOOL latency_changed; + + /* Config */ + BOOL autoconnect; + BOOL fixed_bufsize; + LONG preferred_bufsize; + + /* JACK MIDI ports */ + BOOL midi_enabled; + MidiChannel midi_input; + MidiChannel midi_output; +} AsioStream; + +enum { Loaded = 0, Initialized, Prepared, Running }; + +static BOOL jack_loaded = FALSE; + +/* Load JACK library */ +static BOOL load_jack(void) +{ + if (jack_loaded) return TRUE; + + jack_handle = dlopen("libjack.so.0", RTLD_NOW); + if (!jack_handle) { + WARN("Failed to load libjack.so.0: %s\n", dlerror()); + return FALSE; + } + + #define LOAD_SYM(name) \ + p##name = dlsym(jack_handle, #name); \ + if (!p##name) { WARN("Missing symbol: " #name "\n"); } + + LOAD_SYM(jack_client_open) + LOAD_SYM(jack_client_close) + LOAD_SYM(jack_get_client_name) + LOAD_SYM(jack_get_sample_rate) + LOAD_SYM(jack_get_buffer_size) + LOAD_SYM(jack_set_buffer_size) + LOAD_SYM(jack_port_register) + LOAD_SYM(jack_port_unregister) + LOAD_SYM(jack_port_get_buffer) + LOAD_SYM(jack_port_name) + LOAD_SYM(jack_connect) + LOAD_SYM(jack_disconnect) + LOAD_SYM(jack_get_ports) + LOAD_SYM(jack_free) + LOAD_SYM(jack_activate) + LOAD_SYM(jack_deactivate) + LOAD_SYM(jack_set_process_callback) + LOAD_SYM(jack_set_buffer_size_callback) + LOAD_SYM(jack_set_sample_rate_callback) + LOAD_SYM(jack_set_latency_callback) + LOAD_SYM(jack_port_get_latency_range) + LOAD_SYM(jack_transport_query) + + #undef LOAD_SYM + + /* JACK MIDI functions - load manually, optional */ + pjack_midi_get_event_count = dlsym(jack_handle, "jack_midi_get_event_count"); + pjack_midi_event_get = dlsym(jack_handle, "jack_midi_event_get"); + pjack_midi_clear_buffer = dlsym(jack_handle, "jack_midi_clear_buffer"); + pjack_midi_event_write = dlsym(jack_handle, "jack_midi_event_write"); + if (pjack_midi_get_event_count && pjack_midi_clear_buffer) { + TRACE("JACK MIDI functions loaded\n"); + } + + if (!pjack_client_open || !pjack_client_close) { + dlclose(jack_handle); + jack_handle = NULL; + return FALSE; + } + + jack_loaded = TRUE; + TRACE("JACK library loaded successfully\n"); + return TRUE; +} + +/* Get current time in nanoseconds */ +static INT64 get_system_time(void) +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (INT64)ts.tv_sec * 1000000000LL + ts.tv_nsec; +} + +/* Helper: write MIDI event to ringbuffer */ +static void midi_ringbuffer_write(MidiRingBuffer *rb, const unsigned char *data, size_t size, jack_nframes_t time) +{ + int next_write = (rb->write_pos + 1) % MIDI_RINGBUFFER_SIZE; + if (next_write == rb->read_pos) { + /* Buffer full, drop event */ + return; + } + + if (size > MAX_MIDI_EVENT_SIZE) size = MAX_MIDI_EVENT_SIZE; + + memcpy(rb->events[rb->write_pos].data, data, size); + rb->events[rb->write_pos].size = size; + rb->events[rb->write_pos].time = time; + rb->write_pos = next_write; +} + +/* JACK process callback - runs in realtime thread */ +static int jack_process_callback(jack_nframes_t nframes, void *arg) +{ + AsioStream *stream = (AsioStream *)arg; + int i; + + /* Process JACK MIDI input - always, even when audio not running */ + if (stream->midi_enabled && stream->midi_input.port && pjack_midi_get_event_count) { + void *midi_buf = pjack_port_get_buffer(stream->midi_input.port, nframes); + if (midi_buf) { + jack_nframes_t event_count = pjack_midi_get_event_count(midi_buf); + for (jack_nframes_t j = 0; j < event_count; j++) { + jack_midi_event_t event; + if (pjack_midi_event_get(&event, midi_buf, j) == 0) { + midi_ringbuffer_write(&stream->midi_input.ringbuffer, + event.buffer, event.size, event.time); + } + } + } + } + + /* Process JACK MIDI output */ + if (stream->midi_enabled && stream->midi_output.port && pjack_midi_clear_buffer) { + void *midi_buf = pjack_port_get_buffer(stream->midi_output.port, nframes); + if (midi_buf) { + pjack_midi_clear_buffer(midi_buf); + + /* Write pending MIDI events from ringbuffer */ + MidiRingBuffer *rb = &stream->midi_output.ringbuffer; + while (rb->read_pos != rb->write_pos) { + MidiEvent *ev = &rb->events[rb->read_pos]; + if (pjack_midi_event_write) { + pjack_midi_event_write(midi_buf, ev->time % nframes, ev->data, ev->size); + } + rb->read_pos = (rb->read_pos + 1) % MIDI_RINGBUFFER_SIZE; + } + } + } + + if (stream->state != Running) { + /* Output silence */ + for (i = 0; i < stream->num_outputs; i++) { + if (stream->outputs[i].port) { + void *buf = pjack_port_get_buffer(stream->outputs[i].port, nframes); + if (buf) memset(buf, 0, sizeof(jack_default_audio_sample_t) * nframes); + } + } + return 0; + } + + /* Copy JACK input buffers to our buffer */ + for (i = 0; i < stream->num_inputs; i++) { + if (stream->inputs[i].active && stream->inputs[i].port) { + void *jack_buf = pjack_port_get_buffer(stream->inputs[i].port, nframes); + if (jack_buf && stream->inputs[i].audio_buffer) { + memcpy(&stream->inputs[i].audio_buffer[nframes * stream->buffer_index], + jack_buf, sizeof(jack_default_audio_sample_t) * nframes); + } + } + } + + /* Copy our output buffer to JACK */ + for (i = 0; i < stream->num_outputs; i++) { + if (stream->outputs[i].active && stream->outputs[i].port) { + void *jack_buf = pjack_port_get_buffer(stream->outputs[i].port, nframes); + if (jack_buf && stream->outputs[i].audio_buffer) { + memcpy(jack_buf, + &stream->outputs[i].audio_buffer[nframes * stream->buffer_index], + sizeof(jack_default_audio_sample_t) * nframes); + } + } + } + + /* Update sample position */ + stream->sample_position += nframes; + stream->system_time = get_system_time(); + + /* Signal buffer switch to PE side */ + pthread_mutex_lock(&stream->callback_lock); + stream->pending_buffer_index = stream->buffer_index; + stream->buffer_switch_pending = TRUE; + pthread_mutex_unlock(&stream->callback_lock); + + /* Switch buffers */ + stream->buffer_index = stream->buffer_index ? 0 : 1; + + return 0; +} + +/* JACK buffer size callback */ +static int jack_buffer_size_callback(jack_nframes_t nframes, void *arg) +{ + AsioStream *stream = (AsioStream *)arg; + + TRACE("Buffer size changed to %u\n", nframes); + stream->buffer_size = nframes; + + pthread_mutex_lock(&stream->callback_lock); + stream->reset_request = TRUE; + pthread_mutex_unlock(&stream->callback_lock); + + return 0; +} + +/* JACK sample rate callback */ +static int jack_sample_rate_callback(jack_nframes_t nframes, void *arg) +{ + AsioStream *stream = (AsioStream *)arg; + + TRACE("Sample rate changed to %u\n", nframes); + + pthread_mutex_lock(&stream->callback_lock); + stream->sample_rate_changed = TRUE; + stream->new_sample_rate = (double)nframes; + pthread_mutex_unlock(&stream->callback_lock); + + stream->sample_rate = (double)nframes; + + return 0; +} + +/* JACK latency callback */ +static void jack_latency_callback(jack_latency_callback_mode_t mode, void *arg) +{ + AsioStream *stream = (AsioStream *)arg; + + pthread_mutex_lock(&stream->callback_lock); + stream->latency_changed = TRUE; + pthread_mutex_unlock(&stream->callback_lock); +} + +static AsioStream *handle_to_stream(asio_handle h) +{ + return (AsioStream *)(UINT_PTR)h; +} + +/* + * Unix function implementations + */ + +static NTSTATUS asio_init(void *args) +{ + struct asio_init_params *params = args; + AsioStream *stream; + jack_status_t status; + jack_options_t options; + int i; + + TRACE("Initializing WineASIO\n"); + + if (!load_jack()) { + params->result = ASE_NotPresent; + return STATUS_SUCCESS; + } + + stream = calloc(1, sizeof(*stream)); + if (!stream) { + params->result = ASE_NoMemory; + return STATUS_SUCCESS; + } + + /* Copy config */ + stream->num_inputs = params->config.num_inputs > 0 ? params->config.num_inputs : 2; + stream->num_outputs = params->config.num_outputs > 0 ? params->config.num_outputs : 2; + stream->preferred_bufsize = params->config.preferred_bufsize > 0 ? params->config.preferred_bufsize : 1024; + stream->fixed_bufsize = params->config.fixed_bufsize; + stream->autoconnect = params->config.autoconnect; + + if (stream->num_inputs > MAX_CHANNELS) stream->num_inputs = MAX_CHANNELS; + if (stream->num_outputs > MAX_CHANNELS) stream->num_outputs = MAX_CHANNELS; + + /* Set client name */ + if (params->config.client_name[0]) { + memcpy(stream->client_name, params->config.client_name, MAX_NAME_LENGTH - 1); + stream->client_name[MAX_NAME_LENGTH - 1] = '\0'; + } else { + memcpy(stream->client_name, "WineASIO", 9); + } + + /* Open JACK client */ + options = JackNoStartServer; /* Don't start JACK if not running */ + stream->client = pjack_client_open(stream->client_name, options, &status); + if (!stream->client) { + WARN("Failed to open JACK client: status=0x%x\n", status); + free(stream); + params->result = ASE_NotPresent; + return STATUS_SUCCESS; + } + + TRACE("JACK client opened as '%s'\n", pjack_get_client_name(stream->client)); + + /* Get JACK info */ + stream->sample_rate = pjack_get_sample_rate(stream->client); + stream->buffer_size = pjack_get_buffer_size(stream->client); + + /* Initialize mutex */ + pthread_mutex_init(&stream->callback_lock, NULL); + + /* Register ports */ + for (i = 0; i < stream->num_inputs; i++) { + snprintf(stream->inputs[i].name, MAX_NAME_LENGTH, "in_%d", i + 1); + stream->inputs[i].port = pjack_port_register(stream->client, + stream->inputs[i].name, JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0); + stream->inputs[i].active = FALSE; + } + + for (i = 0; i < stream->num_outputs; i++) { + snprintf(stream->outputs[i].name, MAX_NAME_LENGTH, "out_%d", i + 1); + stream->outputs[i].port = pjack_port_register(stream->client, + stream->outputs[i].name, JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); + stream->outputs[i].active = FALSE; + } + + /* Register JACK MIDI ports */ + stream->midi_enabled = FALSE; + if (pjack_midi_get_event_count && pjack_midi_clear_buffer) { + /* MIDI input port */ + snprintf(stream->midi_input.name, MAX_NAME_LENGTH, "midi_in"); + stream->midi_input.port = pjack_port_register(stream->client, + stream->midi_input.name, JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0); + stream->midi_input.active = TRUE; + stream->midi_input.ringbuffer.read_pos = 0; + stream->midi_input.ringbuffer.write_pos = 0; + + /* MIDI output port */ + snprintf(stream->midi_output.name, MAX_NAME_LENGTH, "midi_out"); + stream->midi_output.port = pjack_port_register(stream->client, + stream->midi_output.name, JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0); + stream->midi_output.active = TRUE; + stream->midi_output.ringbuffer.read_pos = 0; + stream->midi_output.ringbuffer.write_pos = 0; + + if (stream->midi_input.port && stream->midi_output.port) { + stream->midi_enabled = TRUE; + TRACE("JACK MIDI ports registered: %s, %s\n", + stream->midi_input.name, stream->midi_output.name); + } else { + WARN("Failed to register JACK MIDI ports\n"); + } + } else { + TRACE("JACK MIDI functions not available\n"); + } + + /* Get physical ports */ + stream->jack_input_ports = pjack_get_ports(stream->client, NULL, NULL, + JackPortIsPhysical | JackPortIsOutput); + for (stream->jack_num_input_ports = 0; + stream->jack_input_ports && stream->jack_input_ports[stream->jack_num_input_ports]; + stream->jack_num_input_ports++); + + stream->jack_output_ports = pjack_get_ports(stream->client, NULL, NULL, + JackPortIsPhysical | JackPortIsInput); + for (stream->jack_num_output_ports = 0; + stream->jack_output_ports && stream->jack_output_ports[stream->jack_num_output_ports]; + stream->jack_num_output_ports++); + + /* Set callbacks */ + pjack_set_process_callback(stream->client, jack_process_callback, stream); + pjack_set_buffer_size_callback(stream->client, jack_buffer_size_callback, stream); + pjack_set_sample_rate_callback(stream->client, jack_sample_rate_callback, stream); + if (pjack_set_latency_callback) + pjack_set_latency_callback(stream->client, jack_latency_callback, stream); + + /* Activate JACK client */ + if (pjack_activate(stream->client)) { + WARN("Failed to activate JACK client\n"); + pjack_client_close(stream->client); + free(stream); + params->result = ASE_HWMalfunction; + return STATUS_SUCCESS; + } + + /* Auto-connect if requested */ + if (stream->autoconnect) { + for (i = 0; i < stream->num_inputs && i < stream->jack_num_input_ports; i++) { + if (stream->inputs[i].port && stream->jack_input_ports[i]) { + pjack_connect(stream->client, stream->jack_input_ports[i], + pjack_port_name(stream->inputs[i].port)); + } + } + for (i = 0; i < stream->num_outputs && i < stream->jack_num_output_ports; i++) { + if (stream->outputs[i].port && stream->jack_output_ports[i]) { + pjack_connect(stream->client, pjack_port_name(stream->outputs[i].port), + stream->jack_output_ports[i]); + } + } + } + + stream->state = Initialized; + + /* Return info */ + params->handle = (asio_handle)(UINT_PTR)stream; + params->input_channels = stream->num_inputs; + params->output_channels = stream->num_outputs; + params->sample_rate = stream->sample_rate; + params->result = ASE_OK; + + TRACE("WineASIO initialized: %d inputs, %d outputs, %.0f Hz, %d samples, MIDI=%s\n", + stream->num_inputs, stream->num_outputs, stream->sample_rate, stream->buffer_size, + stream->midi_enabled ? "enabled" : "disabled"); + + return STATUS_SUCCESS; +} + +static NTSTATUS asio_exit(void *args) +{ + struct asio_exit_params *params = args; + AsioStream *stream = handle_to_stream(params->handle); + int i; + + if (!stream) { + params->result = ASE_InvalidParameter; + return STATUS_SUCCESS; + } + + TRACE("Shutting down WineASIO\n"); + + /* Deactivate and close */ + if (stream->client) { + pjack_deactivate(stream->client); + + /* Unregister audio ports */ + for (i = 0; i < stream->num_inputs; i++) { + if (stream->inputs[i].port) + pjack_port_unregister(stream->client, stream->inputs[i].port); + } + for (i = 0; i < stream->num_outputs; i++) { + if (stream->outputs[i].port) + pjack_port_unregister(stream->client, stream->outputs[i].port); + } + + /* Unregister MIDI ports */ + if (stream->midi_input.port) + pjack_port_unregister(stream->client, stream->midi_input.port); + if (stream->midi_output.port) + pjack_port_unregister(stream->client, stream->midi_output.port); + + pjack_client_close(stream->client); + } + + /* Free port lists */ + if (stream->jack_input_ports) + pjack_free(stream->jack_input_ports); + if (stream->jack_output_ports) + pjack_free(stream->jack_output_ports); + + /* Free audio buffers */ + for (i = 0; i < stream->num_inputs; i++) + free(stream->inputs[i].audio_buffer); + for (i = 0; i < stream->num_outputs; i++) + free(stream->outputs[i].audio_buffer); + + free(stream->callback_audio_buffer); + + pthread_mutex_destroy(&stream->callback_lock); + + free(stream); + params->result = ASE_OK; + + return STATUS_SUCCESS; +} + +static NTSTATUS asio_start(void *args) +{ + struct asio_start_params *params = args; + AsioStream *stream = handle_to_stream(params->handle); + int i; + + if (!stream || stream->state != Prepared) { + params->result = ASE_InvalidMode; + return STATUS_SUCCESS; + } + + /* Zero buffers */ + for (i = 0; i < stream->num_inputs; i++) { + if (stream->inputs[i].audio_buffer) + memset(stream->inputs[i].audio_buffer, 0, + sizeof(jack_default_audio_sample_t) * stream->buffer_size * 2); + } + for (i = 0; i < stream->num_outputs; i++) { + if (stream->outputs[i].audio_buffer) + memset(stream->outputs[i].audio_buffer, 0, + sizeof(jack_default_audio_sample_t) * stream->buffer_size * 2); + } + + stream->buffer_index = 0; + stream->sample_position = 0; + stream->system_time = get_system_time(); + stream->buffer_switch_pending = FALSE; + + stream->state = Running; + params->result = ASE_OK; + + TRACE("WineASIO started\n"); + + return STATUS_SUCCESS; +} + +static NTSTATUS asio_stop(void *args) +{ + struct asio_stop_params *params = args; + AsioStream *stream = handle_to_stream(params->handle); + + if (!stream || stream->state != Running) { + params->result = ASE_InvalidMode; + return STATUS_SUCCESS; + } + + stream->state = Prepared; + params->result = ASE_OK; + + TRACE("WineASIO stopped\n"); + + return STATUS_SUCCESS; +} + +static NTSTATUS asio_get_channels(void *args) +{ + struct asio_get_channels_params *params = args; + AsioStream *stream = handle_to_stream(params->handle); + + if (!stream) { + params->result = ASE_InvalidParameter; + return STATUS_SUCCESS; + } + + params->num_inputs = stream->num_inputs; + params->num_outputs = stream->num_outputs; + params->result = ASE_OK; + + return STATUS_SUCCESS; +} + +static NTSTATUS asio_get_latencies(void *args) +{ + struct asio_get_latencies_params *params = args; + AsioStream *stream = handle_to_stream(params->handle); + jack_latency_range_t range; + + if (!stream) { + params->result = ASE_InvalidParameter; + return STATUS_SUCCESS; + } + + /* Get latency from JACK if available */ + stream->input_latency = stream->buffer_size; + stream->output_latency = stream->buffer_size * 2; + + if (pjack_port_get_latency_range && stream->num_inputs > 0 && stream->inputs[0].port) { + pjack_port_get_latency_range(stream->inputs[0].port, JackCaptureLatency, &range); + stream->input_latency = range.max > 0 ? range.max : stream->buffer_size; + } + + if (pjack_port_get_latency_range && stream->num_outputs > 0 && stream->outputs[0].port) { + pjack_port_get_latency_range(stream->outputs[0].port, JackPlaybackLatency, &range); + stream->output_latency = range.max > 0 ? range.max : stream->buffer_size * 2; + } + + params->input_latency = stream->input_latency; + params->output_latency = stream->output_latency; + params->result = ASE_OK; + + return STATUS_SUCCESS; +} + +static NTSTATUS asio_get_buffer_size(void *args) +{ + struct asio_get_buffer_size_params *params = args; + AsioStream *stream = handle_to_stream(params->handle); + + if (!stream) { + params->result = ASE_InvalidParameter; + return STATUS_SUCCESS; + } + + if (stream->fixed_bufsize) { + params->min_size = stream->buffer_size; + params->max_size = stream->buffer_size; + params->preferred_size = stream->buffer_size; + params->granularity = 0; + } else { + params->min_size = 16; + params->max_size = 8192; + params->preferred_size = stream->preferred_bufsize; + params->granularity = 1; + } + + params->result = ASE_OK; + + return STATUS_SUCCESS; +} + +static NTSTATUS asio_can_sample_rate(void *args) +{ + struct asio_can_sample_rate_params *params = args; + AsioStream *stream = handle_to_stream(params->handle); + + if (!stream) { + params->result = ASE_InvalidParameter; + return STATUS_SUCCESS; + } + + /* JACK typically only supports one sample rate at a time */ + if ((int)params->sample_rate == (int)stream->sample_rate) { + params->result = ASE_OK; + } else { + params->result = ASE_NoClock; + } + + return STATUS_SUCCESS; +} + +static NTSTATUS asio_get_sample_rate(void *args) +{ + struct asio_get_sample_rate_params *params = args; + AsioStream *stream = handle_to_stream(params->handle); + + if (!stream) { + params->result = ASE_InvalidParameter; + return STATUS_SUCCESS; + } + + params->sample_rate = stream->sample_rate; + params->result = ASE_OK; + + return STATUS_SUCCESS; +} + +static NTSTATUS asio_set_sample_rate(void *args) +{ + struct asio_set_sample_rate_params *params = args; + AsioStream *stream = handle_to_stream(params->handle); + + if (!stream) { + params->result = ASE_InvalidParameter; + return STATUS_SUCCESS; + } + + /* JACK controls the sample rate, we can't change it */ + if ((int)params->sample_rate == (int)stream->sample_rate) { + params->result = ASE_OK; + } else { + params->result = ASE_NoClock; + } + + return STATUS_SUCCESS; +} + +static NTSTATUS asio_get_channel_info(void *args) +{ + struct asio_get_channel_info_params *params = args; + AsioStream *stream = handle_to_stream(params->handle); + int channel = params->info.channel; + BOOL is_input = params->info.is_input; + + if (!stream) { + params->result = ASE_InvalidParameter; + return STATUS_SUCCESS; + } + + if (is_input) { + if (channel < 0 || channel >= stream->num_inputs) { + params->result = ASE_InvalidParameter; + return STATUS_SUCCESS; + } + params->info.is_active = stream->inputs[channel].active; + params->info.channel_group = 0; + params->info.sample_type = ASIOSTFloat32LSB; + memcpy(params->info.name, stream->inputs[channel].name, 31); + params->info.name[31] = '\0'; + } else { + if (channel < 0 || channel >= stream->num_outputs) { + params->result = ASE_InvalidParameter; + return STATUS_SUCCESS; + } + params->info.is_active = stream->outputs[channel].active; + params->info.channel_group = 0; + params->info.sample_type = ASIOSTFloat32LSB; + memcpy(params->info.name, stream->outputs[channel].name, 31); + params->info.name[31] = '\0'; + } + + params->result = ASE_OK; + + return STATUS_SUCCESS; +} + +static NTSTATUS asio_create_buffers(void *args) +{ + struct asio_create_buffers_params *params = args; + AsioStream *stream = handle_to_stream(params->handle); + struct asio_buffer_info *infos = params->buffer_infos; + int i, j; + size_t buffer_bytes; + + if (!stream || stream->state < Initialized) { + params->result = ASE_InvalidMode; + return STATUS_SUCCESS; + } + + if (!infos || params->num_channels <= 0) { + params->result = ASE_InvalidParameter; + return STATUS_SUCCESS; + } + + /* Set buffer size if different and not fixed */ + if (params->buffer_size != stream->buffer_size && !stream->fixed_bufsize) { + if (pjack_set_buffer_size) + pjack_set_buffer_size(stream->client, params->buffer_size); + stream->buffer_size = pjack_get_buffer_size(stream->client); + } + + buffer_bytes = sizeof(jack_default_audio_sample_t) * stream->buffer_size * 2; + + /* Allocate buffers for each channel */ + for (i = 0; i < params->num_channels; i++) { + int ch = infos[i].channel_num; + + if (infos[i].is_input) { + if (ch < 0 || ch >= stream->num_inputs) { + params->result = ASE_InvalidParameter; + return STATUS_SUCCESS; + } + + /* Free old buffer if exists */ + free(stream->inputs[ch].audio_buffer); + + /* Allocate new double buffer */ + stream->inputs[ch].audio_buffer = calloc(1, buffer_bytes); + if (!stream->inputs[ch].audio_buffer) { + params->result = ASE_NoMemory; + return STATUS_SUCCESS; + } + + stream->inputs[ch].active = TRUE; + + /* Return buffer pointers */ + infos[i].buffer_ptr[0] = (UINT64)(UINT_PTR)&stream->inputs[ch].audio_buffer[0]; + infos[i].buffer_ptr[1] = (UINT64)(UINT_PTR)&stream->inputs[ch].audio_buffer[stream->buffer_size]; + } else { + if (ch < 0 || ch >= stream->num_outputs) { + params->result = ASE_InvalidParameter; + return STATUS_SUCCESS; + } + + /* Free old buffer if exists */ + free(stream->outputs[ch].audio_buffer); + + /* Allocate new double buffer */ + stream->outputs[ch].audio_buffer = calloc(1, buffer_bytes); + if (!stream->outputs[ch].audio_buffer) { + params->result = ASE_NoMemory; + return STATUS_SUCCESS; + } + + stream->outputs[ch].active = TRUE; + + /* Return buffer pointers */ + infos[i].buffer_ptr[0] = (UINT64)(UINT_PTR)&stream->outputs[ch].audio_buffer[0]; + infos[i].buffer_ptr[1] = (UINT64)(UINT_PTR)&stream->outputs[ch].audio_buffer[stream->buffer_size]; + } + } + + /* Count active channels */ + stream->active_inputs = FALSE; + stream->active_outputs = FALSE; + for (j = 0; j < stream->num_inputs; j++) + if (stream->inputs[j].active) stream->active_inputs = TRUE; + for (j = 0; j < stream->num_outputs; j++) + if (stream->outputs[j].active) stream->active_outputs = TRUE; + + stream->state = Prepared; + params->result = ASE_OK; + + TRACE("Buffers created: %d channels, %d samples\n", + params->num_channels, stream->buffer_size); + + return STATUS_SUCCESS; +} + +static NTSTATUS asio_dispose_buffers(void *args) +{ + struct asio_dispose_buffers_params *params = args; + AsioStream *stream = handle_to_stream(params->handle); + int i; + + if (!stream) { + params->result = ASE_InvalidParameter; + return STATUS_SUCCESS; + } + + if (stream->state == Running) { + stream->state = Prepared; + } + + /* Free buffers and mark inactive */ + for (i = 0; i < stream->num_inputs; i++) { + free(stream->inputs[i].audio_buffer); + stream->inputs[i].audio_buffer = NULL; + stream->inputs[i].active = FALSE; + } + for (i = 0; i < stream->num_outputs; i++) { + free(stream->outputs[i].audio_buffer); + stream->outputs[i].audio_buffer = NULL; + stream->outputs[i].active = FALSE; + } + + stream->state = Initialized; + params->result = ASE_OK; + + TRACE("Buffers disposed\n"); + + return STATUS_SUCCESS; +} + +static NTSTATUS asio_output_ready(void *args) +{ + struct asio_output_ready_params *params = args; + + /* We don't need this optimization since JACK handles timing */ + params->result = ASE_NotPresent; + + return STATUS_SUCCESS; +} + +static NTSTATUS asio_get_sample_position(void *args) +{ + struct asio_get_sample_position_params *params = args; + AsioStream *stream = handle_to_stream(params->handle); + + if (!stream) { + params->result = ASE_InvalidParameter; + return STATUS_SUCCESS; + } + + params->sample_position = stream->sample_position; + params->system_time = stream->system_time; + params->result = ASE_OK; + + return STATUS_SUCCESS; +} + +static NTSTATUS asio_get_callback(void *args) +{ + struct asio_get_callback_params *params = args; + AsioStream *stream = handle_to_stream(params->handle); + + if (!stream) { + params->result = ASE_InvalidParameter; + return STATUS_SUCCESS; + } + + pthread_mutex_lock(&stream->callback_lock); + + params->buffer_switch_ready = stream->buffer_switch_pending; + params->buffer_index = stream->pending_buffer_index; + params->direct_process = TRUE; + + params->time_info.speed = 1.0; + params->time_info.system_time = stream->system_time; + params->time_info.sample_position = stream->sample_position; + params->time_info.sample_rate = stream->sample_rate; + params->time_info.flags = 0x7; /* Valid flags */ + + params->sample_rate_changed = stream->sample_rate_changed; + params->new_sample_rate = stream->new_sample_rate; + params->reset_request = stream->reset_request; + params->resync_request = FALSE; + params->latency_changed = stream->latency_changed; + + /* Clear pending flags */ + stream->buffer_switch_pending = FALSE; + stream->sample_rate_changed = FALSE; + stream->reset_request = FALSE; + stream->latency_changed = FALSE; + + pthread_mutex_unlock(&stream->callback_lock); + + params->result = ASE_OK; + + return STATUS_SUCCESS; +} + +static NTSTATUS asio_callback_done(void *args) +{ + struct asio_callback_done_params *params = args; + /* Currently unused - the callback is processed synchronously */ + params->result = ASE_OK; + return STATUS_SUCCESS; +} + +static NTSTATUS asio_control_panel(void *args) +{ + struct asio_control_panel_params *params = args; + pid_t pid; + + TRACE("Control panel requested - launching wineasio-settings\n"); + + pid = fork(); + if (pid == 0) { + /* Child process */ + static char arg0[] = "wineasio-settings"; + static char *arg_list[] = { arg0, NULL }; + + /* Try to execute wineasio-settings from PATH */ + execvp(arg0, arg_list); + + /* If that fails, try common locations */ + execl("/usr/bin/wineasio-settings", "wineasio-settings", NULL); + execl("/usr/local/bin/wineasio-settings", "wineasio-settings", NULL); + + /* If all fail, exit silently */ + _exit(1); + } else if (pid < 0) { + /* Fork failed */ + WARN("Failed to fork for control panel\n"); + params->result = ASE_NotPresent; + return STATUS_SUCCESS; + } + + /* Parent process - don't wait for child */ + params->result = ASE_OK; + + return STATUS_SUCCESS; +} + +static NTSTATUS asio_future(void *args) +{ + struct asio_future_params *params = args; + AsioStream *stream = handle_to_stream(params->handle); + + if (!stream) { + params->result = ASE_InvalidParameter; + return STATUS_SUCCESS; + } + + switch (params->selector) { + case kAsioCanTimeInfo: + case kAsioCanTimeCode: + params->result = ASE_SUCCESS; + break; + + case kAsioEnableTimeCodeRead: + case kAsioDisableTimeCodeRead: + params->result = ASE_SUCCESS; + break; + + case kAsioCanInputMonitor: + case kAsioCanTransport: + case kAsioCanInputGain: + case kAsioCanInputMeter: + case kAsioCanOutputGain: + case kAsioCanOutputMeter: + params->result = ASE_NotPresent; + break; + + default: + TRACE("Unknown future selector: %d\n", params->selector); + params->result = ASE_NotPresent; + break; + } + + return STATUS_SUCCESS; +} + +/* Unix function table */ +const unixlib_entry_t __wine_unix_call_funcs[] = +{ + asio_init, + asio_exit, + asio_start, + asio_stop, + asio_get_channels, + asio_get_latencies, + asio_get_buffer_size, + asio_can_sample_rate, + asio_get_sample_rate, + asio_set_sample_rate, + asio_get_channel_info, + asio_create_buffers, + asio_dispose_buffers, + asio_output_ready, + asio_get_sample_position, + asio_get_callback, + asio_callback_done, + asio_control_panel, + asio_future, +}; + +C_ASSERT(ARRAYSIZE(__wine_unix_call_funcs) == unix_funcs_count); + +#ifdef _WIN64 +/* WoW64 thunks would go here if needed */ +const unixlib_entry_t __wine_unix_call_wow64_funcs[] = +{ + asio_init, + asio_exit, + asio_start, + asio_stop, + asio_get_channels, + asio_get_latencies, + asio_get_buffer_size, + asio_can_sample_rate, + asio_get_sample_rate, + asio_set_sample_rate, + asio_get_channel_info, + asio_create_buffers, + asio_dispose_buffers, + asio_output_ready, + asio_get_sample_position, + asio_get_callback, + asio_callback_done, + asio_control_panel, + asio_future, +}; + +C_ASSERT(ARRAYSIZE(__wine_unix_call_wow64_funcs) == unix_funcs_count); +#endif \ No newline at end of file diff --git a/gui/Makefile b/gui/Makefile index c7150d5..b881c77 100644 --- a/gui/Makefile +++ b/gui/Makefile @@ -9,9 +9,31 @@ PREFIX = /usr PYUIC ?= pyuic5 PYRCC ?= pyrcc5 +# MinGW compilers for Windows launcher +MINGW32 = i686-w64-mingw32-gcc +MINGW64 = x86_64-w64-mingw32-gcc + +# Windows launcher executables +LAUNCHER_SRC = wineasio-settings-launcher.c +LAUNCHER32 = wineasio-settings.exe +LAUNCHER64 = wineasio-settings64.exe + # --------------------------------------------------------------------------------------------------------------------- -all: +all: launchers + +launchers: $(LAUNCHER32) $(LAUNCHER64) + @echo "Windows launcher executables built" + @echo " 32-bit: $(LAUNCHER32)" + @echo " 64-bit: $(LAUNCHER64)" + +$(LAUNCHER32): $(LAUNCHER_SRC) + @echo "Building 32-bit Windows launcher..." + $(MINGW32) -o $@ $< -mwindows -O2 + +$(LAUNCHER64): $(LAUNCHER_SRC) + @echo "Building 64-bit Windows launcher..." + $(MINGW64) -o $@ $< -mwindows -O2 # --------------------------------------------------------------------------------------------------------------------- # UI code @@ -56,14 +78,14 @@ ui_%.py: %.ui # --------------------------------------------------------------------------------------------------------------------- clean: - rm -f *~ *.pyc + rm -f *~ *.pyc $(LAUNCHER32) $(LAUNCHER64) destroy: clean rm -f ui_*.py # --------------------------------------------------------------------------------------------------------------------- -install: +install: launchers # Create directories install -d $(DESTDIR)$(PREFIX)/bin/ install -d $(DESTDIR)$(PREFIX)/share/wineasio/ @@ -80,6 +102,21 @@ install: # Install code install -m 644 *.py $(DESTDIR)$(PREFIX)/share/wineasio/ + # Install Windows launcher executables (for use from Wine apps like FL Studio) + install -m 755 $(LAUNCHER32) $(DESTDIR)$(PREFIX)/share/wineasio/ + install -m 755 $(LAUNCHER64) $(DESTDIR)$(PREFIX)/share/wineasio/ + + @echo "" + @echo "Installation complete!" + @echo "" + @echo "Native Linux GUI: $(PREFIX)/bin/wineasio-settings" + @echo "Windows launchers (for FL Studio etc.):" + @echo " $(PREFIX)/share/wineasio/$(LAUNCHER32)" + @echo " $(PREFIX)/share/wineasio/$(LAUNCHER64)" + @echo "" + @echo "To use from FL Studio, copy the appropriate .exe to your Wine prefix:" + @echo " cp $(PREFIX)/share/wineasio/$(LAUNCHER64) ~/.wine/drive_c/Program\ Files/WineASIO/" + # --------------------------------------------------------------------------------------------------------------------- uninstall: diff --git a/gui/wineasio-settings-launcher.c b/gui/wineasio-settings-launcher.c new file mode 100644 index 0000000..afa4d83 --- /dev/null +++ b/gui/wineasio-settings-launcher.c @@ -0,0 +1,90 @@ +/* + * WineASIO Settings Launcher + * + * A minimal Windows executable that launches the native Linux + * wineasio-settings GUI from within Wine applications (like FL Studio). + * + * Compile with mingw: + * i686-w64-mingw32-gcc -o wineasio-settings.exe wineasio-settings-launcher.c -mwindows + * x86_64-w64-mingw32-gcc -o wineasio-settings64.exe wineasio-settings-launcher.c -mwindows + * + * Copyright (C) 2025 + * License: GPL v2+ + */ + +#define WIN32_LEAN_AND_MEAN +#include +#include + +/* Try to launch the native Linux wineasio-settings via various methods */ +static BOOL launch_native_settings(void) +{ + STARTUPINFOA si; + PROCESS_INFORMATION pi; + char cmd[512]; + + ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); + ZeroMemory(&pi, sizeof(pi)); + + /* Method 1: Use start.exe /unix to launch native tool */ + /* This is the recommended Wine method for launching Unix executables */ + lstrcpyA(cmd, "start.exe /unix /usr/bin/wineasio-settings"); + if (CreateProcessA(NULL, cmd, NULL, NULL, FALSE, + CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) { + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + return TRUE; + } + + /* Method 2: Try /usr/local/bin path */ + ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); + ZeroMemory(&pi, sizeof(pi)); + + lstrcpyA(cmd, "start.exe /unix /usr/local/bin/wineasio-settings"); + if (CreateProcessA(NULL, cmd, NULL, NULL, FALSE, + CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) { + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + return TRUE; + } + + /* Method 3: Try with winepath (in case start.exe doesn't work) */ + ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); + ZeroMemory(&pi, sizeof(pi)); + + lstrcpyA(cmd, "cmd.exe /c start /unix wineasio-settings"); + if (CreateProcessA(NULL, cmd, NULL, NULL, FALSE, + CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) { + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + return TRUE; + } + + return FALSE; +} + +int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, + LPSTR lpCmdLine, int nCmdShow) +{ + (void)hInstance; + (void)hPrevInstance; + (void)lpCmdLine; + (void)nCmdShow; + + if (!launch_native_settings()) { + MessageBoxA(NULL, + "Could not launch WineASIO Settings.\n\n" + "Please make sure wineasio-settings is installed:\n" + " /usr/bin/wineasio-settings\n" + " or /usr/local/bin/wineasio-settings\n\n" + "You can also run it manually from the Linux command line.", + "WineASIO Settings", + MB_OK | MB_ICONWARNING); + return 1; + } + + return 0; +} \ No newline at end of file diff --git a/ntdll_wine.def b/ntdll_wine.def new file mode 100644 index 0000000..24a2e05 --- /dev/null +++ b/ntdll_wine.def @@ -0,0 +1,6 @@ +; Wine-specific symbols from ntdll.dll for WineASIO (64-bit) +; NtQueryVirtualMemory is loaded dynamically via GetProcAddress +LIBRARY ntdll.dll +EXPORTS + __wine_unix_call_dispatcher DATA + __wine_unixlib_handle DATA diff --git a/ntdll_wine32.def b/ntdll_wine32.def new file mode 100644 index 0000000..c78a5d1 --- /dev/null +++ b/ntdll_wine32.def @@ -0,0 +1,7 @@ +; Wine-specific symbols from ntdll.dll for WineASIO (32-bit) +; NtQueryVirtualMemory is loaded dynamically via GetProcAddress to avoid +; stdcall decoration mismatch between mingw (@24) and Wine (undecorated) +LIBRARY ntdll.dll +EXPORTS + __wine_unix_call_dispatcher DATA + __wine_unixlib_handle DATA diff --git a/package.sh b/package.sh new file mode 100755 index 0000000..359cd0e --- /dev/null +++ b/package.sh @@ -0,0 +1,334 @@ +#!/bin/bash +# +# WineASIO 1.4.0 Release Packaging Script +# Creates release tarballs for distribution +# + +set -e + +VERSION="1.4.0" +PACKAGE_NAME="wineasio-${VERSION}" +BUILD_DIR="build_wine11" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +echo -e "${GREEN}========================================${NC}" +echo -e "${GREEN} WineASIO ${VERSION} Release Packager${NC}" +echo -e "${GREEN}========================================${NC}" +echo + +# Check if we're in the right directory +if [ ! -f "Makefile.wine11" ]; then + echo -e "${RED}Error: Run this script from the WineASIO source directory${NC}" + exit 1 +fi + +# Create dist directory +DIST_DIR="dist" +rm -rf "${DIST_DIR}" +mkdir -p "${DIST_DIR}" + +echo -e "${YELLOW}[1/5]${NC} Creating source tarball..." + +# Files to include in source release +SOURCE_FILES=( + # Core source files (Wine 11) + "asio_pe.c" + "asio_unix.c" + "unixlib.h" + + # Legacy source files + "asio.c" + "main.c" + "regsvr.c" + "jackbridge.c" + "jackbridge.h" + + # Build system + "Makefile.wine11" + "Makefile" + "Makefile.mk" + + # Definition files + "wineasio.def" + "ntdll_wine.def" + "ntdll_wine32.def" + "wineasio.dll.spec" + "wineasio32.dll.spec" + "wineasio64.dll.spec" + + # Documentation + "README.md" + "RELEASE_NOTES.md" + "WINE11_PORTING.md" + "COPYING.LIB" + "COPYING.GUI" + + # Scripts + "wineasio-register" + "wineasio-register.sh" + "package.sh" + + # Assets + "screenshot.png" +) + +# Create source package directory +SOURCE_PKG="${DIST_DIR}/${PACKAGE_NAME}-source" +mkdir -p "${SOURCE_PKG}" +mkdir -p "${SOURCE_PKG}/gui" +mkdir -p "${SOURCE_PKG}/docker" + +# Copy source files +for file in "${SOURCE_FILES[@]}"; do + if [ -f "$file" ]; then + cp "$file" "${SOURCE_PKG}/" + fi +done + +# Copy GUI directory +if [ -d "gui" ]; then + cp gui/settings.py "${SOURCE_PKG}/gui/" 2>/dev/null || true + cp gui/settings.ui "${SOURCE_PKG}/gui/" 2>/dev/null || true + cp gui/ui_settings.py "${SOURCE_PKG}/gui/" 2>/dev/null || true + cp gui/wineasio-settings "${SOURCE_PKG}/gui/" 2>/dev/null || true + cp gui/wineasio-settings-launcher.c "${SOURCE_PKG}/gui/" 2>/dev/null || true + cp gui/wineasio-settings.bat "${SOURCE_PKG}/gui/" 2>/dev/null || true + cp gui/Makefile "${SOURCE_PKG}/gui/" 2>/dev/null || true +fi + +# Copy docker directory if exists +if [ -d "docker" ]; then + cp -r docker/* "${SOURCE_PKG}/docker/" 2>/dev/null || true +fi + +# Create source tarball +cd "${DIST_DIR}" +tar -czvf "${PACKAGE_NAME}-source.tar.gz" "${PACKAGE_NAME}-source" +cd .. + +echo -e "${GREEN} ✓ Created ${DIST_DIR}/${PACKAGE_NAME}-source.tar.gz${NC}" + +echo -e "${YELLOW}[2/5]${NC} Creating binary tarball..." + +# Check if binaries exist +if [ ! -d "${BUILD_DIR}" ]; then + echo -e "${RED} Warning: Build directory not found. Run 'make -f Makefile.wine11 all' first.${NC}" + echo -e "${YELLOW} Skipping binary package...${NC}" +else + BINARY_PKG="${DIST_DIR}/${PACKAGE_NAME}-binaries" + mkdir -p "${BINARY_PKG}/64bit" + mkdir -p "${BINARY_PKG}/32bit" + mkdir -p "${BINARY_PKG}/gui" + + # Copy 64-bit binaries + if [ -f "${BUILD_DIR}/wineasio64.dll" ]; then + cp "${BUILD_DIR}/wineasio64.dll" "${BINARY_PKG}/64bit/" + fi + if [ -f "${BUILD_DIR}/wineasio64.so" ]; then + cp "${BUILD_DIR}/wineasio64.so" "${BINARY_PKG}/64bit/" + fi + + # Copy 32-bit binaries + if [ -f "${BUILD_DIR}/wineasio.dll" ]; then + cp "${BUILD_DIR}/wineasio.dll" "${BINARY_PKG}/32bit/" + fi + if [ -f "${BUILD_DIR}/wineasio.so" ]; then + cp "${BUILD_DIR}/wineasio.so" "${BINARY_PKG}/32bit/" + fi + + # Copy GUI launchers + if [ -f "gui/wineasio-settings.exe" ]; then + cp gui/wineasio-settings.exe "${BINARY_PKG}/gui/" + fi + if [ -f "gui/wineasio-settings64.exe" ]; then + cp gui/wineasio-settings64.exe "${BINARY_PKG}/gui/" + fi + if [ -f "gui/wineasio-settings" ]; then + cp gui/wineasio-settings "${BINARY_PKG}/gui/" + fi + if [ -f "gui/settings.py" ]; then + cp gui/settings.py "${BINARY_PKG}/gui/" + fi + if [ -f "gui/ui_settings.py" ]; then + cp gui/ui_settings.py "${BINARY_PKG}/gui/" + fi + + # Copy documentation + cp README.md "${BINARY_PKG}/" + cp RELEASE_NOTES.md "${BINARY_PKG}/" 2>/dev/null || true + cp COPYING.LIB "${BINARY_PKG}/" + cp COPYING.GUI "${BINARY_PKG}/" + + # Create install script for binary package + cat > "${BINARY_PKG}/install.sh" << 'INSTALL_EOF' +#!/bin/bash +# +# WineASIO Binary Installation Script +# + +set -e + +# Detect Wine prefix +if [ -z "$WINE_PREFIX" ]; then + # Try common locations + if [ -d "/opt/wine-stable/lib/wine" ]; then + WINE_PREFIX="/opt/wine-stable" + elif [ -d "/opt/wine-staging/lib/wine" ]; then + WINE_PREFIX="/opt/wine-staging" + elif [ -d "/usr/lib/wine" ]; then + WINE_PREFIX="/usr" + else + echo "Could not detect Wine installation." + echo "Please set WINE_PREFIX environment variable." + exit 1 + fi +fi + +echo "Installing WineASIO to ${WINE_PREFIX}..." + +# 64-bit installation +if [ -f "64bit/wineasio64.dll" ]; then + sudo cp 64bit/wineasio64.dll "${WINE_PREFIX}/lib/wine/x86_64-windows/" + sudo cp 64bit/wineasio64.so "${WINE_PREFIX}/lib/wine/x86_64-unix/" + echo " ✓ 64-bit libraries installed" +fi + +# 32-bit installation +if [ -f "32bit/wineasio.dll" ]; then + sudo cp 32bit/wineasio.dll "${WINE_PREFIX}/lib/wine/i386-windows/" + sudo cp 32bit/wineasio.so "${WINE_PREFIX}/lib/wine/i386-unix/" + echo " ✓ 32-bit libraries installed" +fi + +# GUI installation +if [ -f "gui/wineasio-settings" ]; then + sudo cp gui/wineasio-settings /usr/bin/ + sudo chmod +x /usr/bin/wineasio-settings + sudo mkdir -p /usr/share/wineasio + sudo cp gui/settings.py /usr/share/wineasio/ + sudo cp gui/ui_settings.py /usr/share/wineasio/ 2>/dev/null || true + if [ -f "gui/wineasio-settings.exe" ]; then + sudo cp gui/wineasio-settings*.exe /usr/share/wineasio/ + fi + echo " ✓ GUI installed" +fi + +echo "" +echo "Installation complete!" +echo "" +echo "Now register the driver:" +echo " wine regsvr32 wineasio64.dll" +echo " wine ~/.wine/drive_c/windows/syswow64/regsvr32.exe wineasio.dll" +INSTALL_EOF + chmod +x "${BINARY_PKG}/install.sh" + + # Create binary tarball + cd "${DIST_DIR}" + tar -czvf "${PACKAGE_NAME}-binaries.tar.gz" "${PACKAGE_NAME}-binaries" + cd .. + + echo -e "${GREEN} ✓ Created ${DIST_DIR}/${PACKAGE_NAME}-binaries.tar.gz${NC}" +fi + +echo -e "${YELLOW}[3/5]${NC} Creating checksums..." + +cd "${DIST_DIR}" +sha256sum *.tar.gz > SHA256SUMS.txt +cd .. + +echo -e "${GREEN} ✓ Created ${DIST_DIR}/SHA256SUMS.txt${NC}" + +echo -e "${YELLOW}[4/5]${NC} Creating GitHub release template..." + +cat > "${DIST_DIR}/GITHUB_RELEASE.md" << 'RELEASE_EOF' +# WineASIO 1.4.0 - Wine 11 Edition 🍷🎵 + +## What's New + +This is a major release bringing **full Wine 11 compatibility** with a complete rewrite of the build system and internal architecture. + +### ✨ Highlights + +- **Wine 11 Support** - New PE/Unix split architecture for Wine 10.2+/11 +- **Settings GUI** - Launch settings directly from your DAW's ASIO control panel +- **JACK MIDI** - New `WineASIO:midi_in` and `WineASIO:midi_out` ports + +### 📦 Downloads + +| File | Description | +|------|-------------| +| `wineasio-1.4.0-source.tar.gz` | Complete source code | +| `wineasio-1.4.0-binaries.tar.gz` | Pre-built binaries (DLLs + SOs) | + +### 🔧 Quick Install + +```bash +# From source +tar -xzf wineasio-1.4.0-source.tar.gz +cd wineasio-1.4.0-source +make -f Makefile.wine11 all +sudo make -f Makefile.wine11 install +make -f Makefile.wine11 register + +# From binaries +tar -xzf wineasio-1.4.0-binaries.tar.gz +cd wineasio-1.4.0-binaries +./install.sh +``` + +### 💻 Requirements + +- Wine 10.2+ or Wine 11 +- JACK Audio Connection Kit +- PyQt5 or PyQt6 (for settings GUI) + +### 📊 Tested DAWs + +| DAW | Status | +|-----|--------| +| FL Studio | ✅ Working | +| Reaper | ✅ Working | +| Ableton Live | ✅ Working | +| Bitwig Studio | ✅ Working | + +### 📄 Checksums + +See `SHA256SUMS.txt` for file checksums. + +--- + +**Full changelog:** See RELEASE_NOTES.md +RELEASE_EOF + +echo -e "${GREEN} ✓ Created ${DIST_DIR}/GITHUB_RELEASE.md${NC}" + +echo -e "${YELLOW}[5/5]${NC} Cleanup..." + +# Remove temporary directories (keep tarballs) +rm -rf "${DIST_DIR}/${PACKAGE_NAME}-source" +rm -rf "${DIST_DIR}/${PACKAGE_NAME}-binaries" + +echo -e "${GREEN} ✓ Cleanup complete${NC}" + +echo +echo -e "${GREEN}========================================${NC}" +echo -e "${GREEN} Package creation complete!${NC}" +echo -e "${GREEN}========================================${NC}" +echo +echo "Files created in ${DIST_DIR}/:" +ls -la "${DIST_DIR}/" +echo +echo -e "${YELLOW}Next steps:${NC}" +echo "1. Create a new GitHub repository: https://github.com/new" +echo "2. Push the source code" +echo "3. Create a new release and upload:" +echo " - ${DIST_DIR}/${PACKAGE_NAME}-source.tar.gz" +echo " - ${DIST_DIR}/${PACKAGE_NAME}-binaries.tar.gz" +echo " - ${DIST_DIR}/SHA256SUMS.txt" +echo "4. Use ${DIST_DIR}/GITHUB_RELEASE.md as release description" +echo diff --git a/unixlib.h b/unixlib.h new file mode 100644 index 0000000..f15fd71 --- /dev/null +++ b/unixlib.h @@ -0,0 +1,319 @@ +/* + * WineASIO Unix Library Interface + * Copyright (C) 2024 WineASIO contributors + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#ifndef __WINEASIO_UNIXLIB_H +#define __WINEASIO_UNIXLIB_H + +#include + +/* Platform-specific includes */ +#ifdef WINE_UNIX_LIB +/* Unix side - building the .so */ +#include "windef.h" +#include "winbase.h" +#include "winternl.h" +#include "wine/unixlib.h" +#else +/* PE side - building with mingw or winegcc */ +#ifdef _WIN32 +#include +#include +#else +#include "windef.h" +#include "winbase.h" +#endif + +/* Define unixlib types for PE side if not available */ +#ifndef __WINE_WINE_UNIXLIB_H +typedef uint64_t unixlib_handle_t; + +/* These are imported from ntdll */ +extern unixlib_handle_t __wine_unixlib_handle; +extern NTSTATUS (WINAPI *__wine_unix_call_dispatcher)(unixlib_handle_t, unsigned int, void *); +extern NTSTATUS WINAPI __wine_init_unix_call(void); + +static inline NTSTATUS __wine_unix_call(unixlib_handle_t handle, unsigned int code, void *args) +{ + return __wine_unix_call_dispatcher(handle, code, args); +} +#endif /* __WINE_WINE_UNIXLIB_H */ + +#endif /* WINE_UNIX_LIB */ + +/* Maximum number of channels */ +#define WINEASIO_MAX_CHANNELS 128 + +/* Sample types matching ASIO SDK */ +typedef enum { + ASIOSTInt16MSB = 0, + ASIOSTInt24MSB = 1, + ASIOSTInt32MSB = 2, + ASIOSTFloat32MSB = 3, + ASIOSTFloat64MSB = 4, + ASIOSTInt32MSB16 = 8, + ASIOSTInt32MSB18 = 9, + ASIOSTInt32MSB20 = 10, + ASIOSTInt32MSB24 = 11, + ASIOSTInt16LSB = 16, + ASIOSTInt24LSB = 17, + ASIOSTInt32LSB = 18, + ASIOSTFloat32LSB = 19, + ASIOSTFloat64LSB = 20, + ASIOSTInt32LSB16 = 24, + ASIOSTInt32LSB18 = 25, + ASIOSTInt32LSB20 = 26, + ASIOSTInt32LSB24 = 27, +} ASIOSampleType; + +/* Stream handle - opaque pointer to Unix-side stream */ +typedef UINT64 asio_handle; + +/* Buffer info for channel setup */ +struct asio_buffer_info { + BOOL is_input; + LONG channel_num; + UINT64 buffer_ptr[2]; /* Double-buffering pointers */ +}; + +/* Channel info */ +struct asio_channel_info { + LONG channel; + BOOL is_input; + BOOL is_active; + LONG channel_group; + ASIOSampleType sample_type; + char name[32]; +}; + +/* Time info from JACK */ +struct asio_time_info { + double speed; + INT64 system_time; + INT64 sample_position; + double sample_rate; + UINT32 flags; +}; + +/* Configuration read from registry (passed to Unix side) */ +struct asio_config { + LONG num_inputs; + LONG num_outputs; + LONG preferred_bufsize; + BOOL fixed_bufsize; + BOOL autoconnect; + char client_name[64]; +}; + +/* + * Parameter structures for each Unix function call + */ + +struct asio_init_params { + struct asio_config config; + HRESULT result; + asio_handle handle; + LONG input_channels; + LONG output_channels; + double sample_rate; +}; + +struct asio_exit_params { + asio_handle handle; + HRESULT result; +}; + +struct asio_start_params { + asio_handle handle; + HRESULT result; +}; + +struct asio_stop_params { + asio_handle handle; + HRESULT result; +}; + +struct asio_get_channels_params { + asio_handle handle; + HRESULT result; + LONG num_inputs; + LONG num_outputs; +}; + +struct asio_get_latencies_params { + asio_handle handle; + HRESULT result; + LONG input_latency; + LONG output_latency; +}; + +struct asio_get_buffer_size_params { + asio_handle handle; + HRESULT result; + LONG min_size; + LONG max_size; + LONG preferred_size; + LONG granularity; +}; + +struct asio_can_sample_rate_params { + asio_handle handle; + double sample_rate; + HRESULT result; +}; + +struct asio_get_sample_rate_params { + asio_handle handle; + HRESULT result; + double sample_rate; +}; + +struct asio_set_sample_rate_params { + asio_handle handle; + double sample_rate; + HRESULT result; +}; + +struct asio_get_channel_info_params { + asio_handle handle; + struct asio_channel_info info; + HRESULT result; +}; + +struct asio_create_buffers_params { + asio_handle handle; + LONG num_channels; + LONG buffer_size; + struct asio_buffer_info *buffer_infos; /* Array of buffer_info */ + HRESULT result; +}; + +struct asio_dispose_buffers_params { + asio_handle handle; + HRESULT result; +}; + +struct asio_output_ready_params { + asio_handle handle; + HRESULT result; +}; + +struct asio_get_sample_position_params { + asio_handle handle; + HRESULT result; + INT64 sample_position; + INT64 system_time; +}; + +/* Callback notification - PE side polls for this */ +struct asio_get_callback_params { + asio_handle handle; + HRESULT result; + BOOL buffer_switch_ready; + LONG buffer_index; + BOOL direct_process; + struct asio_time_info time_info; + BOOL sample_rate_changed; + double new_sample_rate; + BOOL reset_request; + BOOL resync_request; + BOOL latency_changed; +}; + +/* Acknowledge that callback was processed */ +struct asio_callback_done_params { + asio_handle handle; + LONG buffer_index; + HRESULT result; +}; + +struct asio_control_panel_params { + asio_handle handle; + HRESULT result; +}; + +struct asio_future_params { + asio_handle handle; + LONG selector; + UINT64 opt; /* Pointer to optional parameter */ + HRESULT result; +}; + +/* + * Unix function IDs + */ +enum unix_funcs { + unix_asio_init, + unix_asio_exit, + unix_asio_start, + unix_asio_stop, + unix_asio_get_channels, + unix_asio_get_latencies, + unix_asio_get_buffer_size, + unix_asio_can_sample_rate, + unix_asio_get_sample_rate, + unix_asio_set_sample_rate, + unix_asio_get_channel_info, + unix_asio_create_buffers, + unix_asio_dispose_buffers, + unix_asio_output_ready, + unix_asio_get_sample_position, + unix_asio_get_callback, + unix_asio_callback_done, + unix_asio_control_panel, + unix_asio_future, + unix_funcs_count +}; + +/* Error codes matching ASIO SDK */ +#define ASE_OK 0 +#define ASE_SUCCESS 0x3f4847a0 +#define ASE_NotPresent (-1000) +#define ASE_HWMalfunction (-999) +#define ASE_InvalidParameter (-998) +#define ASE_InvalidMode (-997) +#define ASE_SPNotAdvancing (-996) +#define ASE_NoClock (-995) +#define ASE_NoMemory (-994) + +/* Future selectors */ +#define kAsioEnableTimeCodeRead 1 +#define kAsioDisableTimeCodeRead 2 +#define kAsioSetInputMonitor 3 +#define kAsioTransport 4 +#define kAsioSetInputGain 5 +#define kAsioGetInputMeter 6 +#define kAsioSetOutputGain 7 +#define kAsioGetOutputMeter 8 +#define kAsioCanInputMonitor 9 +#define kAsioCanTimeInfo 10 +#define kAsioCanTimeCode 11 +#define kAsioCanTransport 12 +#define kAsioCanInputGain 13 +#define kAsioCanInputMeter 14 +#define kAsioCanOutputGain 15 +#define kAsioCanOutputMeter 16 +#define kAsioOptionalOne 17 +#define kAsioSetIoFormat 0x23111961 +#define kAsioGetIoFormat 0x23111983 +#define kAsioCanDoIoFormat 0x23112004 +#define kAsioCanReportOverload 0x24042012 +#define kAsioGetInternalBufferSamples 0x25042012 +#define kAsioSupportsInputResampling 0x26092017 + +#endif /* __WINEASIO_UNIXLIB_H */ \ No newline at end of file diff --git a/wineasio.def b/wineasio.def new file mode 100644 index 0000000..b93721c --- /dev/null +++ b/wineasio.def @@ -0,0 +1,9 @@ +; WineASIO DLL Export Definitions +; This file ensures exports use undecorated names for Wine compatibility +LIBRARY wineasio +EXPORTS + DllCanUnloadNow + DllGetClassObject + DllMain + DllRegisterServer + DllUnregisterServer