build-driver.sh (4279B)
1 #!/usr/bin/env bash 2 # build-driver.sh — build + ad-hoc-sign the Hydra virtual audio driver, WITHOUT Xcode. 3 # 4 # Hydra's driver is a rebranded BlackHole (GPL-3.0). An AudioServerPlugIn is just a 5 # loadable bundle (a `-bundle` Mach-O + Info.plist), so we build it directly with clang 6 # from the single vendored BlackHole.c — no full Xcode install required, only the 7 # Command Line Tools. BlackHole exposes its identity + channel count as compile-time 8 # constants, so we get a first-party "Hydra" device with ZERO edits to upstream source. 9 # 10 # ./scripts/build-driver.sh 11 # 12 # Output: driver/build/Hydra.driver (universal arm64+x86_64 where buildable, ad-hoc 13 # signed). Install is a separate, user-run, sudo step — see install-driver.sh. 14 set -euo pipefail 15 16 ROOT="$(cd "$(dirname "$0")/.." && pwd)" 17 UP="$ROOT/driver/upstream" 18 SRC="$UP/BlackHole/BlackHole.c" 19 PLIST_TMPL="$UP/BlackHole/BlackHole.plist" 20 ICON="$UP/BlackHole/BlackHole.icns" 21 OUT="$ROOT/driver/build" 22 APP="$OUT/Hydra.driver" 23 24 DRIVER_NAME="Hydra" 25 BUNDLE_ID="com.ganten.hydra.driver" 26 # Default 2ch stereo (matches a normal mic / Loopback). Override for DAW use with 27 # HYDRA_DRIVER_CHANNELS=N. 28 CHANNELS="${HYDRA_DRIVER_CHANNELS:-2}" 29 # Default startup sample rate 44100. THIS was the real "one ear in Discord" cause: Hydra 30 # defaulted to 48000 while the host setup ran at 44100, and the resample in the capture path 31 # collapsed stereo to one channel. Loopback worked because it matched the host at 44100. 32 # The device still *supports* 48000 etc.; this only sets the rate it comes up at. 33 # Override with HYDRA_DRIVER_RATE=48000 if your setup is 48k. 34 RATE="${HYDRA_DRIVER_RATE:-44100}" 35 MANUFACTURER="Ganten" 36 SIGN_ID="${HYDRA_SIGN_ID:--}" # '-' = ad-hoc 37 38 [ -f "$SRC" ] || { echo "error: $SRC missing — clone upstream (see driver/README.md)." >&2; exit 1; } 39 40 echo "• compiling BlackHole.c → Hydra (${CHANNELS}ch, $BUNDLE_ID)" 41 rm -rf "$OUT" 42 mkdir -p "$APP/Contents/MacOS" "$APP/Contents/Resources" 43 44 # Rebrand purely via -D overrides (kHas_Driver_Name_Format=false drops the "%ich" suffix, 45 # so the device shows as a clean "Hydra"). No edits to upstream source. 46 DEFS=( 47 -DkDriver_Name="\"$DRIVER_NAME\"" 48 -DkPlugIn_BundleID="\"$BUNDLE_ID\"" 49 -DkPlugIn_Icon="\"$DRIVER_NAME.icns\"" 50 -DkManufacturer_Name="\"$MANUFACTURER\"" 51 -DkNumber_Of_Channels=$CHANNELS 52 -DkDefault_SampleRate=${RATE}.0 53 -DkHas_Driver_Name_Format=false 54 -DkDevice_Name="\"$DRIVER_NAME\"" 55 -DkDevice2_Name="\"$DRIVER_NAME Mirror\"" 56 ) 57 58 # This toolchain can drop the cross slice when both -arch flags are passed at once, so 59 # compile each arch separately and lipo whatever succeeds into the final binary. 60 # Hydra's manifest reader (device name from devices.json) compiles alongside BlackHole.c. 61 CFG="$ROOT/driver/hydra_cfg.c" 62 SLICES=() 63 for arch in arm64 x86_64; do 64 obj="$OUT/Hydra-$arch" 65 if clang -bundle "$SRC" "$CFG" -o "$obj" \ 66 -fobjc-arc -O2 -arch "$arch" -mmacosx-version-min=11.0 \ 67 -framework CoreAudio -framework CoreFoundation -framework Foundation -framework Accelerate \ 68 "${DEFS[@]}" 2>/dev/null; then 69 SLICES+=("$obj") 70 else 71 echo " (skipping $arch — not buildable on this toolchain)" 72 fi 73 done 74 [ ${#SLICES[@]} -gt 0 ] || { echo "✗ no architecture built" >&2; exit 1; } 75 lipo -create "${SLICES[@]}" -output "$APP/Contents/MacOS/$DRIVER_NAME" 76 rm -f "${SLICES[@]}" 77 78 # The CFPlugIn factory symbol must be exported or coreaudiod can't instantiate the driver. 79 nm -gU "$APP/Contents/MacOS/$DRIVER_NAME" | grep -q "_BlackHole_Create" \ 80 || { echo "✗ factory symbol _BlackHole_Create not exported" >&2; exit 1; } 81 82 echo "• writing Info.plist + resources" 83 sed -e "s/\${EXECUTABLE_NAME}/$DRIVER_NAME/g" \ 84 -e "s/\${PRODUCT_NAME}/$DRIVER_NAME/g" \ 85 -e "s/\$(PRODUCT_BUNDLE_IDENTIFIER)/$BUNDLE_ID/g" \ 86 -e "s/\$(MARKETING_VERSION)/0.1.0/g" \ 87 "$PLIST_TMPL" > "$APP/Contents/Info.plist" 88 cp "$ICON" "$APP/Contents/Resources/$DRIVER_NAME.icns" 89 90 echo "• signing (identity: $SIGN_ID)" 91 codesign --force --deep --sign "$SIGN_ID" "$APP" 92 codesign --verify --verbose "$APP" 2>&1 | tail -1 93 94 echo 95 echo "✓ built $APP" 96 echo " $(lipo -info "$APP/Contents/MacOS/$DRIVER_NAME" | sed 's/.*: //')" 97 echo " install (sudo, restarts audio): ./scripts/install-driver.sh"