libcsound 0.18.2__tar.gz → 0.18.3__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 (60) hide show
  1. {libcsound-0.18.2 → libcsound-0.18.3}/PKG-INFO +1 -1
  2. {libcsound-0.18.2 → libcsound-0.18.3}/libcsound/_install.py +36 -252
  3. {libcsound-0.18.2 → libcsound-0.18.3}/libcsound/_util.py +36 -21
  4. {libcsound-0.18.2 → libcsound-0.18.3}/libcsound/data/getcsound.ps1 +100 -29
  5. libcsound-0.18.3/libcsound/data/getcsound.sh +949 -0
  6. {libcsound-0.18.2 → libcsound-0.18.3}/libcsound.egg-info/PKG-INFO +1 -1
  7. libcsound-0.18.3/libcsound.egg-info/scm_version.json +8 -0
  8. libcsound-0.18.2/libcsound/data/getcsound.sh +0 -506
  9. libcsound-0.18.2/libcsound.egg-info/scm_version.json +0 -8
  10. {libcsound-0.18.2 → libcsound-0.18.3}/.github/workflows/test.yml +0 -0
  11. {libcsound-0.18.2 → libcsound-0.18.3}/AGENTS.md +0 -0
  12. {libcsound-0.18.2 → libcsound-0.18.3}/LICENSE +0 -0
  13. {libcsound-0.18.2 → libcsound-0.18.3}/MANIFEST.in +0 -0
  14. {libcsound-0.18.2 → libcsound-0.18.3}/README.rst +0 -0
  15. {libcsound-0.18.2 → libcsound-0.18.3}/cmake/csound-custom-macos.cmake +0 -0
  16. {libcsound-0.18.2 → libcsound-0.18.3}/docs/Makefile +0 -0
  17. {libcsound-0.18.2 → libcsound-0.18.3}/docs/_static/custom.css +0 -0
  18. {libcsound-0.18.2 → libcsound-0.18.3}/docs/conf.py +0 -0
  19. {libcsound-0.18.2 → libcsound-0.18.3}/docs/csound6.rst +0 -0
  20. {libcsound-0.18.2 → libcsound-0.18.3}/docs/csound7.rst +0 -0
  21. {libcsound-0.18.2 → libcsound-0.18.3}/docs/index.rst +0 -0
  22. {libcsound-0.18.2 → libcsound-0.18.3}/docs/introduction.rst +0 -0
  23. {libcsound-0.18.2 → libcsound-0.18.3}/docs/make.bat +0 -0
  24. {libcsound-0.18.2 → libcsound-0.18.3}/docs/notebooks/01-Introduction.ipynb +0 -0
  25. {libcsound-0.18.2 → libcsound-0.18.3}/docs/notebooks/02-Threading.ipynb +0 -0
  26. {libcsound-0.18.2 → libcsound-0.18.3}/docs/notebooks/no-queue.png +0 -0
  27. {libcsound-0.18.2 → libcsound-0.18.3}/docs/notebooks/no-queue.wav +0 -0
  28. {libcsound-0.18.2 → libcsound-0.18.3}/docs/notebooks/threading-example1.mp3 +0 -0
  29. {libcsound-0.18.2 → libcsound-0.18.3}/docs/notebooks/threading-example2.mp3 +0 -0
  30. {libcsound-0.18.2 → libcsound-0.18.3}/docs/notebooks/with-queue.wav +0 -0
  31. {libcsound-0.18.2 → libcsound-0.18.3}/docs/notebooks.rst +0 -0
  32. {libcsound-0.18.2 → libcsound-0.18.3}/docs/porting.rst +0 -0
  33. {libcsound-0.18.2 → libcsound-0.18.3}/docs/requirements.txt +0 -0
  34. {libcsound-0.18.2 → libcsound-0.18.3}/libcsound/__init__.py +0 -0
  35. {libcsound-0.18.2 → libcsound-0.18.3}/libcsound/_dll.py +0 -0
  36. {libcsound-0.18.2 → libcsound-0.18.3}/libcsound/_terminal.py +0 -0
  37. {libcsound-0.18.2 → libcsound-0.18.3}/libcsound/api6.py +0 -0
  38. {libcsound-0.18.2 → libcsound-0.18.3}/libcsound/api7.py +0 -0
  39. {libcsound-0.18.2 → libcsound-0.18.3}/libcsound/common.py +0 -0
  40. {libcsound-0.18.2 → libcsound-0.18.3}/libcsound/py.typed +0 -0
  41. {libcsound-0.18.2 → libcsound-0.18.3}/libcsound.egg-info/SOURCES.txt +0 -0
  42. {libcsound-0.18.2 → libcsound-0.18.3}/libcsound.egg-info/dependency_links.txt +0 -0
  43. {libcsound-0.18.2 → libcsound-0.18.3}/libcsound.egg-info/requires.txt +0 -0
  44. {libcsound-0.18.2 → libcsound-0.18.3}/libcsound.egg-info/scm_file_list.json +0 -0
  45. {libcsound-0.18.2 → libcsound-0.18.3}/libcsound.egg-info/top_level.txt +0 -0
  46. {libcsound-0.18.2 → libcsound-0.18.3}/pyproject.toml +0 -0
  47. {libcsound-0.18.2 → libcsound-0.18.3}/setup.cfg +0 -0
  48. {libcsound-0.18.2 → libcsound-0.18.3}/test/actions-test1.py +0 -0
  49. {libcsound-0.18.2 → libcsound-0.18.3}/test/actionstest.csd +0 -0
  50. {libcsound-0.18.2 → libcsound-0.18.3}/test/bufferInOut.csd +0 -0
  51. {libcsound-0.18.2 → libcsound-0.18.3}/test/check-installer-sync.py +0 -0
  52. {libcsound-0.18.2 → libcsound-0.18.3}/test/linux-install-render.py +0 -0
  53. {libcsound-0.18.2 → libcsound-0.18.3}/test/macos-install-render.py +0 -0
  54. {libcsound-0.18.2 → libcsound-0.18.3}/test/numpfields.csd +0 -0
  55. {libcsound-0.18.2 → libcsound-0.18.3}/test/simple1.csd +0 -0
  56. {libcsound-0.18.2 → libcsound-0.18.3}/test/simple2.csd +0 -0
  57. {libcsound-0.18.2 → libcsound-0.18.3}/test/test-numpfields.py +0 -0
  58. {libcsound-0.18.2 → libcsound-0.18.3}/test/test0.py +0 -0
  59. {libcsound-0.18.2 → libcsound-0.18.3}/test/test1.py +0 -0
  60. {libcsound-0.18.2 → libcsound-0.18.3}/test/windows-install-render.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: libcsound
