From c11cddf753631c31b89e35b4d375f128b521b0ba Mon Sep 17 00:00:00 2001 From: Kimmo Fonsell Date: Mon, 5 Oct 2026 23:14:00 +0300 Subject: [PATCH] Add an Intel Mac build beside the Apple silicon one The macOS packages are now AmpSim--macos-apple-silicon and -macos-intel, each a .pkg and a .dmg, so the files say which Mac they are for. The Intel build is AVX2 and FMA, as the Windows build is AVX2. package.sh takes AMPSIM_ARCH, checks every binary with lipo, and does not copy a cross build into ~/Library. Each installer refuses the wrong Mac by name, and the Intel one a processor without AVX2. CI runs the macOS job as a matrix on macos-latest and macos-15-intel, and a tag publishes both. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/build.yml | 60 ++++++++++++++++++++++++++++++------- .gitignore | 1 + CHANGELOG.md | 15 ++++++++++ CLAUDE.md | 21 ++++++++++--- CMakeLists.txt | 10 +++++++ README.md | 12 ++++---- docs/guide/index.html | 6 +++- packaging/README.md | 30 ++++++++++++++++--- packaging/distribution.xml | 37 ++++++++++++++++++++++- packaging/package.sh | 55 +++++++++++++++++++++++++++++----- 10 files changed, 214 insertions(+), 33 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1c54edd..382fa0b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,7 +1,7 @@ name: build # Every push to main and every pull request, a button in the Actions tab, and version tags -# (v1.2.3), which also publish a GitHub Release with the installer and disk image. +# (v1.2.3), which also publish a GitHub Release with the installers and disk images. on: push: branches: [main] @@ -142,11 +142,32 @@ jobs: path: build/artefacts/*-setup.exe if-no-files-found: error + # One leg per kind of Mac, each built, tested and validated natively on that kind of Mac rather + # than cross-built: auval and pluginval load the plugin, which an Intel binary cannot do on Apple + # silicon without Rosetta, and the AVX2 Intel build is only proven by running it on Intel. macos: - name: macOS (Release) - # macos-latest rather than a pinned version: GitHub retires old runner images, and a pinned - # label that has been retired fails before it builds anything. - runs-on: macos-latest + name: macOS ${{ matrix.name }} (Release) + strategy: + # Let both finish, so one failing does not hide what the other would have said. A tag still + # publishes nothing unless both pass: the release job needs the whole matrix. + fail-fast: false + matrix: + include: + # macos-latest rather than a pinned version: GitHub retires old runner images, and a + # pinned label that has been retired fails before it builds anything. + - name: Apple silicon + runner: macos-latest + arch: arm64 + artifact: AmpSim-macOS-AppleSilicon + # Pinned, because there is no "latest" Intel label: macos-15-intel is the last Intel + # image GitHub provides, and it is retired in autumn 2027. After that this leg has + # nowhere to run, and the Intel build will have to be cross-built on Apple silicon and + # tested under Rosetta, or dropped. + - name: Intel + runner: macos-15-intel + arch: x86_64 + artifact: AmpSim-macOS-Intel + runs-on: ${{ matrix.runner }} steps: - uses: actions/checkout@v4 @@ -159,8 +180,11 @@ jobs: - name: Install Ninja run: brew install ninja + # The architecture named rather than left to the runner, so a runner that turned out to be + # the other kind of Mac fails the build instead of quietly producing the wrong one; the + # package step checks every binary against it. AMPSIM_BUILD_TOOLS for the benchmark below. - name: Configure - run: cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release + run: cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_ARCHITECTURES=${{ matrix.arch }} -DAMPSIM_BUILD_TOOLS=ON - name: Build run: cmake --build build @@ -192,16 +216,30 @@ jobs: --strictness-level 10 \ --validate build/AmpSim_artefacts/Release/VST3/AmpSim.vst3 + # Asserts nothing, as on Windows: compare figures only within one job. On the Intel leg it + # also proves the AVX2 build is what was made, since a build without it passes every test + # and only costs more. 48 kHz is the model's own rate; 44.1 kHz puts the resampler in. + - name: Benchmark + run: | + if [ "${{ matrix.arch }}" = x86_64 ]; then + for flag in -mavx2 -mfma; do + grep -q -- "$flag" build/build.ninja || { echo "$flag did not reach the compile lines" >&2; exit 1; } + done + fi + sysctl -n machdep.cpu.brand_string + ./build/ampsim_bench 48000 + ./build/ampsim_bench 44100 + # Proves the installer still builds and signs on every run, and is what a tagged run # publishes. - name: Package # Reuses the build above rather than making a second one; the script only has to # reconfigure to drop the tests. - run: AMPSIM_BUILD_DIR=build ./packaging/package.sh + run: AMPSIM_BUILD_DIR=build AMPSIM_ARCH=${{ matrix.arch }} ./packaging/package.sh - uses: actions/upload-artifact@v4 with: - name: AmpSim-macOS + name: ${{ matrix.artifact }} path: build/artefacts/* if-no-files-found: error @@ -223,9 +261,11 @@ jobs: with: fetch-depth: 1 + # Both Macs' packages, into the one directory: their names already say which is which. - uses: actions/download-artifact@v4 with: - name: AmpSim-macOS + pattern: AmpSim-macOS-* + merge-multiple: true path: release - uses: actions/download-artifact@v4 @@ -249,7 +289,7 @@ jobs: --verify-tag < Privacy & Security > Open Anyway. + **macOS:** macOS 11 or later, in two builds. Files ending \`macos-apple-silicon\` are for M-series Macs; files ending \`macos-intel\` are for Intel Macs, which need a processor with AVX2 (every Intel Mac that runs macOS 11 except the 2013 Mac Pro). The installer refuses the wrong one. \`.pkg\` installs the Audio Unit, the VST3 and the standalone; \`.dmg\` holds the standalone alone. Ad-hoc signed: the first use needs System Settings > Privacy & Security > Open Anyway. **Windows:** Windows 10 version 2004 or later, or Windows 11, on x64 with an AVX2 processor (Intel Core from 2013, any AMD Ryzen). \`setup.exe\` installs the VST3 and the standalone, for you or for everyone. Unsigned: SmartScreen needs More info > Run anyway. In the standalone, choose your interface's ASIO driver in Options for low latency. diff --git a/.gitignore b/.gitignore index bb61f8b..654dd20 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # Build output build/ build-release/ +build-release-*/ cmake-build-*/ # macOS diff --git a/CHANGELOG.md b/CHANGELOG.md index 284a8c1..0b77129 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,21 @@ What changed in each published release. The [user guide](https://catastrophiccoder.github.io/ampsim/) describes how the current version behaves; this records what moved between versions. +## [Unreleased] + +### Added + +- **Intel Macs.** A second macOS build, for Intel Macs on macOS 11 or later with an AVX2 + processor — every Intel Mac that runs macOS 11 except the 2013 Mac Pro. It is a separate + package rather than a universal binary. + +### Changed + +- **The macOS files say which Mac they are for**: `AmpSim--macos-apple-silicon` and + `AmpSim--macos-intel`, each as a `.pkg` and a `.dmg`, where there used to be one + unlabelled pair. The installer refuses the wrong one by name, where before the Apple silicon + package would install on an Intel Mac and leave plugins that could not load. + ## [0.4.0] — 2026-10-04 **Windows.** The VST3 and the standalone now build, test and install on Windows as well as diff --git a/CLAUDE.md b/CLAUDE.md index a130fa5..ffb9a68 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -556,7 +556,7 @@ a modelled passive stack. Each is explained where it is implemented. ## What the project is -A **minimal** guitar amp simulator plugin (JUCE, C++17), built as AU / VST3 / Standalone on macOS and as VST3 / Standalone on Windows. Four parts, per the Goal section of `ampsim_plan.md`: +A **minimal** guitar amp simulator plugin (JUCE, C++17), built as AU / VST3 / Standalone on macOS (Apple silicon and Intel, separately) and as VST3 / Standalone on Windows. Four parts, per the Goal section of `ampsim_plan.md`: 1. **Amp** — a pre-trained `.nam` model run through [NeuralAmpModelerCore](https://github.com/sdatkinson/NeuralAmpModelerCore) (MIT). No hand-written amp DSP; the model supplies the tone. 2. **Custom amp-like UI** — a front panel with Gain, three-band EQ (Bass, Mid, Treble) and Master Volume. This is the project's own contribution and the reason it is not just a NAM loader. @@ -647,9 +647,22 @@ commit and push, then git tag -a vX.Y.Z -m "AmpSim X.Y.Z" && git push origin vX.Y.Z # after the version commit is pushed ``` -The runner is `macos-latest`, which is Apple silicon, and nothing sets `CMAKE_OSX_ARCHITECTURES`, -so the published build is arm64 against a deployment target of macOS 11. The Windows build is x64 -and needs AVX2 and Windows 10 2004 or later, which the installer checks. +**There are two Mac builds, not one universal binary**: `AmpSim--macos-apple-silicon` +and `-macos-intel`, each a `.pkg` and a `.dmg`, both against a deployment target of macOS 11. The +macOS job is a matrix, and each leg builds, tests and validates on its own kind of Mac: +`macos-latest` for arm64 and `macos-15-intel` for x86_64. That Intel label is the last GitHub +offers and is retired in autumn 2027; after that the Intel build has to be cross-built on Apple +silicon and tested under Rosetta, which runs AVX2 (all 126 tests passed that way) though +`sysctl hw.optional.avx2_0` says 0 under it. `package.sh` takes `AMPSIM_ARCH`, refuses a binary +`lipo` says is the other architecture, and turns off `AMPSIM_COPY_PLUGIN` for a cross build so an +Intel AU never replaces the one the hosts on an Apple silicon Mac load. + +**The Intel Mac build is AVX2 and FMA**, `-mavx2 -mfma` in `CMakeLists.txt`, for the reason the +Windows build is; clang does not imply FMA from AVX2 the way MSVC's `/arch:AVX2` does. The +installer's `installation-check` in `packaging/distribution.xml` refuses an Intel package on +Apple silicon, an Apple silicon one on Intel, and an Intel Mac without AVX2 (only the 2013 Mac Pro +among those macOS 11 supports). The Windows build is x64 and needs AVX2 and Windows 10 2004 or +later, which its installer checks. ## What it costs diff --git a/CMakeLists.txt b/CMakeLists.txt index 9d5802b..8012c0e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,6 +24,16 @@ if(MSVC) add_compile_options(/arch:AVX2) endif() +# The Intel Mac build makes the same choice, for the same reason. Every Intel Mac that runs +# macOS 11 has AVX2 except the 2013 Mac Pro, and the installer refuses that one (see +# packaging/distribution.xml). AVX2 does not bring FMA with it on clang the way /arch:AVX2 does +# on MSVC, so ask for both. A single-architecture build only: there is no AVX2 on arm64, and a +# universal build would need the flags per slice, which nothing here produces. +if(APPLE AND (CMAKE_OSX_ARCHITECTURES STREQUAL "x86_64" + OR (NOT CMAKE_OSX_ARCHITECTURES AND CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64"))) + add_compile_options(-mavx2 -mfma) +endif() + # The C++ runtime linked statically on Windows (/MT), so the standalone and the VST3 need nothing # installed beside them. CMake's default is the DLL runtime, and a plugin that cannot find # VCRUNTIME140.dll fails to load in the host with no useful message. A variable rather than a diff --git a/README.md b/README.md index 5d40f2d..1042fbe 100644 --- a/README.md +++ b/README.md @@ -66,9 +66,11 @@ it so the repeats are of the already-distorted tone. Ready-made packages are on the [releases page](https://github.com/CatastrophicCoder/ampsim/releases). -**macOS** (Apple silicon, macOS 11 or later): the `.pkg` installs the AU and VST3 into -`/Library/Audio/Plug-Ins/` and the standalone app into `/Applications`; the `.dmg` holds just the -app. In Logic the plugin appears as **Catastrophic Audio: AmpSim**, with the other amps and +**macOS** (macOS 11 or later): take the files ending `macos-apple-silicon` for an M-series Mac +and `macos-intel` for an Intel one, which needs AVX2 — every Intel Mac that runs macOS 11 has it +except the 2013 Mac Pro. The installer refuses the wrong one. The `.pkg` installs the AU and VST3 +into `/Library/Audio/Plug-Ins/` and the standalone app into `/Applications`; the `.dmg` holds just +the app. In Logic the plugin appears as **Catastrophic Audio: AmpSim**, with the other amps and distortion plugins. **Windows** (Windows 10 version 2004 or later, or 11, on x64 with an AVX2 processor): the @@ -235,8 +237,8 @@ assistant. ## What it does not do -- **macOS on Apple silicon, and Windows on x64 with AVX2.** No Linux build, no Intel Mac build, - and no native Windows-on-ARM build: there the x64 build loads only in an x64 host. +- **macOS on Apple silicon or Intel with AVX2, and Windows on x64 with AVX2.** No Linux build, no + universal Mac binary — two separate packages instead — and no native Windows-on-ARM build: there the x64 build loads only in an x64 host. - **Mono.** Stereo input is summed at the top of the chain. - **One model, one cabinet, no channel switching.** - **The mic-position blend is unproven musically.** The interpolation is exact and tested, but diff --git a/docs/guide/index.html b/docs/guide/index.html index e2bea65..eff6b46 100644 --- a/docs/guide/index.html +++ b/docs/guide/index.html @@ -119,7 +119,11 @@

