loristrck.util
Utility functions to manipulate / transform partials and list of partials
Example 1
Select a group of partials and render the selection as sound
import loristrck as lt
partials = lt.read_sdif(...)
selected, _ = lt.util.select(partials, minbps=2, mindur=0.005,
minamp=-80, maxfreq=17000)
lt.util.patials_render(partials, 44100, "selected.wav")
concat
Concatenate multiple Partials to produce a new one.
def concat(partials: list[np.ndarray], fade: float = 0.005,
edgefade: float = 0.0) -> np.ndarray
Assumes that the partials are non-overlapping and sorted
Note
partials need to be non-simultaneous and sorted
Args
- partials (
List[np.ndarray]
): a seq. of partials (each partial is a 2D-array) - fade (
float
): fadetime to apply at the end/beginning of each concatenated partial (default: 0.005) - edgefade (
float
): a fade to apply at the beginning of the first and at the end of the last partial (default: 0.0)
Returns
(np.ndarray
) a numpy array representing the concatenation of the given partials
partial_at
Evaluates partial p
at time t
def partial_at(p: np.ndarray, t: float, extend: bool = False) -> Opt[np.ndarray]
Args
- p (
np.ndarray
): the partial, a 2D numpy array - t (
float
): the time to evaluate the partial at - extend (
bool
): should the partial be extended to -inf, +inf? If True, querying a partial outside its boundaries will result in the values at the boundaries. Otherwise, None is returned (default: False)
Returns
(Opt[np.ndarray]
) with columns (time, freq, amp, bw). Returns None if the array is not defined at the given time
partial_crop
Crop partial at times t0, t1
def partial_crop(p: np.ndarray, t0: float, t1: float) -> np.ndarray
Note
- Returns p if p is included in the interval t0-t1
- Returns None if partial is not defined between t0-t1
- Otherwise crops the partial at t0 and t1, places a breakpoint at that time with the interpolated value
Args
- p (
np.ndarray
): the partial - t0 (
float
): the start time - t1 (
float
): the end time
Returns
(np.ndarray
) if the partial is not defined within the given time constraints)
partials_sample
Samples the partials between times t0
and t1
with a sampling period dt
def partials_sample(partials: list[np.ndarray], dt: float = 0.002,
t0: float = -1, t1: float = -1, maxactive: int = 0,
interleave: bool = True) -> Any
To be used in connection with pack
, which packs short non-simultaneous
partials into longer ones. The result is a 2D matrix representing the partials.
Sampling times is calculated as: times = arange(t0, t1+dt, dt)
If interleave is True, it returns a big matrix of format
[[t0, f0, amp0, bw0, f1, amp1, bw1, …, fN, ampN, bwN],
[t1, f0, amp0, bw0, f1, amp1, bw1, …, fN, ampN, bwN],
...
]
Where (f0, amp0, bw0) represent the freq, amplitude and bandwidth
of partial 0 at a given time, (f1, amp1, bw0)
the corresponding data
for partial 1, etc.
If interleave is False, it returns three arrays: freqs, amps, bws of the form:
[[f0, f1, f2, ..., fn] # at times[0]
[f0, f1, f2, ..., fn] # at times[1]
]
Note
phase information is not sampled
Args
- partials (
List[np.ndarray]
): a list of 2D-arrays, each representing a partial - dt (
float
): sampling period (default: 0.002) - t0 (
float
): start time, or None to use the start time of the spectrum (default: -1) - t1 (
float
): end time, or None to use the end time of the spectrum (default: -1) - maxactive (
int
): limit the number of active partials to this number. If the number of active streams (partials with non-zero amplitude) is higher thanmaxactive
, the softest partials will be zeroed. During resynthesis, zeroed partials are skipped. This strategy is followed to allow to pack all partials at the cost of having a great amount of streams, and limit the streams (for better performance) at the synthesis stage. (default: 0) - interleave (
bool
): if True, all columns of each partial are interleaved (see below) (default: True)
Returns
if interleave is True, returns a big matrix where all partials are interleaved and present at all times. Otherwise returns three arrays, (freqs, amps, bws) where freqs represents the frequencies of all partials, etc. See below
meanamp
Returns the mean amplitude of a partial
def meanamp(partial: np.ndarray) -> float
Args
- partial (
np.ndarray
): a numpy 2D-array representing a Partial
Returns
(float
) the average amplitude
meanfreq
Returns the mean frequency of a partial, optionally
def meanfreq(partial: np.ndarray, weighted: bool = False) -> float
weighting this mean by the amplitude of each breakpoint
Args
- partial (
np.ndarray
): - weighted (
bool
): (default: False)
partial_energy
Integrate the partial amplitude over time. Serves as measurement
def partial_energy(partial: np.ndarray) -> float
for the energy contributed by the partial.
Example
Select loudest partials within the range 50 Hz - 6000 Hz
import loristrck as lt
partials = lt.read_sdif("path.sdif")
partials2 = lt.util.select(partials, minfreq=50, maxfreq=6000,
minbps=4, mindur=0.005)
sorted_partials = sorted(partials2, key=lt.util.partial_energy, reverse=True)
loudest = sorted_partials[:100]
lt.util.plot_partials(loudest)
Args
- partial (
np.ndarray
):
select
Selects a seq. of partials matching the given conditions
def select(partials: list[np.ndarray], mindur: float = 0.0, minamp: int = -120,
maxfreq: int = 24000, minfreq: int = 0, minbps: int = 1,
t0: float = 0.0, t1: float = 0.0
) -> tuple[list[np.ndarray], list[np.ndarray]]
Select only those partials which have
- a min. duration of at least
mindur
AND - an avg. amplitude of at least
minamp
AND - a freq. between
minfreq
andmaxfreq
AND - have at least
minbps
breakpoints
Example
import loristrck as lt
partials = lt.read_sdif(...)
selected, _ = lt.util.select(partials, minbps=2, mindur=0.005,
minamp=-80, maxfreq=17000)
Args
- partials (
List[np.ndarray]
): a list of numpy 2D arrays, each array representing a partial - mindur (
float
): min. duration (in seconds) (default: 0.0) - minamp (
int
): min. amplitude (in dB) (default: -120) - maxfreq (
int
): max. frequency (default: 24000) - minfreq (
int
): min. frequency (default: 0) - minbps (
int
): min. breakpoints (default: 1) - t0 (
float
): only partials defined after t0 (default: 0.0) - t1 (
float
): only partials defined before t1 (default: 0.0)
Returns
(Tuple[List[np.ndarray], List[np.ndarray]]
) (selected partials, discarded partials)
filter
Similar to select, but returns a generator yielding only selected partials
def filter(partials: list[np.ndarray], mindur: float = 0.0, mindb: int = -120,
maxfreq: int = 2400, minfreq: int = 0, minbps: int = 1,
t0: float = 0.0, t1: float = 0.0) -> None
Args
- partials (
List[np.ndarray]
): - mindur (
float
): (default: 0.0) - mindb (
int
): (default: -120) - maxfreq (
int
): (default: 2400) - minfreq (
int
): (default: 0) - minbps (
int
): (default: 1) - t0 (
float
): (default: 0.0) - t1 (
float
): (default: 0.0)
sndread
Read a sound file.
def sndread(path: str, contiguous: bool = True) -> tuple[np.ndarray, int]
Args
- path (
str
): The path to the soundfile - contiguous (
bool
): If True, it is ensured that the returned array is contiguous. This should be set to True if the samples are to be passed toanalyze
, which expects a contiguous array (default: True)
Returns
(Tuple[np.ndarray, int]
) a tuple (samples:np.ndarray, sr:int)
sndreadmono
Read a sound file as mono.
def sndreadmono(path: str, chan: int = 0, contiguous: bool = True
) -> tuple[np.ndarray, int]
If the soundfile is multichannel, the indicated channel chan
is returned.
Args
- path (
str
): The path to the soundfile - chan (
int
): The channel to return if the file is multichannel (default: 0) - contiguous (
bool
): If True, it is ensured that the returned array is contiguous. This should be set to True if the samples are to be passed toanalyze
, which expects a contiguous array (default: True)
Returns
(Tuple[np.ndarray, int]
) a tuple (samples:np.ndarray, sr:int)
sndwrite
Write the samples to a soundfile
def sndwrite(samples: np.ndarray, sr: int, path: str, encoding: str = None
) -> None
Args
- samples (
np.ndarray
): the samples to write - sr (
int
): samplerate - path (
str
): the outfile to write the samples to (the extension will determine the format) - encoding (
str
): the encoding of the samples. If None, a default is used, according to the extension of the outfile given. Otherwise, a string 'floatXX' or 'pcmXX' is expected, where XX represent the bits per sample (15, 24, 32, 64 for pcm, 32 or 64 for float). Not all encodings are supported by all formats. (default: None)
plot_partials
Plot the partials using matplotlib
def plot_partials(partials: list[np.ndarray], downsample: int = 1,
cmap: str = inferno, exp: float = 1.0, linewidth: int = 1,
ax=None, avg: bool = True) -> Any
Args
- partials (
List[np.ndarray]
): a list of numpy arrays, each representing a partial - downsample (
int
): If > 1, only one everydownsample
breakpoints will be taken into account. (default: 1) - cmap (
str
): a string defining the colormap used (see https://matplotlib.org/users/colormaps.html) (default: inferno) - exp (
float
): an exponential to apply to the amplitudes before plotting (default: 1.0) - linewidth (
int
): the line width of the plot (default: 1) - ax: A matplotlib axes. If one is passed, plotting will be done to this axes. Otherwise a new axes is created (default: None)
- avg (
bool
): if True, the colour of a segment depends on the average between the amplitude at the start breakpoint and the end breakpoint of the segment Otherwise only the amplitude of the start breakpoint is used (default: True)
Returns
a matplotlib axes
kaiser_length
Returns the length in samples of a Kaiser window from the desired main lobe width.
def kaiser_length(width: float, sr: int, atten: int) -> int
Note
computeLength
Compute the length (in samples) of the Kaiser window from the desired (approximate) main lobe width and the control parameter. Of course, since the window must be an integer number of samples in length, your actual lobal mileage may vary. This equation appears in Kaiser and Schafer 1980 (on the use of the I0 window class for spectral analysis) as Equation 9. The main width of the main lobe must be normalized by the sample rate, that is, it is a fraction of the sample rate.
Args
- width (
float
): the width of the main lobe in Hz - sr (
int
): the sample rate, in samples / sec - atten (
int
): the attenuation in possitive dB
Returns
(int
) the length of the window, in samples
partials_stretch
Stretch the partials in time by a given constant factor
def partials_stretch(partials: list[np.ndarray], factor: float,
inplace: bool = False) -> list[np.ndarray]
Args
- partials (
List[np.ndarray]
): a list of partials - factor (
float
): float. a factor to multiply all times by - inplace (
bool
): modify partials in place (default: False)
Returns
(List[np.ndarray]
) the stretched partials
partials_transpose
Transpose the partials by a given interval
def partials_transpose(partials: list[np.ndarray], interval: float,
inplace: bool = False) -> list[np.ndarray]
Args
- partials (
List[np.ndarray]
): - interval (
float
): - inplace (
bool
): (default: False)
partials_between
Return the partials present between t0 and t1
def partials_between(partials: list[np.ndarray], t0: float = 0.0,
t1: float = 0.0) -> list[np.ndarray]
Args
- partials (
List[np.ndarray]
): a list of partials - t0 (
float
): start time in secs (default: 0.0) - t1 (
float
): end time in secs (default: 0.0)
Returns
(List[np.ndarray]
) the partials within the time range (t0, t1)
partials_at
Return the breakpoints at time t which satisfy the given conditions
def partials_at(partials: list[np.ndarray], t: float, maxcount: int = 0,
mindb: int = -120, minfreq: int = 10, maxfreq: int = 22000
) -> Any
Args
- partials (
List[np.ndarray]
): the partials analyzed - t (
float
): the time in seconds - maxcount (
int
): the max. partials to detect, ordered by amplitude (0=all) (default: 0) - mindb (
int
): the min. amplitude a partial has to have att
in order to be counted (default: -120) - minfreq (
int
): only partials with a freq. higher than this (default: 10) - maxfreq (
int
): only partials with a freq. lower than this (default: 22000)
Returns
the breakpoints at time t which satisfy the given conditions
partials_render
Render partials as a soundfile
def partials_render(partials: list[np.ndarray], outfile: str, sr: int = 44100,
fadetime: float = -1.0, start: float = -1.0,
end: float = -1.0, encoding: str = None) -> None
Args
- partials (
List[np.ndarray]
): the partials to render - outfile (
str
): the outfile to write to. If not given, a temporary file is used - sr (
int
): samplerate to render with (default: 44100) - fadetime (
float
): fade partials in/out when they don't end in a 0-amp bp (default: -1.0) - start (
float
): start time of render (default: start time of spectrum) (default: -1.0) - end (
float
): end time to render (default: end time of spectrum) (default: -1.0) - encoding (
str
): if given, the encoding to use (default: None)
estimate_sampling_interval
Estimate a sampling interval (dt) for this spectrum
def estimate_sampling_interval(partials: list[np.ndarray], maxpartials: int = 0,
percentile: int = 25, ksmps: int = 64,
sr: int = 44100) -> float
The usage is to find a sampling interval which neither oversamples nor undersamples the partials for a synthesis strategy based on blocks of computation (like in csound, supercollider, etc, where each ugen is given a buffer of samples to fill instead of working sample by sample)
Args
- partials (
List[np.ndarray]
): a list of partials - maxpartials (
int
): if given, only consider this number of partials to calculate dt (default: 0) - percentile (
int
): ??? (default: 25) - ksmps (
int
): samples per cycle used when rendering. This is used in the estimation to prevent oversampling (default: 64) - sr (
int
): the sample rate used for playback, used together with ksmps to estimate a useful sampling interval (default: 44100)
Returns
(float
) the most appropriate sampling interval for the data and the playback conditions
pack
Pack non-simultenous partials into longer partials with silences in between.
def pack(partials: list[np.ndarray], maxtracks: int = 0, gap: float = 0.01,
fade: float = -1.0, acceptabledist: float = 0.1, minbps: int = 2
) -> tuple[list[np.ndarray], list[np.ndarray]]
These packed partials can be used as tracks for resynthesis, minimizing the need of oscillators.
Note
Amplitude is always faded out between partials
See also: partials_save_matrix
Args
- partials (
List[np.ndarray]
): a list of arrays, where each array represents a partial, as returned by analyze - maxtracks (
int
): if > 0, sets the maximum number of tracks. Partials not fitting in will be discarded. Consider living this at 0, to allow for unlimited tracks, and limit the amount of active streams later on (default: 0) - gap (
float
): minimum gap between partials in a track. Should be longer than 2 times the sampling interval, if the packed partials are later going to be resampled. (default: 0.01) - fade (
float
): apply a fade to the partials before joining them. If not given, a default value is calculated (default: -1.0) - acceptabledist (
float
): instead of searching for the best possible fit, pack two partials together if they are near enough (default: 0.1) - minbps (
int
): the min. number of breakpoints for a partial to the packed. Partials with less that this number of breakpoints are filtered out (default: 2)
Returns
(Tuple[List[np.ndarray], List[np.ndarray]]
) a tuple (tracks, unpacked partials)
partials_save_matrix
Packs short partials into longer partials and saves the result as a matrix
def partials_save_matrix(partials: list[np.ndarray], outfile: str,
dt: float = None, gapfactor: float = 3.0,
maxtracks: int = 0, maxactive: int = 0
) -> tuple[list[np.ndarray], np.ndarray]
Example
import loristrck as lt
partials, labels = lt.read_sdif(sdiffile)
selected, rest = lt.util.select(partials, minbps=2, mindur=0.005, minamp=-80)
lt.util.partials_save_matrix(selected, 0.002, "packed.mtx")
Args
- partials (
List[np.ndarray]
): a list of numpy 2D-arrays, each representing a partial - outfile (
str
): path to save the sampled partials. Supported formats:.mtx
,.npy
(See matrix_save for more information) - dt (
float
): sampling period to sample the packed partials. If not given, it will be estimated with sensible defaults. To have more control over this stage, you can call estimate_sampling_interval yourself. At the cost of oversampling, a good value can be ksmps/sr, which results in 64/44100 = 0.0014 secs for typical values (default: None) - gapfactor (
float
): partials are packed with a gap = dt * gapfactor. It should be at least 2. A gap is a minimal amount of silence between the partials to allow for a fade out and fade in (default: 3.0) - maxtracks (
int
): Partials are packed in tracks and represented as a 2D matrix where each track is a row. If filesize and save/load time are a concern, a max. value for the amount of tracks can be given here, with the consequence that partials might be left out if there are no available tracks to pack them into. See alsomaxactive
(default: 0) - maxactive (
int
): Partials are packed in simultaneous tracks, which correspond to an oscillator bank for resynthesis. If maxactive is given, a max. ofmaxactive
is allowed, and the softer partials are zeroed to signal that they can be skipped during resynthesis. (default: 0)
Returns
(Tuple[List[np.ndarray], np.ndarray]
) a tuple (packed spectrum, matrix)