from __future__ import annotations
import numpy as np
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from .abstractrenderer import AbstractRenderer
[docs]
class TableProxy:
"""
A TableProxy is a proxy to an existing csound table
Args:
tabnum: the csound table number
sr: the sample rate of the table
nchnls: the number of channels in the table
numframes: the number of frames (data = numframes * nchnls)
engine: the corresponding Engine
path: the path to the output, if known
freeself: if True, csound will free the table when this object goes out of scope
.. warning::
A TableProxy is **never** created by the user directly. It is returned
by certain operations within a :class:`~csoundengine.session.Session` or
a :class:`~csoundengine.offline.OfflineSession`, like
:meth:`~csoundengine.session.Session.readSoundfile` or
:meth:`~csoundengine.session.Session.makeTable`
Example
~~~~~~~
.. code::
>>> from csoundengine import *
>>> session = Engine().session()
>>> table = session.readSoundfile("mono.wav")
>>> table
TableProxy(tabnum=1, sr=44100, nchnls=1, engine=Engine(…), numframes=102400, path='mono.wav', freeself=False)
>>> session.playSample(table, loop=True)
>>> table.plot()
.. image:: assets/tableproxy-plot.png
.. code::
>>> table.plotSpectrogram()
.. image:: assets/tableproxy-plotspectrogram.png
"""
__slots__ = ('tabnum', 'sr', 'nchnls', 'parent', 'numframes', 'path', 'freeself', '_array',
'skiptime')
def __init__(self,
tabnum: int,
numframes: int,
parent: AbstractRenderer | None = None,
sr: int = 0,
nchnls: int = 1,
path: str = '',
skiptime=0.,
freeself=False):
# if path:
# path = os.path.abspath(os.path.expanduser(path))
self.tabnum = tabnum
"""The table number assigned to this table"""
self.sr = sr
"""Samplerate"""
self.nchnls = nchnls
"""Number of channels, of applicable"""
self.parent = parent
"""The parent Renderer"""
self.numframes = numframes
"""The number of frames (samples = numframes * nchnls)"""
self.path = path
"""The path to load the data from, if applicable"""
self.freeself = freeself
"""If True, bind the lifetime of the proxy to the lifetime of the table itself"""
self.skiptime = skiptime
"""Skiptime of the table. This applies only to soundfiles"""
self._array: np.ndarray | None = None
"""The data, if applicable"""
if parent:
parent._registerTable(self)
@property
def size(self) -> int:
return self.numframes * self.nchnls
def __repr__(self):
return (f"TableProxy(source={self.tabnum}, sr={self.sr},"
f" nchnls={self.nchnls},"
f" numframes={self.numframes}, path={self.path},"
f" freeself={self.freeself})")
def __int__(self):
return self.tabnum
def __float__(self):
return float(self.tabnum)
def online(self) -> bool:
return self.parent is not None and self.parent.renderMode() == 'online'
[docs]
def data(self) -> np.ndarray:
"""
Get the table data as a numpy array.
The returned numpy array is a pointer to the csound memory (a view)
if the table is live or the samples read from disk otherwise
Returns:
the data as a numpy array
"""
if self._array is not None:
return self._array
if self.parent is None:
import sndfileio
samples, sr = sndfileio.sndread(self.path)
out = samples
else:
out = self.parent._getTableData(self)
if out is None:
raise RuntimeError("Could not access table data")
self._array = out
return out
def free(self, delay=0.):
if self.parent is None:
raise RuntimeError("Cannot free this table since it is not associated "
"with any online or offline csound process")
self.parent.freeTable(table=self.tabnum, delay=delay)
[docs]
def duration(self) -> float:
"""
Duration of the sample data in this table.
This is only possible if the table holds sample data and the
table has a samplerate
Raises ValueError if the table has no samplerate
Returns:
the duration of the sample data, in seconds
"""
if not self.sr:
raise ValueError("This table has no samplerate")
return self.numframes / self.sr
def __del__(self):
if not self.freeself:
return
if self.parent:
self.parent.freeTable(table=self.tabnum)
[docs]
def plot(self) -> None:
"""
Plot the table
"""
from . import plotting
if self.sr:
plotting.plotSamples(self.data(), self.sr, profile='high')
else:
# TODO: implement generic plotting
data = self.data()
plotting.plt.plot(data)
[docs]
def plotSpectrogram(self, fftsize=2048, mindb=-90, maxfreq=0, overlap=4,
minfreq=20) -> None:
"""
Plot a spectrogram of the sample data in this table.
Requires that the samplerate is set
Args:
fftsize: the size of the fft
mindb: the min. dB to plot
maxfreq: the max. frequency to plot (0=default)
overlap: the number of overlaps per window
minfreq: the min. frequency to plot
"""
if not self.sr:
raise ValueError("This table has no samplerate, cannot plot")
from . import plotting
plotting.plotSpectrogram(self.data(), self.sr, fftsize=fftsize, mindb=mindb,
maxfreq=maxfreq, minfreq=minfreq, overlap=overlap)