AmpSim user guide

Installing

macOS

-

Apple silicon, macOS 11 or later. The installer puts three things on your machine: an Audio Unit +

macOS 11 or later, on Apple silicon or an Intel Mac. There is a package for each: the files + ending macos-apple-silicon are for M-series Macs, and those ending + macos-intel for Intel ones, which need a processor with AVX2 — every Intel + Mac that runs macOS 11 has it except the 2013 Mac Pro. The installer checks, and refuses the + wrong one. It puts three things on your machine: an Audio Unit and a VST3 in /Library/Audio/Plug-Ins/, and a standalone app in /Applications. Take whichever you need.

    diff --git a/packaging/README.md b/packaging/README.md index 7fa34fb..3f779f6 100644 --- a/packaging/README.md +++ b/packaging/README.md @@ -57,15 +57,37 @@ false. Anything added to the installer later needs the same treatment. It builds Release, ad-hoc signs the three formats, and writes to `build-release/artefacts`: -- `AmpSim-.pkg` — an installer offering the AU, the VST3 and the standalone app - separately. Installs to `/Library/Audio/Plug-Ins/` and `/Applications`, so it asks for an +- `AmpSim--.pkg` — an installer offering the AU, the VST3 and the standalone + app separately. Installs to `/Library/Audio/Plug-Ins/` and `/Applications`, so it asks for an administrator password. -- `AmpSim-.dmg` — the standalone app alone, with a link to Applications to drag it into. +- `AmpSim--.dmg` — the standalone app alone, with a link to Applications to drag + it into. + +`` is `macos-apple-silicon` or `macos-intel`. The script builds for the Mac it runs on; +`AMPSIM_ARCH` picks the other one: + +```sh +AMPSIM_ARCH=x86_64 ./packaging/package.sh # Intel, into build-release-x86_64/artefacts +AMPSIM_ARCH=arm64 ./packaging/package.sh # Apple silicon, from an Intel Mac +``` + +A build for the other kind of Mac goes in a tree of its own and is not copied into +`~/Library/Audio/Plug-Ins`, where it would replace the plugin this Mac's hosts load. The script +checks every binary with `lipo` before packaging it, so a build tree configured for the wrong Mac +fails rather than producing a package with the wrong name. + +The two are separate packages rather than one universal binary. The Intel build is compiled for +AVX2, as the Windows build is (`docs/windows.md` has the measurements), and that takes +per-architecture compiler flags. Each package's installer checks the Mac it is opened on and +refuses the wrong one by name, and the Intel one also refuses a processor without AVX2 — of the +Macs that run macOS 11, only the 2013 Mac Pro. The Intel build runs under Rosetta on Apple silicon, +AVX2 included, which is how to run its tests on an M-series Mac; Rosetta's own overhead makes its +benchmark figures meaningless there. To install without an administrator password, into your own library rather than the system one: ```sh -installer -pkg build-release/artefacts/AmpSim-*.pkg -target CurrentUserHomeDirectory +installer -pkg build-release/artefacts/AmpSim-*-macos-apple-silicon.pkg -target CurrentUserHomeDirectory ``` That puts the plugins in `~/Library/Audio/Plug-Ins/` and the app in `~/Applications`, which every diff --git a/packaging/distribution.xml b/packaging/distribution.xml index 3430a87..27859a9 100644 --- a/packaging/distribution.xml +++ b/packaging/distribution.xml @@ -1,8 +1,43 @@ - AmpSim @VERSION@ + AmpSim @VERSION@ for @MAC@ + + + diff --git a/packaging/package.sh b/packaging/package.sh index 8939d1a..3cf2c5e 100755 --- a/packaging/package.sh +++ b/packaging/package.sh @@ -11,9 +11,27 @@ set -euo pipefail root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +# AMPSIM_ARCH picks the Mac it is for: arm64 (Apple silicon) or x86_64 (Intel). It defaults to +# the machine running the script. The two are separate packages rather than one universal binary: +# the Intel build is compiled for AVX2 (CMakeLists.txt), which needs per-architecture flags a +# universal build would have to carry slice by slice, and each half is built and tested on its own +# kind of Mac in CI. +arch="${AMPSIM_ARCH:-$(uname -m)}" +case "$arch" in + arm64) platform="macos-apple-silicon"; mac="Apple silicon" ;; + x86_64) platform="macos-intel"; mac="Intel Macs" ;; + *) echo "AMPSIM_ARCH must be arm64 or x86_64, not $arch" >&2; exit 1 ;; +esac + # AMPSIM_BUILD_DIR lets a caller that has already built Release — CI, say — point at that tree -# instead of paying for a second full build. -build="${AMPSIM_BUILD_DIR:-$root/build-release}" +# instead of paying for a second full build. Building for the other kind of Mac gets a tree of its +# own, so the two never share a cache that was configured for the wrong one. +if [ "$arch" = "$(uname -m)" ]; then + default_build="$root/build-release" +else + default_build="$root/build-release-$arch" +fi +build="${AMPSIM_BUILD_DIR:-$default_build}" staging="$build/package" out="$build/artefacts" @@ -22,14 +40,34 @@ version="$(sed -n 's/^project(AmpSim VERSION \([0-9.]*\).*/\1/p' "$root/CMakeLis identifier="com.catastrophicaudio.ampsim" -echo "==> AmpSim $version" +echo "==> AmpSim $version for $platform ($arch)" # --- build ------------------------------------------------------------------------------------- -cmake -S "$root" -B "$build" -G Ninja -DCMAKE_BUILD_TYPE=Release -DAMPSIM_BUILD_TESTS=OFF > /dev/null +# A cross build is not copied into ~/Library/Audio/Plug-Ins: an Intel AU there would replace the +# Apple silicon one this Mac's hosts load, and fail to load in them. +copy_args=() +if [ "$arch" != "$(uname -m)" ]; then + copy_args=(-DAMPSIM_COPY_PLUGIN=OFF) +fi + +cmake -S "$root" -B "$build" -G Ninja -DCMAKE_BUILD_TYPE=Release -DAMPSIM_BUILD_TESTS=OFF \ + -DCMAKE_OSX_ARCHITECTURES="$arch" ${copy_args[@]+"${copy_args[@]}"} > /dev/null cmake --build "$build" --target AmpSim_AU AmpSim_VST3 AmpSim_Standalone > /dev/null artefacts="$build/AmpSim_artefacts/Release" +# A build tree reused from elsewhere could have been configured for the other Mac. Refuse to label +# a binary as something it is not. +for binary in "$artefacts/AU/AmpSim.component/Contents/MacOS/AmpSim" \ + "$artefacts/VST3/AmpSim.vst3/Contents/MacOS/AmpSim" \ + "$artefacts/Standalone/AmpSim.app/Contents/MacOS/AmpSim"; do + built="$(lipo -archs "$binary")" + if [ "$built" != "$arch" ]; then + echo "$binary is $built, not $arch" >&2 + exit 1 + fi +done + # --- sign -------------------------------------------------------------------------------------- # Ad-hoc ("-"), and each bundle on its own rather than with --deep, which Apple deprecated for # signing: it silently re-signs nested code that should be signed on its own terms. @@ -84,12 +122,13 @@ build_component "$staging/vst3" "$identifier.vst3" "$staging/pkgs/vst3.pkg build_component "$staging/app" "$identifier.standalone" "$staging/pkgs/app.pkg" # --- installer --------------------------------------------------------------------------------- -sed "s/@VERSION@/$version/g" "$root/packaging/distribution.xml" > "$staging/distribution.xml" +sed -e "s/@VERSION@/$version/g" -e "s/@ARCH@/$arch/g" -e "s/@MAC@/$mac/g" \ + "$root/packaging/distribution.xml" > "$staging/distribution.xml" productbuild --distribution "$staging/distribution.xml" \ --package-path "$staging/pkgs" \ --resources "$root/packaging/resources" \ - "$out/AmpSim-$version.pkg" > /dev/null + "$out/AmpSim-$version-$platform.pkg" > /dev/null # --- disk image for the standalone -------------------------------------------------------------- dmg_staging="$staging/dmg" @@ -98,8 +137,8 @@ cp -R "$artefacts/Standalone/AmpSim.app" "$dmg_staging/" cp "$root/packaging/README.md" "$dmg_staging/Read me first.txt" ln -s /Applications "$dmg_staging/Applications" -hdiutil create -volname "AmpSim $version" -srcfolder "$dmg_staging" -ov -quiet -format UDZO \ - "$out/AmpSim-$version.dmg" +hdiutil create -volname "AmpSim $version ($mac)" -srcfolder "$dmg_staging" -ov -quiet -format UDZO \ + "$out/AmpSim-$version-$platform.dmg" echo echo "==> built:"