cycsound 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. cycsound-0.2.0/.github/workflows/wheels.yml +112 -0
  2. cycsound-0.2.0/.gitignore +69 -0
  3. cycsound-0.2.0/CHANGELOG.md +379 -0
  4. cycsound-0.2.0/CMakeLists.txt +193 -0
  5. cycsound-0.2.0/LICENSE +26 -0
  6. cycsound-0.2.0/Makefile +111 -0
  7. cycsound-0.2.0/PKG-INFO +305 -0
  8. cycsound-0.2.0/README.md +274 -0
  9. cycsound-0.2.0/TODO.md +15 -0
  10. cycsound-0.2.0/docs/comparison.md +138 -0
  11. cycsound-0.2.0/docs/ctcsound.py +2853 -0
  12. cycsound-0.2.0/docs/dev/deadlock-audit.md +121 -0
  13. cycsound-0.2.0/docs/dev/issues/01-reset-drops-message-string-callback.md +54 -0
  14. cycsound-0.2.0/docs/dev/issues/02-sensekey-ignores-keyboard-callback.md +67 -0
  15. cycsound-0.2.0/docs/dev/issues/03-udp-console-ignores-string-callback.md +61 -0
  16. cycsound-0.2.0/docs/dev/issues/04-init-pvs-channel-before-start-segfault.md +43 -0
  17. cycsound-0.2.0/docs/dev/issues/05-set-pvs-data-overread.md +60 -0
  18. cycsound-0.2.0/docs/dev/issues/06-kill-instance-unlocks-unowned-mutex.md +35 -0
  19. cycsound-0.2.0/docs/dev/issues/07-atexit-destroy-runs-host-callbacks.md +28 -0
  20. cycsound-0.2.0/docs/dev/issues/08-channel-creation-race.md +73 -0
  21. cycsound-0.2.0/docs/dev/issues/09-ugen-init-error-longjmp.md +69 -0
  22. cycsound-0.2.0/docs/dev/issues/10-append-opcode-keeps-caller-strings.md +72 -0
  23. cycsound-0.2.0/docs/dev/issues/11-readline-host-mode-claims-stdin.md +80 -0
  24. cycsound-0.2.0/docs/dev/issues/README.md +21 -0
  25. cycsound-0.2.0/docs/scratch.py +41 -0
  26. cycsound-0.2.0/include/arrays.h +381 -0
  27. cycsound-0.2.0/include/cfgvar.h +252 -0
  28. cycsound-0.2.0/include/coreDefs.h +179 -0
  29. cycsound-0.2.0/include/csPerfThread.h +187 -0
  30. cycsound-0.2.0/include/csPerfThread.hpp +268 -0
  31. cycsound-0.2.0/include/cs_par_structs.h +69 -0
  32. cycsound-0.2.0/include/csdebug.h +487 -0
  33. cycsound-0.2.0/include/csdl.h +200 -0
  34. cycsound-0.2.0/include/csound.h +1594 -0
  35. cycsound-0.2.0/include/csound.hpp +548 -0
  36. cycsound-0.2.0/include/csoundCore.h +1982 -0
  37. cycsound-0.2.0/include/csound_circular_buffer.h +127 -0
  38. cycsound-0.2.0/include/csound_compiler.h +93 -0
  39. cycsound-0.2.0/include/csound_data_structures.h +132 -0
  40. cycsound-0.2.0/include/csound_files.h +228 -0
  41. cycsound-0.2.0/include/csound_graph_display.h +72 -0
  42. cycsound-0.2.0/include/csound_misc.h +237 -0
  43. cycsound-0.2.0/include/csound_rtaudio.h +98 -0
  44. cycsound-0.2.0/include/csound_rtmidi.h +110 -0
  45. cycsound-0.2.0/include/csound_server.h +69 -0
  46. cycsound-0.2.0/include/csound_structs.h +32 -0
  47. cycsound-0.2.0/include/csound_threads.h +228 -0
  48. cycsound-0.2.0/include/csound_type_system.h +182 -0
  49. cycsound-0.2.0/include/csound_types.h +50 -0
  50. cycsound-0.2.0/include/cwindow.h +92 -0
  51. cycsound-0.2.0/include/float-version.h +34 -0
  52. cycsound-0.2.0/include/interlocks.h +61 -0
  53. cycsound-0.2.0/include/modload.h +58 -0
  54. cycsound-0.2.0/include/msg_attr.h +72 -0
  55. cycsound-0.2.0/include/plugin.h +1250 -0
  56. cycsound-0.2.0/include/pstream.h +282 -0
  57. cycsound-0.2.0/include/pvfileio.h +166 -0
  58. cycsound-0.2.0/include/soundfile.h +235 -0
  59. cycsound-0.2.0/include/soundio.h +78 -0
  60. cycsound-0.2.0/include/sysdep.h +678 -0
  61. cycsound-0.2.0/include/text.h +140 -0
  62. cycsound-0.2.0/include/ugen.h +254 -0
  63. cycsound-0.2.0/include/version.h +48 -0
  64. cycsound-0.2.0/pyproject.toml +99 -0
  65. cycsound-0.2.0/scripts/gen.py +42 -0
  66. cycsound-0.2.0/scripts/load_csnd.py +4 -0
  67. cycsound-0.2.0/scripts/parse.py +71 -0
  68. cycsound-0.2.0/scripts/vendor_csound7.sh +135 -0
  69. cycsound-0.2.0/src/cycsound/__init__.py +111 -0
  70. cycsound-0.2.0/src/cycsound/__main__.py +273 -0
  71. cycsound-0.2.0/src/cycsound/_core.pyx +3420 -0
  72. cycsound-0.2.0/src/cycsound/csound.pxd +616 -0
  73. cycsound-0.2.0/tests/README.md +8 -0
  74. cycsound-0.2.0/tests/diskgrain.csd +69 -0
  75. cycsound-0.2.0/tests/example11.py +77 -0
  76. cycsound-0.2.0/tests/example12.py +101 -0
  77. cycsound-0.2.0/tests/example8.py +100 -0
  78. cycsound-0.2.0/tests/example9.py +93 -0
  79. cycsound-0.2.0/tests/init.py +6 -0
  80. cycsound-0.2.0/tests/marimba.aif +0 -0
  81. cycsound-0.2.0/tests/singFemale.aif +0 -0
  82. cycsound-0.2.0/tests/test1.csd +18 -0
  83. cycsound-0.2.0/tests/test_array_channel.py +98 -0
  84. cycsound-0.2.0/tests/test_callbacks.py +467 -0
  85. cycsound-0.2.0/tests/test_channel_control.py +65 -0
  86. cycsound-0.2.0/tests/test_channel_hints.py +82 -0
  87. cycsound-0.2.0/tests/test_channel_update.py +112 -0
  88. cycsound-0.2.0/tests/test_circular_buffer.py +55 -0
  89. cycsound-0.2.0/tests/test_cli.py +105 -0
  90. cycsound-0.2.0/tests/test_compile_file.py +19 -0
  91. cycsound-0.2.0/tests/test_compile_file2.py +24 -0
  92. cycsound-0.2.0/tests/test_compile_text.py +27 -0
  93. cycsound-0.2.0/tests/test_cs7_700.py +121 -0
  94. cycsound-0.2.0/tests/test_cs7_beta18.py +216 -0
  95. cycsound-0.2.0/tests/test_cycsound.py +248 -0
  96. cycsound-0.2.0/tests/test_generate.py +41 -0
  97. cycsound-0.2.0/tests/test_graph_files.py +113 -0
  98. cycsound-0.2.0/tests/test_init.py +9 -0
  99. cycsound-0.2.0/tests/test_loop.py +29 -0
  100. cycsound-0.2.0/tests/test_midi.py +62 -0
  101. cycsound-0.2.0/tests/test_midi_host.py +157 -0
  102. cycsound-0.2.0/tests/test_opcodes_hostdata.py +70 -0
  103. cycsound-0.2.0/tests/test_perf_thread.py +238 -0
  104. cycsound-0.2.0/tests/test_recompile.py +34 -0
  105. cycsound-0.2.0/tests/test_thread.py +43 -0
  106. cycsound-0.2.0/tests/test_ugen.py +332 -0
  107. cycsound-0.2.0/uv.lock +651 -0
@@ -0,0 +1,112 @@
1
+ name: Build Wheels
2
+
3
+ on:
4
+ # Every commit on every branch. A branches filter with no tags filter
5
+ # excludes tag pushes, so the publish job stays off.
6
+ push:
7
+ branches: ["**"]
8
+ workflow_dispatch:
9
+
10
+ # A newer push to the same branch cancels the run in progress.
11
+ concurrency:
12
+ group: ${{ github.workflow }}-${{ github.ref }}
13
+ cancel-in-progress: true
14
+
15
+ jobs:
16
+ build_wheels:
17
+ name: Build wheels on ${{ matrix.os }}
18
+ runs-on: ${{ matrix.os }}
19
+ strategy:
20
+ fail-fast: false
21
+ matrix:
22
+ # macos-13 is retired; macos-15-intel builds the x86_64 wheels.
23
+ os: [ubuntu-latest, macos-15-intel, macos-14, windows-latest]
24
+
25
+ steps:
26
+ - uses: actions/checkout@v7
27
+
28
+ # Linux and macOS builds vendor the Csound 7 release binaries during
29
+ # the CMake configure step (scripts/vendor_csound7.sh). Package managers
30
+ # (apt, Homebrew, choco) still ship Csound 6, which this wrapper does not
31
+ # target. cibuildwheel settings live in pyproject.toml.
32
+ - name: Install Csound 7 (Windows)
33
+ if: runner.os == 'Windows'
34
+ shell: pwsh
35
+ run: |
36
+ $zip = Join-Path $env:RUNNER_TEMP csound.zip
37
+ $dir = Join-Path $env:RUNNER_TEMP csound
38
+ Invoke-WebRequest https://github.com/csound/csound/releases/download/7.0.0/csound-windows-7.0.0.zip -OutFile $zip
39
+ Expand-Archive $zip -DestinationPath $dir
40
+ $exe = Get-ChildItem $dir -Recurse -Filter *.exe | Select-Object -First 1
41
+ # Inno Setup installer; pin the path CMakeLists.txt expects.
42
+ Start-Process $exe.FullName -Wait -ArgumentList '/VERYSILENT','/SUPPRESSMSGBOXES','/NORESTART','/DIR="C:\Program Files\Csound7_x64"'
43
+ Get-ChildItem 'C:\Program Files\Csound7_x64' -Recurse -Include csound64.*,csound.h | Select-Object FullName
44
+
45
+ - name: Build wheels
46
+ uses: pypa/cibuildwheel@v3.3.1
47
+
48
+ - uses: actions/upload-artifact@v7
49
+ with:
50
+ name: wheels-${{ matrix.os }}
51
+ path: ./wheelhouse/*.whl
52
+
53
+ build_sdist:
54
+ name: Build source distribution
55
+ runs-on: ubuntu-latest
56
+ steps:
57
+ - uses: actions/checkout@v7
58
+
59
+ - name: Build sdist
60
+ run: pipx run build --sdist
61
+
62
+ - uses: actions/upload-artifact@v7
63
+ with:
64
+ name: sdist
65
+ path: dist/*.tar.gz
66
+
67
+ # One download with every wheel and the sdist. Runs when some builds fail,
68
+ # so the successful ones are still downloadable.
69
+ collect:
70
+ name: Collect distributions
71
+ needs: [build_wheels, build_sdist]
72
+ if: ${{ !cancelled() }}
73
+ runs-on: ubuntu-latest
74
+ steps:
75
+ - uses: actions/download-artifact@v8
76
+ with:
77
+ path: dist
78
+ merge-multiple: true
79
+
80
+ - run: ls -l dist
81
+
82
+ - uses: actions/upload-artifact@v7
83
+ with:
84
+ name: cycsound-dist
85
+ path: dist/*
86
+ if-no-files-found: error
87
+
88
+ publish:
89
+ name: Publish to PyPI
90
+ needs: [build_wheels, build_sdist]
91
+ runs-on: ubuntu-latest
92
+ if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
93
+ environment:
94
+ name: pypi
95
+ url: https://pypi.org/p/cycsound
96
+ permissions:
97
+ id-token: write
98
+
99
+ steps:
100
+ - uses: actions/download-artifact@v8
101
+ with:
102
+ pattern: wheels-*
103
+ path: dist
104
+ merge-multiple: true
105
+
106
+ - uses: actions/download-artifact@v8
107
+ with:
108
+ name: sdist
109
+ path: dist
110
+
111
+ - name: Publish to PyPI
112
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,69 @@
1
+ .DS_Store
2
+ __pycache__
3
+ *.pyc
4
+ .pytest_cache
5
+ .venv
6
+ cycsound.c
7
+ dist/
8
+ build/
9
+ tests/test.aif
10
+
11
+ # Prerequisites
12
+ *.d
13
+
14
+ # Object files
15
+ *.o
16
+ *.ko
17
+ *.obj
18
+ *.elf
19
+
20
+ # Linker output
21
+ *.ilk
22
+ *.map
23
+ *.exp
24
+
25
+ # Precompiled Headers
26
+ *.gch
27
+ *.pch
28
+
29
+ # Libraries
30
+ *.lib
31
+ *.a
32
+ *.la
33
+ *.lo
34
+
35
+ # Shared objects (inc. Windows DLLs)
36
+ *.dll
37
+ *.so
38
+ *.so.*
39
+ *.dylib
40
+
41
+ # Executables
42
+ *.exe
43
+ *.out
44
+ *.app
45
+ *.i*86
46
+ *.x86_64
47
+ *.hex
48
+
49
+ # Debug files
50
+ *.dSYM/
51
+ *.su
52
+ *.idb
53
+ *.pdb
54
+
55
+ # Kernel Module Compile Results
56
+ *.mod*
57
+ *.cmd
58
+ .tmp_versions/
59
+ modules.order
60
+ Module.symvers
61
+ Mkfile.old
62
+ dkms.conf
63
+
64
+ # claude
65
+ .claude
66
+ CLAUDE.md
67
+
68
+ # vendored Csound 7 framework (fetched via scripts/vendor_csound7.sh)
69
+ vendor/
@@ -0,0 +1,379 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ Sync to Csound **7.0.0** (from beta.16).
11
+
12
+ ### Build
13
+
14
+ - `scripts/vendor_csound7.sh` now defaults to the `7.0.0` tag and
15
+ vendors on Linux as well as macOS. On Linux it stages the
16
+ `csound-linux-minimal-x86_64` release asset as `vendor/lib/` (the shared
17
+ library) and `vendor/plugins/` (the opcode plugins), and gives the plugins an
18
+ rpath to the vendored library when `patchelf` is available.
19
+ - `CMakeLists.txt`: the Linux branch links the vendored `libcsound64.so` with an
20
+ rpath instead of a system `csound64`, and takes its headers from the
21
+ repo-local `include/` (the Linux release ships none). It auto-runs the
22
+ vendoring script when the library is missing, as the macOS branch does. A
23
+ distro `csound` package is Csound 6, whose host API this wrapper does not
24
+ target.
25
+ - `CMakeLists.txt`: the Windows branch points at `C:/Program Files/Csound7_x64`
26
+ (was `Csound6_x64`) and exposes `CSOUND_ROOT` as a cache variable.
27
+ - `Makefile`: the vendoring prerequisite resolves per platform
28
+ (`vendor/CsoundLib64.framework` on macOS, `vendor/lib/libcsound64.so`
29
+ elsewhere).
30
+ - CI and `cibuildwheel` no longer install a system Csound 6 on Linux/macOS;
31
+ those builds vendor Csound 7 during CMake configure.
32
+ - `include/` synced to the beta.18 headers. beta.18 renames `MYFLT` to
33
+ `cs_float` (`csound_types.h`) and re-declares `MYFLT` as a deprecated
34
+ typedef, so the `MYFLT=double` compile definition is dropped: it expanded
35
+ that typedef to `typedef cs_float double` and broke the build.
36
+ - `include/` synced to the 7.0.0 headers. No wrapped signature changed;
37
+ `OPARMS` gains a trailing `no_deprecated_warnings` field.
38
+ - Wheels: the upstream 7.0.0 binaries raise the platform floors. macOS wheels
39
+ target 12.0, and Linux wheels build on `manylinux_2_34` because
40
+ `libcsound64.so` needs `GLIBC_2.29`. Windows CI installs the 7.0.0 release
41
+ (choco ships 6.17), and `delvewheel` bundles `csound64.dll`. `macos-13`
42
+ (retired) is replaced by `macos-15-intel`. cibuildwheel settings now live
43
+ only in `pyproject.toml`. The wheel workflow runs on every branch push;
44
+ tag pushes, and so PyPI publishing, stay off.
45
+
46
+ ### Changed
47
+
48
+ - `Csound.event()` raises `RuntimeError` when called synchronously before
49
+ `start()`. Csound dropped such events silently (both `i` and `f`). Use
50
+ `event_string()`, or `async_=True` for at most 1024 queued events.
51
+ - `set_keyboard_callback()` registers its C function once and swaps only the
52
+ Python callable, including on `None`. Csound frees keyboard-list entries
53
+ without a lock while the performing thread walks the list.
54
+ - The C host data of a `Csound` instance now points at its callback state,
55
+ not at the `host_data` object. Code that read `csoundGetHostData()` from
56
+ Cython must use `Csound.host_data`.
57
+
58
+ ### Fixed
59
+
60
+ - `run()` returned 0 when performance failed. In 7.0.0 `csoundPerformKsmps`
61
+ returns a negative value on error, e.g. on re-entry from a callback during
62
+ a render pass. `run()` now returns that value.
63
+ - `compile_tree()` and `udp_server_close()` deadlocked with a callback set. A
64
+ performing thread holds `API_lock` while its callback waits for the GIL.
65
+ `compile_tree` held the GIL while its sync merge waited for `API_lock`.
66
+ `udp_server_close` held it while joining a server thread that prints on
67
+ exit. Both now release the GIL. See `docs/dev/deadlock-audit.md`.
68
+ - `udp_console()` and a message callback or hook now raise `RuntimeError`
69
+ while the other is active: the console replaces Csound's message callback.
70
+ `stop_udp_console()` re-installs the callback.
71
+ - Callback workarounds for Csound behaviour, found while adding callbacks
72
+ (upstream drafts in `docs/dev/issues/`):
73
+ - `reset()` reverts the message string callback to the default printer;
74
+ the wrapper re-installs it.
75
+ - `csoundDoCallback_` returns 1 even when a keyboard callback succeeds, so
76
+ `sensekey` polled stdin and overwrote the key. Registering a keyboard
77
+ callback or hook now calls `csoundKeyPress(cs, 0)`, which stops the
78
+ polling, and repeats it after `reset()`.
79
+ - Csound's C atexit handler destroys live instances after Python has
80
+ finalized. A message callback then blocked forever waiting for the GIL.
81
+ Callbacks are now detached from live instances in a Python atexit
82
+ handler, and the trampolines skip Python once finalization starts.
83
+ - `Csound.get_control_channel_hints()` leaked the `attributes` string; Csound
84
+ allocates a copy per call, now released with
85
+ `csoundDeleteControlChannelHints`.
86
+ - Linux: the extension is now loaded with `RTLD_GLOBAL`, and `OPCODE7DIR64` is
87
+ pointed at `vendor/plugins` for a source build when it is unset. The Linux
88
+ release is a minimal build whose opcode library ships as external plugins;
89
+ those resolve Csound symbols out of the global symbol namespace, so without
90
+ this they failed to register silently and orchestras using them (`vco2`,
91
+ `moogladder`, `diskgrain`, ...) did not compile.
92
+
93
+ ### Added
94
+
95
+ - `csound.pxd` now declares the full public surface of Csound 7's `csound.h`.
96
+ Previously missing: the message callbacks (`csoundSetMessageCallback`,
97
+ `csoundSetMessageStringCallback`, `csoundSetDefaultMessageCallback`,
98
+ `csoundMessageV`), the keyboard callbacks
99
+ (`csoundRegisterKeyboardCallback`/`csoundRemoveKeyboardCallback`), the
100
+ input/output channel callbacks, `csoundGetChannelVarType`, the array/string/
101
+ fsig channel data accessors (`csoundInitArrayChannel`, `csoundArrayData*`,
102
+ `csoundGet`/`SetArrayData`, `csoundGet`/`SetStringData`,
103
+ `csoundInitPvsChannel`, `csoundPvsData*`, `csoundGet`/`SetPvsData`),
104
+ `csoundAppendOpcode`/`csoundAppendOpcodes`, the `DEBUG_STATUS` levels, the
105
+ `CSOUNDINIT_*` flags and the `CSOUND_CALLBACK_KBD_*` types.
106
+ - `Csound.set_audio_channel(name, block)` / `Csound.get_audio_channel(name)` -
107
+ read and write a ksmps-sized block on an audio channel.
108
+ - `Csound.set_control_channel_hints(...)` /
109
+ `Csound.get_control_channel_hints(name)` - control channel display hints,
110
+ with a `ChannelBehavior` enum for the behaviour values.
111
+ - `Csound.run_utility(name, *args)` - `csoundRunUtility`.
112
+ - `set_global_env(name, value)` and `sleep(milliseconds)` module functions.
113
+ - beta.18 host API:
114
+ - `Csound.set_command_line_args(*args)` / `get_command_line_args()` - the
115
+ arguments after `--` that the orchestra reads with `argv`.
116
+ - `Csound.copy_channel(source, dest)` - `csoundGetChannel` +
117
+ `csoundSetChannel`; copies a channel of any type. A raw `CS_VAR_MEM`
118
+ handle is not exposed: Python cannot interpret it without the type system.
119
+ - `Csound.init_pvs_channel(...)`, `set_pvs_channel(name, data)`,
120
+ `get_pvs_channel(name, data)`, and the `PvsData`, `PvsWindow` and
121
+ `PvsFormat` types. `init_pvs_channel` raises before `start()` because
122
+ Csound segfaults allocating the frame then. A `PvsData` raises after
123
+ `reset()` instead of reading freed memory. `PvsData.set_frame` writes
124
+ exactly `fft_size + 2` floats instead of calling `csoundSetPvsData`, which
125
+ copies the allocated frame size and overreads once a smaller frame has
126
+ been copied in.
127
+ - `Csound.readline_push_text(text)` - input for the `readline` opcode.
128
+ - `Csound.perform_output_fade(frames)` - a no-op without realtime output.
129
+ - `get_size_of_cs_float()` / `get_size_of_cs_double()`;
130
+ `get_size_of_myflt` is kept as an alias.
131
+ - `Params.recursion_depth`.
132
+ - 7.0.0 host API: `Csound.set_readline_callback(callback)` and
133
+ `readline_submit(request_id, text)`, host-side input for the `readline`
134
+ opcode. `callback(request_id, prompt)` gets the prompt when a line request
135
+ opens and `None` when it closes. 7.0.0 `readline` claims stdin for the whole
136
+ process even in this mode, so a second live instance gets an init error
137
+ (`docs/dev/issues/11-readline-host-mode-claims-stdin.md`).
138
+ - Host-implemented realtime MIDI (`csound_rtmidi.h`), for use with
139
+ `set_host_midi_io()`: `set_midi_in_callbacks(open, read, close)`,
140
+ `set_midi_out_callbacks(open, write, close)`,
141
+ `set_midi_device_list_callback()` and `set_midi_error_string_hook()`. Each
142
+ slot takes a Python callable or a C function address. `open` returns a
143
+ handle object that `read`/`write`/`close` receive, so no `void **` user data
144
+ reaches Python. A raised exception returns -1 to Csound. The wrapper
145
+ re-installs the callbacks after `reset()`, which replaces them with
146
+ Csound's dummies.
147
+ - Unit generator API (`ugen.h`): `Csound.ugen_factory()` returns a
148
+ `UgenFactory`, which creates `Ugen`, `UgenVar`, `UgenContext` and
149
+ `UgenGraph` objects and lists opcode entries. Each object keeps its parents
150
+ alive, because `csoundUgenDelete()` runs the opcode's deinit against the
151
+ factory's or context's state, and a wired input reads its source's memory.
152
+ The objects raise after `reset()`, which frees their pool memory.
153
+ `set_input_var()` rejects mismatched types, and `UgenVar.value` rejects
154
+ non-scalars: Csound checks neither, and a k source on an a input reads
155
+ ksmps floats from a one-float slot. `Ugen.set_in_value()`,
156
+ `get_out_value()`, `set_in_string()` and `get_out_string()` call the
157
+ `csoundUgen*Value`/`*String` index helpers directly with the same guards;
158
+ they avoid a `UgenVar` per call (about 45 ns vs 105 ns).
159
+ `UgenGraph.delete_all()` frees the graph and its Ugens immediately
160
+ (`csoundUgenGraphDeleteAll`), so deinit runs without waiting for the GC.
161
+ It refuses a Ugen that occurs twice, sits in another graph, or feeds a Ugen
162
+ outside the graph, since each would then use freed memory; afterwards the
163
+ deleted objects raise on use. `Ugen.delete()` does the same for one Ugen
164
+ (`csoundUgenDelete`) and refuses while any graph holds it. An opcode init error crashes the
165
+ process (`docs/dev/issues/09-ugen-init-error-longjmp.md`).
166
+ - Array channels: `Csound.init_array_channel(name, type, sizes)` returns an
167
+ `ArrayChannel` with `type`, `sizes`, flat `get()`/`set()` for i/k/a arrays
168
+ and `get_string()`/`set_string()` for S arrays. Element strides come from
169
+ `ARRAYDAT`, so the build now includes the plugin header `csdl.h`.
170
+ `set_string()` refuses elements whose buffer aliases another string:
171
+ `csoundSetStringData()` would write into, or reallocate, memory it does not
172
+ own.
173
+ - `Csound.create_circular_buffer(size, element_size)` returns a
174
+ `CircularBuffer` (Csound's single-reader, single-writer ring buffer) that
175
+ moves bytes in whole elements and exposes its `address` for C code.
176
+ - Graph display: `set_is_graphable()` and `set_graph_callbacks(make, draw,
177
+ kill, exit)`. Python callbacks get a `GraphWindow` snapshot, since the
178
+ `WINDAT` belongs to Csound; `make()` may return the window's `windid`. The
179
+ wrapper re-applies both after `reset()`: reset clears the graphable flag,
180
+ and a `-d` run leaves no-op callbacks in the block reset preserves. Csound
181
+ 7.0.0 never calls `kill`.
182
+ - File callbacks: `set_file_open_callback()` (notification, with a
183
+ `FileType` enum), `set_open_file_callback()` and
184
+ `set_open_sound_file_callback()` (return a `FILE *` or sound-file handle
185
+ address, or None to let Csound open the file), and
186
+ `set_sndfile_callbacks(address)`. Csound reverts the sndfile callbacks on
187
+ `reset()`, so the wrapper keeps a copy of the struct and re-applies it.
188
+ - `append_opcode()` (C function addresses) and `append_opcodes()` (an
189
+ `OENTRY` array) register C opcodes; `OPDS_SIZE` locates the argument
190
+ pointers. Csound keeps the name and type-string pointers instead of copying
191
+ them, so `append_opcode()` holds the encoded strings until `reset()`
192
+ (`docs/dev/issues/10-append-opcode-keeps-caller-strings.md`).
193
+ - `Csound.host_data` is settable. `csoundSetHostData` itself stays
194
+ unexposed: the C host data routes callbacks to the instance.
195
+ - Callbacks for messages, `invalue`/`outvalue` channels and keyboard input,
196
+ at two levels:
197
+ - Python callables, called with the GIL: `set_message_callback`,
198
+ `set_input_channel_callback`, `set_output_channel_callback`,
199
+ `set_keyboard_callback`, with a `KeyCallbackType` enum.
200
+ - C function pointers, passed as int addresses and called without the GIL:
201
+ `set_message_hook`, `set_input_channel_hook`, `set_output_channel_hook`,
202
+ `register_keyboard_hook` / `remove_keyboard_hook`. Addresses over a
203
+ Cython-level API because the wheel ships no `.pxd`; any C-callable
204
+ (Cython, ctypes, cffi, numba `cfunc`) works.
205
+
206
+ A hook and a callback may share a slot; the hook runs first. A message
207
+ callback and the message buffer cannot both be set, since Csound routes to
208
+ one: each setter raises `RuntimeError` while the other is active.
209
+ - `Csound.host_data` returns the constructor's `host_data`.
210
+ - `CsoundPerformanceThread(csound)`: performs a started instance on a native
211
+ thread, wrapping Csound's `csPerfThread` C API. Its unbounded, locked
212
+ message queue is the safe way to send events, code and score changes from
213
+ other threads. The Csound API's own `async` queue holds 1024 entries and
214
+ busy-waits when full. Process callbacks come at both levels
215
+ (`set_process_callback`, `set_process_hook`). `eval_code` and
216
+ `request_callback` run Python on the performance thread. `join`,
217
+ `flush_message_queue` and destruction release the GIL, and live threads
218
+ are stopped at interpreter exit. `tests/test_thread.py` is rewritten for
219
+ the Csound 7 API and no longer skipped.
220
+
221
+ ### Known gaps
222
+
223
+ Declared in `csound.pxd` but not exposed: `csoundTableCopyOut`,
224
+ `csoundScoreSort`/`csoundScoreExtract` (`FILE *` arguments), `csoundMessageV`
225
+ (`va_list`), `csoundSetDefaultMessageCallback`, and `csoundGetChannelVarType`
226
+ (`get_channel_var_type_name()` returns the same name).
227
+
228
+ ## [0.2.0]
229
+
230
+ Port from Csound 6 to **Csound 7** (beta). Csound 7 reworked the host C API, so
231
+ this is a breaking change to both the build and the Python API.
232
+
233
+ ### Build
234
+
235
+ - Target the Csound 7 `CsoundLib64.framework` on macOS, vendored into a
236
+ repo-local `vendor/` directory by `scripts/vendor_csound7.sh` (downloads the
237
+ beta `.pkg`, extracts the framework, repoints its install id, and re-signs).
238
+ `/Library/Frameworks` is no longer used, so a Csound 6 install can coexist.
239
+ - The framework is downloaded over plain HTTPS with `curl` (with a `wget`
240
+ fallback) from the public GitHub release asset -- no GitHub account or `gh`
241
+ CLI required. A tag can be passed to the script, e.g.
242
+ `scripts/vendor_csound7.sh 7.0.0-beta.16`.
243
+ - The framework is fetched automatically when missing: the `build`/`test`/`sync`
244
+ Makefile targets carry an order-only prerequisite on
245
+ `vendor/CsoundLib64.framework`, and `CMakeLists.txt` also runs the vendoring
246
+ script during configure (covering `pip install .` / `uv build`, which do not
247
+ go through `make`).
248
+ - `CMakeLists.txt` points at the vendored framework; macOS static linking is
249
+ disabled (the framework ships dynamic libraries only).
250
+ - `include/` headers refreshed to the Csound 7 set.
251
+ - `make test` now invokes `python -m pytest`.
252
+
253
+ ### Changed / Added (Python API mirrors Csound 7)
254
+
255
+ - `Csound(host_data=None, opcodedir=None)` - `csoundCreate` takes an opcode
256
+ directory override.
257
+ - `compile_orc(orc, async_=False)` - merged async flag (no `compile_orc_async`).
258
+ - `compile_csd(file)` / `compile_csd_text(text)` - both call `csoundCompileCSD`.
259
+ - `event(type, params, async_=False)` and `event_string(message, async_=False)`
260
+ replace `read_score`, `input_message`, and the `score_event*` family.
261
+ - `get_instr_number(name)`, `get_kcounter()`, `err_cnt()`, `lock_channel()` /
262
+ `unlock_channel()`, `set_host_audio_io()` / `set_host_midi_io()`,
263
+ `get_channels(is_input)` (plus `get_nchnls()`/`get_nchnls_input()` helpers).
264
+ - `get_params()` returns a **read-only** `Params` view of the `OPARMS` struct;
265
+ `set_params()` was removed (options are set via `set_option`).
266
+ - New `EventType` and `ChannelType` `cpdef` enums.
267
+ - Context manager now calls `reset()` on exit (there is no `csoundCleanup`).
268
+ - Pure-Python channel/table helpers: `list_channels()`, `get_string_channel()`,
269
+ `get_table()`, `get_table_args()`, `set_table()`, `get_audio_dev_list()`,
270
+ `get_midi_dev_list()`, `get_module()`.
271
+
272
+ ### Removed (no Csound 7 equivalent)
273
+
274
+ - `get_api_version()`, `set_opcode_dir()`, `FileType` enum.
275
+ - `perform()`, `perform_buffer()`, `stop()`, `cleanup()`.
276
+ - `read_score()`, `score_event*`, `input_message*`, `kill_instance()`.
277
+ - `table_get()` / `table_set()` single-slot accessors; the input/output buffer
278
+ and spin-sample accessors; `is_named_gen()` / `get_named_gen()`.
279
+ - Low-level Cython-only helpers for threads, mutexes, circular buffers, global
280
+ variables, utilities, RNG, timers, and the various callback setters were not
281
+ carried over in this port and may be re-added against the Csound 7 callback
282
+ API in a later release.
283
+
284
+ ## [0.1.0]
285
+
286
+ ### Added
287
+
288
+ - Initial scikit-build-core based build system
289
+ - **Context manager support** for `Csound` class:
290
+ ```python
291
+ with cycsound.Csound() as cs:
292
+ cs.compile_csd("score.csd")
293
+ cs.run()
294
+ # cleanup() called automatically
295
+ ```
296
+ - **`run()` method** - convenience method to perform entire score
297
+ - **`Status` enum** (`cpdef enum`) - Csound return status codes:
298
+ - `Status.SUCCESS`, `Status.ERROR`, `Status.INITIALIZATION`
299
+ - `Status.PERFORMANCE`, `Status.MEMORY`, `Status.SIGNAL`
300
+ - **`FileType` enum** (`cpdef enum`) - 66 Csound file type constants:
301
+ - Audio: `WAVE`, `FLAC`, `OGG`, `AIFF`, `AU`, `CAF`, `MPEG`, etc.
302
+ - Csound: `UNIFIED_CSD`, `ORCHESTRA`, `SCORE`
303
+ - MIDI: `STD_MIDI`, `MIDI_SYSEX`, `SOUNDFONT`
304
+ - Analysis: `HETRO`, `PVC`, `PVCEX`, `CVANAL`, `LPC`, `ATS`, `SDIF`
305
+ - Command-line interface (`cycsound` command):
306
+ - `play` - Play CSD files in real-time
307
+ - `render` - Render CSD to audio files (WAV, AIFF, FLAC, OGG)
308
+ - `check` - Validate CSD syntax
309
+ - `info` - Display Csound version information
310
+ - `eval` - Evaluate orchestra code
311
+ - Static linking support for macOS (`make wheel-static` or `-C cmake.define.STATIC=ON`)
312
+ - Bundles Csound and all audio codec libraries
313
+ - Produces standalone wheel (~2MB) requiring no Csound installation
314
+ - Only depends on system libraries and macOS frameworks
315
+ - CMakeLists.txt with platform-specific Csound library detection:
316
+ - macOS: CsoundLib64 framework at `/Library/Frameworks/`
317
+ - Windows: Csound6_x64 installation at `C:/Program Files/Csound6_x64/`
318
+ - Linux: System `csound64` library
319
+ - Test suite with 36 tests covering:
320
+ - Core API functions (version, options, compilation, performance)
321
+ - CLI commands (play, render, check, info, eval)
322
+ - Channel control and updates
323
+ - Threading and recompilation
324
+ - MIDI functionality
325
+ - GIL release support for all blocking operations, enabling true multi-threading
326
+ - GitHub Actions workflow for CI/CD with cibuildwheel
327
+ - PyPI publication support (`make publish`, `make publish-test`)
328
+
329
+ ### Changed
330
+
331
+ - **Build system**: Migrated from setuptools/Cython to scikit-build-core/CMake
332
+ - **Project structure**: Reorganized to `src/cycsound/` layout
333
+ - **API naming**: Renamed `csoundInitialize()` to `initialize()` for consistency
334
+ - **GIL handling**: Added `nogil` declarations to all 219 C API functions in `csound.pxd`
335
+ - **Performance methods**: Now release the GIL during execution:
336
+ - `perform()`, `perform_ksmps()`, `perform_buffer()`
337
+ - `start()`, `stop()`, `cleanup()`, `reset()`
338
+ - **Compilation methods**: Now release the GIL during execution:
339
+ - `compile_orc()`, `compile_orc_async()`, `eval_code()`
340
+ - `compile_args()`, `compile()`, `compile_csd()`, `compile_csd_text()`
341
+ - **Score methods**: `read_score()`, `read_score_async()` now release the GIL
342
+ - **Threading primitives**: All blocking operations release the GIL:
343
+ - `join_thread()`, `wait_thread_lock()`, `wait_thread_lock_no_timeout()`
344
+ - `lock_mutex()`, `wait_barrier()`, `cond_wait()`, `sleep()`, `spin_lock()`
345
+ - **Status return types**: Functions now return `Status` enum instead of `int`:
346
+ - `set_option()`, `udp_server_start()`, `udp_server_close()`, `upd_console()`
347
+ - Allows comparison with `Status.SUCCESS`, `Status.ERROR`, etc.
348
+
349
+ ### Fixed
350
+
351
+ - GIL contention issues that prevented effective multi-threading
352
+ - GUI freezing when calling blocking Csound operations
353
+
354
+ ### API Coverage
355
+
356
+ - 98.6% coverage of the Csound C API (216/219 functions)
357
+ - Intentionally unwrapped (3 functions using `va_list`):
358
+ - `csoundMessageV` - use `message()` instead
359
+ - `csoundSetDefaultMessageCallback` - use `set_message_string_callback()` instead
360
+ - `csoundSetMessageCallback` - use `set_message_string_callback()` instead
361
+
362
+ ### Build Requirements
363
+
364
+ - Python >= 3.9
365
+ - scikit-build-core
366
+ - Cython
367
+ - CMake >= 3.15
368
+ - Csound 6.x installed:
369
+ - macOS: CsoundLib64.framework in `/Library/Frameworks/`
370
+ - Windows: Csound6_x64 in `C:/Program Files/`
371
+ - Linux: libcsound64 development package
372
+
373
+ ### Static Build Requirements (macOS only)
374
+
375
+ For `make wheel-static`:
376
+ - Homebrew with static libraries installed:
377
+ - `brew install libsndfile flac opus libvorbis libogg mpg123 lame`
378
+ - Static libraries from CsoundLib64.framework:
379
+ - `libCsoundLib64.a`, `libcsnd6.a`