3
- Version: 0.18.2
3
+ Version: 0.18.3
4
4
  Summary: Python bindings to the Csound API, supporting all versions of csound higher or equal to 6.18, including csound 7
5
5
  Author-email: Eduardo Moguillansky <eduardo.moguillansky@gmail.com>
6
6
  License: GNU LESSER GENERAL PUBLIC LICENSE
@@ -1,6 +1,5 @@
1
1
  from __future__ import annotations
2
2
  import ctypes as ct
3
- import hashlib
4
3
  from pathlib import Path
5
4
  from typing import Sequence
6
5
 
@@ -8,134 +7,6 @@ from .common import logger
8
7
  from ._terminal import *
9
8
 
10
9
 
11
- def print_download_info(url: str, target: str) -> None:
12
- """Print a compact, colored summary of an imminent download."""
13
- stream = sys.stdout
14
- asset = os.path.basename(target)
15
- target_dir = os.path.dirname(target) or "."
16
-
17
- title = paint(f"Downloading {asset}", "bold", "cyan", stream=stream)
18
- rule_char = "─" if supports_color(stream) and supports_unicode(stream) else "-"
19
- try:
20
- width = os.get_terminal_size().columns
21
- except Exception:
22
- width = 80
23
- rule = rule_char * max(0, width - 4)
24
-
25
- print()
26
- print(f" {title}")
27
- print(f" {rule}")
28
- for label, value, color in (
29
- ("source:", url, "blue"),
30
- ("target:", target_dir, "blue"),
31
- ):
32
- label_text = paint(f"{label:<8}", "bold", stream=stream)
33
- value_text = paint(value, color, stream=stream)
34
- print(f" {label_text} {value_text}")
35
- print()
36
-
37
-
38
- def hexdigest(path: str) -> str:
39
- hashsum = hashlib.sha256()
40
- with open(path, "rb") as f:
41
- for chunk in iter(lambda: f.read(65536), b""):
42
- hashsum.update(chunk)
43
- return hashsum.hexdigest()
44
-
45
-
46
- def content_length(resp: Any) -> int | None:
47
- try:
48
- value = resp.headers.get("Content-Length")
49
- if value is not None:
50
- return int(value)
51
- except (KeyError, TypeError, ValueError):
52
- pass
53
- return None
54
-
55
-
56
- def copy_with_progress(resp: Any, out: Any, asset: str) -> None:
57
- """Copy ``resp`` to ``out``, rendering a progress bar on stderr."""
58
- import time
59
-
60
- stream = sys.stderr
61
- tty = stream_is_tty(stream)
62
- total = content_length(resp)
63
- start = time.monotonic()
64
- done = 0
65
- last_render = 0.0
66
- last_render_done = -1
67
- interval = 0.1 if tty else 0.5
68
- while True:
69
- chunk = resp.read(65536)
70
- if not chunk:
71
- break
72
- out.write(chunk)
73
- done += len(chunk)
74
- now = time.monotonic()
75
- if now - last_render >= interval:
76
- render_progress_line(stream, asset, done, total, now - start, tty)
77
- last_render = now
78
- last_render_done = done
79
- if tty:
80
- clear_progress_line(stream)
81
- print_progress_done(stream, asset, done)
82
- elif done != last_render_done:
83
- render_progress_line(stream, asset, done, total,
84
- time.monotonic() - start, tty)
85
-
86
-
87
- def download(url: str, target: str, verbose: bool = False, retries: int = 3,
88
- progress: bool = False) -> None:
89
- """Download ``url`` and store it as ``target``.
90
-
91
- Args:
92
- url: Source URL.
93
- target: Destination file path.
94
- verbose: Print a human-readable summary of the download.
95
- retries: Number of attempts before giving up.
96
- progress: Show a progress bar while downloading (tty only).
97
- """
98
- import time
99
- import urllib.request
100
- import http.client
101
- import shutil
102
- asset = os.path.basename(target)
103
- if verbose:
104
- print_download_info(url, target)
105
- else:
106
- logger.info("Downloading URL: %s to %s...", url, asset)
107
- last_error: Exception | None = None
108
- for attempt in range(1, retries + 1):
109
- try:
110
- req = urllib.request.Request(url, headers={"User-Agent": "libcsound"})
111
- with urllib.request.urlopen(req, timeout=120) as resp, \
112
- open(target, "wb") as out:
113
- if progress:
114
- copy_with_progress(resp, out, asset)
115
- else:
116
- shutil.copyfileobj(resp, out)
117
- return
118
- except (OSError, http.client.HTTPException, ValueError) as e:
119
- # OSError covers urllib.error.URLError/HTTPError, socket and file errors;
120
- # HTTPException covers BadStatusLine/IncompleteRead; ValueError covers
121
- # malformed/unknown URLs.
122
- last_error = e
123
- if progress and stream_is_tty(sys.stderr):
124
- try:
125
- clear_progress_line(sys.stderr)
126
- except Exception:
127
- pass
128
- if attempt < retries:
129
- wait = 2 * attempt
130
- logger.warning("Download of '%s' failed (attempt %d/%d): %s. "
131
- "Retrying in %ds...",
132
- asset, attempt, retries, e, wait)
133
- time.sleep(wait)
134
- raise RuntimeError(f"Failed to download '{asset}' from {url} "
135
- f"(temporary file: {target}) after {retries} attempts\n"
136
- f"{last_error}") from last_error
137
-
138
-
139
10
  def run_installer(cmd: Sequence[str], failure_hint: str = "") -> None:
