diff --git a/.github/workflows/packaging-lint.yml b/.github/workflows/packaging-lint.yml
index 88694ec..ce126e3 100644
--- a/.github/workflows/packaging-lint.yml
+++ b/.github/workflows/packaging-lint.yml
@@ -54,4 +54,14 @@ jobs:
desktop-file-validate packaging/linux/ai.storyteller.soundcraft.desktop
sed -e 's/@VERSION@/0.0.0/' -e 's/@DATE@/2026-01-01/' packaging/linux/ai.storyteller.soundcraft.metainfo.xml.in >"$RUNNER_TEMP/ai.storyteller.soundcraft.metainfo.xml"
appstreamcli validate --no-net --explain "$RUNNER_TEMP/ai.storyteller.soundcraft.metainfo.xml"
- python3 -c "import yaml; m = yaml.safe_load(open('packaging/linux/flatpak/ai.storyteller.soundcraft.yml')); assert m['id'] == 'ai.storyteller.soundcraft' and m['command'] == 'soundcraft'"
+ python3 - <<'EOF'
+ import yaml
+ src = yaml.safe_load(open("packaging/linux/flatpak/ai.storyteller.soundcraft.yml"))
+ bundle = yaml.safe_load(open("packaging/linux/flatpak/ai.storyteller.soundcraft.bundle.yml"))
+ for m in (src, bundle):
+ assert m["id"] == "ai.storyteller.soundcraft" and m["command"] == "soundcraft", m["id"]
+ assert "--socket=pulseaudio" in m["finish-args"], "audio needs --socket=pulseaudio"
+ for key in ("runtime", "runtime-version", "sdk", "finish-args", "add-extensions"):
+ assert src[key] == bundle[key], f"Flatpak manifests disagree on {key}: {src[key]} vs {bundle[key]}"
+ print("Flatpak manifests ok")
+ EOF
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 1c10b5e..b1c0a39 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -1,9 +1,11 @@
# Release pipeline: every push to `release` builds signed installers for every platform and
# creates or updates the draft GitHub Release "SoundCraft v". See docs/releasing.md.
#
-# All jobs run in the `release` environment (restricted to the `release` branch), which holds
-# the signing secrets. Every secret is optional: missing ones produce unsigned artifacts and a
-# warning, never a failed build.
+# The jobs that sign (macOS, Windows) and the one that drafts the release run in the `release`
+# environment (restricted to the `release` branch), which holds the signing secrets. Every secret
+# is optional: missing ones produce unsigned artifacts and a warning, never a failed build. The
+# other jobs sign nothing and get no secrets, so a dispatch on any branch builds and checks them
+# (a dry run); the signed jobs are refused there, and so the draft release is skipped.
name: Release
on:
@@ -35,7 +37,7 @@ jobs:
version:
name: version
runs-on: ubuntu-latest
- environment: release
+ # No `environment: release`: this job reads no secrets (and gates every other job).
outputs:
version: ${{ steps.v.outputs.version }}
date: ${{ steps.v.outputs.date }}
@@ -157,7 +159,7 @@ jobs:
# The oldest GitHub-hosted image, so the binaries only need glibc >= 2.35 (Ubuntu 22.04+,
# Debian 12+, Fedora 36+, RHEL 10). See docs/releasing.md.
runs-on: ${{ matrix.runner }}
- environment: release
+ # No `environment: release`: this job signs nothing, so it gets no release secrets.
strategy:
fail-fast: false
matrix:
@@ -176,7 +178,7 @@ jobs:
sudo apt-get install -y --no-install-recommends \
libxkbcommon-dev libwayland-dev libx11-dev libxrandr-dev libxi-dev libgl1-mesa-dev \
libasound2-dev pkg-config \
- desktop-file-utils appstream python3-yaml
+ desktop-file-utils appstream python3-yaml zsync
curl -fsSL -o "$RUNNER_TEMP/nfpm.deb" \
"https://github.com/goreleaser/nfpm/releases/download/v${NFPM_VERSION}/nfpm_${NFPM_VERSION}_${{ matrix.deb }}.deb"
sudo dpkg -i "$RUNNER_TEMP/nfpm.deb"
@@ -200,6 +202,9 @@ jobs:
for appimage in dist/release/*.AppImage; do
chmod +x "$appimage"
APPIMAGE_EXTRACT_AND_RUN=1 "$appimage" --version
+ # Update information is embedded, and the .zsync for delta updates sits beside it.
+ "$appimage" --appimage-updateinformation | grep -F 'gh-releases-zsync|storytold|soundcraft|latest|soundcraft-*-linux-${{ matrix.arch }}.AppImage.zsync'
+ test -s "$appimage.zsync"
done
- uses: actions/upload-artifact@v4
with:
@@ -207,13 +212,51 @@ jobs:
path: dist/release/*
if-no-files-found: error
+ flatpak:
+ name: Flatpak ${{ matrix.arch }}
+ needs: [version, linux]
+ # Repackages the Linux job's tarball (the same binaries as the AppImage/deb/rpm); no Rust
+ # build here. 24.04 for a newer flatpak-builder (1.4) than 22.04 ships.
+ runs-on: ${{ matrix.runner }}
+ # No `environment: release`: this job signs nothing, so it gets no release secrets.
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - { arch: x86_64, runner: ubuntu-24.04 }
+ - { arch: aarch64, runner: ubuntu-24.04-arm }
+ env:
+ SOUNDCRAFT_VERSION: ${{ needs.version.outputs.version }}
+ SOUNDCRAFT_BUILD_DATE: ${{ needs.version.outputs.date }}
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/download-artifact@v4
+ with:
+ name: release-linux-${{ matrix.arch }}
+ path: dist/linux
+ - name: Install flatpak-builder
+ run: |
+ sudo apt-get update
+ # librsvg2-common: the SVG pixbuf loader. flatpak-builder runs the host's
+ # `appstreamcli compose`, which renders the scalable icon and fails without it.
+ sudo apt-get install -y --no-install-recommends flatpak flatpak-builder librsvg2-common
+ # Ubuntu 24.04's AppArmor blocks the unprivileged user namespaces bubblewrap needs.
+ sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
+ - name: Build the Flatpak bundle
+ run: packaging/linux/flatpak-bundle.sh "dist/linux/soundcraft-$SOUNDCRAFT_VERSION-linux-${{ matrix.arch }}.tar.gz"
+ - uses: actions/upload-artifact@v4
+ with:
+ name: release-flatpak-${{ matrix.arch }}
+ path: dist/release/*.flatpak
+ if-no-files-found: error
+
freebsd:
name: FreeBSD x86_64
needs: version
# GitHub has no FreeBSD runners: build in a FreeBSD 14.3 VM (same image as freebsd.yml).
runs-on: ubuntu-latest
timeout-minutes: 150
- environment: release
+ # No `environment: release`: this job signs nothing, so it gets no release secrets.
env:
SOUNDCRAFT_VERSION: ${{ needs.version.outputs.version }}
SOUNDCRAFT_BUILD_DATE: ${{ needs.version.outputs.date }}
@@ -253,7 +296,7 @@ jobs:
name: Web (wasm)
needs: version
runs-on: ubuntu-latest
- environment: release
+ # No `environment: release`: this job signs nothing, so it gets no release secrets.
env:
SOUNDCRAFT_VERSION: ${{ needs.version.outputs.version }}
SOUNDCRAFT_BUILD_DATE: ${{ needs.version.outputs.date }}
@@ -284,7 +327,8 @@ jobs:
release:
name: Draft GitHub Release
- needs: [version, macos, windows, linux, freebsd, web]
+ # Needs the signed jobs, which only run on the `release` branch: a branch dispatch never drafts.
+ needs: [version, macos, windows, linux, flatpak, freebsd, web]
runs-on: ubuntu-latest
environment: release
permissions:
diff --git a/.gitignore b/.gitignore
index 75bd19e..901797f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,3 +9,4 @@ plan/
/target-*/
/craft-fonts/
plans/
+.DS_Store
diff --git a/ATTRIBUTION.md b/ATTRIBUTION.md
index 69f0f0b..084f424 100644
--- a/ATTRIBUTION.md
+++ b/ATTRIBUTION.md
@@ -38,6 +38,7 @@ and, at runtime, the operating system's own UI fonts when available (never redis
| `docs/brand/artcraft-mark-black.svg` | ArtCraft Team | craftrules `assets/brand/` | ArtCraft brand terms |
| `docs/brand/artcraft-mark-black.png` | ArtCraft Team | craftrules `assets/brand/` | ArtCraft brand terms |
| `assets/app-icon/icon.svg` | SoundCraft contributors | original (hand-placed SVG paths; placeholder app icon) | MIT OR Apache-2.0 |
+| `packaging/macos/dmg/` (all files) | @XusBadia | original (macOS DMG window background and layout, from the app icon; see `packaging/macos/dmg/README.md`) | MIT OR Apache-2.0 |
| `assets/app-icon/soundcraft-1024.png` | SoundCraft contributors | original, rendered from `icon.svg` | MIT OR Apache-2.0 |
| `assets/app-icon/soundcraft-macos-512.png` | SoundCraft contributors | original, rendered from `icon.svg` | MIT OR Apache-2.0 |
| `assets/app-icon/soundcraft.icns` | SoundCraft contributors | original, rendered from `icon.svg` | MIT OR Apache-2.0 |
diff --git a/Cargo.lock b/Cargo.lock
index 67d4a78..164e0ed 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3403,7 +3403,7 @@ dependencies = [
[[package]]
name = "soundcraft"
-version = "0.1.0"
+version = "0.3.0"
dependencies = [
"eframe",
"egui",
@@ -3419,7 +3419,7 @@ dependencies = [
[[package]]
name = "soundcraft-au-host"
-version = "0.1.0"
+version = "0.3.0"
dependencies = [
"log",
"serde",
@@ -3429,7 +3429,7 @@ dependencies = [
[[package]]
name = "soundcraft-audio-io"
-version = "0.1.0"
+version = "0.3.0"
dependencies = [
"log",
"proptest",
@@ -3441,7 +3441,7 @@ dependencies = [
[[package]]
name = "soundcraft-automation"
-version = "0.1.0"
+version = "0.3.0"
dependencies = [
"log",
"serde_json",
@@ -3451,7 +3451,7 @@ dependencies = [
[[package]]
name = "soundcraft-clap-host"
-version = "0.1.0"
+version = "0.3.0"
dependencies = [
"clap-sys",
"libloading",
@@ -3463,7 +3463,7 @@ dependencies = [
[[package]]
name = "soundcraft-cli"
-version = "0.1.0"
+version = "0.3.0"
dependencies = [
"serde_json",
"soundcraft-audio-io",
@@ -3474,7 +3474,7 @@ dependencies = [
[[package]]
name = "soundcraft-dsp"
-version = "0.1.0"
+version = "0.3.0"
dependencies = [
"log",
"proptest",
@@ -3485,7 +3485,7 @@ dependencies = [
[[package]]
name = "soundcraft-engine"
-version = "0.1.0"
+version = "0.3.0"
dependencies = [
"log",
"proptest",
@@ -3505,7 +3505,7 @@ dependencies = [
[[package]]
name = "soundcraft-midi"
-version = "0.1.0"
+version = "0.3.0"
dependencies = [
"proptest",
"serde",
@@ -3515,7 +3515,7 @@ dependencies = [
[[package]]
name = "soundcraft-mix"
-version = "0.1.0"
+version = "0.3.0"
dependencies = [
"log",
"rayon",
@@ -3531,7 +3531,7 @@ dependencies = [
[[package]]
name = "soundcraft-model"
-version = "0.1.0"
+version = "0.3.0"
dependencies = [
"proptest",
"serde",
@@ -3544,7 +3544,7 @@ dependencies = [
[[package]]
name = "soundcraft-playback"
-version = "0.1.0"
+version = "0.3.0"
dependencies = [
"cpal",
"log",
@@ -3557,7 +3557,7 @@ dependencies = [
[[package]]
name = "soundcraft-time"
-version = "0.1.0"
+version = "0.3.0"
dependencies = [
"proptest",
"serde",
@@ -3567,7 +3567,7 @@ dependencies = [
[[package]]
name = "soundcraft-ui-egui"
-version = "0.1.0"
+version = "0.3.0"
dependencies = [
"egui",
"egui_kittest",
@@ -3592,7 +3592,7 @@ dependencies = [
[[package]]
name = "soundcraft-video"
-version = "0.1.0"
+version = "0.3.0"
dependencies = [
"image",
"proptest",
@@ -3603,7 +3603,7 @@ dependencies = [
[[package]]
name = "soundcraft-vst3-host"
-version = "0.1.0"
+version = "0.3.0"
dependencies = [
"libloading",
"log",
@@ -3618,7 +3618,7 @@ dependencies = [
[[package]]
name = "soundcraft-web"
-version = "0.1.0"
+version = "0.3.0"
dependencies = [
"eframe",
"egui",
@@ -5165,7 +5165,7 @@ checksum = "e450f9b2ed1dff33c94c12589a87338689467b9c4f5d8a5710bd09a847d2c8a7"
[[package]]
name = "xtask"
-version = "0.1.0"
+version = "0.3.0"
dependencies = [
"serde_json",
]
diff --git a/Cargo.toml b/Cargo.toml
index 35520ba..6f07dfb 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -4,7 +4,7 @@ members = ["crates/*", "apps/*", "xtask"]
default-members = ["crates/*", "apps/*", "xtask"]
[workspace.package]
-version = "0.1.0"
+version = "0.3.0"
edition = "2024"
license = "MIT OR Apache-2.0"
rust-version = "1.90"
diff --git a/README.md b/README.md
index c0d4591..da490e2 100644
--- a/README.md
+++ b/README.md
@@ -54,6 +54,7 @@
Agents & scripts ·
How it's built ·
Status ·
+ Downloads ·
The Crafting Apps ·
License
@@ -157,6 +158,28 @@ Useful shortcuts: Space play/stop, ⌘= Mix/Edi
separate, ⌘D duplicate, Enter new marker, ⌘⇧N
new tracks. **Setup › Keyboard Shortcuts** lists them all.
+### Logs
+
+The desktop app writes its `log` records to standard error and to `logs/soundcraft.log` in the
+settings directory, beside `ui.json`, `Autosave/` and `Presets/` (Linux `~/.config/soundcraft/logs/`,
+or `$XDG_CONFIG_HOME/soundcraft/logs/`; macOS `~/Library/Application Support/SoundCraft/logs/`;
+Windows `%APPDATA%\SoundCraft\logs\`). A start from a desktop menu or the Dock has no terminal, so
+this file is what to attach to a bug report: the engine's panic report, audio devices that failed to
+open or broke, plugins that refused their stored state, CLAP plugins' own messages and files that
+failed to open land there. Each launch moves the previous log to `soundcraft.1.log` (and that one to
+`soundcraft.2.log`), so the log of a run that crashed survives the next start. The file stops
+growing at 16 MiB. `--version` writes no file, and runs with `SOUNDCRAFT_NO_PREFS` log to standard
+error only.
+
+| Variable | Effect |
+|---|---|
+| `RUST_LOG` | Log levels for standard error and the log file. Default: `info` for SoundCraft's own crates, `warn` for everything else. env_logger-style directives replace that, e.g. `RUST_LOG=debug`, `RUST_LOG=warn,soundcraft_mix=trace` or `RUST_LOG=info,wgpu_core=warn`; a directive ending in `*` covers every target starting with it (`soundcraft*=debug`). |
+| `SOUNDCRAFT_NO_PREFS` | No preferences read or written and no log file (agents' test runs). |
+
+The realtime audio thread never writes a record itself: the logger keeps the first one it logs
+(without waiting or allocating), counts the rest, and the UI thread writes them. The logger is
+`apps/soundcraft/src/logging.rs`. The web app logs to the browser console instead.
+
## For agents and scripts
Everything you can click is also a command with an id and JSON parameters, and the same commands
@@ -213,6 +236,49 @@ honest status, what's next and our effort estimates are in [`ROADMAP.md`](ROADMA
menu-by-menu comparison is in [`docs/parity.md`](docs/parity.md). Bug reports and wish lists are
very welcome, in the issues or on Discord.
+## Downloads
+
+**Download SoundCraft** from GitHub: the [latest release](https://github.com/storytold/soundcraft/releases/latest) has every build listed below, and [all releases](https://github.com/storytold/soundcraft/releases) has earlier versions and their notes. `` in the file names is the version number, and `SHA256SUMS.txt` lists a checksum for every file.
+
+### Windows
+
+| Build | Installer | Portable |
+|---|---|---|
+| x64 (64-bit Intel/AMD) | `soundcraft--windows-x64.msi` | `soundcraft--windows-x64-portable.zip` |
+| arm64 (Snapdragon and other ARM PCs) | `soundcraft--windows-arm64.msi` | `soundcraft--windows-arm64-portable.zip` |
+| x86 (32-bit) | `soundcraft--windows-x86.msi` | `soundcraft--windows-x86-portable.zip` |
+
+Installers and executables are code-signed.
+
+### macOS
+
+| Build | File | Notes |
+|---|---|---|
+| App, universal (Apple silicon + Intel) | `soundcraft--macos-universal.dmg` | Signed and notarized |
+| Command-line tool, universal | `soundcraft-cli--macos-universal.zip` | Signed and notarized |
+
+### Linux
+
+| Format | x86_64 | aarch64 (ARM64) | Notes |
+|---|---|---|---|
+| AppImage | `soundcraft--linux-x86_64.AppImage` | `soundcraft--linux-aarch64.AppImage` | Runs anywhere; updates itself with [AppImageUpdate](https://github.com/AppImageCommunity/AppImageUpdate) (`.zsync` files) |
+| Flatpak | `soundcraft--linux-x86_64.flatpak` | `soundcraft--linux-aarch64.flatpak` | Sandboxed; `flatpak install --user ` |
+| Debian/Ubuntu | `soundcraft--linux-x86_64.deb` | `soundcraft--linux-aarch64.deb` | |
+| Fedora/RHEL/openSUSE | `soundcraft--linux-x86_64.rpm` | `soundcraft--linux-aarch64.rpm` | |
+| Tarball | `soundcraft--linux-x86_64.tar.gz` | `soundcraft--linux-aarch64.tar.gz` | Unpack anywhere |
+
+### FreeBSD
+
+| Build | File |
+|---|---|
+| x86_64 | `soundcraft--freebsd-x86_64.tar.gz` |
+
+### Web (WebAssembly)
+
+| Build | File | Notes |
+|---|---|---|
+| Static site | `soundcraft-web-.zip` | Runs in a modern browser; host it on any static server |
+
## The Crafting Apps
SoundCraft is one of the **Crafting Apps**: free, open-source creative tools from the
diff --git a/apps/soundcraft-web/index.html b/apps/soundcraft-web/index.html
index b68f2d7..af72b21 100644
--- a/apps/soundcraft-web/index.html
+++ b/apps/soundcraft-web/index.html
@@ -15,6 +15,27 @@
+
Loading SoundCraft…