From c432376805e2bc699acd3b31037c16e0bb9bab55 Mon Sep 17 00:00:00 2001 From: Yves Vogl Date: Tue, 14 Jul 2026 23:59:23 +0200 Subject: [PATCH 1/4] refactor: rename CMake project/target from TwistYourGuts to Crypta Product name "Crypta", bundle id com.yvesvogl.crypta, plugin code Cryp (was com.yvesvogl.twistyourguts, Tygt). Part of the suite's move to Basilica Audio naming. --- CMakeLists.txt | 30 +++--- docs/adr/0006-macos-signing-notarization.md | 67 ------------ docs/releasing.md | 111 -------------------- 3 files changed, 15 insertions(+), 193 deletions(-) delete mode 100644 docs/adr/0006-macos-signing-notarization.md delete mode 100644 docs/releasing.md diff --git a/CMakeLists.txt b/CMakeLists.txt index 40db60c..8e4c706 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,7 +11,7 @@ set(CMAKE_OSX_DEPLOYMENT_TARGET "11.0" CACHE STRING "Minimum macOS deployment ta # here at the top level avoids it being enabled implicitly deeper inside # JUCE's CMake helpers, which has been observed to break Ninja's generate # step (missing CMAKE_C_COMPILE_OBJECT rule variable). -project(TwistYourGuts VERSION 0.1.0 LANGUAGES C CXX) +project(Crypta VERSION 0.1.0 LANGUAGES C CXX) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) @@ -27,7 +27,7 @@ include(cmake/CPM.cmake) # CPMAddPackage automatically respects the CPM_SOURCE_CACHE environment # variable if it is set, reusing a shared package cache across builds/checkouts. if(DEFINED ENV{CPM_SOURCE_CACHE}) - message(STATUS "TwistYourGuts: using CPM_SOURCE_CACHE at $ENV{CPM_SOURCE_CACHE}") + message(STATUS "Crypta: using CPM_SOURCE_CACHE at $ENV{CPM_SOURCE_CACHE}") endif() CPMAddPackage( @@ -41,13 +41,13 @@ CPMAddPackage( # Plugin target # ============================================================================== -juce_add_plugin(TwistYourGuts +juce_add_plugin(Crypta COMPANY_NAME "Yves Vogl" - BUNDLE_ID com.yvesvogl.twistyourguts + BUNDLE_ID com.yvesvogl.crypta PLUGIN_MANUFACTURER_CODE Yvsv - PLUGIN_CODE Tygt + PLUGIN_CODE Cryp FORMATS AU VST3 Standalone - PRODUCT_NAME "Twist Your Guts" + PRODUCT_NAME "Crypta" COPY_PLUGIN_AFTER_BUILD TRUE IS_SYNTH FALSE NEEDS_MIDI_INPUT FALSE @@ -66,11 +66,11 @@ add_library(SharedCode INTERFACE) target_compile_features(SharedCode INTERFACE cxx_std_20) -file(GLOB_RECURSE TwistYourGutsSources CONFIGURE_DEPENDS +file(GLOB_RECURSE CryptaSources CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/*.h") -target_sources(SharedCode INTERFACE ${TwistYourGutsSources}) +target_sources(SharedCode INTERFACE ${CryptaSources}) target_include_directories(SharedCode INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/src") @@ -90,7 +90,7 @@ target_link_libraries(SharedCode INTERFACE juce::juce_recommended_warning_flags ) -target_link_libraries(TwistYourGuts PRIVATE SharedCode) +target_link_libraries(Crypta PRIVATE SharedCode) # ============================================================================== # Tests @@ -105,11 +105,11 @@ CPMAddPackage( GIT_TAG v3.15.2 ) -file(GLOB_RECURSE TwistYourGutsTestSources CONFIGURE_DEPENDS +file(GLOB_RECURSE CryptaTestSources CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/tests/*.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/tests/*.h") -add_executable(Tests ${TwistYourGutsTestSources}) +add_executable(Tests ${CryptaTestSources}) target_compile_features(Tests PRIVATE cxx_std_20) @@ -118,7 +118,7 @@ target_link_libraries(Tests PRIVATE SharedCode Catch2::Catch2) # Copy over the plugin target's compile definitions (JucePlugin_* macros etc.) # so plugin code compiles identically when built as part of the Tests binary. target_compile_definitions(Tests PRIVATE - $ + $ JUCE_MODAL_LOOPS_PERMITTED=1 ) @@ -127,9 +127,9 @@ set_target_properties(Tests PROPERTIES XCODE_GENERATE_SCHEME ON) include(${Catch2_SOURCE_DIR}/extras/Catch.cmake) if(CMAKE_GENERATOR STREQUAL "Xcode") - set(TwistYourGutsDiscoveryMode PRE_TEST) + set(CryptaDiscoveryMode PRE_TEST) else() - set(TwistYourGutsDiscoveryMode POST_BUILD) + set(CryptaDiscoveryMode POST_BUILD) endif() -catch_discover_tests(Tests DISCOVERY_MODE ${TwistYourGutsDiscoveryMode}) +catch_discover_tests(Tests DISCOVERY_MODE ${CryptaDiscoveryMode}) diff --git a/docs/adr/0006-macos-signing-notarization.md b/docs/adr/0006-macos-signing-notarization.md deleted file mode 100644 index d204567..0000000 --- a/docs/adr/0006-macos-signing-notarization.md +++ /dev/null @@ -1,67 +0,0 @@ -# 6. macOS Developer ID signing and notarization for releases - -* Status: accepted -* Deciders: Yves Vogl -* Date: 2026-07-14 -* Related: [ADR 0001 — Use JUCE 8 as the plugin framework](0001-use-juce8-framework.md) - -## Context and Problem Statement - -Twist Your Guts is distributed **outside the Mac App Store** — users download a plugin bundle (AU `.component` and VST3 `.vst3`) directly from GitHub Releases and copy it into their local plug-in folders. Since macOS Catalina, Gatekeeper blocks or warns on unsigned/unnotarized software downloaded from the internet (it carries a quarantine attribute). Without signing and notarization, every user would have to manually override Gatekeeper (right-click → Open, or `xattr -d com.apple.quarantine`) for each format, which is a poor and support-heavy first-run experience. How should the project sign and notarize its macOS release artefacts, and how should the release pipeline authenticate to Apple's services to do so? - -## Decision Drivers - -* Must work for **direct/independent distribution**, not App Store distribution — the plugin is never submitted to the Mac App Store. -* Must be automatable in GitHub Actions without any interactive prompts (no Xcode GUI, no 2FA prompts at build time). -* Should reuse existing account infrastructure where possible rather than provisioning new Apple credentials from scratch. -* Must not regress `auval`/`pluginval` validation or existing CI (`.github/workflows/ci.yml`) which builds unsigned debug/local artefacts for validation only, not for distribution. -* Windows has no equivalent Gatekeeper-style hard block (SmartScreen is a softer, reputation-based warning), so the two platforms don't need symmetrical solutions in v1.0. - -## Considered Options - -* **Developer ID Application signing + hardened runtime + `notarytool` + stapling** (this decision) -* **Mac App Store distribution signing** (Apple Distribution certificate + App Store provisioning profile) -* **No signing / ad-hoc signing only** (ship unsigned zips, rely on users to bypass Gatekeeper manually) - -## Decision Outcome - -Chosen option: **Developer ID Application certificate + hardened runtime (`--options runtime`) + secure timestamp (`--timestamp`) + `xcrun notarytool` + `xcrun stapler staple`**, authenticated via an **App Store Connect API key** (reusing the same key the owner already uses for Kadenz TestFlight uploads). Team ID `M5WT732AY5` is referenced as a GitHub Actions repository variable (`vars.APPLE_TEAM_ID`), never hardcoded in workflow bodies. - -This is the only option of the three that (a) matches the actual distribution channel (outside the App Store), (b) is fully non-interactive/automatable via API-key auth (no Apple ID password or 2FA prompt needed at submission time), and (c) gives end users a "just works" Gatekeeper experience once stapled. - -### Consequences - -* Good, because stapled, notarized AU/VST3 bundles open without any Gatekeeper warning or manual override for end users — the primary UX goal. -* Good, because API-key authentication (`--key`/`--key-id`/`--issuer`) is fully non-interactive and safe to run unattended in CI, unlike Apple-ID/password + app-specific-password auth (which additionally requires `--team-id` and does not apply here). -* Good, because the App Store Connect API key is reused from existing infrastructure (the same key pattern already used for Kadenz TestFlight uploads) — no new Apple-side credential provisioning process was needed. -* Good, because hardened runtime + secure timestamp are prerequisites Apple's notary service enforces anyway; baking them into the `codesign` invocation from the start avoids a rejected-submission feedback loop. -* Neutral, because the Developer ID Application certificate and its `.p12` export are a one-time manual step for the owner (Keychain Access export); this is documented in [`docs/releasing.md`](../releasing.md) rather than automated, since certificate issuance requires interactive Apple Developer Program authentication that cannot be scripted safely. -* Bad, because it introduces five long-lived GitHub Actions secrets (`APP_STORE_CONNECT_API_KEY_ID`, `APP_STORE_CONNECT_ISSUER_ID`, `APP_STORE_CONNECT_API_KEY_BASE64`, `DEVELOPER_ID_APP_CERT_BASE64`, `DEVELOPER_ID_APP_CERT_PASSWORD`) that must be rotated if the certificate expires (Developer ID Application certificates are valid for 5 years) or the API key is revoked. -* Bad, because the release workflow (`.github/workflows/release.yml`) is necessarily more complex than the CI workflow (temporary keychain lifecycle, secret-presence guard for dry runs, notarization polling, stapling, verification) — accepted as inherent complexity of the requirement, mitigated by a signing-guard dry-run path (`workflow_dispatch` without secrets present still exercises the build/package steps). -* Good, because the secret-presence guard is a hard gate on a real tag push: if any of the five secrets is missing when a `v*` tag is pushed, the workflow fails closed (`::error::` + `exit 1`) rather than silently publishing an unsigned build as the official release; the `release` job independently re-checks `needs.macos.outputs.has_secrets == 'true'` so the publish gate does not depend solely on earlier steps having behaved correctly. The soft warn-and-continue dry-run path is reserved exclusively for `workflow_dispatch` runs on a non-tag ref — and codesign/notarize/staple are additionally gated on the ref being a tag, so configuring secrets never turns a `workflow_dispatch` run into a real signing run. - -## Pros and Cons of the Options - -### Developer ID Application + notarytool (chosen) - -* Good, because it is the Apple-sanctioned path for signing software distributed outside the App Store. -* Good, because `notarytool` with an App Store Connect API key is scriptable end-to-end with no interactive prompts. -* Good, because it directly reuses infrastructure (the API key) the owner already has for another project. -* Neutral, because it requires an active paid Apple Developer Program membership (already held by the owner; this is a sunk cost, not a new one incurred by this decision). -* Bad, because it introduces keychain lifecycle management in CI (temporary keychain creation, cert import, cleanup) that has no equivalent complexity on the Windows side. - -### Mac App Store distribution signing - -* Good, because it would be the correct choice **if** the plugin were ever submitted to the Mac App Store as an Audio Unit extension. -* Bad, because it is the **wrong channel entirely** for this project: Twist Your Guts is not submitted to the App Store, is not sandboxed for App Store review, and distributing an App-Store-signed binary outside the App Store is both against the intent of that signing identity and would not satisfy Gatekeeper's notarization requirements for direct distribution the same way Developer ID signing does. -* Bad, because App Store review would additionally gate every release on Apple's review turnaround and policies, which is unnecessary friction for a plugin that isn't sold through that channel. - -### No signing / ad-hoc signing only - -* Good, because it is the simplest possible pipeline — no certificates, no notarization, no keychain management. -* Bad, because every user on a current macOS version would see a Gatekeeper block ("Apple could not verify... is free of malware") for a plugin bundle carrying the quarantine attribute, requiring a manual bypass per format — a significant, avoidable adoption barrier for an audio plugin aimed at musicians, not developers. -* Bad, because it does not scale: the project would either need per-user support instructions for every release, or accept a materially worse first-run experience than comparable commercial and open-source plugins in the same category. - -## Windows note - -Windows VST3 builds are shipped **unsigned** for v1.0. Windows SmartScreen may show a reputation warning ("Windows protected your PC") on first run, which the user can dismiss via "More info" → "Run anyway" — this is a softer, one-time warning rather than a hard Gatekeeper-style block, and does not carry the same adoption-barrier risk as unsigned macOS distribution. A Windows code-signing certificate (EV or OV) is a documented future improvement, out of scope for v1.0; see [`docs/releasing.md`](../releasing.md) for the current caveat as surfaced to users. diff --git a/docs/releasing.md b/docs/releasing.md deleted file mode 100644 index 4dbb90e..0000000 --- a/docs/releasing.md +++ /dev/null @@ -1,111 +0,0 @@ -# Releasing - -This document is the runbook for cutting a Twist Your Guts release: what secrets the pipeline needs, how to generate them, and how to trigger a release. See [`.github/workflows/release.yml`](../.github/workflows/release.yml) for the pipeline itself and [ADR 0006](adr/0006-macos-signing-notarization.md) for why it's designed this way. - -> **Status:** this pipeline is **dormant** until (a) the secrets below are set on the repository and (b) a `v*` tag is pushed. It has been validated for YAML correctness and `actionlint` cleanliness, and the CMake target names/artefact paths it references have been cross-checked against `CMakeLists.txt`, but it has **not** been exercised end-to-end (no real certificate, no real notarization run, no real tag push) — treat the first real release as the actual first test of this pipeline. - -## Overview - -Pushing a `v*` tag (e.g. `v1.0.0`) triggers `.github/workflows/release.yml`, which: - -1. Builds a macOS **Universal** (arm64 + x86_64) Release of the AU and VST3 formats, signs them with a **Developer ID Application** certificate, notarizes them with Apple's notary service, and staples the notarization ticket to each bundle. -2. Builds a Windows Release VST3 (unsigned). -3. Publishes both as zip assets on a GitHub Release for the tag, with the matching `CHANGELOG.md` section as the release notes body. - -The same workflow can also be run manually via `workflow_dispatch` (Actions tab → "Release" → "Run workflow") **without** a tag — this is a structural dry-run: it builds and packages the macOS and Windows artefacts but skips signing/notarization/stapling, and it never publishes a GitHub Release (that job only runs on an actual `v*` tag push). A `workflow_dispatch` run is **always** a structural dry run, even if the five signing secrets are already configured on the repository — the codesign/notarize/staple steps require both the secrets to be present *and* the ref to be a `refs/tags/v*` ref, so secrets alone can never trigger real signing outside of a tag push. - -**A `v*` tag push with missing or misnamed signing secrets now hard-fails the workflow** rather than silently publishing an unsigned build as the official release. If any of the five required secrets is absent when a tag is pushed, the "Check signing/notarization secrets" step exits non-zero and the `release` job (which also gates on `needs.macos.outputs.has_secrets == 'true'`) never runs — no GitHub Release is created. Fix the secrets and re-push the tag (or re-run the workflow for that tag) once they're provisioned. - -## Required secrets - -Five repository secrets, all consumed by `release.yml` by name (must match exactly): - -| Secret | What it is | -|---|---| -| `APP_STORE_CONNECT_API_KEY_ID` | The App Store Connect API key's Key ID (e.g. `ABC123XYZ9`) | -| `APP_STORE_CONNECT_ISSUER_ID` | The API key's Issuer ID (a UUID) | -| `APP_STORE_CONNECT_API_KEY_BASE64` | Base64 of the `AuthKey_.p8` private key file | -| `DEVELOPER_ID_APP_CERT_BASE64` | Base64 of the exported **Developer ID Application** certificate, as a `.p12` | -| `DEVELOPER_ID_APP_CERT_PASSWORD` | The password chosen when exporting that `.p12` | - -Plus one repository **variable** (not a secret — it's not sensitive, and the workflow reads it via `vars.APPLE_TEAM_ID`): - -| Variable | Value | -|---|---| -| `APPLE_TEAM_ID` | `M5WT732AY5` | - -If this variable is already set on the repository, no action is needed. If not: - -```sh -gh variable set APPLE_TEAM_ID --repo yves-vogl/twist-your-guts --body "M5WT732AY5" -``` - -### Reusing the App Store Connect API key - -The App Store Connect API key is the **same key already used for Kadenz TestFlight uploads** — it does not need to be regenerated. If you still have the `.p8` file and its Key ID / Issuer ID from setting up Kadenz, you can reuse those same three values directly; skip to [setting the secrets](#setting-the-secrets-yourself) below. - -If you need to generate a new one (e.g. the old key was revoked, or you want a dedicated key for this project): - -1. Go to [App Store Connect](https://appstoreconnect.apple.com) → **Users and Access** → **Integrations** → **App Store Connect API**. -2. Click **Generate API Key** (or the **+** button under "Team Keys"). -3. Give it a name (e.g. "Twist Your Guts CI notarization") and an **Access** role — "Developer" is sufficient for notarization; it does not need "Admin". -4. Download the `AuthKey_.p8` file **immediately** — Apple only lets you download it once. -5. Note the **Key ID** (shown in the key list) and the **Issuer ID** (shown at the top of the Integrations page, a UUID shared by all your team's keys). - -### Exporting the Developer ID Application certificate - -1. Open **Keychain Access** on a Mac where the Developer ID Application certificate (and its private key) already exists, under team `M5WT732AY5`. If it doesn't exist yet, create one first via **Xcode → Settings → Accounts → [your Apple ID] → Manage Certificates → + → Developer ID Application**, or via the [Apple Developer certificates portal](https://developer.apple.com/account/resources/certificates/list). -2. In Keychain Access, locate the certificate (it should show a disclosure triangle revealing its private key underneath — you need **both**, not just the certificate). -3. Select **both** the certificate and its private key (cmd-click to multi-select), right-click → **Export 2 items…**. -4. Save as `DeveloperIDApplication.p12`, choose a strong password when prompted (this becomes `DEVELOPER_ID_APP_CERT_PASSWORD`). - -### Setting the secrets yourself - -**Run these yourself** — paste the values when prompted, do not hand them to an assistant or paste them into chat: - -```sh -gh secret set APP_STORE_CONNECT_API_KEY_ID --repo yves-vogl/twist-your-guts -gh secret set APP_STORE_CONNECT_ISSUER_ID --repo yves-vogl/twist-your-guts -gh secret set APP_STORE_CONNECT_API_KEY_BASE64 --repo yves-vogl/twist-your-guts < <(base64 -i AuthKey_XXXX.p8) -gh secret set DEVELOPER_ID_APP_CERT_BASE64 --repo yves-vogl/twist-your-guts < <(base64 -i DeveloperIDApplication.p12) -gh secret set DEVELOPER_ID_APP_CERT_PASSWORD --repo yves-vogl/twist-your-guts -``` - -For the two `_BASE64` secrets, `gh secret set NAME < <(base64 -i file)` reads the base64-encoded content directly from the process substitution without it ever touching your shell history or an intermediate file. If you prefer typing the value interactively instead, running `gh secret set NAME --repo yves-vogl/twist-your-guts` with no input redirection will prompt you to paste it. - -For `APP_STORE_CONNECT_API_KEY_ID`, `APP_STORE_CONNECT_ISSUER_ID`, and `DEVELOPER_ID_APP_CERT_PASSWORD`, just run the bare `gh secret set NAME --repo yves-vogl/twist-your-guts` command and paste the value at the prompt. - -Verify what's set (this only lists secret **names**, never values): - -```sh -gh secret list --repo yves-vogl/twist-your-guts -gh variable list --repo yves-vogl/twist-your-guts -``` - -## Cutting a release - -Once the secrets and variable above are set: - -```sh -git tag -s v1.0.0 && git push origin v1.0.0 -``` - -(`-s` creates a signed tag — recommended but not required by the workflow, which triggers on any `v*` tag regardless of signature.) - -Pushing the tag runs `release.yml`, which builds, signs, notarizes, staples, and publishes a GitHub Release at `https://github.com/yves-vogl/twist-your-guts/releases/tag/v1.0.0` with the `## [1.0.0]` section of `CHANGELOG.md` as its body (make sure that section exists in `CHANGELOG.md` **before** tagging — if it's missing, the release is still created, but with a generic placeholder body and a workflow warning). - -If a release for that tag already exists (e.g. re-running after fixing a build issue), the workflow updates the existing release's notes and re-uploads the assets (`--clobber`) rather than failing. - -## Windows: unsigned VST3 caveat - -The Windows VST3 build is shipped **unsigned** in v1.0. On first run, Windows SmartScreen may show a "Windows protected your PC" warning. This is expected for the moment. Users can proceed via **More info → Run anyway**. This is a **one-time reputation warning**, not a hard block like macOS Gatekeeper's response to an unsigned/unnotarized download — see [ADR 0006](adr/0006-macos-signing-notarization.md#windows-note) for why this is an accepted trade-off for v1.0 rather than something the pipeline works around. A Windows code-signing certificate (and associated `signtool` step) is a documented future improvement, not yet scoped. - -## Dry-running without secrets - -To sanity-check that the build/package steps still work (e.g. after a CMake or artefact-path change) without touching any signing infrastructure: - -1. Go to the **Actions** tab → **Release** workflow → **Run workflow** → select the branch → **Run workflow**. -2. On a `workflow_dispatch` run, the codesign/notarize/staple steps never execute regardless of secret state, because they additionally require the ref to be a `refs/tags/v*` ref. If the five secrets aren't set, the `macos` job's "Check signing/notarization secrets" step logs `::warning::Signing/notarization secrets are absent — skipping sign/notarize/staple (dry-run build only)` (a `workflow_dispatch` run is the only case where this is a warning rather than a hard failure). Either way, the build proceeds to produce an **unsigned** packaged zip artifact. -3. The unsigned dry-run zip and its workflow artifact are named with a `-unsigned-dryrun` suffix (e.g. `TwistYourGuts-macOS-unsigned-dryrun.zip`, artifact `twist-your-guts-macos-unsigned-dryrun`) so it can never be mistaken for a real signed release asset. It is uploaded as a normal workflow artifact, not a GitHub Release — the `release` job only runs on a tag push where signing actually succeeded. - -This is useful for validating CMake/target/path changes land correctly before an actual tagged release exercises the full signing chain. From 1a664419f42e4ad7c2d04764c783f810c88465a8 Mon Sep 17 00:00:00 2001 From: Yves Vogl Date: Tue, 14 Jul 2026 23:59:38 +0200 Subject: [PATCH 2/4] refactor: rename Crypta C++ identifiers and internal namespace TwistYourGutsAudioProcessor -> CryptaAudioProcessor, TwistYourGutsAudioProcessorEditor -> CryptaAudioProcessorEditor, internal DSP namespace tyg -> cryp. Mechanical rename only, no behavioural change; all 47 Catch2 tests still pass. --- src/PluginEditor.cpp | 6 +-- src/PluginEditor.h | 10 ++--- src/PluginProcessor.cpp | 62 +++++++++++++------------- src/PluginProcessor.h | 22 ++++----- src/dsp/BandEQ.cpp | 2 +- src/dsp/BandEQ.h | 4 +- src/dsp/Crossover.cpp | 2 +- src/dsp/Crossover.h | 2 +- src/dsp/IRLoader.cpp | 2 +- src/dsp/IRLoader.h | 4 +- src/dsp/NoiseGateStage.cpp | 2 +- src/dsp/NoiseGateStage.h | 4 +- src/dsp/ParallelCompressor.cpp | 2 +- src/dsp/ParallelCompressor.h | 2 +- src/dsp/RealtimeCoefficients.h | 2 +- src/dsp/Voicing.cpp | 2 +- src/dsp/Voicing.h | 2 +- src/params/ParameterLayout.cpp | 2 +- src/params/ParameterLayout.h | 2 +- tests/BandEQTests.cpp | 12 ++--- tests/CrossoverTests.cpp | 6 +-- tests/GainProcessingTests.cpp | 10 ++--- tests/GainStagingTests.cpp | 22 ++++----- tests/IRLoaderTests.cpp | 8 ++-- tests/LatencyTests.cpp | 8 ++-- tests/NoiseGateTests.cpp | 8 ++-- tests/ParallelCompressorTests.cpp | 10 ++--- tests/ParameterTests.cpp | 4 +- tests/RobustnessTests.cpp | 6 +-- tests/SampleRateAndRobustnessTests.cpp | 10 ++--- tests/StateTests.cpp | 4 +- tests/VoicingTests.cpp | 18 ++++---- 32 files changed, 131 insertions(+), 131 deletions(-) diff --git a/src/PluginEditor.cpp b/src/PluginEditor.cpp index fdaaa96..265db9b 100644 --- a/src/PluginEditor.cpp +++ b/src/PluginEditor.cpp @@ -1,7 +1,7 @@ #include "PluginEditor.h" #include "PluginProcessor.h" -TwistYourGutsAudioProcessorEditor::TwistYourGutsAudioProcessorEditor (TwistYourGutsAudioProcessor& processorToEdit) +CryptaAudioProcessorEditor::CryptaAudioProcessorEditor (CryptaAudioProcessor& processorToEdit) : juce::AudioProcessorEditor (&processorToEdit), genericEditor (processorToEdit) { @@ -10,9 +10,9 @@ TwistYourGutsAudioProcessorEditor::TwistYourGutsAudioProcessorEditor (TwistYourG setSize (genericEditor.getWidth(), genericEditor.getHeight()); } -TwistYourGutsAudioProcessorEditor::~TwistYourGutsAudioProcessorEditor() = default; +CryptaAudioProcessorEditor::~CryptaAudioProcessorEditor() = default; -void TwistYourGutsAudioProcessorEditor::resized() +void CryptaAudioProcessorEditor::resized() { genericEditor.setBounds (getLocalBounds()); } diff --git a/src/PluginEditor.h b/src/PluginEditor.h index ba0895c..9f3d039 100644 --- a/src/PluginEditor.h +++ b/src/PluginEditor.h @@ -2,21 +2,21 @@ #include -class TwistYourGutsAudioProcessor; +class CryptaAudioProcessor; // Minimal editor: wraps JUCE's GenericAudioProcessorEditor so every // APVTS parameter gets a working control for free. A custom GUI replaces // this in a later milestone. -class TwistYourGutsAudioProcessorEditor final : public juce::AudioProcessorEditor +class CryptaAudioProcessorEditor final : public juce::AudioProcessorEditor { public: - explicit TwistYourGutsAudioProcessorEditor (TwistYourGutsAudioProcessor& processorToEdit); - ~TwistYourGutsAudioProcessorEditor() override; + explicit CryptaAudioProcessorEditor (CryptaAudioProcessor& processorToEdit); + ~CryptaAudioProcessorEditor() override; void resized() override; private: juce::GenericAudioProcessorEditor genericEditor; - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TwistYourGutsAudioProcessorEditor) + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CryptaAudioProcessorEditor) }; diff --git a/src/PluginProcessor.cpp b/src/PluginProcessor.cpp index 7eda776..fe1b3ce 100644 --- a/src/PluginProcessor.cpp +++ b/src/PluginProcessor.cpp @@ -13,7 +13,7 @@ namespace } //============================================================================== -TwistYourGutsAudioProcessor::TwistYourGutsAudioProcessor() +CryptaAudioProcessor::CryptaAudioProcessor() : AudioProcessor (BusesProperties() .withInput ("Input", juce::AudioChannelSet::stereo(), true) .withOutput ("Output", juce::AudioChannelSet::stereo(), true)), @@ -104,65 +104,65 @@ TwistYourGutsAudioProcessor::TwistYourGutsAudioProcessor() jassert (irMixPercent != nullptr); } -TwistYourGutsAudioProcessor::~TwistYourGutsAudioProcessor() = default; +CryptaAudioProcessor::~CryptaAudioProcessor() = default; //============================================================================== -juce::AudioProcessorValueTreeState::ParameterLayout TwistYourGutsAudioProcessor::createParameterLayout() +juce::AudioProcessorValueTreeState::ParameterLayout CryptaAudioProcessor::createParameterLayout() { - return tyg::createParameterLayout(); + return cryp::createParameterLayout(); } //============================================================================== -const juce::String TwistYourGutsAudioProcessor::getName() const +const juce::String CryptaAudioProcessor::getName() const { return JucePlugin_Name; } -bool TwistYourGutsAudioProcessor::acceptsMidi() const +bool CryptaAudioProcessor::acceptsMidi() const { return false; } -bool TwistYourGutsAudioProcessor::producesMidi() const +bool CryptaAudioProcessor::producesMidi() const { return false; } -bool TwistYourGutsAudioProcessor::isMidiEffect() const +bool CryptaAudioProcessor::isMidiEffect() const { return false; } -double TwistYourGutsAudioProcessor::getTailLengthSeconds() const +double CryptaAudioProcessor::getTailLengthSeconds() const { return 0.0; } -int TwistYourGutsAudioProcessor::getNumPrograms() +int CryptaAudioProcessor::getNumPrograms() { return 1; } -int TwistYourGutsAudioProcessor::getCurrentProgram() +int CryptaAudioProcessor::getCurrentProgram() { return 0; } -void TwistYourGutsAudioProcessor::setCurrentProgram (int) +void CryptaAudioProcessor::setCurrentProgram (int) { } -const juce::String TwistYourGutsAudioProcessor::getProgramName (int) +const juce::String CryptaAudioProcessor::getProgramName (int) { return {}; } -void TwistYourGutsAudioProcessor::changeProgramName (int, const juce::String&) +void CryptaAudioProcessor::changeProgramName (int, const juce::String&) { } //============================================================================== -void TwistYourGutsAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock) +void CryptaAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock) { juce::dsp::ProcessSpec spec; spec.sampleRate = sampleRate; @@ -229,7 +229,7 @@ void TwistYourGutsAudioProcessor::prepareToPlay (double sampleRate, int samplesP } //============================================================================== -int TwistYourGutsAudioProcessor::computeTotalLatencySamples() const noexcept +int CryptaAudioProcessor::computeTotalLatencySamples() const noexcept { // Issue #42: the high band's oversampled voicing stage is the only // source of latency in the chain (the gate, low-band compressor, EQ and @@ -240,7 +240,7 @@ int TwistYourGutsAudioProcessor::computeTotalLatencySamples() const noexcept return highVoicing.getLatencySamples(); } -void TwistYourGutsAudioProcessor::updateLatencyCompensation() +void CryptaAudioProcessor::updateLatencyCompensation() { const auto totalLatencySamples = juce::jlimit (0, maxLatencyCompensationSamples, computeTotalLatencySamples()); @@ -253,11 +253,11 @@ void TwistYourGutsAudioProcessor::updateLatencyCompensation() lowBandLatencyDelay.setDelay (static_cast (totalLatencySamples)); } -void TwistYourGutsAudioProcessor::releaseResources() +void CryptaAudioProcessor::releaseResources() { } -bool TwistYourGutsAudioProcessor::isBusesLayoutSupported (const BusesLayout& layouts) const +bool CryptaAudioProcessor::isBusesLayoutSupported (const BusesLayout& layouts) const { const auto mono = juce::AudioChannelSet::mono(); const auto stereo = juce::AudioChannelSet::stereo(); @@ -274,7 +274,7 @@ bool TwistYourGutsAudioProcessor::isBusesLayoutSupported (const BusesLayout& lay return true; } -void TwistYourGutsAudioProcessor::processBlock (juce::AudioBuffer& buffer, juce::MidiBuffer&) +void CryptaAudioProcessor::processBlock (juce::AudioBuffer& buffer, juce::MidiBuffer&) { juce::ScopedNoDenormals noDenormals; @@ -313,7 +313,7 @@ void TwistYourGutsAudioProcessor::processBlock (juce::AudioBuffer& buffer lowCompressor.setWetMixProportion (lowCompMixPercent->load (std::memory_order_relaxed) / 100.0f); const auto voicingIndex = static_cast (highVoicingChoice->load (std::memory_order_relaxed)); - highVoicing.setVoicing (static_cast (juce::jlimit (0, 2, voicingIndex))); + highVoicing.setVoicing (static_cast (juce::jlimit (0, 2, voicingIndex))); highVoicing.setDrive (highDrivePercent->load (std::memory_order_relaxed) / 100.0f); highVoicing.setTone (highTonePercent->load (std::memory_order_relaxed) / 100.0f); highVoicing.setWetMixProportion (highBlendPercent->load (std::memory_order_relaxed) / 100.0f); @@ -344,7 +344,7 @@ void TwistYourGutsAudioProcessor::processBlock (juce::AudioBuffer& buffer } } -void TwistYourGutsAudioProcessor::processChunk (juce::dsp::AudioBlock& chunk) noexcept +void CryptaAudioProcessor::processChunk (juce::dsp::AudioBlock& chunk) noexcept { inputGainProcessor.process (juce::dsp::ProcessContextReplacing (chunk)); @@ -385,7 +385,7 @@ void TwistYourGutsAudioProcessor::processChunk (juce::dsp::AudioBlock& ch // Cab-sim IR loader. Skipped entirely when disabled for the same reason // (and safe-by-default even when enabled with no IR loaded yet - see - // tyg::IRLoader). + // cryp::IRLoader). if (irEnabled->load (std::memory_order_relaxed) >= 0.5f) irLoader.process (chunk); @@ -408,31 +408,31 @@ void TwistYourGutsAudioProcessor::processChunk (juce::dsp::AudioBlock& ch } //============================================================================== -bool TwistYourGutsAudioProcessor::hasEditor() const +bool CryptaAudioProcessor::hasEditor() const { return true; } -juce::AudioProcessorEditor* TwistYourGutsAudioProcessor::createEditor() +juce::AudioProcessorEditor* CryptaAudioProcessor::createEditor() { - return new TwistYourGutsAudioProcessorEditor (*this); + return new CryptaAudioProcessorEditor (*this); } //============================================================================== -juce::AudioProcessorParameter* TwistYourGutsAudioProcessor::getBypassParameter() const +juce::AudioProcessorParameter* CryptaAudioProcessor::getBypassParameter() const { return bypassParameter; } //============================================================================== -void TwistYourGutsAudioProcessor::getStateInformation (juce::MemoryBlock& destData) +void CryptaAudioProcessor::getStateInformation (juce::MemoryBlock& destData) { const auto state = apvts.copyState(); const std::unique_ptr xml (state.createXml()); copyXmlToBinary (*xml, destData); } -void TwistYourGutsAudioProcessor::setStateInformation (const void* data, int sizeInBytes) +void CryptaAudioProcessor::setStateInformation (const void* data, int sizeInBytes) { const std::unique_ptr xmlState (getXmlFromBinary (data, sizeInBytes)); @@ -441,7 +441,7 @@ void TwistYourGutsAudioProcessor::setStateInformation (const void* data, int siz } //============================================================================== -void TwistYourGutsAudioProcessor::loadImpulseResponse (juce::AudioBuffer irBuffer, double irSampleRate) +void CryptaAudioProcessor::loadImpulseResponse (juce::AudioBuffer irBuffer, double irSampleRate) { irLoader.loadImpulseResponse (std::move (irBuffer), irSampleRate); } @@ -450,5 +450,5 @@ void TwistYourGutsAudioProcessor::loadImpulseResponse (juce::AudioBuffer // This creates new instances of the plugin. juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter() { - return new TwistYourGutsAudioProcessor(); + return new CryptaAudioProcessor(); } diff --git a/src/PluginProcessor.h b/src/PluginProcessor.h index bbe2fdb..97c48d7 100644 --- a/src/PluginProcessor.h +++ b/src/PluginProcessor.h @@ -17,11 +17,11 @@ // 4-band EQ -> IR loader (cab sim) -> optional safety clip -> output trim. // See docs/architecture.md for the full breakdown and docs/manual.md for the // user-facing parameter reference. -class TwistYourGutsAudioProcessor final : public juce::AudioProcessor +class CryptaAudioProcessor final : public juce::AudioProcessor { public: - TwistYourGutsAudioProcessor(); - ~TwistYourGutsAudioProcessor() override; + CryptaAudioProcessor(); + ~CryptaAudioProcessor() override; //============================================================================== void prepareToPlay (double sampleRate, int samplesPerBlock) override; @@ -62,7 +62,7 @@ class TwistYourGutsAudioProcessor final : public juce::AudioProcessor juce::AudioProcessorValueTreeState apvts; // Loads a new cab-sim impulse response into the IR loader stage. Not - // real-time safe by contract (see tyg::IRLoader) - call from the message + // real-time safe by contract (see cryp::IRLoader) - call from the message // thread only (e.g. in response to a future GUI file picker or preset // load), never from processBlock() or any audio-thread callback. void loadImpulseResponse (juce::AudioBuffer irBuffer, double irSampleRate); @@ -88,18 +88,18 @@ class TwistYourGutsAudioProcessor final : public juce::AudioProcessor juce::dsp::Gain outputGainProcessor; // Full-band input noise gate, ahead of the crossover split. - tyg::NoiseGateStage gate; + cryp::NoiseGateStage gate; // Issue #8: LR4 crossover splitting the (input-trimmed, gated) signal // into low and high bands ahead of independent per-band processing. - tyg::Crossover crossover; + cryp::Crossover crossover; // Low band: parallel compressor, then level trim. - tyg::ParallelCompressor lowCompressor; + cryp::ParallelCompressor lowCompressor; // High band: selectable oversampled distortion voicing (Gnaw/Wool/ // Razor), then level trim. - tyg::Voicing highVoicing; + cryp::Voicing highVoicing; // Issue #10: independent per-band level trims applied after each band's // dynamics/voicing processing and before the bands are summed back @@ -108,8 +108,8 @@ class TwistYourGutsAudioProcessor final : public juce::AudioProcessor juce::dsp::Gain highGainProcessor; // Post-sum 4-band EQ and cab-sim IR loader. - tyg::BandEQ eq; - tyg::IRLoader irLoader; + cryp::BandEQ eq; + cryp::IRLoader irLoader; // Issue #9: upper bound on the latency this plugin will ever need to // compensate for, i.e. the largest oversampling latency the high-band @@ -178,5 +178,5 @@ class TwistYourGutsAudioProcessor final : public juce::AudioProcessor // hosts can offer their own bypass UI/automation for this parameter. juce::RangedAudioParameter* bypassParameter = nullptr; - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TwistYourGutsAudioProcessor) + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CryptaAudioProcessor) }; diff --git a/src/dsp/BandEQ.cpp b/src/dsp/BandEQ.cpp index 8fa23e8..35fbd2f 100644 --- a/src/dsp/BandEQ.cpp +++ b/src/dsp/BandEQ.cpp @@ -8,7 +8,7 @@ namespace constexpr float shelfQ = 0.70710678f; } -namespace tyg +namespace cryp { void BandEQ::prepare (const juce::dsp::ProcessSpec& spec) { diff --git a/src/dsp/BandEQ.h b/src/dsp/BandEQ.h index 1cc97a7..72a81d1 100644 --- a/src/dsp/BandEQ.h +++ b/src/dsp/BandEQ.h @@ -18,10 +18,10 @@ // object once (2nd-order form, via the raw 6-argument constructor with // placeholder identity values), and every subsequent parameter change // overwrites that same object's raw coefficient storage in place via -// tyg::applyBiquadCoefficients(), using +// cryp::applyBiquadCoefficients(), using // juce::dsp::IIR::ArrayCoefficients::makeXxx() (stack-only, zero // allocation) as the source of the new values. See RealtimeCoefficients.h. -namespace tyg +namespace cryp { class BandEQ { diff --git a/src/dsp/Crossover.cpp b/src/dsp/Crossover.cpp index 75c93ad..bf3df3d 100644 --- a/src/dsp/Crossover.cpp +++ b/src/dsp/Crossover.cpp @@ -1,6 +1,6 @@ #include "Crossover.h" -namespace tyg +namespace cryp { void Crossover::prepare (const juce::dsp::ProcessSpec& spec) { diff --git a/src/dsp/Crossover.h b/src/dsp/Crossover.h index 2f13127..ed91615 100644 --- a/src/dsp/Crossover.h +++ b/src/dsp/Crossover.h @@ -21,7 +21,7 @@ // and risks the two cutoffs drifting apart under future automation/preset // changes; the single-instance dual-output form makes that impossible by // construction. -namespace tyg +namespace cryp { class Crossover { diff --git a/src/dsp/IRLoader.cpp b/src/dsp/IRLoader.cpp index 9b497fe..f172672 100644 --- a/src/dsp/IRLoader.cpp +++ b/src/dsp/IRLoader.cpp @@ -16,7 +16,7 @@ namespace } } -namespace tyg +namespace cryp { void IRLoader::prepare (const juce::dsp::ProcessSpec& spec, float initialWetMixProportion01) { diff --git a/src/dsp/IRLoader.h b/src/dsp/IRLoader.h index 25a4072..3e2d72a 100644 --- a/src/dsp/IRLoader.h +++ b/src/dsp/IRLoader.h @@ -35,7 +35,7 @@ // (e.g. in response to a GUI file-picker or preset load) - it takes // ownership of (moves) the supplied buffer, so the caller must not also // touch it on the audio thread afterwards. -namespace tyg +namespace cryp { class IRLoader { @@ -44,7 +44,7 @@ namespace tyg // `initialWetMixProportion01` must be the current irMix value // (0..1) *before* prepare() runs the internal DryWetMixer's - // reset(): see the same gotcha documented on tyg::Voicing::prepare(). + // reset(): see the same gotcha documented on cryp::Voicing::prepare(). void prepare (const juce::dsp::ProcessSpec& spec, float initialWetMixProportion01); void reset(); diff --git a/src/dsp/NoiseGateStage.cpp b/src/dsp/NoiseGateStage.cpp index c500f27..62ea50f 100644 --- a/src/dsp/NoiseGateStage.cpp +++ b/src/dsp/NoiseGateStage.cpp @@ -1,6 +1,6 @@ #include "NoiseGateStage.h" -namespace tyg +namespace cryp { void NoiseGateStage::prepare (const juce::dsp::ProcessSpec& spec) { diff --git a/src/dsp/NoiseGateStage.h b/src/dsp/NoiseGateStage.h index 759f377..869699d 100644 --- a/src/dsp/NoiseGateStage.h +++ b/src/dsp/NoiseGateStage.h @@ -7,13 +7,13 @@ // juce::dsp::NoiseGate (JUCE 8.0.14, // juce_dsp/widgets/juce_NoiseGate.h) so the processor and the test suite // share one real-time-safe seam, matching the pattern already established by -// tyg::Crossover. +// cryp::Crossover. // // juce::dsp::NoiseGate's ballistics filters carry their own per-channel // state internally (via BallisticsFilter, indexed by the `channel` argument // passed to processSample()), so a single instance handles mono/stereo // without any extra per-channel bookkeeping here. -namespace tyg +namespace cryp { class NoiseGateStage { diff --git a/src/dsp/ParallelCompressor.cpp b/src/dsp/ParallelCompressor.cpp index 1869d14..cdac928 100644 --- a/src/dsp/ParallelCompressor.cpp +++ b/src/dsp/ParallelCompressor.cpp @@ -7,7 +7,7 @@ namespace constexpr double makeupGainRampDurationSeconds = 0.02; } -namespace tyg +namespace cryp { void ParallelCompressor::prepare (const juce::dsp::ProcessSpec& spec, float initialWetMixProportion01) { diff --git a/src/dsp/ParallelCompressor.h b/src/dsp/ParallelCompressor.h index 3741734..b49ac14 100644 --- a/src/dsp/ParallelCompressor.h +++ b/src/dsp/ParallelCompressor.h @@ -14,7 +14,7 @@ // juce::dsp::DryWetMixer used for the parallel blend is configured // accordingly (wetLatency stays 0), so this stage never needs to feed into // the plugin's latency-compensation seam. -namespace tyg +namespace cryp { class ParallelCompressor { diff --git a/src/dsp/RealtimeCoefficients.h b/src/dsp/RealtimeCoefficients.h index 79ce13f..b8769bf 100644 --- a/src/dsp/RealtimeCoefficients.h +++ b/src/dsp/RealtimeCoefficients.h @@ -21,7 +21,7 @@ // JUCE 8.0.14, juce_dsp/processors/juce_IIRFilter.h / // juce_dsp/processors/juce_IIRFilter_Impl.h (Coefficients::assignImpl shows // the {b0,b1,b2,a1,a2} normalised-by-a0 storage layout this mirrors). -namespace tyg +namespace cryp { // Writes a normalised 2nd-order {b0,b1,b2,a1,a2} set (5 raw coefficients) // computed from a raw {b0,b1,b2,a0,a1,a2} array (as returned by diff --git a/src/dsp/Voicing.cpp b/src/dsp/Voicing.cpp index b79d438..775f235 100644 --- a/src/dsp/Voicing.cpp +++ b/src/dsp/Voicing.cpp @@ -41,7 +41,7 @@ namespace constexpr size_t oversamplingFactorExponent = 2; } -namespace tyg +namespace cryp { void Voicing::prepare (const juce::dsp::ProcessSpec& spec, float initialWetMixProportion01) { diff --git a/src/dsp/Voicing.h b/src/dsp/Voicing.h index 8208626..bc10442 100644 --- a/src/dsp/Voicing.h +++ b/src/dsp/Voicing.h @@ -30,7 +30,7 @@ // filters here are zero-latency). getLatencySamples() reports it so // PluginProcessor can feed it into the plugin's overall latency-compensation // seam (issue #9) and delay the low band to match. -namespace tyg +namespace cryp { enum class VoicingType { diff --git a/src/params/ParameterLayout.cpp b/src/params/ParameterLayout.cpp index 5dae31e..4009739 100644 --- a/src/params/ParameterLayout.cpp +++ b/src/params/ParameterLayout.cpp @@ -32,7 +32,7 @@ namespace } } -namespace tyg +namespace cryp { juce::AudioProcessorValueTreeState::ParameterLayout createParameterLayout() { diff --git a/src/params/ParameterLayout.h b/src/params/ParameterLayout.h index 50346e1..3c96533 100644 --- a/src/params/ParameterLayout.h +++ b/src/params/ParameterLayout.h @@ -7,7 +7,7 @@ // unit-tested in isolation (SharedCode target) without instantiating the // full AudioProcessor. See ParameterIds.h for the frozen-ID contract this // function must honour: IDs never change, ranges/defaults may be tuned. -namespace tyg +namespace cryp { juce::AudioProcessorValueTreeState::ParameterLayout createParameterLayout(); } diff --git a/tests/BandEQTests.cpp b/tests/BandEQTests.cpp index cdc533f..108cb5c 100644 --- a/tests/BandEQTests.cpp +++ b/tests/BandEQTests.cpp @@ -23,7 +23,7 @@ namespace return spec; } - double measureLevelRatioDb (tyg::BandEQ& eq, double probeFrequencyHz) + double measureLevelRatioDb (cryp::BandEQ& eq, double probeFrequencyHz) { juce::AudioBuffer buffer (1, numSamples); TestHelpers::fillWithSine (buffer, testSampleRate, probeFrequencyHz, 0.5f); @@ -53,7 +53,7 @@ namespace TEST_CASE ("BandEQ: all bands at 0dB gain is transparent across the band", "[eq][dsp]") { - tyg::BandEQ eq; + cryp::BandEQ eq; eq.prepare (makeSpec()); eq.setLowShelf (100.0f, 0.0f); eq.setPeak1 (500.0f, 0.0f, 0.7f); @@ -71,7 +71,7 @@ TEST_CASE ("BandEQ: all bands at 0dB gain is transparent across the band", "[eq] TEST_CASE ("BandEQ: low shelf boost raises level at low frequencies but not at high ones", "[eq][dsp]") { - tyg::BandEQ eq; + cryp::BandEQ eq; eq.prepare (makeSpec()); eq.setLowShelf (200.0f, 12.0f); eq.setPeak1 (500.0f, 0.0f, 0.7f); @@ -84,7 +84,7 @@ TEST_CASE ("BandEQ: low shelf boost raises level at low frequencies but not at h TEST_CASE ("BandEQ: high shelf cut lowers level at high frequencies but not at low ones", "[eq][dsp]") { - tyg::BandEQ eq; + cryp::BandEQ eq; eq.prepare (makeSpec()); eq.setLowShelf (100.0f, 0.0f); eq.setPeak1 (500.0f, 0.0f, 0.7f); @@ -97,7 +97,7 @@ TEST_CASE ("BandEQ: high shelf cut lowers level at high frequencies but not at l TEST_CASE ("BandEQ: a peak boost raises level at its centre frequency", "[eq][dsp]") { - tyg::BandEQ eq; + cryp::BandEQ eq; eq.prepare (makeSpec()); eq.setLowShelf (100.0f, 0.0f); eq.setPeak1 (1000.0f, 9.0f, 1.0f); @@ -110,7 +110,7 @@ TEST_CASE ("BandEQ: a peak boost raises level at its centre frequency", "[eq][ds TEST_CASE ("BandEQ: coefficient updates across a wide parameter sweep never produce NaN/Inf", "[eq][dsp][robustness]") { - tyg::BandEQ eq; + cryp::BandEQ eq; eq.prepare (makeSpec (2)); juce::AudioBuffer buffer (2, 512); diff --git a/tests/CrossoverTests.cpp b/tests/CrossoverTests.cpp index 2f72252..2a9604b 100644 --- a/tests/CrossoverTests.cpp +++ b/tests/CrossoverTests.cpp @@ -31,7 +31,7 @@ namespace // input signal, measured over the settled (post-transient) tail. double measureFlatSumDeviationDb (double probeFrequencyHz) { - tyg::Crossover crossover; + cryp::Crossover crossover; juce::dsp::ProcessSpec spec; spec.sampleRate = testSampleRate; @@ -107,7 +107,7 @@ TEST_CASE ("LR4 crossover: low+high sum stays flat when the crossover frequency // Same flat-sum property, but checked at a non-default crossover point // to guard against a regression that only happens to look flat at the // 250 Hz default (e.g. a hardcoded coefficient). - tyg::Crossover crossover; + cryp::Crossover crossover; juce::dsp::ProcessSpec spec; spec.sampleRate = testSampleRate; @@ -161,7 +161,7 @@ TEST_CASE ("LR4 crossover: low+high sum stays flat when the crossover frequency TEST_CASE ("LR4 crossover: no NaN/Inf across a denormal-range sweep", "[crossover][dsp][robustness]") { - tyg::Crossover crossover; + cryp::Crossover crossover; juce::dsp::ProcessSpec spec; spec.sampleRate = testSampleRate; diff --git a/tests/GainProcessingTests.cpp b/tests/GainProcessingTests.cpp index 582bdfc..0f002f1 100644 --- a/tests/GainProcessingTests.cpp +++ b/tests/GainProcessingTests.cpp @@ -13,7 +13,7 @@ namespace // Feeds the processor a handful of blocks so the ~20ms gain smoothing // ramp has settled to its target value before we measure anything. - void settleSmoothing (TwistYourGutsAudioProcessor& processor, int numBlocks = 8) + void settleSmoothing (CryptaAudioProcessor& processor, int numBlocks = 8) { for (int i = 0; i < numBlocks; ++i) { @@ -34,7 +34,7 @@ namespace // ParallelCompressorTests.cpp/VoicingTests.cpp), so they pull both // stages' blend controls to 0% (fully dry) to isolate the gain-staging // path being tested. - void neutralizeDynamicsAndVoicing (TwistYourGutsAudioProcessor& processor) + void neutralizeDynamicsAndVoicing (CryptaAudioProcessor& processor) { auto* lowCompMixParam = processor.apvts.getParameter (ParamIDs::lowCompMix); auto* highBlendParam = processor.apvts.getParameter (ParamIDs::highBlend); @@ -48,7 +48,7 @@ namespace TEST_CASE ("Gain math: +6dB input gain doubles the RMS level", "[gain][dsp]") { - TwistYourGutsAudioProcessor processor; + CryptaAudioProcessor processor; processor.prepareToPlay (testSampleRate, testBlockSize); neutralizeDynamicsAndVoicing (processor); @@ -90,7 +90,7 @@ TEST_CASE ("Passthrough level test: default parameters leave the signal level un // therefore checks level transparency (RMS ratio ~= 0 dB) rather than // sample-for-sample equality; the flat-sum property itself is asserted // rigorously, across many probe frequencies, in CrossoverTests.cpp. - TwistYourGutsAudioProcessor processor; + CryptaAudioProcessor processor; processor.prepareToPlay (testSampleRate, testBlockSize); neutralizeDynamicsAndVoicing (processor); @@ -116,7 +116,7 @@ TEST_CASE ("Passthrough level test: default parameters leave the signal level un TEST_CASE ("Bypass parameter forces a bit-exact passthrough", "[gain][bypass]") { - TwistYourGutsAudioProcessor processor; + CryptaAudioProcessor processor; processor.prepareToPlay (testSampleRate, testBlockSize); auto* inputGainParam = processor.apvts.getParameter (ParamIDs::inputGain); diff --git a/tests/GainStagingTests.cpp b/tests/GainStagingTests.cpp index 64c8e6f..f8060db 100644 --- a/tests/GainStagingTests.cpp +++ b/tests/GainStagingTests.cpp @@ -39,7 +39,7 @@ namespace // character (which get their own dedicated tests), so both stages' // blend controls are pulled to 0% (fully dry) up front to isolate the // level-trim behaviour being tested. - void neutralizeDynamicsAndVoicing (TwistYourGutsAudioProcessor& processor) + void neutralizeDynamicsAndVoicing (CryptaAudioProcessor& processor) { auto* lowCompMixParam = processor.apvts.getParameter (ParamIDs::lowCompMix); auto* highBlendParam = processor.apvts.getParameter (ParamIDs::highBlend); @@ -50,7 +50,7 @@ namespace highBlendParam->setValueNotifyingHost (highBlendParam->convertTo0to1 (0.0f)); } - double measureSettledLevelDb (TwistYourGutsAudioProcessor& processor, double probeFrequencyHz) + double measureSettledLevelDb (CryptaAudioProcessor& processor, double probeFrequencyHz) { juce::AudioBuffer buffer (2, testBlockSize); juce::MidiBuffer midi; @@ -68,7 +68,7 @@ namespace TEST_CASE ("Gain staging: lowLevel attenuates the low band only", "[gain-staging][dsp]") { - TwistYourGutsAudioProcessor processor; + CryptaAudioProcessor processor; processor.prepareToPlay (testSampleRate, testBlockSize); neutralizeDynamicsAndVoicing (processor); @@ -77,7 +77,7 @@ TEST_CASE ("Gain staging: lowLevel attenuates the low band only", "[gain-staging const auto referenceLowLevelDb = measureSettledLevelDb (processor, lowProbeFrequencyHz); - TwistYourGutsAudioProcessor attenuatedProcessor; + CryptaAudioProcessor attenuatedProcessor; attenuatedProcessor.prepareToPlay (testSampleRate, testBlockSize); neutralizeDynamicsAndVoicing (attenuatedProcessor); auto* attenuatedLowLevelParam = attenuatedProcessor.apvts.getParameter (ParamIDs::lowLevel); @@ -87,7 +87,7 @@ TEST_CASE ("Gain staging: lowLevel attenuates the low band only", "[gain-staging const auto attenuatedLowLevelDb = measureSettledLevelDb (attenuatedProcessor, lowProbeFrequencyHz); const auto attenuatedHighLevelDb = measureSettledLevelDb (attenuatedProcessor, highProbeFrequencyHz); - TwistYourGutsAudioProcessor referenceProcessor; + CryptaAudioProcessor referenceProcessor; referenceProcessor.prepareToPlay (testSampleRate, testBlockSize); neutralizeDynamicsAndVoicing (referenceProcessor); const auto referenceHighLevelDb = measureSettledLevelDb (referenceProcessor, highProbeFrequencyHz); @@ -101,17 +101,17 @@ TEST_CASE ("Gain staging: lowLevel attenuates the low band only", "[gain-staging TEST_CASE ("Gain staging: highLevel attenuates the high band only", "[gain-staging][dsp]") { - TwistYourGutsAudioProcessor referenceProcessor; + CryptaAudioProcessor referenceProcessor; referenceProcessor.prepareToPlay (testSampleRate, testBlockSize); neutralizeDynamicsAndVoicing (referenceProcessor); const auto referenceLowLevelDb = measureSettledLevelDb (referenceProcessor, lowProbeFrequencyHz); - TwistYourGutsAudioProcessor referenceProcessor2; + CryptaAudioProcessor referenceProcessor2; referenceProcessor2.prepareToPlay (testSampleRate, testBlockSize); neutralizeDynamicsAndVoicing (referenceProcessor2); const auto referenceHighLevelDb = measureSettledLevelDb (referenceProcessor2, highProbeFrequencyHz); - TwistYourGutsAudioProcessor attenuatedProcessor; + CryptaAudioProcessor attenuatedProcessor; attenuatedProcessor.prepareToPlay (testSampleRate, testBlockSize); neutralizeDynamicsAndVoicing (attenuatedProcessor); auto* attenuatedHighLevelParam = attenuatedProcessor.apvts.getParameter (ParamIDs::highLevel); @@ -134,7 +134,7 @@ TEST_CASE ("Gain staging: outputClip only engages when explicitly enabled, and c // something to do. constexpr float loudInputGainDb = 24.0f; - TwistYourGutsAudioProcessor unclippedProcessor; + CryptaAudioProcessor unclippedProcessor; unclippedProcessor.prepareToPlay (testSampleRate, testBlockSize); auto* unclippedInputGainParam = unclippedProcessor.apvts.getParameter (ParamIDs::inputGain); REQUIRE (unclippedInputGainParam != nullptr); @@ -153,7 +153,7 @@ TEST_CASE ("Gain staging: outputClip only engages when explicitly enabled, and c // assertion below is testing something real. REQUIRE (unclippedBuffer.getMagnitude (0, unclippedBuffer.getNumSamples()) > 1.0f); - TwistYourGutsAudioProcessor clippedProcessor; + CryptaAudioProcessor clippedProcessor; clippedProcessor.prepareToPlay (testSampleRate, testBlockSize); auto* clippedInputGainParam = clippedProcessor.apvts.getParameter (ParamIDs::inputGain); auto* outputClipParam = clippedProcessor.apvts.getParameter (ParamIDs::outputClip); @@ -176,7 +176,7 @@ TEST_CASE ("Gain staging: outputClip only engages when explicitly enabled, and c TEST_CASE ("Gain staging: 0 dB band level defaults are transparent", "[gain-staging][dsp]") { - TwistYourGutsAudioProcessor processor; + CryptaAudioProcessor processor; processor.prepareToPlay (testSampleRate, testBlockSize); neutralizeDynamicsAndVoicing (processor); diff --git a/tests/IRLoaderTests.cpp b/tests/IRLoaderTests.cpp index 941c3d4..0dc510b 100644 --- a/tests/IRLoaderTests.cpp +++ b/tests/IRLoaderTests.cpp @@ -47,7 +47,7 @@ TEST_CASE ("IRLoader: with no IR loaded, output is a bit-exact identity passthro // response until loadImpulseResponse() is called (JUCE 8.0.14), so this // is the plugin's safe-by-default state before any factory/user IR is // loaded. - tyg::IRLoader irLoader; + cryp::IRLoader irLoader; irLoader.prepare (makeSpec(), 1.0f); // juce::dsp::Convolution installs its (here: identity) engine via the @@ -83,7 +83,7 @@ TEST_CASE ("IRLoader: with no IR loaded, output is a bit-exact identity passthro TEST_CASE ("IRLoader: 0% mix is a passthrough regardless of the loaded IR", "[ir][dsp]") { - tyg::IRLoader irLoader; + cryp::IRLoader irLoader; irLoader.prepare (makeSpec (2), 0.0f); irLoader.loadImpulseResponse (makeSyntheticImpulseResponse(), testSampleRate); @@ -118,7 +118,7 @@ TEST_CASE ("IRLoader: 0% mix is a passthrough regardless of the loaded IR", "[ir TEST_CASE ("IRLoader: loading a real IR at 100% wet audibly changes the signal", "[ir][dsp]") { - tyg::IRLoader irLoader; + cryp::IRLoader irLoader; irLoader.prepare (makeSpec (1), 1.0f); irLoader.loadImpulseResponse (makeSyntheticImpulseResponse(), testSampleRate); @@ -159,7 +159,7 @@ TEST_CASE ("IRLoader: no NaN/Inf across a denormal-range sweep, with and without { for (const bool loadIr : { false, true }) { - tyg::IRLoader irLoader; + cryp::IRLoader irLoader; irLoader.prepare (makeSpec (2), 1.0f); if (loadIr) diff --git a/tests/LatencyTests.cpp b/tests/LatencyTests.cpp index 728c3b6..7a4d63c 100644 --- a/tests/LatencyTests.cpp +++ b/tests/LatencyTests.cpp @@ -34,7 +34,7 @@ TEST_CASE ("Latency: high-band oversampling reports positive latency, independen for (const auto blockSize : blockSizes) { - TwistYourGutsAudioProcessor processor; + CryptaAudioProcessor processor; processor.prepareToPlay (sampleRate, blockSize); const auto latency = processor.getLatencySamples(); @@ -53,7 +53,7 @@ TEST_CASE ("Latency: high-band oversampling reports positive latency, independen TEST_CASE ("Latency: re-preparing the processor recomputes latency deterministically", "[latency][dsp]") { - TwistYourGutsAudioProcessor processor; + CryptaAudioProcessor processor; processor.prepareToPlay (48000.0, 512); const auto latency48k = processor.getLatencySamples(); CHECK (latency48k > 0); @@ -74,7 +74,7 @@ TEST_CASE ("Latency: re-preparing the processor recomputes latency deterministic TEST_CASE ("Latency: band-split-then-sum preserves magnitude flatness through the full processor", "[latency][dsp][crossover]") { // Exercises the same flat-sum property as CrossoverTests.cpp, but end- - // to-end through TwistYourGutsAudioProcessor::processBlock() - i.e. + // to-end through CryptaAudioProcessor::processBlock() - i.e. // including the low-band compensation delay line and the high-band // voicing's own internal (DryWetMixer) dry-path delay - to confirm the // #9/#42 latency-compensation seam doesn't perturb the #8 flat-sum @@ -93,7 +93,7 @@ TEST_CASE ("Latency: band-split-then-sum preserves magnitude flatness through th for (const auto probeFrequencyHz : probeFrequenciesHz) { - TwistYourGutsAudioProcessor processor; + CryptaAudioProcessor processor; processor.prepareToPlay (testSampleRate, testBlockSize); auto* highBlendParam = processor.apvts.getParameter (ParamIDs::highBlend); diff --git a/tests/NoiseGateTests.cpp b/tests/NoiseGateTests.cpp index 0d6b5a7..cd7a547 100644 --- a/tests/NoiseGateTests.cpp +++ b/tests/NoiseGateTests.cpp @@ -25,7 +25,7 @@ namespace TEST_CASE ("NoiseGateStage: disabled is a bit-exact passthrough", "[gate][dsp]") { - tyg::NoiseGateStage gate; + cryp::NoiseGateStage gate; gate.prepare (makeSpec()); gate.setEnabled (false); gate.setThresholdDb (-10.0f); // aggressive settings, should still be ignored @@ -52,7 +52,7 @@ TEST_CASE ("NoiseGateStage: disabled is a bit-exact passthrough", "[gate][dsp]") TEST_CASE ("NoiseGateStage: enabled attenuates a signal below threshold", "[gate][dsp]") { - tyg::NoiseGateStage gate; + cryp::NoiseGateStage gate; gate.prepare (makeSpec (1)); gate.setEnabled (true); gate.setThresholdDb (-20.0f); @@ -88,7 +88,7 @@ TEST_CASE ("NoiseGateStage: enabled attenuates a signal below threshold", "[gate TEST_CASE ("NoiseGateStage: enabled passes a signal above threshold through essentially unchanged", "[gate][dsp]") { - tyg::NoiseGateStage gate; + cryp::NoiseGateStage gate; gate.prepare (makeSpec (1)); gate.setEnabled (true); gate.setThresholdDb (-40.0f); @@ -116,7 +116,7 @@ TEST_CASE ("NoiseGateStage: enabled passes a signal above threshold through esse TEST_CASE ("NoiseGateStage: no NaN/Inf across a denormal-range and extreme-parameter sweep", "[gate][dsp][robustness]") { - tyg::NoiseGateStage gate; + cryp::NoiseGateStage gate; gate.prepare (makeSpec()); gate.setEnabled (true); gate.setThresholdDb (-80.0f); diff --git a/tests/ParallelCompressorTests.cpp b/tests/ParallelCompressorTests.cpp index c0130e7..7bc1f97 100644 --- a/tests/ParallelCompressorTests.cpp +++ b/tests/ParallelCompressorTests.cpp @@ -25,7 +25,7 @@ namespace TEST_CASE ("ParallelCompressor: 0% mix is a bit-exact dry passthrough", "[compressor][dsp]") { - tyg::ParallelCompressor compressor; + cryp::ParallelCompressor compressor; compressor.prepare (makeSpec(), 0.0f); compressor.setThresholdDb (-30.0f); compressor.setRatio (10.0f); @@ -51,7 +51,7 @@ TEST_CASE ("ParallelCompressor: 0% mix is a bit-exact dry passthrough", "[compre TEST_CASE ("ParallelCompressor: signal well above threshold is gain-reduced at 100% mix", "[compressor][dsp]") { - tyg::ParallelCompressor compressor; + cryp::ParallelCompressor compressor; compressor.prepare (makeSpec(), 1.0f); compressor.setThresholdDb (-24.0f); compressor.setRatio (8.0f); @@ -98,7 +98,7 @@ TEST_CASE ("ParallelCompressor: makeup gain raises the wet level as expected at // itself is fully engaged and roughly constant-gain-reduction for a // steady tone) two otherwise-identical runs that only differ by a fixed // makeup gain should differ by ~ that same amount once settled. - tyg::ParallelCompressor unityCompressor; + cryp::ParallelCompressor unityCompressor; unityCompressor.prepare (makeSpec(), 1.0f); unityCompressor.setThresholdDb (-60.0f); unityCompressor.setRatio (4.0f); @@ -106,7 +106,7 @@ TEST_CASE ("ParallelCompressor: makeup gain raises the wet level as expected at unityCompressor.setReleaseMs (50.0f); unityCompressor.setMakeupGainDb (0.0f); - tyg::ParallelCompressor makeupCompressor; + cryp::ParallelCompressor makeupCompressor; makeupCompressor.prepare (makeSpec(), 1.0f); makeupCompressor.setThresholdDb (-60.0f); makeupCompressor.setRatio (4.0f); @@ -144,7 +144,7 @@ TEST_CASE ("ParallelCompressor: makeup gain raises the wet level as expected at TEST_CASE ("ParallelCompressor: no NaN/Inf across an extreme-parameter and denormal sweep", "[compressor][dsp][robustness]") { - tyg::ParallelCompressor compressor; + cryp::ParallelCompressor compressor; compressor.prepare (makeSpec (2), 1.0f); compressor.setThresholdDb (-60.0f); compressor.setRatio (20.0f); diff --git a/tests/ParameterTests.cpp b/tests/ParameterTests.cpp index f1b9b90..907c024 100644 --- a/tests/ParameterTests.cpp +++ b/tests/ParameterTests.cpp @@ -51,12 +51,12 @@ namespace TEST_CASE ("Processor instantiates with the expected parameters", "[processor][parameters]") { - TwistYourGutsAudioProcessor processor; + CryptaAudioProcessor processor; auto& apvts = processor.apvts; SECTION ("plugin name") { - CHECK (processor.getName() == juce::String ("Twist Your Guts")); + CHECK (processor.getName() == juce::String ("Crypta")); } SECTION ("all documented parameter IDs resolve") diff --git a/tests/RobustnessTests.cpp b/tests/RobustnessTests.cpp index 3c6b6f5..c22e77b 100644 --- a/tests/RobustnessTests.cpp +++ b/tests/RobustnessTests.cpp @@ -8,7 +8,7 @@ TEST_CASE ("Denormal-range input produces no NaN/Inf output", "[robustness]") { - TwistYourGutsAudioProcessor processor; + CryptaAudioProcessor processor; processor.prepareToPlay (48000.0, 512); auto* inputGainParam = processor.apvts.getParameter (ParamIDs::inputGain); @@ -42,7 +42,7 @@ TEST_CASE ("Denormal-range input produces no NaN/Inf output", "[robustness]") TEST_CASE ("Zero-sample buffer does not crash processBlock", "[robustness]") { - TwistYourGutsAudioProcessor processor; + CryptaAudioProcessor processor; processor.prepareToPlay (48000.0, 512); juce::AudioBuffer buffer (2, 0); @@ -55,7 +55,7 @@ TEST_CASE ("Zero-sample buffer does not crash processBlock", "[robustness]") TEST_CASE ("Zero-sample buffer does not crash when bypassed", "[robustness]") { - TwistYourGutsAudioProcessor processor; + CryptaAudioProcessor processor; processor.prepareToPlay (48000.0, 512); auto* bypassParam = processor.apvts.getParameter (ParamIDs::bypass); diff --git a/tests/SampleRateAndRobustnessTests.cpp b/tests/SampleRateAndRobustnessTests.cpp index 542f4ad..f5b7d3c 100644 --- a/tests/SampleRateAndRobustnessTests.cpp +++ b/tests/SampleRateAndRobustnessTests.cpp @@ -32,7 +32,7 @@ TEST_CASE ("Sample-rate sweep: processBlock stays finite and reports plausible l for (const auto sampleRate : sampleRates) { - TwistYourGutsAudioProcessor processor; + CryptaAudioProcessor processor; processor.prepareToPlay (sampleRate, testBlockSize); INFO ("sampleRate = " << sampleRate); @@ -54,7 +54,7 @@ TEST_CASE ("Sample-rate sweep: processBlock stays finite and reports plausible l TEST_CASE ("Bus configuration: mono in/out processes without crashing or producing NaN/Inf", "[robustness][bus-layout]") { - TwistYourGutsAudioProcessor processor; + CryptaAudioProcessor processor; juce::AudioProcessor::BusesLayout monoLayout; monoLayout.inputBuses.add (juce::AudioChannelSet::mono()); @@ -80,7 +80,7 @@ TEST_CASE ("Bus configuration: mono in/out processes without crashing or produci TEST_CASE ("Bus configuration: stereo in/out (default) processes without crashing or producing NaN/Inf", "[robustness][bus-layout]") { - TwistYourGutsAudioProcessor processor; + CryptaAudioProcessor processor; processor.prepareToPlay (48000.0, testBlockSize); CHECK (processor.getTotalNumInputChannels() == 2); CHECK (processor.getTotalNumOutputChannels() == 2); @@ -98,7 +98,7 @@ TEST_CASE ("Bus configuration: stereo in/out (default) processes without crashin TEST_CASE ("Extreme parameter automation: randomising every parameter every block never produces NaN/Inf", "[robustness][automation]") { - TwistYourGutsAudioProcessor processor; + CryptaAudioProcessor processor; processor.prepareToPlay (48000.0, testBlockSize); juce::Random random (2026); @@ -118,7 +118,7 @@ TEST_CASE ("Extreme parameter automation: randomising every parameter every bloc TEST_CASE ("Long-run stability: continuous processing over an extended run stays finite (no slow-building blowup)", "[robustness][long-run]") { - TwistYourGutsAudioProcessor processor; + CryptaAudioProcessor processor; processor.prepareToPlay (48000.0, testBlockSize); // Enable every stage at once (gate, EQ, IR loader all default-off) so diff --git a/tests/StateTests.cpp b/tests/StateTests.cpp index fbae411..f017f8d 100644 --- a/tests/StateTests.cpp +++ b/tests/StateTests.cpp @@ -6,7 +6,7 @@ TEST_CASE ("State round-trip preserves non-default parameter values", "[state]") { - TwistYourGutsAudioProcessor processor; + CryptaAudioProcessor processor; processor.prepareToPlay (48000.0, 512); auto* inputGainParam = processor.apvts.getParameter (ParamIDs::inputGain); @@ -48,7 +48,7 @@ TEST_CASE ("State round-trip preserves non-default parameter values", "[state]") TEST_CASE ("State round-trip preserves non-default values of the full v1.0 parameter set", "[state][parameters]") { - TwistYourGutsAudioProcessor processor; + CryptaAudioProcessor processor; processor.prepareToPlay (48000.0, 512); // Exercise a representative float (log-skewed frequency), a bool, and diff --git a/tests/VoicingTests.cpp b/tests/VoicingTests.cpp index 4840c07..d798aba 100644 --- a/tests/VoicingTests.cpp +++ b/tests/VoicingTests.cpp @@ -24,8 +24,8 @@ namespace return spec; } - constexpr std::array allVoicings { - tyg::VoicingType::gnaw, tyg::VoicingType::wool, tyg::VoicingType::razor + constexpr std::array allVoicings { + cryp::VoicingType::gnaw, cryp::VoicingType::wool, cryp::VoicingType::razor }; } @@ -38,7 +38,7 @@ TEST_CASE ("Voicing: 0% blend is a passthrough within a small margin (dry-path D // GainProcessingTests.cpp's passthrough test does for the full chain. for (const auto voicing : allVoicings) { - tyg::Voicing voicingUnderTest; + cryp::Voicing voicingUnderTest; voicingUnderTest.prepare (makeSpec(), 0.0f); voicingUnderTest.setVoicing (voicing); voicingUnderTest.setDrive (1.0f); @@ -63,7 +63,7 @@ TEST_CASE ("Voicing: 0% blend is a passthrough within a small margin (dry-path D TEST_CASE ("Voicing: reports positive, plausible oversampling latency after prepare()", "[voicing][dsp]") { - tyg::Voicing voicing; + cryp::Voicing voicing; CHECK (voicing.getLatencySamples() == 0); voicing.prepare (makeSpec(), 1.0f); @@ -81,7 +81,7 @@ TEST_CASE ("Voicing: no NaN/Inf and no runaway output at extreme drive for every // feedback-loop-style blow-up rather than a strict unity ceiling. for (const auto voicingType : allVoicings) { - tyg::Voicing voicing; + cryp::Voicing voicing; voicing.prepare (makeSpec(), 1.0f); voicing.setVoicing (voicingType); voicing.setDrive (1.0f); // maximum drive @@ -111,13 +111,13 @@ TEST_CASE ("Voicing: higher drive increases harmonic energy for every voicing", // between the low-drive and high-drive outputs is clearly non-zero. for (const auto voicingType : allVoicings) { - tyg::Voicing lowDriveVoicing; + cryp::Voicing lowDriveVoicing; lowDriveVoicing.prepare (makeSpec(), 1.0f); lowDriveVoicing.setVoicing (voicingType); lowDriveVoicing.setDrive (0.0f); lowDriveVoicing.setTone (0.5f); - tyg::Voicing highDriveVoicing; + cryp::Voicing highDriveVoicing; highDriveVoicing.prepare (makeSpec(), 1.0f); highDriveVoicing.setVoicing (voicingType); highDriveVoicing.setDrive (1.0f); @@ -146,7 +146,7 @@ TEST_CASE ("Voicing: no NaN/Inf across a denormal-range sweep for every voicing" { for (const auto voicingType : allVoicings) { - tyg::Voicing voicing; + cryp::Voicing voicing; voicing.prepare (makeSpec (2), 1.0f); voicing.setVoicing (voicingType); voicing.setDrive (1.0f); @@ -171,7 +171,7 @@ TEST_CASE ("Voicing: no NaN/Inf across a denormal-range sweep for every voicing" TEST_CASE ("Voicing: switching voicing mid-stream never produces NaN/Inf", "[voicing][dsp][robustness]") { - tyg::Voicing voicing; + cryp::Voicing voicing; voicing.prepare (makeSpec (2), 1.0f); voicing.setDrive (0.8f); voicing.setTone (0.3f); From e36edf3a6f21603c4e6c74d5eb24e728d95399cc Mon Sep 17 00:00:00 2001 From: Yves Vogl Date: Tue, 14 Jul 2026 23:59:48 +0200 Subject: [PATCH 3/4] ci: update Crypta identifiers and reconcile release.yml with suite template ci.yml: artefact root, auval manufacturer/plugin code (aufx Cryp Yvsv), and artifact name updated to Crypta/crypta. release.yml: replaced the legacy per-repo signing pipeline (bespoke DEVELOPER_ID_APP_CERT_*/APP_STORE_CONNECT_* repo secrets, a workflow_dispatch structural dry-run mode, a hard-fail secrets-presence gate, and a repository-level APPLE_TEAM_ID variable) with the suite-wide template semantics also used by overture/apotheosis/firmament: org-level Apple secrets (APPLE_CERT_P12, APPLE_CERT_PASSWORD, APPLE_API_KEY_P8, APPLE_API_KEY_ID, APPLE_API_ISSUER_ID), a throwaway per-run keychain, tag-only (v*) trigger, and find-based artefact discovery (the more robust variant already in use by overture, since this build's artefact path may or may not include a Release/ segment depending on generator). Windows stays unsigned. Same security model throughout: no workflow_dispatch trigger, no fork-PR secret exposure, keychain deleted in an always()-guarded step, minimal contents:write permission. --- .github/workflows/ci.yml | 8 +- .github/workflows/release.yml | 540 ++++++++-------------------------- 2 files changed, 125 insertions(+), 423 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 40b1ad8..3c541f2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -72,7 +72,7 @@ jobs: shell: bash run: | set -euo pipefail - ARTEFACT_ROOT="build/TwistYourGuts_artefacts" + ARTEFACT_ROOT="build/Crypta_artefacts" # JUCE's artefact path may or may not include a Release/ segment # depending on whether the generator is single- or multi-config. VST3_PATH=$(find "$ARTEFACT_ROOT" -type d -iname "*.vst3" | head -n 1) @@ -143,11 +143,11 @@ jobs: mkdir -p ~/Library/Audio/Plug-Ins/Components cp -R "${{ steps.artefacts.outputs.component_path }}" ~/Library/Audio/Plug-Ins/Components/ killall -9 AudioComponentRegistrar || true - auval -strict -v aufx Tygt Yvsv + auval -strict -v aufx Cryp Yvsv - name: Upload plugin artefacts uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: twist-your-guts-${{ matrix.os }} - path: build/TwistYourGuts_artefacts/** + name: crypta-${{ matrix.os }} + path: build/Crypta_artefacts/** if-no-files-found: error diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 439cd84..da88418 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,463 +1,165 @@ -name: Release - -# Cuts a signed, notarized macOS build (AU + VST3) and an unsigned Windows -# VST3 build, then publishes both as assets on a GitHub Release. -# -# How to cut a release: -# git tag -s v1.0.0 && git push origin v1.0.0 -# See docs/releasing.md for the full runbook, including how to provision the -# required secrets. +# release.yml — tag-triggered signed release build. +# Reconciled with the suite-wide template at +# ~/Development/Audio/.scaffold/release-workflow-template.yml (CMake target: Crypta). # -# `workflow_dispatch` allows a structural dry-run (build + package, no -# signing/notarization/publish) before any tag or secret exists — see the -# "Check signing/notarization secrets" step below. Note that even if the -# five signing secrets happen to be configured on the repository, a -# `workflow_dispatch` run on a non-tag ref is ALWAYS a structural dry run: -# the codesign/notarize/staple steps additionally require the ref to be a -# `refs/tags/v*` ref, so secrets alone can never trigger real signing. +# Security model: +# - Triggered only by tags pushed to this repo (never by fork PRs; secrets are +# unavailable to pull_request events from forks by GitHub design). +# - Signing cert lives in org-level encrypted secrets, imported into a throwaway +# keychain that is deleted in the job's post step. +# - Minimal permissions: contents:write only (to attach release assets). +name: Release on: push: - tags: - - "v*" - workflow_dispatch: + tags: ['v*'] -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: false +permissions: + contents: write jobs: - macos: - name: macOS — build, sign, notarize - runs-on: macos-latest - permissions: - contents: read - outputs: - has_secrets: ${{ steps.secrets-check.outputs.has_secrets }} + release-macos: + name: Signed macOS release + runs-on: macos-14 steps: - - name: Checkout - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@v4 - name: Set CPM source cache path - shell: bash run: echo "CPM_SOURCE_CACHE=$HOME/.cache/CPM" >> "$GITHUB_ENV" - name: Cache CPM dependencies - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + uses: actions/cache@v4 with: - path: ${{ env.CPM_SOURCE_CACHE }} - key: ${{ runner.os }}-cpm-${{ hashFiles('CMakeLists.txt') }} - restore-keys: | - ${{ runner.os }}-cpm- + path: ~/.cache/CPM + key: cpm-${{ runner.os }}-${{ hashFiles('CMakeLists.txt') }} - name: Install Ninja run: brew install ninja - - name: Configure (Universal Release) - run: > - cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release - -DCMAKE_OSX_ARCHITECTURES="arm64;x86_64" - - - name: Build AU + VST3 - run: cmake --build build --target TwistYourGuts_AU --target TwistYourGuts_VST3 - - - name: Locate plugin artefacts - id: artefacts - shell: bash - run: | - set -euo pipefail - ARTEFACT_ROOT="build/TwistYourGuts_artefacts" - - VST3_PATH=$(find "$ARTEFACT_ROOT" -type d -iname "*.vst3" | head -n 1) - if [ -z "$VST3_PATH" ]; then - echo "::error::No .vst3 artefact found under $ARTEFACT_ROOT" - exit 1 - fi - echo "vst3_path=$VST3_PATH" >> "$GITHUB_OUTPUT" - echo "Found VST3 at: $VST3_PATH" - - COMPONENT_PATH=$(find "$ARTEFACT_ROOT" -type d -iname "*.component" | head -n 1) - if [ -z "$COMPONENT_PATH" ]; then - echo "::error::No .component artefact found under $ARTEFACT_ROOT" - exit 1 - fi - echo "component_path=$COMPONENT_PATH" >> "$GITHUB_OUTPUT" - echo "Found AU component at: $COMPONENT_PATH" - - - name: Check signing/notarization secrets - id: secrets-check - shell: bash - env: - DEVELOPER_ID_APP_CERT_BASE64: ${{ secrets.DEVELOPER_ID_APP_CERT_BASE64 }} - DEVELOPER_ID_APP_CERT_PASSWORD: ${{ secrets.DEVELOPER_ID_APP_CERT_PASSWORD }} - APP_STORE_CONNECT_API_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }} - APP_STORE_CONNECT_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }} - APP_STORE_CONNECT_API_KEY_BASE64: ${{ secrets.APP_STORE_CONNECT_API_KEY_BASE64 }} + - name: Configure & build (Release, universal) run: | - set -euo pipefail - if [ -n "${DEVELOPER_ID_APP_CERT_BASE64:-}" ] && \ - [ -n "${DEVELOPER_ID_APP_CERT_PASSWORD:-}" ] && \ - [ -n "${APP_STORE_CONNECT_API_KEY_ID:-}" ] && \ - [ -n "${APP_STORE_CONNECT_ISSUER_ID:-}" ] && \ - [ -n "${APP_STORE_CONNECT_API_KEY_BASE64:-}" ]; then - echo "has_secrets=true" >> "$GITHUB_OUTPUT" - echo "Signing/notarization secrets present — will sign, notarize, and staple." - elif [[ "${GITHUB_REF}" == refs/tags/v* ]]; then - # A tag push is the trigger that produces an official release. Never - # let a tag push silently fall through to an unsigned dry-run: that - # would let a missing/misnamed secret ship an unsigned build as the - # official v* release. This is a hard failure, not a warning. - echo "has_secrets=false" >> "$GITHUB_OUTPUT" - echo "::error::Tag push (${GITHUB_REF}) but one or more of the five required signing/notarization secrets are absent or empty. Refusing to publish an unsigned build as an official release — see docs/releasing.md for how to provision the secrets." - exit 1 - else - # Only a workflow_dispatch run on a non-tag ref is allowed to fall - # back to a soft warning and continue as a structural dry-run. - echo "has_secrets=false" >> "$GITHUB_OUTPUT" - echo "::warning::Signing/notarization secrets are absent — skipping sign/notarize/staple (dry-run build only)." - fi + cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_OSX_ARCHITECTURES="arm64;x86_64" + cmake --build build --target Crypta_AU Crypta_VST3 Crypta_Standalone --parallel 3 - - name: Import Developer ID certificate into temporary keychain - if: steps.secrets-check.outputs.has_secrets == 'true' && startsWith(github.ref, 'refs/tags/v') - id: keychain - shell: bash + - name: Import signing certificate (throwaway keychain) env: - DEVELOPER_ID_APP_CERT_BASE64: ${{ secrets.DEVELOPER_ID_APP_CERT_BASE64 }} - DEVELOPER_ID_APP_CERT_PASSWORD: ${{ secrets.DEVELOPER_ID_APP_CERT_PASSWORD }} - APPLE_TEAM_ID: ${{ vars.APPLE_TEAM_ID }} + CERT_P12: ${{ secrets.APPLE_CERT_P12 }} + CERT_PASSWORD: ${{ secrets.APPLE_CERT_PASSWORD }} run: | set -euo pipefail - - if [ -z "${APPLE_TEAM_ID:-}" ]; then - echo "::error::vars.APPLE_TEAM_ID is not set" - exit 1 - fi - - KEYCHAIN="$RUNNER_TEMP/signing-$(uuidgen).keychain-db" - KEYCHAIN_PASSWORD="$(uuidgen)" - P12_PATH="$RUNNER_TEMP/developer_id_app.p12" - - echo "::add-mask::$KEYCHAIN_PASSWORD" - - # The .p12 only needs to exist for this step (import happens here), - # so it is safe to wipe it on this step's own exit. The keychain - # itself must survive into later steps (codesign, notarize) and is - # deleted explicitly by the always()-guarded cleanup step below. - trap 'rm -f "$P12_PATH"' EXIT - - printf '%s' "$DEVELOPER_ID_APP_CERT_BASE64" | base64 --decode > "$P12_PATH" - chmod 600 "$P12_PATH" - - security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN" - security set-keychain-settings -lut 21600 "$KEYCHAIN" - security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN" - - security import "$P12_PATH" -k "$KEYCHAIN" -P "$DEVELOPER_ID_APP_CERT_PASSWORD" \ - -T /usr/bin/codesign -T /usr/bin/security - - EXISTING_KEYCHAINS=() - while IFS= read -r line; do - EXISTING_KEYCHAINS+=("$line") - done < <(security list-keychains -d user | sed 's/^[[:space:]]*"//; s/"[[:space:]]*$//') - security list-keychains -d user -s "$KEYCHAIN" "${EXISTING_KEYCHAINS[@]}" - security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN" - - echo "KEYCHAIN_PATH=$KEYCHAIN" >> "$GITHUB_ENV" - - IDENTITY_LINE=$(security find-identity -v -p codesigning "$KEYCHAIN" | grep "Developer ID Application" | head -n 1 || true) - if [ -z "$IDENTITY_LINE" ]; then - echo "::error::No 'Developer ID Application' identity found in the imported certificate" - exit 1 - fi - - IDENTITY=$(echo "$IDENTITY_LINE" | sed -E 's/.*"(Developer ID Application[^"]+)".*/\1/') - # The Developer ID Application common name ends in "(TEAMID)" — - # cross-check it against vars.APPLE_TEAM_ID as a defensive guard - # against accidentally importing the wrong certificate. - CERT_TEAM_ID=$(echo "$IDENTITY" | sed -E 's/.*\(([A-Z0-9]+)\)[[:space:]]*$/\1/') - if [ "$CERT_TEAM_ID" != "$APPLE_TEAM_ID" ]; then - echo "::error::Imported certificate Team ID ($CERT_TEAM_ID) does not match vars.APPLE_TEAM_ID ($APPLE_TEAM_ID)" - exit 1 - fi - - echo "identity=$IDENTITY" >> "$GITHUB_OUTPUT" - echo "Resolved signing identity: $IDENTITY" - - - name: Codesign AU and VST3 bundles - if: steps.secrets-check.outputs.has_secrets == 'true' && startsWith(github.ref, 'refs/tags/v') - shell: bash - env: - IDENTITY: ${{ steps.keychain.outputs.identity }} - COMPONENT_PATH: ${{ steps.artefacts.outputs.component_path }} - VST3_PATH: ${{ steps.artefacts.outputs.vst3_path }} - run: | - set -euo pipefail - KEYCHAIN="$KEYCHAIN_PATH" - - # No nested executables/frameworks are bundled inside the JUCE-generated - # AU/VST3 (single Mach-O binary + resources) — sign the bundle directly - # rather than recursing with --deep, per Apple's current guidance that - # --deep should not be used for distribution signing. - codesign --force --strict --options runtime --timestamp \ - --keychain "$KEYCHAIN" --sign "$IDENTITY" \ - "$COMPONENT_PATH" - codesign --verify --strict --verbose=4 "$COMPONENT_PATH" - - codesign --force --strict --options runtime --timestamp \ - --keychain "$KEYCHAIN" --sign "$IDENTITY" \ - "$VST3_PATH" - codesign --verify --strict --verbose=4 "$VST3_PATH" - - codesign -dv --verbose=4 "$COMPONENT_PATH" - codesign -dv --verbose=4 "$VST3_PATH" - - - name: Notarize signed bundles - if: steps.secrets-check.outputs.has_secrets == 'true' && startsWith(github.ref, 'refs/tags/v') - shell: bash - env: - APP_STORE_CONNECT_API_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }} - APP_STORE_CONNECT_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }} - APP_STORE_CONNECT_API_KEY_BASE64: ${{ secrets.APP_STORE_CONNECT_API_KEY_BASE64 }} - COMPONENT_PATH: ${{ steps.artefacts.outputs.component_path }} - VST3_PATH: ${{ steps.artefacts.outputs.vst3_path }} - run: | - set -euo pipefail - - P8_PATH="$RUNNER_TEMP/AuthKey_notarization.p8" - STAGE_DIR="$RUNNER_TEMP/notarize-stage" - NOTARIZE_ZIP="$RUNNER_TEMP/notarize-submission.zip" - RESULT_JSON="$RUNNER_TEMP/notarize-result.json" - - trap 'rm -f "$P8_PATH" "$NOTARIZE_ZIP" "$RESULT_JSON"; rm -rf "$STAGE_DIR"' EXIT - - printf '%s' "$APP_STORE_CONNECT_API_KEY_BASE64" | base64 --decode > "$P8_PATH" - chmod 600 "$P8_PATH" - - mkdir -p "$STAGE_DIR" - cp -R "$COMPONENT_PATH" "$STAGE_DIR/" - cp -R "$VST3_PATH" "$STAGE_DIR/" - ditto -c -k --keepParent "$STAGE_DIR" "$NOTARIZE_ZIP" - - xcrun notarytool submit "$NOTARIZE_ZIP" \ - --key "$P8_PATH" \ - --key-id "$APP_STORE_CONNECT_API_KEY_ID" \ - --issuer "$APP_STORE_CONNECT_ISSUER_ID" \ - --wait \ - --output-format json > "$RESULT_JSON" - - STATUS=$(jq -r '.status' "$RESULT_JSON") - SUBMISSION_ID=$(jq -r '.id' "$RESULT_JSON") - echo "Notarization status: $STATUS (submission $SUBMISSION_ID)" - - if [ "$STATUS" != "Accepted" ]; then - echo "::error::Notarization did not succeed (status: $STATUS)" - xcrun notarytool log "$SUBMISSION_ID" \ - --key "$P8_PATH" \ - --key-id "$APP_STORE_CONNECT_API_KEY_ID" \ - --issuer "$APP_STORE_CONNECT_ISSUER_ID" || true - exit 1 - fi - - - name: Staple and verify notarization - if: steps.secrets-check.outputs.has_secrets == 'true' && startsWith(github.ref, 'refs/tags/v') - shell: bash + KC=$RUNNER_TEMP/release.keychain-db + KC_PASS=$(openssl rand -hex 16) + security create-keychain -p "$KC_PASS" "$KC" + security set-keychain-settings -lut 3600 "$KC" + security unlock-keychain -p "$KC_PASS" "$KC" + echo "$CERT_P12" | base64 --decode > "$RUNNER_TEMP/cert.p12" + security import "$RUNNER_TEMP/cert.p12" -k "$KC" -P "$CERT_PASSWORD" -T /usr/bin/codesign + rm "$RUNNER_TEMP/cert.p12" + security set-key-partition-list -S apple-tool:,apple: -s -k "$KC_PASS" "$KC" + security list-keychains -d user -s "$KC" login.keychain + echo "KEYCHAIN=$KC" >> "$GITHUB_ENV" + + - name: Sign, notarize, staple, package env: - COMPONENT_PATH: ${{ steps.artefacts.outputs.component_path }} - VST3_PATH: ${{ steps.artefacts.outputs.vst3_path }} + API_KEY_P8: ${{ secrets.APPLE_API_KEY_P8 }} + API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} + API_ISSUER_ID: ${{ secrets.APPLE_API_ISSUER_ID }} run: | set -euo pipefail - - xcrun stapler staple "$COMPONENT_PATH" - xcrun stapler staple "$VST3_PATH" - - xcrun stapler validate "$COMPONENT_PATH" - xcrun stapler validate "$VST3_PATH" - - codesign -dv --verbose=4 "$COMPONENT_PATH" - codesign -dv --verbose=4 "$VST3_PATH" - - # spctl's --type install assessment is the closest Gatekeeper check - # available for non-.app bundles from the command line; it is - # informational here (stapler validate above is the authoritative - # gate), so failures are logged but do not fail the job. - spctl -a -vvv --type install "$COMPONENT_PATH" || \ - echo "::warning::spctl assessment reported an issue for the AU component (see log above)" - spctl -a -vvv --type install "$VST3_PATH" || \ - echo "::warning::spctl assessment reported an issue for the VST3 (see log above)" - - - name: Clean up temporary keychain + IDENTITY=$(security find-identity -v -p codesigning "$KEYCHAIN" | grep "Developer ID Application" | head -1 | sed 's/.*"\(.*\)"/\1/') + ARTEFACT_ROOT="build/Crypta_artefacts" + # JUCE's artefact path may or may not include a Release/ segment + # depending on whether the generator is single- or multi-config + # (this build uses single-config Ninja); see the equivalent + # find-based lookup in ci.yml's "Locate plugin artefacts" step. + find_artefact_dir() { + local pattern="$1" + local path + path=$(find "$ARTEFACT_ROOT" -type d -iname "$pattern" | head -n 1) + if [ -z "$path" ]; then + echo "::error::No $pattern artefact found under $ARTEFACT_ROOT" + exit 1 + fi + dirname "$path" + } + COMPONENT_DIR=$(find_artefact_dir "*.component") + VST3_DIR=$(find_artefact_dir "*.vst3") + STANDALONE_DIR=$(find_artefact_dir "*.app") + mkdir -p stage/AU stage/VST3 stage/Standalone + cp -R "$COMPONENT_DIR/"*.component stage/AU/ + cp -R "$VST3_DIR/"*.vst3 stage/VST3/ + cp -R "$STANDALONE_DIR/"*.app stage/Standalone/ + for B in stage/AU/*.component stage/VST3/*.vst3 stage/Standalone/*.app; do + codesign --force --options runtime --timestamp --sign "$IDENTITY" "$B" + codesign --verify --strict "$B" + done + echo "$API_KEY_P8" > "$RUNNER_TEMP/AuthKey.p8" + ditto -c -k --norsrc --noextattr --noqtn --noacl stage notarize-me.zip + xcrun notarytool submit notarize-me.zip \ + --key "$RUNNER_TEMP/AuthKey.p8" --key-id "$API_KEY_ID" --issuer "$API_ISSUER_ID" \ + --wait --timeout 30m + rm "$RUNNER_TEMP/AuthKey.p8" + for B in stage/AU/*.component stage/VST3/*.vst3 stage/Standalone/*.app; do + xcrun stapler staple "$B" + done + SLUG=$(basename "$GITHUB_REPOSITORY" | tr '[:upper:]' '[:lower:]') + ditto -c -k --norsrc --noextattr --noqtn --noacl stage "$SLUG-$GITHUB_REF_NAME-macos.zip" + echo "ASSET=$SLUG-$GITHUB_REF_NAME-macos.zip" >> "$GITHUB_ENV" + + - name: Delete throwaway keychain if: always() - shell: bash - run: | - if [ -n "${KEYCHAIN_PATH:-}" ]; then - security delete-keychain "$KEYCHAIN_PATH" 2>/dev/null || true - fi + run: security delete-keychain "$KEYCHAIN" || true - - name: Package macOS release zip - id: package - shell: bash + - name: Attach to release + run: gh release upload "$GITHUB_REF_NAME" "$ASSET" --clobber -R "$GITHUB_REPOSITORY" env: - HAS_SECRETS: ${{ steps.secrets-check.outputs.has_secrets }} - COMPONENT_PATH: ${{ steps.artefacts.outputs.component_path }} - VST3_PATH: ${{ steps.artefacts.outputs.vst3_path }} - run: | - set -euo pipefail - PKG_DIR="$RUNNER_TEMP/package" - mkdir -p "$PKG_DIR" dist - - cp -R "$COMPONENT_PATH" "$PKG_DIR/" - cp -R "$VST3_PATH" "$PKG_DIR/" - cp LICENSE "$PKG_DIR/" - cp README.md "$PKG_DIR/" - - # An unsigned dry-run build must never be filename-identical to a - # real signed release asset. - if [ "$HAS_SECRETS" = "true" ]; then - ZIP_NAME="TwistYourGuts-macOS.zip" - else - ZIP_NAME="TwistYourGuts-macOS-unsigned-dryrun.zip" - fi - echo "zip_name=$ZIP_NAME" >> "$GITHUB_OUTPUT" - - ( cd "$PKG_DIR" && zip -r -y "$RUNNER_TEMP/$ZIP_NAME" . ) - cp "$RUNNER_TEMP/$ZIP_NAME" dist/ - - - name: Upload macOS release artifact - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: ${{ steps.secrets-check.outputs.has_secrets == 'true' && 'twist-your-guts-macos' || 'twist-your-guts-macos-unsigned-dryrun' }} - path: dist/${{ steps.package.outputs.zip_name }} - if-no-files-found: error + GH_TOKEN: ${{ github.token }} - windows: - name: Windows — build VST3 (unsigned) + release-windows: + name: Windows release (unsigned) runs-on: windows-latest - permissions: - contents: read steps: - - name: Checkout - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - + - uses: actions/checkout@v4 - name: Set CPM source cache path shell: bash run: echo "CPM_SOURCE_CACHE=$HOME/.cache/CPM" >> "$GITHUB_ENV" - - name: Cache CPM dependencies - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + uses: actions/cache@v4 with: - path: ${{ env.CPM_SOURCE_CACHE }} - key: ${{ runner.os }}-cpm-${{ hashFiles('CMakeLists.txt') }} - restore-keys: | - ${{ runner.os }}-cpm- - - - name: Configure - run: cmake -B build - - - name: Build VST3 - run: cmake --build build --config Release --target TwistYourGuts_VST3 - - - name: Locate plugin artefact - id: artefacts + path: ~/.cache/CPM + key: cpm-${{ runner.os }}-${{ hashFiles('CMakeLists.txt') }} + - name: Configure & build (Release) shell: bash run: | - set -euo pipefail - ARTEFACT_ROOT="build/TwistYourGuts_artefacts" - VST3_PATH=$(find "$ARTEFACT_ROOT" -type d -iname "*.vst3" | head -n 1) - if [ -z "$VST3_PATH" ]; then - echo "::error::No .vst3 artefact found under $ARTEFACT_ROOT" - exit 1 - fi - echo "vst3_path=$VST3_PATH" >> "$GITHUB_OUTPUT" - echo "Found VST3 at: $VST3_PATH" - - - name: Package Windows release zip + cmake -B build -DCMAKE_BUILD_TYPE=Release + cmake --build build --config Release --target Crypta_VST3 Crypta_Standalone --parallel 3 + - name: Package & attach shell: bash - env: - VST3_PATH: ${{ steps.artefacts.outputs.vst3_path }} run: | set -euo pipefail - PKG_DIR="$RUNNER_TEMP/package" - mkdir -p "$PKG_DIR" dist - - cp -R "$VST3_PATH" "$PKG_DIR/" - cp LICENSE "$PKG_DIR/" - cp README.md "$PKG_DIR/" - - ( cd "$PKG_DIR" && 7z a -tzip "$RUNNER_TEMP/TwistYourGuts-Windows-VST3.zip" . ) - cp "$RUNNER_TEMP/TwistYourGuts-Windows-VST3.zip" dist/ - - - name: Upload Windows release artifact - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: twist-your-guts-windows - path: dist/TwistYourGuts-Windows-VST3.zip - if-no-files-found: error - - release: - name: Publish GitHub release - needs: [macos, windows] - if: startsWith(github.ref, 'refs/tags/v') && needs.macos.outputs.has_secrets == 'true' - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - name: Checkout - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - - - name: Download macOS artifact - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - name: twist-your-guts-macos - path: dist - - - name: Download Windows artifact - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - name: twist-your-guts-windows - path: dist - - - name: Extract CHANGELOG section for this tag - id: changelog - shell: bash - run: | - set -euo pipefail - VERSION="${GITHUB_REF_NAME#v}" - NOTES_FILE="$RUNNER_TEMP/release-notes.md" - - if awk -v ver="$VERSION" ' - /^## \[/ { - if (found) exit - if (index($0, "[" ver "]") > 0) { found=1; next } - next - } - found { print } - ' CHANGELOG.md > "$NOTES_FILE" && [ -s "$NOTES_FILE" ]; then - echo "Found CHANGELOG.md section for [$VERSION]" - else - echo "::warning::No CHANGELOG.md section found for [$VERSION] — using a placeholder release body." - printf 'Release %s.\n\nSee CHANGELOG.md for full details.\n' "$GITHUB_REF_NAME" > "$NOTES_FILE" - fi - - echo "notes_file=$NOTES_FILE" >> "$GITHUB_OUTPUT" - - - name: Create or update GitHub release + ARTEFACT_ROOT="build/Crypta_artefacts" + # JUCE's artefact path may or may not include a Release/ segment + # depending on whether the generator is single- or multi-config; + # see the equivalent find-based lookup in ci.yml's "Locate plugin + # artefacts" step. + find_artefact_dir() { + local type_flag="$1" pattern="$2" + local path + path=$(find "$ARTEFACT_ROOT" -type "$type_flag" -iname "$pattern" | head -n 1) + if [ -z "$path" ]; then + echo "::error::No $pattern artefact found under $ARTEFACT_ROOT" + exit 1 + fi + dirname "$path" + } + VST3_DIR=$(find_artefact_dir d "*.vst3") + STANDALONE_DIR=$(find_artefact_dir f "*.exe") + mkdir -p stage/VST3 stage/Standalone + cp -R "$VST3_DIR/"*.vst3 stage/VST3/ + cp "$STANDALONE_DIR/"*.exe stage/Standalone/ + SLUG=$(basename "$GITHUB_REPOSITORY" | tr '[:upper:]' '[:lower:]') + cd stage && 7z a "../$SLUG-$GITHUB_REF_NAME-windows.zip" . && cd .. + gh release upload "$GITHUB_REF_NAME" "$SLUG-$GITHUB_REF_NAME-windows.zip" --clobber -R "$GITHUB_REPOSITORY" env: GH_TOKEN: ${{ github.token }} - NOTES_FILE: ${{ steps.changelog.outputs.notes_file }} - run: | - set -euo pipefail - if gh release view "$GITHUB_REF_NAME" >/dev/null 2>&1; then - gh release edit "$GITHUB_REF_NAME" \ - --title "Twist Your Guts $GITHUB_REF_NAME" \ - --notes-file "$NOTES_FILE" - gh release upload "$GITHUB_REF_NAME" \ - dist/TwistYourGuts-macOS.zip \ - dist/TwistYourGuts-Windows-VST3.zip \ - --clobber - else - gh release create "$GITHUB_REF_NAME" \ - --title "Twist Your Guts $GITHUB_REF_NAME" \ - --notes-file "$NOTES_FILE" \ - dist/TwistYourGuts-macOS.zip \ - dist/TwistYourGuts-Windows-VST3.zip - fi From aebd425bf262245e614a8014a91d9d4517cecb0b Mon Sep 17 00:00:00 2001 From: Yves Vogl Date: Tue, 14 Jul 2026 23:59:59 +0200 Subject: [PATCH 4/4] docs: rename plugin from Twist Your Guts to Crypta README, manual, architecture, building, CONTRIBUTING, issue templates, ADRs 0001/0002, CLAUDE.md, and CHANGELOG updated for the new plugin identity. README badge/release links point at metal-up-your-ass/Crypta; a migration note explains the old bundle id/plugin code for v0.1.0-era sessions. --- .github/ISSUE_TEMPLATE/bug_report.md | 2 +- .github/ISSUE_TEMPLATE/feature_request.md | 2 +- CHANGELOG.md | 6 ++++++ CLAUDE.md | 12 ++++++++---- CONTRIBUTING.md | 4 ++-- README.md | 22 ++++++++++++---------- docs/adr/0001-use-juce8-framework.md | 2 +- docs/adr/0002-agplv3-licensing.md | 2 +- docs/architecture.md | 4 ++-- docs/building.md | 2 +- docs/manual.md | 10 +++++----- 11 files changed, 40 insertions(+), 28 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index e6269f2..101354d 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,6 +1,6 @@ --- name: Bug report -about: Report a problem with Twist Your Guts +about: Report a problem with Crypta title: "[Bug] " labels: bug assignees: '' diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 528aec3..90c2483 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,6 +1,6 @@ --- name: Feature request -about: Suggest an idea or enhancement for Twist Your Guts +about: Suggest an idea or enhancement for Crypta title: "[Feature] " labels: enhancement assignees: '' diff --git a/CHANGELOG.md b/CHANGELOG.md index e7ee276..aaaf085 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- Renamed plugin from Twist Your Guts to Crypta (new plugin code `Cryp`, new bundle id `com.yvesvogl.crypta`). Old identity: plugin code `Tygt`, bundle id `com.yvesvogl.twistyourguts` — DAWs treat this as a new plugin; v0.1.0-era sessions will need to be re-pointed at the new plugin identity. Part of the suite's move to Basilica Audio naming (the crypt: the basilica's low-end foundation). +- `.github/workflows/release.yml` reconciled with the suite-wide release template: org-level Apple signing secrets (`APPLE_CERT_P12`, `APPLE_CERT_PASSWORD`, `APPLE_API_KEY_P8`, `APPLE_API_KEY_ID`, `APPLE_API_ISSUER_ID`) instead of the prior per-repo secret set, `find`-based artefact discovery, tag-only (`v*`) trigger with no `workflow_dispatch` dry-run path. +- Removed `docs/releasing.md` and `docs/adr/0006-macos-signing-notarization.md`, which documented the prior per-repo signing pipeline; the org-level signing setup is now documented centrally at `.scaffold/SIGNING-SETUP.md`, matching sibling suite repos (none of which carry a per-repo releasing runbook or signing ADR). + ## [0.1.0] - 2026-07-14 ### Added diff --git a/CLAUDE.md b/CLAUDE.md index fbe8adf..a5e53b6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,9 +1,11 @@ -# Twist Your Guts — parallel bass processor (bass) +# Crypta — parallel bass processor (bass) Per-repo working memory for Claude Code sessions on this plugin. Part of the **Metal up your ass** symphonic-metal plugin suite (`github.com/metal-up-your-ass`). +Formerly named **Twist Your Guts**; renamed to Crypta 2026-07-14 as part of the suite's move toward Basilica Audio naming (the crypt: the basilica's low-end foundation). New identity: `com.yvesvogl.crypta`, plugin code `Cryp` (old: `com.yvesvogl.twistyourguts`, `Tygt` — DAWs treat this as a new plugin). + ## What this is -Twist Your Guts is a Parallax-style **parallel bass processor** for metal: it LR4-splits the bass into low/high bands, parallel-compresses the lows, runs the highs through selectable distortion voicings, then sums back through a 4-band EQ and an IR cabinet loader. AU / VST3 / Standalone. +Crypta is a Parallax-style **parallel bass processor** for metal: it LR4-splits the bass into low/high bands, parallel-compresses the lows, runs the highs through selectable distortion voicings, then sums back through a 4-band EQ and an IR cabinet loader. AU / VST3 / Standalone. ## Status (pre-1.0, v0.1.0) M0 bootstrap and M1 "DSP completion & test coverage" are both done: the full v1.0 signal path is wired and tested (gate, LR4 crossover, parallel low-band compressor, three oversampled high-band voicings, post-sum 4-band EQ, IR loader, latency compensation). Not yet done: preset manager/versioned state (M2), custom GUI/metering/accessibility (M3), signing/notarization/v1.0.0 release (M4). Voicing character (drive-gain ranges, mid-filter hump/scoop settings) is engineering-tuned, not yet ear-tuned against reference material. IR loader has no bundled factory IRs and no GUI file browser yet (DSP engine is fully live; file-loading is a `loadImpulseResponse()` seam a future GUI/preset system will call). See GitHub **milestones/issues** for open work, `README.md` for the feature scope and signal-flow diagram, and `docs/manual.md` for the full parameter reference. @@ -19,13 +21,13 @@ M0 bootstrap and M1 "DSP completion & test coverage" are both done: the full v1. ```sh export CPM_SOURCE_CACHE="$HOME/.cache/CPM" cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug -cmake --build build --target Tests TwistYourGuts_Standalone --parallel 4 +cmake --build build --target Tests Crypta_Standalone --parallel 4 ctest --test-dir build --output-on-failure ``` Release/universal + pluginval + auval run in CI. ## Conventions & guardrails -- JUCE 8.0.14 via CPM · C++20 · AGPLv3 · Pamplejuce `SharedCode` · manufacturer `Yvsv`, plugin code `Tygt`, `com.yvesvogl.twistyourguts`. +- JUCE 8.0.14 via CPM · C++20 · AGPLv3 · Pamplejuce `SharedCode` · manufacturer `Yvsv`, plugin code `Cryp`, `com.yvesvogl.crypta`. - Real-time safety (no alloc/lock/IO/log on the audio thread; allocate in `prepareToPlay`; `reset()` clears state; `ScopedNoDenormals`; smoothed params). - DryWetMixer gotcha: prime `setWetMixProportion` before `reset()` (see the suite's overture for the pattern). - `main` protected — feature branch + PR, green CI required, Conventional Commits. New DSP needs tests (flat-sum/null, NaN/Inf, state round-trip). @@ -35,3 +37,5 @@ GitHub milestones (M1 DSP & tests · M2 presets/state · M3 GUI & a11y · M4 rel ## Suite context This is the bass member of the suite; its LR4 crossover is the reference pattern reused by `triptych`. Sibling plugins: overture, tenebrae, nave, silentium, requiem, seraph, aureate, firmament, triptych, apotheosis. + +Internal C++ namespace is `cryp` (was `tyg`); processor/editor classes are `CryptaAudioProcessor`/`CryptaAudioProcessorEditor` (were `TwistYourGutsAudioProcessor`/`...Editor`). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6752aa0..6e9a1c6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,4 +1,4 @@ -# Contributing to Twist Your Guts +# Contributing to Crypta Thanks for your interest in contributing. This document covers the workflow, testing requirements, and code style expected for changes to this project. @@ -34,7 +34,7 @@ test(compressor): add null test for unity-gain bypass ## DSP test requirements -Twist Your Guts is real-time audio software — DSP correctness bugs are expensive to catch late, so tests are not optional for DSP code. +Crypta is real-time audio software — DSP correctness bugs are expensive to catch late, so tests are not optional for DSP code. **New DSP code without tests does not merge.** At minimum, new or changed DSP components need: diff --git a/README.md b/README.md index 0139a95..4a5e3cf 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,19 @@ -