140
11
  """Run an installer command, streaming its output live while capturing it.
141
12
 
@@ -189,18 +60,19 @@ def run_installer(cmd: Sequence[str], failure_hint: str = "") -> None:
189
60
  raise RuntimeError(message)
190
61
 
191
62
 
192
- def install_csound_linux() -> None:
193
- """Install the latest csound 7 portable release for linux.
63
+ def install_csound_linux(quiet=True) -> None:
64
+ """Install csound 7 on linux by running the bundled installer.
194
65
 
195
- This mirrors the process of the one-line installer at
196
- https://csound-plugins.github.io/getcsound.sh
66
+ This runs the bundled copy of the official installer at
67
+ https://csound-plugins.github.io/getcsound.sh with bash. The installer
68
+ downloads the portable csound 7 release from the
69
+ ``csound-plugins/csound-plugins`` GitHub repository, verifies its SHA-256
70
+ checksum and installs it. ``--no-risset`` is always passed so that risset is
71
+ never installed.
197
72
 
198
- It downloads the release asset ``csound7-linux-<arch>.zip`` from the
199
- ``csound-plugins/csound-plugins`` GitHub repository, verifies the
200
- SHA-256 checksum, extracts it and runs the
201
- bundled ``install.sh``. When running inside a tty the installer is run
202
- interactively; otherwise it is run with ``--user -y`` so that csound is
203
- installed to ``~/.local/csound``
73
+ When running inside a terminal the installer is run interactively;
74
+ otherwise it is run with ``--user -y`` so that csound is installed to
75
+ ``~/.local/csound`` without prompting.
204
76
 
205
77
  After a successful installation, the environment variables ``LIBCSOUNDPATH``
206
78
  and ``OPCODE7DIR64`` point to the installed library and plugin
@@ -208,120 +80,29 @@ def install_csound_linux() -> None:
208
80
  the need to open a new terminal.
209
81
 
210
82
  Raises:
211
- RuntimeError: if the download or the checksum verification failed, if the
212
- bundled ``install.sh`` could not be found or failed, or if csound
213
- could not be located post installation.
83
+ RuntimeError: if the bundled installer could not be found or failed, or
84
+ if csound could not be located post installation.
214
85
  """
