diff --git a/conda/meta.yaml b/conda/meta.yaml index b2f1c75..0e2d135 100644 --- a/conda/meta.yaml +++ b/conda/meta.yaml @@ -1,4 +1,4 @@ -{% set version = "0.1.8" %} +{% set version = "0.1.9" %} package: name: audioflux diff --git a/docs/changelog.rst b/docs/changelog.rst index dcbf6ca..1129935 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,5 +1,12 @@ ChangeLog ========= +v0.1.9 +------ +* New features: + * Add `audioflux.utils.synth_f0`. +* Fix bug: + * `#37 `__: Fix `audioflux.display.fill_spec` bug. + v0.1.8 ------ * New features: diff --git a/docs/utils.rst b/docs/utils.rst index aee9b37..b663c2a 100644 --- a/docs/utils.rst +++ b/docs/utils.rst @@ -63,6 +63,7 @@ Utils audioflux.utils.midi_to_hz audioflux.utils.hz_to_note audioflux.utils.hz_to_midi + audioflux.utils.synth_f0 Sample ------ diff --git a/python/audioflux/__version__.py b/python/audioflux/__version__.py index dba04e6..6b6e144 100644 --- a/python/audioflux/__version__.py +++ b/python/audioflux/__version__.py @@ -1,3 +1,3 @@ __title__ = 'audioflux' __description__ = 'A library for audio and music analysis, feature extraction.' -__version__ = '0.1.8' +__version__ = '0.1.9' diff --git a/python/audioflux/display/display.py b/python/audioflux/display/display.py index 4b8da28..72f3590 100644 --- a/python/audioflux/display/display.py +++ b/python/audioflux/display/display.py @@ -1,9 +1,9 @@ import warnings import numpy as np +import matplotlib as mpl import matplotlib.pyplot as plt import matplotlib.axes as plaxes -from matplotlib import colormaps from matplotlib.ticker import Formatter, ScalarFormatter, MaxNLocator, SymmetricalLogLocator, FixedLocator from audioflux.utils import midi_to_note @@ -190,7 +190,12 @@ def fill_spec( if y_axis == 'chroma': y_coords = np.arange(data.shape[-2] + 1) - cmap = colormaps['plasma'] + if hasattr(mpl, 'colormaps'): + cmap = mpl.colormaps['plasma'] + else: + from matplotlib.cm import get_cmap + cmap = get_cmap('plasma') + collection = axes.pcolormesh(x_coords, y_coords, data, cmap=cmap) axes.set_xlim(np.min(x_coords), np.max(x_coords)) diff --git a/python/audioflux/mir/pitch_yin.py b/python/audioflux/mir/pitch_yin.py index 96bf304..876dafd 100644 --- a/python/audioflux/mir/pitch_yin.py +++ b/python/audioflux/mir/pitch_yin.py @@ -52,6 +52,7 @@ class PitchYIN(Base): >>> fre_arr, v1_arr, v2_arr = pitch_obj.pitch(audio_arr) Show pitch plot + >>> import matplotlib.pyplot as plt >>> times = np.arange(fre_arr.shape[-1]) * (pitch_obj.slide_length / sr) >>> fig, ax = plt.subplots(nrows=2, sharex=True) diff --git a/python/audioflux/utils/util.py b/python/audioflux/utils/util.py index deb6716..24c52ae 100644 --- a/python/audioflux/utils/util.py +++ b/python/audioflux/utils/util.py @@ -1,5 +1,8 @@ import warnings import numpy as np +from ctypes import c_int, c_float, c_void_p, POINTER + +from audioflux.fftlib import get_fft_lib __all__ = [ 'ascontiguous_T', @@ -7,7 +10,8 @@ 'format_channel', 'revoke_channel', 'check_audio', - 'check_audio_length' + 'check_audio_length', + 'synth_f0' ] @@ -104,3 +108,76 @@ def check_audio_length(X, radix2_exp): f'only the first fft_length={fft_length} data are valid') X = X[..., :fft_length].copy() return X + + +def synth_f0(times, frequencies, samplate, amplitudes=None): + """ + Generate an audio array based on the frequency f0. + + Parameters + ---------- + times: ndarray [shape=(n)] + Time points for each frequency, in seconds. + + frequencies: ndarray [shape=(n)] + Array of frequencies, in Hz. + + samplate: int + The output sampling rate. + + amplitudes: ndarray [shape=(n)] + The amplitude of each frequency, ranging from 0 to 1. + + Default is None, which means that the amplitude is 1. Like: `np.ones((n,))` + + Returns + ------- + out: ndarray + Return the audio array generated based on the frequencies + + + Examples + -------- + + >>> import audioflux as af + >>> import numpy as np + >>> f0_arr = np.ones((1024,)) * 220 + >>> times = np.arange(0, f0_arr.shape[0]) * (1024 / 32000) + >>> amplitude_arr = np.ones_like(f0_arr) * 0.4 + >>> audio_arr = af.utils.synth_f0(times, f0_arr, 32000, amplitude_arr) + + """ + times = np.asarray(times, dtype=np.float32, order='C') + if times.ndim != 1: + raise ValueError(f"times[ndim={times.ndim}] must be a 1D array") + + frequencies = np.asarray(frequencies, dtype=np.float32, order='C') + if frequencies.ndim != 1: + raise ValueError(f"frequencies[ndim={frequencies.ndim}] must be a 1D array") + + if times.shape[0] != frequencies.shape[0]: + raise ValueError(f"The lengths of times and frequencies must be the same.") + + if amplitudes is not None: + amplitudes = np.asarray(amplitudes, dtype=np.float32, order='C') + if amplitudes.ndim != 1: + raise ValueError(f"amplitudes[ndim={amplitudes.ndim}] must be a 1D array") + + if amplitudes.shape[0] != frequencies.shape[0]: + raise ValueError(f"The lengths of amplitudes and frequencies must be the same.") + + fn = get_fft_lib()['util_synthF0'] + fn.argtypes = [ + np.ctypeslib.ndpointer(dtype=np.float32, ndim=1, flags='C_CONTIGUOUS'), + np.ctypeslib.ndpointer(dtype=np.float32, ndim=1, flags='C_CONTIGUOUS'), + c_int, c_int, + POINTER(c_void_p) if amplitudes is None else np.ctypeslib.ndpointer(dtype=np.float32, ndim=1, flags='C_CONTIGUOUS'), + ] + fn.restype = c_void_p + + length = times.shape[0] + length1 = round(np.max(times) * samplate) + + p = fn(times, frequencies, c_int(length), c_int(samplate), amplitudes) + ret = np.frombuffer((c_float * length1).from_address(p), np.float32).copy() + return ret \ No newline at end of file diff --git a/src/util/flux_util.h b/src/util/flux_util.h index 67d8546..8653e05 100644 --- a/src/util/flux_util.h +++ b/src/util/flux_util.h @@ -82,7 +82,7 @@ void util_delta(float *dataArr1,int length,int order,float *dataArr2); void util_preEmphasis(float *vArr1,int length,float coef,float *vArr2); // synthesized f0; length1=floor(time*fs), ampArr can NULL, default is 1 -float *util_synthF0(float *timeArr,float *freArr,int samplate,int length,float *ampArr); +float *util_synthF0(float *timeArr,float *freArr,int length,int samplate,float *ampArr); // wave int util_readWave(char *name,float **dataArr);