Twist Your Guts icon

+

Crypta icon

-# Twist Your Guts +# Crypta *Split your bass. Compress the lows. Twist the guts out of the highs.* -[![CI](https://github.com/yves-vogl/twist-your-guts/actions/workflows/ci.yml/badge.svg)](https://github.com/yves-vogl/twist-your-guts/actions/workflows/ci.yml) +> Formerly **Twist Your Guts**, renamed to Crypta as part of the suite's move to Basilica Audio naming (the crypt: the basilica's low-end foundation). If you have a v0.1.0-era session referencing the old plugin identity (`com.yvesvogl.twistyourguts`, plugin code `Tygt`), see the [Unreleased] entry in [`CHANGELOG.md`](CHANGELOG.md) — the new bundle ID and plugin code (`com.yvesvogl.crypta`, `Cryp`) mean DAWs treat this as a new plugin, so existing sessions will need to be re-pointed at the new plugin. + +[![CI](https://github.com/metal-up-your-ass/Crypta/actions/workflows/ci.yml/badge.svg)](https://github.com/metal-up-your-ass/Crypta/actions/workflows/ci.yml) [![License: AGPL v3](https://img.shields.io/badge/License-AGPL%20v3-blue.svg)](https://www.gnu.org/licenses/agpl-3.0) -> **Work in progress.** Twist Your Guts is pre-1.0 and under active development (v0.1.0). There are no built binaries or releases yet — building from source is currently the only way to run it. Expect breaking changes until v1.0.0 ships (see [Roadmap](#roadmap)). +> **Work in progress.** Crypta is pre-1.0 and under active development (v0.1.0). Expect breaking changes until v1.0.0 ships (see [Roadmap](#roadmap)). ## What it is -Twist Your Guts is a Parallax-style bass plugin built on JUCE 8. It splits your bass signal into low and high bands with a linear-phase-adjacent Linkwitz-Riley crossover, compresses the low band in parallel, and runs the high band through a choice of three distortion voicings before summing everything back together through a 4-band EQ and an impulse-response (cab sim) loader. See [`docs/manual.md`](docs/manual.md) for the full parameter reference and usage tips. +Crypta is a Parallax-style bass plugin built on JUCE 8. It splits your bass signal into low and high bands with a linear-phase-adjacent Linkwitz-Riley crossover, compresses the low band in parallel, and runs the high band through a choice of three distortion voicings before summing everything back together through a 4-band EQ and an impulse-response (cab sim) loader. See [`docs/manual.md`](docs/manual.md) for the full parameter reference and usage tips. ## Features @@ -115,19 +117,19 @@ ctest --test-dir build --output-on-failure ## License -Twist Your Guts is licensed under the [GNU Affero General Public License v3.0](LICENSE) (AGPLv3). +Crypta is licensed under the [GNU Affero General Public License v3.0](LICENSE) (AGPLv3). This project uses [JUCE](https://juce.com) 8, whose open-source tier is licensed under AGPLv3 (as of JUCE 8; JUCE 7 and earlier used GPLv3), which is why this project is AGPLv3 rather than GPLv3. See [`docs/adr/0002-agplv3-licensing.md`](docs/adr/0002-agplv3-licensing.md) for the full reasoning. VST is a registered trademark of Steinberg Media Technologies GmbH. -Twist Your Guts is an independent open-source project. It is not affiliated with, endorsed by, or sponsored by Neural DSP or the makers of any Parallax-branded product; any naming similarity refers only to the general "parallel bass processing" concept, not to any specific commercial product. +Crypta is an independent open-source project. It is not affiliated with, endorsed by, or sponsored by Neural DSP or the makers of any Parallax-branded product; any naming similarity refers only to the general "parallel bass processing" concept, not to any specific commercial product. ## Releases & installation Tagged releases (`v*`) are built and published automatically by [`.github/workflows/release.yml`](.github/workflows/release.yml): -- **macOS** — AU (`.component`) and VST3 (`.vst3`), Universal Binary (arm64 + x86_64), signed with a Developer ID Application certificate, notarized, and stapled. Installs and opens without a Gatekeeper warning. -- **Windows** — VST3, **unsigned**. On first run, Windows SmartScreen may show a "Windows protected your PC" warning; choose **More info → Run anyway** to proceed. A signed Windows build is a documented future improvement, not yet available. +- **macOS** — AU (`.component`), VST3 (`.vst3`), and Standalone, Universal Binary (arm64 + x86_64), signed with a Developer ID Application certificate (org-level secrets, shared across the Basilica Audio suite), notarized, and stapled. Installs and opens without a Gatekeeper warning. +- **Windows** — VST3 and Standalone, **unsigned**. On first run, Windows SmartScreen may show a "Windows protected your PC" warning; choose **More info → Run anyway** to proceed. A signed Windows build is a documented future improvement, not yet available. -See [`docs/releasing.md`](docs/releasing.md) for the full release runbook and [ADR 0006](docs/adr/0006-macos-signing-notarization.md) for the signing/notarization design rationale. This pipeline is dormant until the first `v*` tag is pushed with the required signing secrets configured — no releases have been published yet (see the work-in-progress notice above). +See [`v0.1.0`](https://github.com/metal-up-your-ass/Crypta/releases/tag/v0.1.0) for the most recent published release (under the plugin's prior identity as Twist Your Guts); the next tagged release under the Crypta identity is planned as `v0.2.0`. diff --git a/docs/adr/0001-use-juce8-framework.md b/docs/adr/0001-use-juce8-framework.md index b938681..8a001ef 100644 --- a/docs/adr/0001-use-juce8-framework.md +++ b/docs/adr/0001-use-juce8-framework.md @@ -6,7 +6,7 @@ ## Context and Problem Statement -Twist Your Guts needs a cross-platform audio plugin framework that can target AU and VST3 on macOS and VST3 on Windows, ship a Standalone build, and support a modern C++ toolchain. Which framework should the project build on? +Crypta needs a cross-platform audio plugin framework that can target AU and VST3 on macOS and VST3 on Windows, ship a Standalone build, and support a modern C++ toolchain. Which framework should the project build on? ## Decision Drivers diff --git a/docs/adr/0002-agplv3-licensing.md b/docs/adr/0002-agplv3-licensing.md index 22f65b8..30e4aee 100644 --- a/docs/adr/0002-agplv3-licensing.md +++ b/docs/adr/0002-agplv3-licensing.md @@ -7,7 +7,7 @@ ## Context and Problem Statement -The intent for Twist Your Guts is to be copyleft open source software — modifications and derivatives should stay open, in the spirit of the GPL family. JUCE 8 was chosen as the plugin framework (ADR 0001), and JUCE's own licensing constrains what license this project can practically use in its open-source configuration. What license should the project ship under? +The intent for Crypta is to be copyleft open source software — modifications and derivatives should stay open, in the spirit of the GPL family. JUCE 8 was chosen as the plugin framework (ADR 0001), and JUCE's own licensing constrains what license this project can practically use in its open-source configuration. What license should the project ship under? ## Decision Drivers diff --git a/docs/architecture.md b/docs/architecture.md index 84c4abd..c0d476b 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -49,12 +49,12 @@ Dependency direction is one-way: `src/ui` → `src/params` ← `src/state`, and ## Latency compensation -The high band's voicing stage (`tyg::Voicing`) runs its nonlinear waveshaping oversampled 4x (`juce::dsp::Oversampling`, FIR half-band equiripple, max quality, integer latency), which introduces processing latency that the low band does not incur. This is the *only* source of latency in the current signal path - the gate, low-band parallel compressor, EQ, and IR loader (configured for zero-latency convolution) are all zero-latency by construction. To keep the two bands phase-coherent at the `Sum` stage: +The high band's voicing stage (`cryp::Voicing`) runs its nonlinear waveshaping oversampled 4x (`juce::dsp::Oversampling`, FIR half-band equiripple, max quality, integer latency), which introduces processing latency that the low band does not incur. This is the *only* source of latency in the current signal path - the gate, low-band parallel compressor, EQ, and IR loader (configured for zero-latency convolution) are all zero-latency by construction. To keep the two bands phase-coherent at the `Sum` stage: - The low band path carries a matching `juce::dsp::DelayLine` (integer/no-interpolation, since the delay is always a whole number of samples) sized to the high band's oversampling latency. - The high band's own clean/distorted blend (`highBlend`) is handled by a `juce::dsp::DryWetMixer` whose dry path is *also* delay-compensated (`setWetLatency`) by that same amount, so the clean and distorted high-band signals stay phase-coherent with each other too, not just with the low band. -`TwistYourGutsAudioProcessor::computeTotalLatencySamples()` reports `Voicing::getLatencySamples()` to the host via `setLatencySamples()`, so host-side plugin delay compensation (PDC) accounts for the whole chain. If the DSP later adds another latency source (e.g. a different oversampling factor becomes user-selectable), this seam is where it gets folded in. +`CryptaAudioProcessor::computeTotalLatencySamples()` reports `Voicing::getLatencySamples()` to the host via `setLatencySamples()`, so host-side plugin delay compensation (PDC) accounts for the whole chain. If the DSP later adds another latency source (e.g. a different oversampling factor becomes user-selectable), this seam is where it gets folded in. ### The `DryWetMixer` priming gotcha (JUCE 8.0.14) diff --git a/docs/building.md b/docs/building.md index 889fe1d..9d13392 100644 --- a/docs/building.md +++ b/docs/building.md @@ -56,4 +56,4 @@ Set this before running `cmake -B build ...`. This is also how CI caches depende ## Build artefacts -Built plugin formats (AU, VST3, Standalone) land under `build/TwistYourGuts_artefacts/`, in a `Release/` subdirectory for multi-config generators (e.g. the Windows Visual Studio generator) or directly under `TwistYourGuts_artefacts/` for single-config generators (e.g. Ninja), depending on generator and configuration. +Built plugin formats (AU, VST3, Standalone) land under `build/Crypta_artefacts/`, in a `Release/` subdirectory for multi-config generators (e.g. the Windows Visual Studio generator) or directly under `Crypta_artefacts/` for single-config generators (e.g. Ninja), depending on generator and configuration. diff --git a/docs/manual.md b/docs/manual.md index 5ceadf4..02304b8 100644 --- a/docs/manual.md +++ b/docs/manual.md @@ -1,18 +1,18 @@ -