215
- import platform
216
- import stat
217
- import tempfile
218
- import zipfile
219
-
220
- repo = "csound-plugins/csound-plugins"
221
- tag = os.getenv("CSOUND7_TAG", "latest")
86
+ import importlib.resources
222
87
 
223
- # Detect the CPU architecture
224
- machine = platform.machine().lower()
225
- if machine in ("x86_64", "amd64"):
226
- arch = "x86_64"
227
- elif machine in ("aarch64", "arm64"):
228
- arch = "aarch64"
229
- else:
230
- raise RuntimeError(f"Unsupported architecture: {machine}. "
231
- "Supported architectures: x86_64, aarch64.")
232
-
233
- asset = os.getenv("CSOUND7_ASSET", f"csound7-linux-{arch}.zip")
234
- checksum_asset = f"{asset}.sha256"
235
- if tag == 'latest':
236
- # Notice how github changes the format to address the latest release
237
- base_url = f"https://github.com/{repo}/releases/latest/download"
238
- else:
239
- # Here this is a real tag, it must exist
240
- base_url = f"https://github.com/{repo}/releases/download/{tag}"
241
-
242
- checksum_url = f"{base_url}/{checksum_asset}"
243
-
244
- with tempfile.TemporaryDirectory(prefix="libcsound-install-") as tmpdir:
245
- zip_path = os.path.join(tmpdir, asset)
246
- checksum_path = f"{zip_path}.sha256"
247
- download(f"{base_url}/{asset}", zip_path, verbose=True,
248
- progress=stdin_is_tty())
249
- download(checksum_url, checksum_path)
250
-
251
- # Verify SHA256 checksum before extracting
252
- try:
253
- with open(checksum_path, encoding="utf-8") as f:
254
- tokens = f.readline().split()
255
- except (OSError, UnicodeDecodeError) as e:
256
- raise RuntimeError(
257
- f"Could not read the checksum file downloaded from {checksum_url}: {e}"
258
- ) from e
259
- expected = tokens[0] if tokens else ""
260
- actual = hexdigest(zip_path)
261
- if not expected or expected != actual:
262
- raise RuntimeError(
263
- f"SHA-256 checksum verification failed for {asset}\n"
264
- f"Expected: {expected}\n"
265
- f"Actual: {actual}\n"
266
- f"The downloaded file may be corrupted or have been modified.\n"
267
- f"Checksum URL: {checksum_url}")
268
- logger.info("Checksum verified.")
269
-
270
- extract_dir = os.path.join(tmpdir, "extracted")
271
- try:
272
- zip_size = os.path.getsize(zip_path)
273
- except OSError:
274
- zip_size = -1
275
- try:
276
- with zipfile.ZipFile(zip_path) as zf:
277
- zf.extractall(extract_dir)
278
- except zipfile.BadZipFile as e:
279
- raise RuntimeError(
280
- f"The downloaded archive '{asset}' is not a valid zip file "
281
- f"(size: {zip_size} bytes, temporary file: {zip_path}): {e}"
282
- ) from e
283
- except zipfile.LargeZipFile as e:
284
- raise RuntimeError(
285
- f"The downloaded archive '{asset}' requires 64-bit zip support "
286
- f"(size: {zip_size} bytes, temporary file: {zip_path}): {e}"
287
- ) from e
288
- except NotImplementedError as e:
289
- # Unsupported compression method.
290
- raise RuntimeError(
291
- f"The downloaded archive '{asset}' uses an unsupported "
292
- f"compression method (temporary file: {zip_path}): {e}"
293
- ) from e
294
- except OSError as e:
295
- raise RuntimeError(
296
- f"Could not extract the downloaded archive '{asset}' "
297
- f"(temporary file: {zip_path}, extract dir: {extract_dir}): {e}"
298
- ) from e
299
-
300
- # Locate the bundled installer
301
- for root, _dirs, files in os.walk(extract_dir):
302
- if "install.sh" in files:
303
- installer = os.path.join(root, "install.sh")
304
- break
305
- else:
306
- raise RuntimeError(f"install.sh was not found inside {asset}")
88
+ installer = importlib.resources.files(__package__).joinpath("data", "getcsound.sh")
89
+ if not installer.is_file():
90
+ raise RuntimeError(f"The bundled installer was not found: {installer}")
307
91
 
308
- try:
309
- os.chmod(installer, os.stat(installer).st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
310
- except OSError as e:
311
- raise RuntimeError(
312
- f"Could not make the bundled installer executable: '{installer}' "
313
- f"(from archive '{asset}'): {e}"
314
- ) from e
315
-
316
- # Run the bundled installer (output streams live and is captured, so a
317
- # failure carries the installer log; see _run_installer)
318
- if sys.stdin is not None and not sys.stdin.closed and sys.stdin.isatty():
319
- # running inside a terminal: run interactively
320
- run_installer([installer])
321
- else:
322
- # no terminal: install for the current user, answering yes to all
323
- # questions so that the installer does not block on any prompt
324
- run_installer([installer, "--user", "-y"])
92
+ # as_file() yields a real filesystem path, extracting to a temporary file
93
+ # first when the package is loaded from an archive (e.g. a zip).
94
+ linux_hint = ("csound can also be installed manually:\n"
95
+ " curl -fsSL https://csound-plugins.github.io/getcsound.sh | bash")
96
+ with importlib.resources.as_file(installer) as script:
97
+ options = ["bash", str(script), "--no-risset"]
98
+ if not stdin_is_tty():
99
+ # No terminal (e.g. on CI): install for the current user and answer
100
+ # yes to any prompt, so that no password is needed and the installer
101
+ # does not block waiting for input.
102
+ options.extend(["--user", "-y"])
103
+ if quiet:
104
+ options.append("--quiet")
105
+ run_installer(options, failure_hint=linux_hint)
325
106
 
326
107
  # Make the freshly installed csound available to the current process
327
108
  home = Path.home()
@@ -346,7 +127,7 @@ def install_csound_linux() -> None:
346
127
  "libcsound64.so was not found in any of the standard locations")
347
128
 
348
129
 
