libcsound 0.9.0__py3-none-any.whl

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.
libcsound/__init__.py ADDED
@@ -0,0 +1,112 @@
1
+ # libcsound
2
+ #
3
+ # fork of ctcsound.py, made to be pip installable and support any
4
+ # version of csound 6 and csound 7
5
+ #
6
+ # Copyright (C) 2024 Eduardo Moguillansky
7
+ #
8
+ # Original copyright follows:
9
+ #
10
+ # ctcsound.py:
11
+ #
12
+ # Copyright (C) 2016 Francois Pinot
13
+ #
14
+ # This file is part of Csound.
15
+ #
16
+ # This code is free software; you can redistribute it
17
+ # and/or modify it under the terms of the GNU Lesser General Public
18
+ # License as published by the Free Software Foundation; either
19
+ # version 2.1 of the License, or (at your option) any later version.
20
+ #
21
+ # Csound is distributed in the hope that it will be useful,
22
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
23
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24
+ # GNU Lesser General Public License for more details.
25
+ #
26
+ # You should have received a copy of the GNU Lesser General Public
27
+ # License along with Csound; if not, write to the Free Software
28
+ # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
29
+ # 02110-1301 USA
30
+ #
31
+
32
+ from . import common
33
+
34
+ if not common.BUILDING_DOCS:
35
+ from . import _dll
36
+ libcsound, libcsoundPath = _dll.csoundDLL()
37
+ VERSION = libcsound.csoundGetVersion()
38
+ if VERSION >= 7000:
39
+ APIVERSION = VERSION
40
+ else:
41
+ APIVERSION = libcsound.csoundGetAPIVersion()
42
+
43
+ if VERSION < 7000:
44
+ from .api6 import *
45
+ else:
46
+ from .api7 import *
47
+ else:
48
+ print("------------- Building documentation -------------")
49
+ VERSION = 0
50
+ from . import api7
51
+ from . import api6
52
+
53
+
54
+ #Instantiation
55
+ def csoundInitialize(signalHandler=True, atExitHandler=True) -> int:
56
+ """
57
+ Initializes Csound library with specific flags.
58
+
59
+ There is generally no need to use it explicitly unless you need to
60
+ avoid default initialization that sets signal handlers and atexit()
61
+ callbacks.
62
+
63
+ Within a python context, it is often necessary to call this function
64
+ with `signalHandler=False` in order for csound not to obstruct
65
+ python's own SIGINT (Keyboard Interrupt) handler. If called explicitely,
66
+ it needs to be called prior to any other function within the API.
67
+
68
+ Args:
69
+ signalHandler: if True, add a signal handler
70
+ atExitHandler: if True, adds a callback to destroy all instances of csound
71
+ when exiting
72
+
73
+ Returns:
74
+ zero on success, positive if initialization was done already, and negative on error.
75
+
76
+ """
77
+ flags = 0
78
+ if not signalHandler:
79
+ flags |= common.CSOUNDINIT_NO_SIGNAL_HANDLER
80
+ if not atExitHandler:
81
+ flags |= common.CSOUNDINIT_NO_ATEXIT
82
+ return libcsound.csoundInitialize(flags)
83
+
84
+
85
+ def setOpcodedir(path: str) -> None:
86
+ """
87
+ Overrides the folder used to locate plugins
88
+
89
+ Args:
90
+ path: folder where to search for plugins.
91
+ """
92
+ libcsound.csoundSetOpcodedir(common.cstring(path))
93
+
94
+
95
+ def setDefaultMessageCallback(function):
96
+ """
97
+ Not fully implemented but useful for disabling messaging
98
+
99
+ Args:
100
+ function: function of the form ``(csound, attr, flags, *args) -> None``,
101
+ will be called each time csound would print any message to the
102
+ console.
103
+
104
+ .. code-block:: python
105
+
106
+ def noMessage(csound, attr, flags, *args):
107
+ pass
108
+
109
+ ctcsound.setDefaultMessageCallback(noMessage)
110
+
111
+ """
112
+ libcsound.csoundSetDefaultMessageCallback(common.DEFMSGFUNC(function))
libcsound/_dll.py ADDED
@@ -0,0 +1,58 @@
1
+ from __future__ import annotations
2
+ import ctypes as ct
3
+ import ctypes.util
4
+ import sys
5
+ import os
6
+ from .common import BUILDING_DOCS
7
+
8
+
9
+ def csoundLibraryName() -> str:
10
+ platform = sys.platform
11
+ if platform.startswith('linux'):
12
+ return 'csound64'
13
+ elif platform.startswith('win'):
14
+ return 'csound64'
15
+ elif platform.startswith('darwin'):
16
+ return 'CsoundLib64'
17
+ else:
18
+ raise RuntimeError(f"Platform '{platform}' not supported")
19
+
20
+
21
+ _libcsound = None
22
+ _libcsoundpath = ''
23
+
24
+
25
+ def csoundDLL() -> tuple[ct.CDLL, str]:
26
+ global _libcsound
27
+ global _libcsoundpath
28
+
29
+ if _libcsound is not None:
30
+ return _libcsound, _libcsoundpath
31
+
32
+ if BUILDING_DOCS:
33
+ raise RuntimeError("Cannot access the dll while building docs")
34
+
35
+ if sys.platform == 'linux':
36
+ try:
37
+ dll = ct.CDLL("libcsound64.so")
38
+ _libcsound = dll
39
+ _libcsoundpath = "libcsound64.so"
40
+ return dll, "libcsound64.so"
41
+ except OSError:
42
+ libname = ctypes.util.find_library("csound64")
43
+ if libname is None:
44
+ raise ImportError("Did not find csound library in linux")
45
+ else:
46
+ libname = csoundLibraryName()
47
+ path = ctypes.util.find_library(libname)
48
+ if path is None:
49
+ if sys.platform.startswith('win'):
50
+ PATH = os.environ.get('PATH')
51
+ raise ImportError(f"Csound library not found (searched for '{libname}'. "
52
+ f"Make sure that csound is installed and the directory containing "
53
+ f"csound64.dll is in the path. PATH='{PATH}'")
54
+ raise ImportError(f"Csound library not found (searched for '{libname}') - Make sure that csound is installed")
55
+ _libcsound = ct.CDLL(path)
56
+ _libcsoundpath = path
57
+
58
+ return _libcsound, _libcsoundpath
libcsound/_util.py ADDED
@@ -0,0 +1,232 @@
1
+ from __future__ import annotations
2
+ import warnings
3
+ import signal
4
+ import ctypes
5
+ import numpy as np
6
+ import sys
7
+
8
+ from .common import (MYFLT,
9
+ CSOUND_CONTROL_CHANNEL,
10
+ CSOUND_AUDIO_CHANNEL,
11
+ CSOUND_ARRAY_CHANNEL,
12
+ CSOUND_PVS_CHANNEL,
13
+ CSOUND_OUTPUT_CHANNEL,
14
+ CSOUND_INPUT_CHANNEL,
15
+ CSOUND_CHANNEL_TYPE_MASK,
16
+ CSOUND_STRING_CHANNEL)
17
+
18
+
19
+ def asciistr(s) -> str:
20
+ if isinstance(s, str):
21
+ return s
22
+ elif isinstance(s, bytes):
23
+ return s.decode('ascii')
24
+ else:
25
+ raise TypeError(f"Expected a bytes or str instance, got {s}")
26
+
27
+
28
+ def packChannelType(kind: str, output: bool, input: bool) -> int:
29
+ chantype = {'control': CSOUND_CONTROL_CHANNEL,
30
+ 'audio': CSOUND_AUDIO_CHANNEL,
31
+ 'str': CSOUND_STRING_CHANNEL,
32
+ 'string': CSOUND_STRING_CHANNEL,
33
+ 'array': CSOUND_ARRAY_CHANNEL,
34
+ 'pvs': CSOUND_PVS_CHANNEL}[kind]
35
+ chanmode = CSOUND_OUTPUT_CHANNEL * int(output) + CSOUND_INPUT_CHANNEL * int(input)
36
+ return chantype + chanmode
37
+
38
+
39
+ def unpackChannelType(chantype: int) -> tuple[str, int]:
40
+ """
41
+ Returns:
42
+ a tuple (type: str, mode: int)
43
+ Where type is one of 'control', 'audio', 'string', 'array' or 'pvs', mode
44
+ is CSOUND_OUTPUT_CHANNEL, CSOUND_INPUT_CHANNEL or
45
+ CSOUND_OUTPUT_CHANNEL + CSOUND_INPUT_CHANNEL
46
+ """
47
+ typecode = chantype & CSOUND_CHANNEL_TYPE_MASK
48
+ typename = {
49
+ CSOUND_CONTROL_CHANNEL: 'control',
50
+ CSOUND_AUDIO_CHANNEL: 'audio',
51
+ CSOUND_STRING_CHANNEL: 'string',
52
+ CSOUND_ARRAY_CHANNEL: 'array',
53
+ CSOUND_PVS_CHANNEL: 'pvs'
54
+ }.get(typecode)
55
+ if typename is None:
56
+ raise ValueError(f"Invalid channel type: {chantype}")
57
+ modecode = chantype - typecode
58
+ return typename, modecode
59
+
60
+
61
+ _sigintHandler = None
62
+
63
+
64
+ def setupSigint(handler=lambda: sys.exit(0)):
65
+ """
66
+ Set a SIGINT handler
67
+
68
+ This can be used in context with csound to avoid the situation where
69
+ ctrl-c is hit during a performance and is handled by csound, making
70
+ it exit without notifying the underlying python process. This results
71
+ often in an unrecoverable situation
72
+
73
+ Args:
74
+ handler: a function taking to args and returning nothing. It will be called
75
+ whenever SIGINT (ctrl-c) is signaled
76
+
77
+ .. note:: to restore the previous sigint handler, call :func:`restoreSigint`
78
+ """
79
+ global _sigintHandler
80
+ _sigintHandler = signal.getsignal(signal.SIGINT)
81
+ def _handler(signal, frame):
82
+ handler()
83
+ signal.signal(signal.SIGINT, _handler)
84
+
85
+
86
+ def restoreSigint() -> None:
87
+ """
88
+ Restores the SIGINT handler previously replaced via :func:`setupSigint`
89
+ """
90
+ global _sigintHandler
91
+ if _sigintHandler is not None:
92
+ signal.signal(signal.SIGINT, _sigintHandler)
93
+
94
+
95
+ def defaultRealtimeModule() -> str:
96
+ """
97
+ Determines the default realtime module for the current architecture
98
+ """
99
+ if sys.platform == 'linux':
100
+ return 'jack'
101
+ elif sys.platform == 'darwin':
102
+ return 'auhal'
103
+ return 'pa_cb'
104
+
105
+
106
+ def realtimeModulesForPlatform(platform='') -> set[str]:
107
+ if not platform:
108
+ platform = sys.platform
109
+ if platform == 'linux':
110
+ return {'portaudio', 'jack', 'pulse', 'alsa'}
111
+ elif platform == 'darwin':
112
+ return {'portaudio', 'auhal', 'jack'}
113
+ elif platform == 'windows':
114
+ return {'portaudio', 'winmme'}
115
+ else:
116
+ return {}
117
+
118
+
119
+ def testCsound(module: str = '',
120
+ sr: float = 0.,
121
+ outdev='',
122
+ nchnls=2,
123
+ dur=10.,
124
+ signal='pinker() * 0.2'
125
+ ) -> None:
126
+ """
127
+ Test csound
128
+
129
+ Args:
130
+ module: the realtime audio module ('jack', 'auhal', 'portaudio', etc.). A default
131
+ is used if not given
132
+ sr: the samplerate to use. Leave unset to use the system sr or the default sr according
133
+ to the module used
134
+ outdev: the output device (unset to use default)
135
+ nchnls: number of output channels
136
+ signal: which signal to use. Any valid sound-generating csound code
137
+ """
138
+ if not module:
139
+ module = defaultRealtimeModule()
140
+ from . import Csound
141
+ csound = Csound()
142
+ if outdev:
143
+ csound.setOption(f'-o{outdev}')
144
+ elif module == 'jack':
145
+ csound.setOption('-odac:_')
146
+ else:
147
+ csound.setOption('-odac')
148
+
149
+ csound.setOption(f'-+rtaudio={module}')
150
+ if sr <= 0:
151
+ csound.setOption('--use-system-sr')
152
+ else:
153
+ csound.setOption(f'--sample-rate={sr}')
154
+ csound.compileOrc(fr"""
155
+ 0dbfs = 1
156
+ ksmps = 64
157
+ nchnls = {nchnls}
158
+
159
+ instr 1
160
+ kchan init -1
161
+ kchan = (kchan + metro:k(1)) % nchnls
162
+ if changed:k(kchan) == 1 then
163
+ println "Channel: %d", kchan + 1
164
+ endif
165
+ asig = {signal}
166
+ outch kchan + 1, asig
167
+ endin
168
+ """)
169
+ csound.start()
170
+ pt = csound.performanceThread()
171
+ pt.play()
172
+ pt.scoreEvent(0, "i", [1, 0, dur])
173
+ setupSigint(lambda: (pt.stop()))
174
+ input(">>> Press any key to stop <<< \n")
175
+ restoreSigint()
176
+ pt.stop()
177
+ csound.stop()
178
+
179
+
180
+ def castarray(ptr: ctypes._Pointer | ctypes.c_void_p, shape: tuple[int, ...]) -> np.ndarray:
181
+ """
182
+ Cast a ctypes pointer to an array
183
+ """
184
+ arrtype = np.ctypeslib.ndpointer(dtype=MYFLT, ndim=len(shape), shape=shape, flags='C_CONTIGUOUS')
185
+ return ctypes.cast(ptr, arrtype).contents
186
+
187
+
188
+ def deprecated(func):
189
+ """
190
+ Decorator used to mark functions as deprecated
191
+
192
+ It will result in a warning being emitted
193
+ when the function is used."""
194
+ import functools
195
+ @functools.wraps(func)
196
+ def newfunc(*args, **kwargs):
197
+ warnings.simplefilter('always', DeprecationWarning) # turn off filter
198
+ warnings.warn("Call to deprecated function {}.".format(func.__name__),
199
+ category=DeprecationWarning,
200
+ stacklevel=2)
201
+ warnings.simplefilter('default', DeprecationWarning) # reset filter
202
+ return func(*args, **kwargs)
203
+ return newfunc
204
+
205
+
206
+ def splitCommandLine(args: str) -> list[str]:
207
+ import re
208
+ return re.findall(r"(?:\".*?\"|\S)+", args)
209
+
210
+
211
+ def waveplot(samples: np.ndarray, samplerate: int):
212
+ def sec2str(seconds):
213
+ s = seconds % 60
214
+ sfrac = str(round((s - int(s)), 3)).split(".")[1]
215
+ return f"{int(seconds//60)}:{int(s):>02}.{sfrac}"
216
+ import matplotlib.pyplot as plt
217
+ import matplotlib.ticker
218
+ numch = 1 if len(samples.shape) == 1 else samples.shape[1]
219
+ fig = plt.figure(figsize=(24, 4))
220
+ formatter = matplotlib.ticker.FuncFormatter(lambda idx, x:sec2str(idx/samplerate))
221
+ axes = ax1 = fig.add_subplot(numch, 1, 1)
222
+ for i in range(numch):
223
+ if i > 0:
224
+ axes = fig.add_subplot(numch, 1, i + 1, sharex=ax1, sharey=ax1)
225
+ if i < numch - 1:
226
+ plt.setp(axes.get_xticklabels(), visible=False)
227
+ chan = samples[:, i] if len(samples.shape) > 1 else samples
228
+ axes.plot(chan, linewidth=1)
229
+ axes.xaxis.set_major_formatter(formatter)
230
+ ax1.set_xlim(0, samples.shape[0])
231
+ fig.tight_layout()
232
+ return fig