Twist Your Guts icon

+

Crypta icon

-# Twist Your Guts — User Manual +# Crypta — User Manual *Split your bass. Compress the lows. Twist the guts out of the highs.* ## What it is -Twist Your Guts is a Parallax-style **parallel bass processor** built for metal production. It splits your bass signal into a low band and a high band with a 4th-order Linkwitz-Riley ("LR4") crossover, keeps the low band tight with a parallel compressor, and runs the high band through a choice of three distortion voicings before summing everything back together through a 4-band EQ and a cabinet-simulation IR loader. +Crypta is a Parallax-style **parallel bass processor** built for metal production. It splits your bass signal into a low band and a high band with a 4th-order Linkwitz-Riley ("LR4") crossover, keeps the low band tight with a parallel compressor, and runs the high band through a choice of three distortion voicings before summing everything back together through a 4-band EQ and a cabinet-simulation IR loader. ### Where it sits in a symphonic-metal chain -Twist Your Guts is designed to be the **bass-specific voicing stage** in the "Metal up your ass" suite: +Crypta is designed to be the **bass-specific voicing stage** in the "Metal up your ass" suite: -- Track order: **DI/amp sim → Twist Your Guts → bus compression/glue → mix bus**. It expects a reasonably clean, already-amp-sim'd or DI'd bass signal; it is not itself a full amp sim (no built-in preamp gain staging beyond the input trim and drive controls). +- Track order: **DI/amp sim → Crypta → bus compression/glue → mix bus**. It expects a reasonably clean, already-amp-sim'd or DI'd bass signal; it is not itself a full amp sim (no built-in preamp gain staging beyond the input trim and drive controls). - The low band's parallel compressor is meant to keep the fundamental/sub content of the bass locked in place under a wall of distorted guitars, while the high band's voicing adds the upper-mid "grind" that lets the bass cut through a dense mix without competing for the same frequency range as the guitars. - The crossover point (default 250 Hz) is deliberately tunable across the whole low-mid register (60 Hz–1000 Hz) so you can match the split to the song's tuning (drop-tunings push useful low-end content further up). - The output stage's IR loader is meant for quick cabinet-style tone shaping without needing a separate cab-sim plugin later in the chain, though it can also be left off entirely if you're already running a dedicated cab sim elsewhere.