Skip to content

Fix pre-existing Linux and macOS CI failures#40

Merged
johnnyclem merged 10 commits into
mainfrom
claude/porta424-swiftui-replica-ozxind
Jul 1, 2026
Merged

Fix pre-existing Linux and macOS CI failures#40
johnnyclem merged 10 commits into
mainfrom
claude/porta424-swiftui-replica-ozxind

Conversation

@johnnyclem

Copy link
Copy Markdown
Owner

Summary

Fixes the two pre-existing CI failures that predate the mixer board work (#39) and currently break the build on every PR, including main. These are unrelated to the mixer feature — surfaced while babysitting #39's CI.

Failures fixed

Linux — setup-swift rejects Ubuntu 24.04

ubuntu-latest now resolves to Ubuntu 24.04, which swift-actions/setup-swift@v1 doesn't support, so the job dies before building:

Error: Version "24.04" of Ubuntu is not supported

Fix: pin the Linux job to runs-on: ubuntu-22.04 (supported by setup-swift@v1).

macOS — PortaDSPAudioUnit.swift won't compile on Apple platforms

The #if canImport(AudioToolbox) branch referenced a static componentDescription and register() that only existed in the Linux stub branch, and threw a non-constructible AUAudioUnitError:

error: type 'PortaDSPAudioUnit' has no member 'register'
error: instance member 'componentDescription' cannot be used on type 'PortaDSPAudioUnit'
error: cannot find 'AUAudioUnitError' in scope

Fix:

  • Add a static componentDescription (effect component, FourCC subtype/manufacturer) and a thread-safe, idempotent register() (one-time AUAudioUnit.registerSubclass) to the real class.
  • Replace both AUAudioUnitError(...) throws with the package's own PortaDSPAudioUnitError, adding formatNotSupported and failedInitialization cases (mirrored in the Linux stub enum for parity).

Verification

No Swift toolchain in this environment, so the fixes were made by inspection against the AUAudioUnit APIs and the file's existing conditional-compilation structure. CI on this PR is the real check — both the Linux and macOS jobs should now go green.

🤖 Generated with Claude Code

https://claude.ai/code/session_01T9m6a6N9BTYjq448t2XAza


Generated by Claude Code

claude added 10 commits June 24, 2026 06:13
These failures predate the mixer board change and affect every PR.

- Linux: swift-actions/setup-swift@v1 rejects Ubuntu 24.04 (what
  ubuntu-latest now resolves to). Pin the Linux job to ubuntu-22.04.
- macOS: PortaDSPAudioUnit.swift failed to compile on Apple platforms.
  The AudioToolbox branch referenced a static componentDescription and
  register() that only existed in the Linux stub, and threw a
  non-constructible AUAudioUnitError. Add the static component
  description plus one-time subclass registration to the real class, and
  throw the package's own PortaDSPAudioUnitError (new formatNotSupported
  and failedInitialization cases) instead.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T9m6a6N9BTYjq448t2XAza
The AudioToolbox branch calls the C DSP functions (porta_create,
porta_destroy, porta_process_interleaved, porta_update_params,
porta_get_meters_dbfs) but never imported the bridge module that
declares them, so it failed to compile on Apple platforms. Match the
other Swift files in the target (PortaDSPWrapper, PortaDSPParams+Bridge)
by importing PortaDSPBridge.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T9m6a6N9BTYjq448t2XAza
Importing PortaDSPBridge surfaced a type clash: the file shadowed the
bridge's handle with a local `porta_dsp_handle = OpaquePointer` typealias,
but the bridge declares `typedef void* porta_dsp_handle` (imported as
UnsafeMutableRawPointer). Drop the shadowing typealias and type dspHandle
as PortaDSPBridge.porta_dsp_handle, matching PortaDSPWrapper.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T9m6a6N9BTYjq448t2XAza
`parameterTree` is an inherited AUAudioUnit property; assigning it before
super.init is a compile error on Apple platforms. Move the assignment to
just after the super.init call.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T9m6a6N9BTYjq448t2XAza
Two pre-existing DSP test failures:

- testProcessInterleavedIsDeterministicAcrossInstances: Hiss seeded its
  RNG from std::random_device in its constructor (never re-seeded in
  prepare), and WowFlutter seeded mRng from random_device too, so two
  instances with identical params produced different output. Re-seed both
  deterministically in prepare()/reset() so freshly-prepared instances are
  reproducible (both modules are intended to be deterministic).

- SaturationTests: `maxObservedTHD` was declared, never updated, then
  asserted > 0.1 — guaranteed to fail. Update it from each drive's THD.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T9m6a6N9BTYjq448t2XAza
- CI: swift-actions/setup-swift@v1 has been unreliable (rejected Ubuntu
  24.04, then flaked importing the Swift signing key). Run the Linux job
  inside the official swift:5.9 container instead, removing that action.

- PortaDSPAudioUnitRenderTests (macOS-only, never compiled): `.offline`
  is not a member of AudioUnitRenderActionFlags — use the offline render
  flag `.offlineUnitRenderAction_Render`. UnsafeMutableAudioBufferListPointer
  has no `deallocate()`; the list is malloc-backed, so free it with free().

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T9m6a6N9BTYjq448t2XAza
The AURenderPullInputBlock's `flags` parameter is a non-optional
UnsafeMutablePointer<AudioUnitRenderActionFlags>; the test used
`if let flags` / XCTAssertNotNil on it. Use flags.pointee directly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T9m6a6N9BTYjq448t2XAza
Per design intent, the module implementations are the source of truth;
these tests asserted naive models they deliberately exceed:

- Saturation is tanh shaped *with* a drive-dependent RMS-compensation
  trim, not raw tanh. Assert the tanh shape with a consistent trim scalar.
- Head bump ramps its biquad coefficients from unity over ~20 ms, so the
  first-sample response is ~unity (steady-state resonance is covered by
  HeadBumpTests). Assert the first sample is near unity.
- Wow/flutter is a deterministically-seeded modulated delay line (pitch,
  not amplitude, modulation). Assert repeated runs are identical and the
  output is finite and bounded.
- SaturationTests: the trim broadly maintains RMS but the downstream
  compander re-levels it, so check RMS stays within a band instead of
  strict per-step monotonicity.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T9m6a6N9BTYjq448t2XAza
- SaturationTests: output RMS actually increases with drive (the trim does
  not hold it constant); the only anomaly is a ~1.3% dip at 0 dB where the
  saturation stage bypasses. Assert RMS is non-decreasing within a 5%
  tolerance instead of a constant-band check.

- PortaDSPAudioUnitRenderTests: the unit renders the full tape chain, not
  pure tanh, and its wow/flutter delay line zeros short renders during
  warmup, so the old `applyTanh` expectation never matched. The unit also
  uses its own pull-input action flags rather than forwarding the offline
  flag. Validate that bypass returns input untouched (exercising the
  interleaved/planar copy paths) and that non-bypass output is finite.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T9m6a6N9BTYjq448t2XAza
- PortaDSPAudioUnit: host/automation edits made through the parameter tree
  never reached the cached params or the DSP, because the unit only observed
  changes via the asynchronous token(byAddingParameterObserver:). Set
  implementorValueObserver, the synchronous hook an AU is meant to use to
  store parameter values, so tree edits update the cache and DSP immediately.
  This fixes testParameterTreeUpdatesCachedParams.

- PortaDSPAudioUnitRenderTests: AUAudioUnit buses are non-interleaved, so the
  interleaved render tests configured an unsupported format and threw
  kAudioUnitErr_FormatNotSupported. Replace them with a test asserting the
  interleaved format is rejected, and run the remaining render tests (incl.
  the max-frames test) with planar buffers.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T9m6a6N9BTYjq448t2XAza
@johnnyclem
johnnyclem merged commit 1f7fc81 into main Jul 1, 2026
2 checks passed
@johnnyclem
johnnyclem deleted the claude/porta424-swiftui-replica-ozxind branch July 1, 2026 15:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants