libcsound 0.10.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 +112 -0
- libcsound/_dll.py +58 -0
- libcsound/_util.py +232 -0
- libcsound/api6.py +3991 -0
- libcsound/api7.py +3087 -0
- libcsound/common.py +361 -0
- libcsound/py.typed +0 -0
- libcsound-0.10.0.dist-info/METADATA +616 -0
- libcsound-0.10.0.dist-info/RECORD +11 -0
- libcsound-0.10.0.dist-info/WHEEL +5 -0
- libcsound-0.10.0.dist-info/top_level.txt +1 -0
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
|