349
- def install_csound_macos() -> None:
130
+ def install_csound_macos(quiet=True) -> None:
350
131
  """Install the latest csound 7 .pkg for macOS by running the bundled installer.
351
132
 
352
133
  The official Csound 7 macOS package is produced by the "csound_builds"
@@ -382,8 +163,11 @@ def install_csound_macos() -> None:
382
163
  "terminal (or passwordless sudo).\n"
383
164
  "Alternatively, csound can be installed manually:\n"
384
165
  " curl -fsSL https://csound-plugins.github.io/getcsound.sh | bash")
166
+ args = ["bash", str(script)]
167
+ if quiet:
168
+ args.append("--quiet")
385
169
  with importlib.resources.as_file(installer) as script:
386
- run_installer(["bash", str(script)], failure_hint=macos_hint)
170
+ run_installer(args, failure_hint=macos_hint)
387
171
 
388
172
  # Make the freshly installed csound available to the current process
389
173
  csound_dir = Path("/Applications/Csound")
@@ -139,10 +139,11 @@ def testCsound(module: str = '',
139
139
  ksmps=64,
140
140
  outdev='',
141
141
  nchnls=2,
142
- dur=10.,
142
+ dur=4.,
143
143
  signal='pinker() * 0.2',
144
144
  opcodeDir='',
145
- options: list[str] | None = None
145
+ options: list[str] | None = None,
146
+
146
147
  ) -> None:
147
148
  """
148
149
  Test csound
@@ -156,29 +157,32 @@ def testCsound(module: str = '',
156
157
  nchnls: number of output channels
157
158
  signal: which signal to use. Any valid sound-generating csound code
158
159
  """
159
- from . import Csound
160
+ from . import Csound, getSystemSr
160
161
  csound = Csound(opcodeDir=opcodeDir)
162
+ if not module:
163
+ module = defaultRealtimeModule()
164
+ csound.setOption(f'-+rtaudio={module}')
165
+ if options is None:
166
+ options = []
161
167
 
162
- if outdev:
163
- if module:
164
- csound.setOption(f'-+rtaudio={module}')
165
- csound.setOption(f'-o{outdev}')
166
- else:
167
- if not module:
168
- module = defaultRealtimeModule()
169
- csound.setOption(f'-+rtaudio={module}')
168
+ if not outdev:
170
169
  if module == 'jack':
171
- csound.setOption('-odac:_')
170
+ outdev = 'dac:_'
172
171
  else:
173
- csound.setOption('-odac')
172
+ outdev = 'dac'
173
+ csound.setOption(f'-o{outdev}')
174
174
 
175
175
  if sr <= 0:
176
- csound.setOption('--use-system-sr')
177
- else:
178
- csound.setOption(f'--sample-rate={sr}')
179
- if options:
180
- for opt in options:
181
- csound.setOption(opt)
176
+ sr, _ = getSystemSr(module=module)
177
+ # csound.setOption('--use-system-sr')
178
+ csound.setOption(f'--sample-rate={sr}')
179
+ if module == 'jack':
180
+ if not any(option.startswith("-B") for option in options):
181
+ options.append("-B2048")
182
+ options.append("-b1024")
183
+
184
+ for opt in options:
185
+ csound.setOption(opt)
182
186
 
183
187
  csound.compileOrc(fr"""
184
188
  0dbfs = 1
@@ -200,8 +204,19 @@ def testCsound(module: str = '',
200
204
  pt = csound.performanceThread()
201
205
  pt.play()
202
206
  pt.scoreEvent(False, "i", [1, 0, dur])
203
- setupSigint(lambda: (pt.stop()))
204
- input("\n>>> Press any key to stop <<< \n")
207
+ running = True
208
+
209
+ def _sigint(*args, **kws):
210
+ nonlocal running
211
+ pt.stop()
212
+ restoreSigint()
213
+ running = False
214
+
215
+ setupSigint(_sigint)
216
+ import time
217
+ while running and dur > 0:
218
+ time.sleep(0.1)
219
+ dur -= 0.1
205
220
  restoreSigint()
206
221
  pt.stop()
207
222
  csound.stop()