Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
95 commits
Select commit Hold shift + click to select a range
1db47e6
Add CI workflow to build Csound 7 plugins
vlazzarini Oct 10, 2026
d8744df
Port chua and linear_algebra opcodes to the Csound 7 API
vlazzarini Oct 10, 2026
a849526
Port fluidOpcodes to Csound 7 and add vcpkg release packaging
vlazzarini Oct 10, 2026
015cc99
Fix vcpkg macOS triplet detection and bump vcpkg baseline
vlazzarini Oct 10, 2026
1621158
Fix CI plugin build: fluidsynth config target and Csound hint on Windows
vlazzarini Oct 10, 2026
31f90ef
Fix vcpkg detection of eigen and fluidsynth in CI
vlazzarini Oct 10, 2026
6f8e8ee
Disable automatic 6.x plugin builds (EOL)
vlazzarini Oct 10, 2026
f26e071
Add beta/full release automation to Csound 7 CI
vlazzarini Oct 10, 2026
ec3a679
Merge pull request #35 from csound/ci/plugins-build-7x
vlazzarini Oct 10, 2026
1961e57
Avoid leaking fluidSynth engines and SoundFonts on recompile
vlazzarini Oct 11, 2026
0de17f2
Only reuse engines from an earlier compile (CodeRabbit)
vlazzarini Oct 11, 2026
2db326b
Add fluidSetting* opcodes for engine settings access (issue #32)
vlazzarini Oct 11, 2026
bfeafe1
Note versioned layout and 6.x EOL status in README
vlazzarini Oct 11, 2026
9d23bf5
Use fluid_settings_dupstr on fluidsynth 2.x in fluidSettingGetString
vlazzarini Oct 11, 2026
b1c5741
Update 7.x README: list all plugins and document fluidSetting* opcodes
vlazzarini Oct 11, 2026
ec45e48
Address remaining CodeRabbit review on fluidOpcodes
vlazzarini Oct 11, 2026
fec05af
Merge pull request #37 from csound/fix/fluid-mem-recompile
vlazzarini Oct 11, 2026
08fe4ce
Port websocket opcode to Csound 7 plugins
vlazzarini Oct 11, 2026
925ffb4
Address CodeRabbit review of websocket port
vlazzarini Oct 11, 2026
53da816
Merge pull request #38 from csound/add-websocket-opcodes-7x
vlazzarini Oct 11, 2026
4e7e33c
Port hdf5 read/write opcodes to Csound 7 plugins
vlazzarini Oct 11, 2026
86231aa
Fix hdf5 build on vcpkg and newer HDF5
vlazzarini Oct 11, 2026
6e063c7
Address CodeRabbit review of hdf5 port
vlazzarini Oct 11, 2026
a58e5ad
Support gmm++ 5.4.3 in linear algebra opcodes
vlazzarini Oct 11, 2026
c71162b
Use an accessor helper for gmm++ lapack_ipvt
vlazzarini Oct 11, 2026
29021fb
Merge pull request #40 from csound/fix/gmm-lapack-ipvt
vlazzarini Oct 11, 2026
8ecea48
Port image opcodes to Csound 7 plugins
vlazzarini Oct 11, 2026
d8f8b83
Select available HDF5 target for vcpkg triplets
vlazzarini Oct 11, 2026
554616f
Harden image opcodes error handling and image handle checks
vlazzarini Oct 11, 2026
f183c08
Address image opcode review: allocation, registry, path and init fixes
vlazzarini Oct 11, 2026
9650adc
Reject hdf5write calls exceeding the 20-argument limit
vlazzarini Oct 12, 2026
6dc2020
Fix image opcode size arithmetic and path leaks
vlazzarini Oct 12, 2026
58cf05b
Fix HDF5 partial-block transfers, array layout, and resource leaks
vlazzarini Oct 12, 2026
b978366
Harden HDF5 attribute reads, output clearing, and buffers
vlazzarini Oct 12, 2026
671c149
Address image opcode overloads, cleanup, and diagnostics
vlazzarini Oct 12, 2026
6ca0422
Merge pull request #39 from csound/add-hdf5-opcodes-7x
vlazzarini Oct 12, 2026
9608a5c
Merge remote-tracking branch 'origin/develop' into add-image-opcodes-7x
vlazzarini Oct 12, 2026
db08fe2
Merge pull request #41 from csound/add-image-opcodes-7x
vlazzarini Oct 12, 2026
8b0f850
Port wiimote opcodes to Csound 7 plugins
vlazzarini Oct 12, 2026
41dcf22
Address wiimote review: bounds checks, poll state, button return
vlazzarini Oct 12, 2026
f39814d
Merge pull request #42 from csound/add-wiimote-opcodes-7x
vlazzarini Oct 12, 2026
81e853f
Port p5glove opcodes to Csound 7 plugins
vlazzarini Oct 12, 2026
158f930
Address p5glove review: input guards and vendored driver fixes
vlazzarini Oct 12, 2026
0beb945
Merge pull request #43 from csound/add-p5glove-opcodes-7x
vlazzarini Oct 12, 2026
3a03c4c
Port CUDA opcodes to Csound 7 plugins
vlazzarini Oct 12, 2026
a84c619
Note that the CUDA opcodes are Linux-only
vlazzarini Oct 12, 2026
108114f
Fix CUDA CI: install ALSA and pkg-config to build Csound
vlazzarini Oct 12, 2026
21aa3d6
Build only the CUDA opcodes in the CUDA CI job
vlazzarini Oct 12, 2026
d0c9578
Address CUDA review: bounds checks, launch sizing and cleanup
vlazzarini Oct 12, 2026
01b4b04
Annotate CUDA sources with FIXMEs for deferred review fixes
vlazzarini Oct 12, 2026
33e0dab
Merge pull request #44 from csound/add-cuda-opcodes-7x
vlazzarini Oct 12, 2026
ad9fdaa
Add OpenCL opcodes (cladsyn)
vlazzarini Oct 12, 2026
961ae5a
Merge pull request #45 from csound/add-opencl-opcodes-7x
vlazzarini Oct 12, 2026
af5d5a0
Enforce Csound 7.0 minimum in FindCSOUND
vlazzarini Oct 12, 2026
361ebf8
Merge pull request #46 from csound/enforce-csound-min-version
vlazzarini Oct 12, 2026
c131612
Document that macOS release plugins are arm64-only
vlazzarini Oct 12, 2026
bd4c923
Merge pull request #47 from csound/docs/macos-plugin-architecture
vlazzarini Oct 12, 2026
ecc5530
Add STK opcodes
vlazzarini Oct 13, 2026
40b8db1
Stage STK rawwaves with the correct .raw extension
vlazzarini Oct 13, 2026
ef0c14e
Add CodeRabbit config excluding vendored STK sources
vlazzarini Oct 13, 2026
d2e2057
Fix STK adapter issues
vlazzarini Oct 13, 2026
23c397d
Fix STK rawwaves staging path in CI
vlazzarini Oct 13, 2026
148c2a2
Merge pull request #48 from csound/add-stk-opcodes-7x
vlazzarini Oct 13, 2026
cdc51c0
feat(wasm): scaffold @csound/plugins npm package
hlolli Oct 13, 2026
0ace7f0
chore(wasm): use reviewed plugin runtime
hlolli Oct 13, 2026
e0b4674
build(wasm): use released SDK and enable Cachix
hlolli Oct 13, 2026
59771ad
build(wasm): test against published browser runtime
hlolli Oct 13, 2026
70ead80
ci(wasm): disable checkout credential persistence
hlolli Oct 13, 2026
50c0e81
Merge pull request #49 from hlolli/feat/wasm-npm-plugins
vlazzarini Oct 13, 2026
3833218
feat(wasm): bundle STK instruments and raw waves
hlolli Oct 14, 2026
68b2cba
Add Faust opcodes for Csound 7
vlazzarini Oct 14, 2026
563dde0
Merge pull request #50 from hlolli/feat/stk-wasm
vlazzarini Oct 14, 2026
461c5bb
Merge pull request #51 from csound/add-faust-opcodes-7x
vlazzarini Oct 14, 2026
97a6a18
Address review findings in Faust opcodes for Csound 7
vlazzarini Oct 14, 2026
4e20eed
Fix standard header include order in faustgen.cpp
vlazzarini Oct 14, 2026
efbeb90
Use a separate mutex for Faust factory compilation
vlazzarini Oct 14, 2026
4a5353b
Resolve faustcompile handles without touching per-note memory
vlazzarini Oct 14, 2026
18cdeee
Merge pull request #52 from csound/fix-faustgen-review
vlazzarini Oct 14, 2026
3d8b12b
Document the Faust opcodes in the 7.x README
vlazzarini Oct 14, 2026
ef7a7ef
Merge pull request #53 from csound/faust-readme-docs
vlazzarini Oct 14, 2026
ce0529a
Document the WASM plugins in the 7.x README
vlazzarini Oct 14, 2026
6089cad
Merge pull request #54 from csound/wasm-readme-7x
vlazzarini Oct 14, 2026
8acfc70
Use cs_float instead of MYFLT in the py opcodes
vlazzarini Oct 15, 2026
ef03c92
Add DSSI/LADSPA opcodes for Csound 7
vlazzarini Oct 15, 2026
2ced540
Fix misplaced semicolon in generated pycall oresult field
vlazzarini Oct 15, 2026
bc46724
Fix dssiaudio/dssisynth input types and multiarch search path
vlazzarini Oct 15, 2026
b06c3b4
Merge pull request #55 from csound/add-dssi-opcodes-7x
vlazzarini Oct 15, 2026
8de8d04
Add DSSI host regression tests and allow omitting audio inputs (#56)
vlazzarini Oct 15, 2026
1267e42
fix(stk): use the Csound sample rate in instrument adapters (#58)
hlolli Oct 18, 2026
7e4af2e
fix(wasm): update npm plugins for shared-memory Csound (#57)
hlolli Oct 18, 2026
b72cfe1
fix: support the Csound 7 plugin API (#59)
hlolli Oct 25, 2026
cb0005e
Flatten Csound 7 plugins tree to repository root
vlazzarini Oct 29, 2026
be89f51
Merge pull request #61 from csound/reorg/flatten-7x
vlazzarini Oct 29, 2026
57a62fa
fix(ci): release plugins from checked-in versions and labelled merges
hlolli Oct 29, 2026
24e99a2
Merge pull request #63 from hlolli/fix/native-release-workflow
vlazzarini Oct 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
7 changes: 7 additions & 0 deletions .coderabbit.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
reviews:
path_filters:
- "!src/stk/src/**"
- "!src/stk/include/**"
- "!src/stk/rawwaves/**"
- "!src/dssi4cs/include/dssi/**"
- "!src/dssi4cs/include/ladspa/**"
98 changes: 98 additions & 0 deletions .github/scripts/release.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
"""Read the release version and select labelled merges for native releases."""

import argparse
import json
import os
from pathlib import Path
import re
import subprocess


VERSION = re.compile(
r"(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)"
r"(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?"
)
RELEASE_BRANCHES = {"refs/heads/main", "refs/heads/develop"}


def read_version(root):
package = json.loads((root / "package.json").read_text())
lock = json.loads((root / "package-lock.json").read_text())
version = package["version"]
match = VERSION.fullmatch(version) if isinstance(version, str) else None
if not match or any(
part.isdigit() and len(part) > 1 and part.startswith("0")
for part in (match[4] or "").split(".")
):
raise ValueError("package.json must contain a SemVer version without build metadata")
if lock["version"] != version or lock["packages"][""]["version"] != version:
raise ValueError("package.json and package-lock.json versions must match")
return version


def release_metadata(version, event, ref, sha, repository, pull_requests):
release = event == "push" and ref in RELEASE_BRANCHES and any(
pr["merged_at"]
and pr["merge_commit_sha"] == sha
and pr["base"]["ref"] == ref.removeprefix("refs/heads/")
and pr["base"]["repo"]["full_name"] == repository
and any(label["name"] == "release" for label in pr["labels"])
for pr in pull_requests
)
prerelease = "-" in version
if release and ref == "refs/heads/main" and prerelease:
raise ValueError("A release from main needs a stable version in package.json")
if release and ref == "refs/heads/develop" and not prerelease:
raise ValueError("A release from develop needs a prerelease version in package.json")
return {
"version": version,
"release": str(bool(release)).lower(),
"prerelease": str(prerelease).lower(),
}


def check_tag(version, sha):
ref = f"refs/tags/{version}"
result = subprocess.run(["git", "show-ref", "--verify", "--quiet", ref])
if result.returncode == 1:
return
result.check_returncode()
target = subprocess.check_output(
["git", "rev-parse", "--verify", f"{ref}^{{commit}}"], text=True
).strip()
if target != sha:
raise ValueError(
f"Tag {version} already points to {target}, not {sha}; choose a new version"
)


def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--check-tag", action="store_true")
args = parser.parse_args()
root = Path(__file__).resolve().parents[2]
version = read_version(root)
if args.check_tag:
check_tag(version, os.environ["GITHUB_SHA"])
return

event = os.environ["GITHUB_EVENT_NAME"]
ref = os.environ["GITHUB_REF"]
sha = os.environ["GITHUB_SHA"]
repository = os.environ["GITHUB_REPOSITORY"]
pull_requests = []
if event == "push" and ref in RELEASE_BRANCHES:
pages = json.loads(subprocess.check_output(
["gh", "api", "--paginate", "--slurp",
f"repos/{repository}/commits/{sha}/pulls?per_page=100"],
text=True,
))
pull_requests = [pr for page in pages for pr in page]
metadata = release_metadata(version, event, ref, sha, repository, pull_requests)
with open(os.environ["GITHUB_OUTPUT"], "a") as output:
for name, value in metadata.items():
print(f"{name}={value}", file=output)


if __name__ == "__main__":
main()
204 changes: 204 additions & 0 deletions .github/scripts/test_release.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
import copy
import json
import os
from pathlib import Path
import subprocess
import tempfile
import unittest
from unittest.mock import patch

import release


SHA = "a" * 40
REPOSITORY = "csound/plugins"
MERGED_PR = {
"merged_at": "2026-10-29T10:35:04Z",
"merge_commit_sha": SHA,
"base": {"ref": "main", "repo": {"full_name": REPOSITORY}},
"labels": [{"name": "release"}],
}


class VersionTests(unittest.TestCase):
def setUp(self):
directory = tempfile.TemporaryDirectory()
self.addCleanup(directory.cleanup)
self.root = Path(directory.name)

def write_manifests(self, version, lock_version=None, root_version=None):
(self.root / "package.json").write_text(json.dumps({"version": version}))
(self.root / "package-lock.json").write_text(json.dumps({
"version": lock_version or version,
"packages": {"": {"version": root_version or version}},
}))

def test_stable_and_prerelease_versions(self):
for version in ["7.0.0", "7.1.0-beta.2", "7.0.0-rc1", "7.0.0-alpha-beta.0"]:
with self.subTest(version=version):
self.write_manifests(version)
self.assertEqual(release.read_version(self.root), version)

def test_invalid_versions(self):
for version in [None, 7, "", "v7.0.0", "7.0", "07.0.0", "7.0.0-beta.01",
"7.0.0-", "7.0.0-beta..1", "7.0.0+build", "7.0.0\nrelease=true"]:
with self.subTest(version=version):
self.write_manifests(version)
with self.assertRaisesRegex(ValueError, "SemVer"):
release.read_version(self.root)

def test_both_lockfile_versions_must_match(self):
for mismatch in [{"lock_version": "7.0.1"}, {"root_version": "7.0.1"}]:
with self.subTest(mismatch=mismatch):
self.write_manifests("7.0.0", **mismatch)
with self.assertRaisesRegex(ValueError, "must match"):
release.read_version(self.root)


class ReleaseTests(unittest.TestCase):
def metadata(self, version="7.0.0", event="push", ref="refs/heads/main", prs=None):
return release.release_metadata(
version, event, ref, SHA, REPOSITORY, [MERGED_PR] if prs is None else prs
)

def test_labelled_main_merge_releases(self):
self.assertEqual(self.metadata(), {
"version": "7.0.0", "release": "true", "prerelease": "false",
})

def test_labelled_develop_merge_prereleases(self):
pr = copy.deepcopy(MERGED_PR)
pr["base"]["ref"] = "develop"
self.assertEqual(self.metadata("7.1.0-beta.2", ref="refs/heads/develop", prs=[pr]), {
"version": "7.1.0-beta.2", "release": "true", "prerelease": "true",
})

def test_only_push_events_to_release_branches_can_release(self):
for event, ref in [
("pull_request", "refs/heads/main"),
("pull_request_target", "refs/heads/main"),
("workflow_dispatch", "refs/heads/main"),
("push", "refs/heads/topic"),
("push", "refs/tags/7.0.0"),
]:
with self.subTest(event=event, ref=ref):
self.assertEqual(self.metadata(event=event, ref=ref)["release"], "false")

def test_requires_exact_merge_base_repository_and_label(self):
for field, value in [
("merged_at", None),
("merge_commit_sha", "b" * 40),
("base", {"ref": "develop", "repo": {"full_name": REPOSITORY}}),
("base", {"ref": "main", "repo": {"full_name": "fork/plugins"}}),
("labels", []),
("labels", [{"name": "no-release"}]),
("labels", [{"name": "release-candidate"}]),
]:
with self.subTest(field=field, value=value):
pr = copy.deepcopy(MERGED_PR)
pr[field] = value
self.assertEqual(self.metadata(prs=[pr])["release"], "false")
self.assertEqual(self.metadata(prs=[])["release"], "false")

def test_release_version_must_match_branch(self):
with self.assertRaisesRegex(ValueError, "main needs a stable version"):
self.metadata(version="7.0.0-beta.2")
pr = copy.deepcopy(MERGED_PR)
pr["base"]["ref"] = "develop"
with self.assertRaisesRegex(ValueError, "develop needs a prerelease version"):
self.metadata(ref="refs/heads/develop", prs=[pr])

def test_ordinary_builds_allow_both_version_types(self):
for ref in ["refs/heads/main", "refs/heads/develop"]:
for version in ["7.0.0", "7.0.0-beta.2"]:
with self.subTest(ref=ref, version=version):
self.assertEqual(self.metadata(version, ref=ref, prs=[])["release"], "false")


class CommandTests(unittest.TestCase):
def setUp(self):
directory = tempfile.TemporaryDirectory()
self.addCleanup(directory.cleanup)
self.output = Path(directory.name) / "output"
self.output.write_text("previous=value\n")
self.environment = {
"GITHUB_EVENT_NAME": "push",
"GITHUB_REF": "refs/heads/main",
"GITHUB_SHA": SHA,
"GITHUB_REPOSITORY": REPOSITORY,
"GITHUB_OUTPUT": str(self.output),
}
self.addCleanup(patch.stopall)
patch.dict(os.environ, self.environment).start()
patch("sys.argv", ["release.py"]).start()
patch.object(release, "read_version", return_value="7.0.0").start()

def test_paginated_pr_lookup_and_output(self):
unrelated = copy.deepcopy(MERGED_PR)
unrelated["merge_commit_sha"] = "b" * 40
pages = json.dumps([[unrelated] * 100, [MERGED_PR]])
with patch.object(release.subprocess, "check_output", return_value=pages) as api:
release.main()
api.assert_called_once_with([
"gh", "api", "--paginate", "--slurp",
f"repos/{REPOSITORY}/commits/{SHA}/pulls?per_page=100",
], text=True)
self.assertEqual(self.output.read_text(),
"previous=value\nversion=7.0.0\nrelease=true\nprerelease=false\n")

def test_pr_and_manual_runs_do_not_query_api(self):
for event in ["pull_request", "workflow_dispatch"]:
with self.subTest(event=event), patch.dict(os.environ, {"GITHUB_EVENT_NAME": event}):
with patch.object(release.subprocess, "check_output") as api:
release.main()
api.assert_not_called()
self.assertNotIn("release=true", self.output.read_text())

def test_api_failure_does_not_write_release_outputs(self):
with patch.object(release.subprocess, "check_output",
side_effect=subprocess.CalledProcessError(1, "gh")):
with self.assertRaises(subprocess.CalledProcessError):
release.main()
self.assertEqual(self.output.read_text(), "previous=value\n")

def test_tag_check_does_not_query_api_or_write_outputs(self):
with patch("sys.argv", ["release.py", "--check-tag"]):
with patch.object(release, "check_tag") as check:
release.main()
check.assert_called_once_with("7.0.0", SHA)
self.assertEqual(self.output.read_text(), "previous=value\n")


class TagTests(unittest.TestCase):
def test_missing_tag_is_allowed(self):
with patch.object(release.subprocess, "run",
return_value=subprocess.CompletedProcess("git", 1)):
with patch.object(release.subprocess, "check_output") as resolve:
release.check_tag("7.0.0", SHA)
resolve.assert_not_called()

def test_existing_tag_must_resolve_to_release_commit(self):
for target in [SHA, "b" * 40]:
with self.subTest(target=target):
with patch.object(release.subprocess, "run",
return_value=subprocess.CompletedProcess("git", 0)):
with patch.object(release.subprocess, "check_output",
return_value=f"{target}\n") as resolve:
if target == SHA:
release.check_tag("7.0.0", SHA)
else:
with self.assertRaisesRegex(ValueError, "choose a new version"):
release.check_tag("7.0.0", SHA)
resolve.assert_called_once_with(
["git", "rev-parse", "--verify", "refs/tags/7.0.0^{commit}"], text=True
)

def test_git_error_does_not_count_as_missing_tag(self):
with patch.object(release.subprocess, "run",
return_value=subprocess.CompletedProcess("git", 128)):
with self.assertRaises(subprocess.CalledProcessError):
release.check_tag("7.0.0", SHA)


if __name__ == "__main__":
unittest.main()
Loading
Loading