diff --git a/src/parselmouth.cpp b/src/parselmouth.cpp index 5efdb46d..50120061 100644 --- a/src/parselmouth.cpp +++ b/src/parselmouth.cpp @@ -35,8 +35,9 @@ Thing_declare(Daata); Thing_declare(Formant); Thing_declare(Function); Thing_declare(Harmonicity); -Thing_declare(Harmonicity); Thing_declare(Intensity); +Thing_declare(LineSpectralFrequencies); +Thing_declare(LPC); Thing_declare(Matrix); Thing_declare(MFCC); Thing_declare(Pitch); @@ -146,6 +147,8 @@ using PraatBindings = Bindings; } // namespace parselmouth diff --git a/src/parselmouth/CMakeLists.txt b/src/parselmouth/CMakeLists.txt index c53c6e4a..46ac456a 100644 --- a/src/parselmouth/CMakeLists.txt +++ b/src/parselmouth/CMakeLists.txt @@ -22,6 +22,8 @@ target_sources(parselmouth PRIVATE Function.cpp Harmonicity.cpp Intensity.cpp + LineSpectralFrequencies.cpp + LPC.cpp Matrix.cpp MFCC.cpp Pitch.cpp diff --git a/src/parselmouth/LPC.cpp b/src/parselmouth/LPC.cpp new file mode 100644 index 00000000..603ca363 --- /dev/null +++ b/src/parselmouth/LPC.cpp @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2017-2021 Yannick Jadoul + * + * This file is part of Parselmouth. + * + * Parselmouth is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Parselmouth is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Parselmouth. If not, see + */ + +#include "Parselmouth.h" + +#include "LPC_docstrings.h" +#include "TimeClassAspects.h" +#include "utils/pybind11/NumericPredicates.h" + +#include +// #include +// #include +// #include +#include +// #include +// #include +#include +#include +#include +#include + +#include +#include + +namespace py = pybind11; +using namespace py::literals; + +namespace parselmouth +{ + +PRAAT_STRUCT_BINDING(Frame, LPC_Frame) +{ + + doc() = FRAME_CLASS_DOCSTRING; + + def_readonly("n_coefficients", &structLPC_Frame::nCoefficients, + FRAME_N_COEFFICIENTS); + def_readonly("gain", &structLPC_Frame::gain, FRAME_GAIN); + def_property_readonly( + "a", + [](LPC_Frame self) { + return py::array({self->nCoefficients}, self->a.cells); + }, + py::return_value_policy::reference_internal, FRAME_A); +} + +PRAAT_CLASS_BINDING(LPC) +{ + addTimeFrameSampledMixin(*this); + + NESTED_BINDINGS(LPC_Frame) + + doc() = CLASS_DOCSTRING; + + def_readonly("sampling_period", &structLPC::samplingPeriod, + SAMPLING_PERIOD_DOCSTRING); + def_readonly("max_n_coefficients", &structLPC::maxnCoefficients, + MAX_N_COEFFICIENTS_DOCSTRING); + + def( + "__len__", [](LPC self) { return self->nx; }, LEN_DOCSTRING); + def( + "__getitem__", + [](LPC self, int i) { + if (i < 0) i += self->nx; // Python-style negative indexing + if (i < 0 || i >= self->nx) + throw py::index_error("LPC index out of range"); + return &self->d_frames.cells[i]; + }, + "i"_a, py::return_value_policy::reference_internal, GETITEM_DOCSTRING); + + def( + "__iter__", + [](LPC self) { + return py::make_iterator(self->d_frames.cells, + self->d_frames.cells + self->nx); + }, + py::keep_alive<0, 1>(), ITER_DOCSTRING); + + // FORM (NEW_LPC_to_Spectrum_slice + def("to_spectrum_slice", &LPC_to_Spectrum, "time"_a, + "minimum_frequency_resolution"_a = 20.0, "bandwidth_reduction"_a = 0.0, + "deemphasis_frequency"_a = 50.0, TO_SPECTRUM_SLICE_DOCSTRING); + + // FORM (NEW_LPC_to_Spectrogram + def("to_spectrogram", &LPC_to_Spectrogram, + "minimum_frequency_resolution"_a = 20.0, "bandwidth_reduction"_a = 0.0, + "deemphasis_frequency"_a = 50.0, TO_SPECTROGRAM_DOCSTRING); + + // FORM (NEW_LPC_to_LineSpectralFrequencies) + def("to_line_spectral_frequencies", &LPC_to_LineSpectralFrequencies, + "grid_size"_a = 0.0, TO_LINE_SPECTRAL_FREQUENCIES); + + // FORM (NEW1_LPC_Sound_to_LPC_robust + def( + "to_lpc_robust", [](LPC self, Sound sound, Positive windowLength, Positive preEmphasisFrequency, Positive numberOfStandardDeviations, Positive maximumNumberOfIterations, double tolerance, bool locationVariable) { + return LPC_Sound_to_LPC_robust(self, sound, windowLength, preEmphasisFrequency, numberOfStandardDeviations, maximumNumberOfIterations, tolerance, locationVariable); + }, + "sound"_a.none(false), "window_length"_a = 0.025, "preemphasis_frequency"_a = 50.0, "number_of_std_dev"_a = 1.5, "maximum_number_of_iterations"_a = 5, "tolerance"_a = 0.000001, "variable_location"_a = false); +} + +} // namespace parselmouth diff --git a/src/parselmouth/LPC_docstrings.h b/src/parselmouth/LPC_docstrings.h new file mode 100644 index 00000000..c21b6b29 --- /dev/null +++ b/src/parselmouth/LPC_docstrings.h @@ -0,0 +1,180 @@ +#pragma once + +#include "common_docstring.h" + +namespace parselmouth { + +constexpr auto FRAME_CLASS_DOCSTRING = R"(Praat LPC Frame Class. + +A class containing the linear predictive coding (LPC) analysis outcome over +a frame of acoustic data samples. + +Note that the $a_0=1$ coefficient is omitted in a. + +This class is instantiated in :obj:`~parselmouth.LPC` object and not +intended to be instantiated by user. + +See also +-------- +:praat:`Sound: LPC analysis` +:obj:`~parselmouth.LPC` +)"; + +constexpr auto FRAME_N_COEFFICIENTS = R"(int : Number of AR coefficients a used to model the frame)"; +constexpr auto FRAME_GAIN = R"(float : Linear predictive model gain)"; +constexpr auto FRAME_A = R"(numpy.ndarray of float : Linear predictive model coefficients.)"; + + +constexpr auto CLASS_DOCSTRING = R"(Praat LPC Class. + +A sequence object contains the outcomes of linear predictive coding (LPC) +analysis outcomes. The analysis is performed repeatedly with a sliding +window, which offsets :attr:`LPC.dt` seconds between +frames. + +The i-th item of this object is a :func:`LPC.Frame` +object representing the i-th frame. + +In the LPC analysis one tries to predict xn on the basis of the p previous +samples, :math:`x_n^' = \sum_{k=1}^p {a_k x_{n-k}}` then +:math:`{a_1, a_2, \ldots, a_p}` can be chosen to minimize the prediction +power :math:`Q_p` where :math:`Q_p = E[ |x_n - x_n^'|^2 ]`. + +This class is not intended to be instantiated by user, instead from a +:obj:`~parselmouth.Sound` object using one of its to_lpc_xxx() +methods. + +See Also +-------- +:praat:`Sound: LPC analysis` +:obj:`parselmouth.LPC.Frame` +:func:`parselmouth.Sound.to_lpc_autocorrelation` +:func:`parselmouth.Sound.to_lpc_covariance` +:func:`parselmouth.Sound.to_lpc_burg` +:func:`parselmouth.Sound.to_lpc_marple` +)"; + +constexpr auto SAMPLING_PERIOD_DOCSTRING = R"(Sampling rate of the audio data)"; +constexpr auto MAX_N_COEFFICIENTS_DOCSTRING = R"(Largest number of coefficients over all frames)"; + +constexpr auto TO_LINE_SPECTRAL_FREQUENCIES=R"(Convert to line spectra. + +Returns :obj:`parselmouth.LineSpectralFrequencies` object with the +line frequencies found in the LPC models. + +Parameter +---------- +grid_size : float, default 0.0 + TBD + +See also +-------- +:obj:`parselmouth.LineSpectralFrequencies` +)"; + + +constexpr auto TO_SPECTRUM_SLICE_DOCSTRING=R"(Convert to spectrogram. + +Returns :obj:`parselmouth.Spectrum` object with the spectral +representation of the LPC model found at specified time t. + +The Spectrum at t will be calculated from the nearest +:obj:`~parselmouth.LPC.Frame`. See :praat:`LPC: To Spectrum (slice)...` +for dedailed algorithm description. + +Parameters +---------- +time : float + Time at which the spectrum should be calculated. + +minimum_frequency_resolution : float, default 20.0 + Maximum distance separation of successive frequencies in the Spectrum, + in Hz + +bandwidth_reduction : float, default 0.0 + Reduces the bandwidth of each zero by this factor (<=0.0 for no + reduction). Formants with small bandwidths show up very well as darker + regions in the spectrogram because the poles lie close to the contour + along which a spectrum is computed (the unit circle in the z-plane). + Peak enhancement can be realized by computing a spectrum in the z-plane + along a contour of radius: + + :math:`r = exp \left(– \pi \times + \frac{bandwidthReduction}{samplingFrequency}\right)`. + +deemphasis_frequency : float, default 50.0 + Performs de-emphasis when value is in this interval, specified in Hz. + (0, Nyquist frequency) + +See also +-------- +:praat:`LPC: To Spectrum (slice)...` +)"; + +constexpr auto TO_SPECTROGRAM_DOCSTRING=R"(Convert to spectrogram. + +Returns :obj:`parselmouth.Spectrogram` object with the spectral +representation of the LPC models. + +For each LPC_Frame the corresponding Spectrum will be calculated according +to the algorithm explained in :func:`parselmouth.LPC.to_spectrum`. For each +frequency the power, i.e., the square of the complex values, will be stored +in the corresponding area in the Spectrogram. + +Parameters +---------- +minimum_frequency_resolution : float, default 20.0 + Maximum distance separation of successive frequencies in the Spectrum, + in Hz + +bandwidth_reduction : float, default 0.0 + Reduces the bandwidth of each zero by this factor (<=0.0 for no + reduction). Formants with small bandwidths show up very well as darker + regions in the spectrogram because the poles lie close to the contour + along which a spectrum is computed (the unit circle in the z-plane). + Peak enhancement can be realized by computing a spectrum in the z-plane + along a contour of radius: + + :math:`r = exp \left(– \pi \times + \frac{bandwidthReduction}{samplingFrequency}\right)`. + +deemphasis_frequency : float, default 50.0 + Performs de-emphasis when value is in this interval, specified in Hz. + (0, Nyquist frequency) + +See also +-------- +:praat:`LPC: To Spectrogram...` +)"; + +constexpr auto TO_LPC_ROBUST_DOCSTRING = R"( +Modify LPC coefficients using Huber's M-estimation approach. + +Parameters +---------- +sound : parselmouth.Sound + Sound object originally used to generate this LPC object + +window_length : float, default 0.025 + LPC frame size in seconds used to generate this LPC object + +preemphasis_frequency : float, default 50.0 + +6dB / octave filtering will be applied above this frequency. If you do + not want pre-emphasis, choose a frequency greater than the Nyquist + frequency. + +number_of_std_dev : float, default 1.5 + TBD + +maximum_number_of_iterations : int, default 5 + Maximum number of iterations for Newton's algorithm + +tolerance : float, default 0.000001 + Newton's algorithm termination criterion on change in coefficients + +variable_location : bool, default False + TBD + +)"; + +}// namespace parselmouth diff --git a/src/parselmouth/LineSpectralFrequencies.cpp b/src/parselmouth/LineSpectralFrequencies.cpp new file mode 100644 index 00000000..43901cb3 --- /dev/null +++ b/src/parselmouth/LineSpectralFrequencies.cpp @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2017-2021 Yannick Jadoul + * + * This file is part of Parselmouth. + * + * Parselmouth is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Parselmouth is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Parselmouth. If not, see + */ + +#include "Parselmouth.h" + +#include "LineSpectralFrequencies_docstrings.h" +#include "TimeClassAspects.h" + +#include +#include + +#include +#include + +namespace py = pybind11; +using namespace py::literals; + +namespace parselmouth +{ + +PRAAT_CLASS_BINDING(LineSpectralFrequencies) +{ + addTimeFrameSampledMixin(*this); + + doc() = CLASS_DOCSTRING; + + oo_DOUBLE(maximumFrequency) oo_INT(maximumNumberOfFrequencies) + + def_readonly("maximum_frequency", + &structLineSpectralFrequencies::maximumFrequency, + MAXIMUM_FREQUENCY_DOCSTRING); + def_readonly("maximum_number_of_frequencies", + &structLineSpectralFrequencies::maximumNumberOfFrequencies, + MAXIMUM_NUMBER_OF_FREQUENCIES_DOCSTRING); + + def( + "__len__", [](LineSpectralFrequencies self) { return self->nx; }, + LEN_DOCSTRING); + def( + "__getitem__", + [](LineSpectralFrequencies self, int i) { + if (i < 0) i += self->nx; // Python-style negative indexing + if (i < 0 || i >= self->nx) throw py::index_error("Index out of range"); + structLineSpectralFrequencies_Frame &item = self->d_frames.cells[i]; + return py::array({item.numberOfFrequencies}, item.frequencies.cells); + }, + "i"_a, py::return_value_policy::reference_internal, GETITEM_DOCSTRING); + + // DIRECT (NEW_LineSpectralFrequencies_to_LPC) { + def("to_lpc", &LineSpectralFrequencies_to_LPC, TO_LPC_DOCSTRING); +} + +} // namespace parselmouth diff --git a/src/parselmouth/LineSpectralFrequencies_docstrings.h b/src/parselmouth/LineSpectralFrequencies_docstrings.h new file mode 100644 index 00000000..fe40ae80 --- /dev/null +++ b/src/parselmouth/LineSpectralFrequencies_docstrings.h @@ -0,0 +1,39 @@ +#pragma once + +#include "common_docstring.h" + +namespace parselmouth { + +constexpr auto CLASS_DOCSTRING = R"(Praat LineSpectralFrequencies Class. + +A sequence object contains a set of frequencies of line spectral contents, +found in the linear predictive coding (LPC) analysis. It contains +frequencies associated with each of LPC analysis frames, which are offset +by :attr:`LineSpectralFrequencies.dt` seconds. + +The i-th item of this object is a numpy.ndarray of floats, containing the +frequencies found in the i-th LPC analysis window. + +This class is not intended to be instantiated by user, instead by calling +:func:`~parselmouth.LPC.to_linespectralfrequencies()` method. + +See Also +-------- +:praat:`Sound: LPC analysis` +:func:`parselmouth.LPC.to_linespectralfrequencies` +)"; + +constexpr auto MAXIMUM_FREQUENCY_DOCSTRING = R"(float, readonly : Largest frequency over all frames.)"; +constexpr auto MAXIMUM_NUMBER_OF_FREQUENCIES_DOCSTRING = R"(int, readonly : Largest number of frequency samples over all frames.)"; + +constexpr auto TO_LPC_DOCSTRING=R"(Convert to LPC object. + +Returns :obj:`parselmouth.LPC` object with equivalent AR models. + +See also +-------- +:obj:`parselmouth.LPC` +:func:`parselmouth.LPC.to_line_spectral_frequencies` +)"; + +}// namespace parselmouth diff --git a/src/parselmouth/Sound.cpp b/src/parselmouth/Sound.cpp index 8024a9ac..f92361f6 100644 --- a/src/parselmouth/Sound.cpp +++ b/src/parselmouth/Sound.cpp @@ -19,12 +19,14 @@ #include "Parselmouth.h" #include "TimeClassAspects.h" +#include "Sound_docstrings.h" #include "utils/SignatureCast.h" #include "utils/praat/MelderUtils.h" #include "utils/pybind11/ImplicitStringToEnumConversion.h" #include "utils/pybind11/NumericPredicates.h" +#include #include #include #include @@ -203,7 +205,7 @@ PRAAT_CLASS_BINDING(Sound) { auto nx = values.shape(ndim - 1); auto ny = ndim == 2 ? values.shape(0) : 1; if (ndim == 2 && ny > nx) - PyErr_WarnEx(PyExc_RuntimeWarning, ("Number of channels (" + std::to_string(ny) + ") is greater than number of samples (" + std::to_string(nx) + "); note that the shape of the `values` array is interpreted as (n_channels, n_samples).").c_str(), 1); + PyErr_WarnEx(PyExc_RuntimeWarning, ("Number of channels (" + std::to_string(ny) + ") is greater than number of samples (" + std::to_string(nx) + "); note that the shape of the `values` array is interpreted as (n_channels, n_samples).").c_str(), 1); auto result = Sound_create(ny, startTime, startTime + nx / samplingFrequency, nx, 1.0 / samplingFrequency, startTime + 0.5 / samplingFrequency); @@ -226,83 +228,83 @@ PRAAT_CLASS_BINDING(Sound) { // TODO Empty constructor? def("save", - [](Sound self, const std::u32string &filePath, SoundFileFormat format) { - auto file = pathToMelderFile(filePath); + [](Sound self, const std::u32string &filePath, SoundFileFormat format) { + auto file = pathToMelderFile(filePath); switch(format) { - case SoundFileFormat::WAV: - Sound_saveAsAudioFile(self, &file, Melder_WAV, 16); - break; + case SoundFileFormat::WAV: + Sound_saveAsAudioFile(self, &file, Melder_WAV, 16); + break; - case SoundFileFormat::AIFF: - Sound_saveAsAudioFile(self, &file, Melder_AIFF, 16); - break; + case SoundFileFormat::AIFF: + Sound_saveAsAudioFile(self, &file, Melder_AIFF, 16); + break; - case SoundFileFormat::AIFC: - Sound_saveAsAudioFile(self, &file, Melder_AIFC, 16); - break; + case SoundFileFormat::AIFC: + Sound_saveAsAudioFile(self, &file, Melder_AIFC, 16); + break; - case SoundFileFormat::NEXT_SUN: - Sound_saveAsAudioFile(self, &file, Melder_NEXT_SUN, 16); - break; + case SoundFileFormat::NEXT_SUN: + Sound_saveAsAudioFile(self, &file, Melder_NEXT_SUN, 16); + break; - case SoundFileFormat::NIST: - Sound_saveAsAudioFile(self, &file, Melder_NIST, 16); - break; + case SoundFileFormat::NIST: + Sound_saveAsAudioFile(self, &file, Melder_NIST, 16); + break; - case SoundFileFormat::FLAC: - Sound_saveAsAudioFile(self, &file, Melder_FLAC, 16); - break; + case SoundFileFormat::FLAC: + Sound_saveAsAudioFile(self, &file, Melder_FLAC, 16); + break; - case SoundFileFormat::KAY: + case SoundFileFormat::KAY: Sound_saveAsKayFile (self, &file); - break; + break; - case SoundFileFormat::SESAM: + case SoundFileFormat::SESAM: Sound_saveAsSesamFile (self, &file); - break; - - case SoundFileFormat::WAV_24: - Sound_saveAsAudioFile(self, &file, Melder_WAV, 24); - break; - - case SoundFileFormat::WAV_32: - Sound_saveAsAudioFile(self, &file, Melder_WAV, 32); - break; - - case SoundFileFormat::RAW_8_SIGNED: - Sound_saveAsRawSoundFile(self, &file, Melder_LINEAR_8_SIGNED); - break; - - case SoundFileFormat::RAW_8_UNSIGNED: - Sound_saveAsRawSoundFile(self, &file, Melder_LINEAR_8_UNSIGNED); - break; - - case SoundFileFormat::RAW_16_BE: - Sound_saveAsRawSoundFile(self, &file, Melder_LINEAR_16_BIG_ENDIAN); - break; - - case SoundFileFormat::RAW_16_LE: - Sound_saveAsRawSoundFile(self, &file, Melder_LINEAR_16_LITTLE_ENDIAN); - break; - - case SoundFileFormat::RAW_24_BE: - Sound_saveAsRawSoundFile(self, &file, Melder_LINEAR_24_BIG_ENDIAN); - break; - - case SoundFileFormat::RAW_24_LE: - Sound_saveAsRawSoundFile(self, &file, Melder_LINEAR_24_LITTLE_ENDIAN); - break; - - case SoundFileFormat::RAW_32_BE: - Sound_saveAsRawSoundFile(self, &file, Melder_LINEAR_32_BIG_ENDIAN); - break; - - case SoundFileFormat::RAW_32_LE: - Sound_saveAsRawSoundFile(self, &file, Melder_LINEAR_32_LITTLE_ENDIAN); - break; - } - }, - "file_path"_a, "format"_a); + break; + + case SoundFileFormat::WAV_24: + Sound_saveAsAudioFile(self, &file, Melder_WAV, 24); + break; + + case SoundFileFormat::WAV_32: + Sound_saveAsAudioFile(self, &file, Melder_WAV, 32); + break; + + case SoundFileFormat::RAW_8_SIGNED: + Sound_saveAsRawSoundFile(self, &file, Melder_LINEAR_8_SIGNED); + break; + + case SoundFileFormat::RAW_8_UNSIGNED: + Sound_saveAsRawSoundFile(self, &file, Melder_LINEAR_8_UNSIGNED); + break; + + case SoundFileFormat::RAW_16_BE: + Sound_saveAsRawSoundFile(self, &file, Melder_LINEAR_16_BIG_ENDIAN); + break; + + case SoundFileFormat::RAW_16_LE: + Sound_saveAsRawSoundFile(self, &file, Melder_LINEAR_16_LITTLE_ENDIAN); + break; + + case SoundFileFormat::RAW_24_BE: + Sound_saveAsRawSoundFile(self, &file, Melder_LINEAR_24_BIG_ENDIAN); + break; + + case SoundFileFormat::RAW_24_LE: + Sound_saveAsRawSoundFile(self, &file, Melder_LINEAR_24_LITTLE_ENDIAN); + break; + + case SoundFileFormat::RAW_32_BE: + Sound_saveAsRawSoundFile(self, &file, Melder_LINEAR_32_BIG_ENDIAN); + break; + + case SoundFileFormat::RAW_32_LE: + Sound_saveAsRawSoundFile(self, &file, Melder_LINEAR_32_LITTLE_ENDIAN); + break; + } + }, + "file_path"_a, "format"_a); // TODO Determine file format based on extension, and make format optional // TODO Coordinate this save function with the (future) save in Data @@ -317,14 +319,14 @@ PRAAT_CLASS_BINDING(Sound) { def("get_sampling_period", [](Sound self) { return self->dx; }); def_property("sampling_period", - [](Sound self) { return self->dx; }, - [](Sound self, double period) { Sound_overrideSamplingFrequency(self, 1 / period); }); + [](Sound self) { return self->dx; }, + [](Sound self, double period) { Sound_overrideSamplingFrequency(self, 1 / period); }); def("get_sampling_frequency", [](Sound self) { return 1 / self->dx; }); def_property("sampling_frequency", - [](Sound self) { return 1 / self->dx; }, - [](Sound self, double frequency) { Sound_overrideSamplingFrequency(self, frequency); }); + [](Sound self) { return 1 / self->dx; }, + [](Sound self, double frequency) { Sound_overrideSamplingFrequency(self, frequency); }); def("get_time_from_index", // TODO PraatIndex to distinguish 1-based silliness? // TODO Get time from sample number... args_cast(&Sampled_indexToX), @@ -339,31 +341,31 @@ PRAAT_CLASS_BINDING(Sound) { // TODO Minimum & maximum (Vector?) def("get_nearest_zero_crossing", // TODO Channel is CHANNEL - [](Sound self, double time, long channel) { - if (channel > self->ny) channel = 1; + [](Sound self, double time, long channel) { + if (channel > self->ny) channel = 1; return Sound_getNearestZeroCrossing (self, time, channel); - }, - "time"_a, "channel"_a = 1); + }, + "time"_a, "channel"_a = 1); // TODO Get mean (Vector?) def("get_root_mean_square", - [](Sound self, std::optional fromTime, std::optional toTime) { return Sound_getRootMeanSquare(self, fromTime.value_or(self->xmin), toTime.value_or(self->xmax)); }, - "from_time"_a = std::nullopt, "to_time"_a = std::nullopt); + [](Sound self, std::optional fromTime, std::optional toTime) { return Sound_getRootMeanSquare(self, fromTime.value_or(self->xmin), toTime.value_or(self->xmax)); }, + "from_time"_a = std::nullopt, "to_time"_a = std::nullopt); // TODO Get standard deviation def("get_rms", - [](Sound self, std::optional fromTime, std::optional toTime) { return Sound_getRootMeanSquare(self, fromTime.value_or(self->xmin), toTime.value_or(self->xmax)); }, - "from_time"_a = std::nullopt, "to_time"_a = std::nullopt); + [](Sound self, std::optional fromTime, std::optional toTime) { return Sound_getRootMeanSquare(self, fromTime.value_or(self->xmin), toTime.value_or(self->xmax)); }, + "from_time"_a = std::nullopt, "to_time"_a = std::nullopt); def("get_energy", - [](Sound self, std::optional fromTime, std::optional toTime) { return Sound_getEnergy(self, fromTime.value_or(self->xmin), toTime.value_or(self->xmax)); }, - "from_time"_a = std::nullopt, "to_time"_a = std::nullopt); + [](Sound self, std::optional fromTime, std::optional toTime) { return Sound_getEnergy(self, fromTime.value_or(self->xmin), toTime.value_or(self->xmax)); }, + "from_time"_a = std::nullopt, "to_time"_a = std::nullopt); def("get_power", - [](Sound self, std::optional fromTime, std::optional toTime) { return Sound_getPower(self, fromTime.value_or(self->xmin), toTime.value_or(self->xmax)); }, - "from_time"_a = std::nullopt, "to_time"_a = std::nullopt); + [](Sound self, std::optional fromTime, std::optional toTime) { return Sound_getPower(self, fromTime.value_or(self->xmin), toTime.value_or(self->xmax)); }, + "from_time"_a = std::nullopt, "to_time"_a = std::nullopt); def("get_energy_in_air", &Sound_getEnergyInAir); @@ -375,8 +377,8 @@ PRAAT_CLASS_BINDING(Sound) { &Sound_getIntensity_dB); def("reverse", - [](Sound self, std::optional fromTime, std::optional toTime) { Sound_reverse(self, fromTime.value_or(self->xmin), toTime.value_or(self->xmax)); }, - "from_time"_a = std::nullopt, "to_time"_a = std::nullopt); + [](Sound self, std::optional fromTime, std::optional toTime) { Sound_reverse(self, fromTime.value_or(self->xmin), toTime.value_or(self->xmax)); }, + "from_time"_a = std::nullopt, "to_time"_a = std::nullopt); // TODO Formula and Formula (part) (reimplement from Vector/Matrix because of different parameters, i.e. channels?) @@ -391,8 +393,8 @@ PRAAT_CLASS_BINDING(Sound) { // TODO Set value at sample number def("set_to_zero", // TODO Set part to zero ? - [](Sound self, std::optional fromTime, std::optional toTime, bool roundToNearestZeroCrossing) { Sound_setZero(self, fromTime.value_or(self->xmin), toTime.value_or(self->xmax), roundToNearestZeroCrossing); }, - "from_time"_a = std::nullopt, "to_time"_a = std::nullopt, "round_to_nearest_zero_crossing"_a = true); + [](Sound self, std::optional fromTime, std::optional toTime, bool roundToNearestZeroCrossing) { Sound_setZero(self, fromTime.value_or(self->xmin), toTime.value_or(self->xmax), roundToNearestZeroCrossing); }, + "from_time"_a = std::nullopt, "to_time"_a = std::nullopt, "round_to_nearest_zero_crossing"_a = true); def("override_sampling_frequency", args_cast<_, Positive<_>>(Sound_overrideSamplingFrequency), @@ -401,21 +403,21 @@ PRAAT_CLASS_BINDING(Sound) { // TODO Filter with one formant (in-line)... def("pre_emphasize", - [](Sound self, double fromFrequency, bool normalize) { - Sound_preEmphasis(self, fromFrequency); - if (normalize) { - Vector_scale(self, 0.99); - } - }, + [](Sound self, double fromFrequency, bool normalize) { + Sound_preEmphasis(self, fromFrequency); + if (normalize) { + Vector_scale(self, 0.99); + } + }, "from_frequency"_a = 50.0, "normalize"_a = true); // TODO Not POSITIVE now!? def("de_emphasize", - [](Sound self, double fromFrequency, bool normalize) { + [](Sound self, double fromFrequency, bool normalize) { Sound_deEmphasis (self, fromFrequency); - if (normalize) { - Vector_scale(self, 0.99); - } - }, + if (normalize) { + Vector_scale(self, 0.99); + } + }, "from_frequency"_a = 50.0, "normalize"_a = true); // TODO Not POSITIVE now!? def("convert_to_mono", @@ -455,24 +457,24 @@ PRAAT_CLASS_BINDING(Sound) { [](Sound self) { return Sound_extractChannel(self, 2); }); def("extract_part", // TODO Something for std::optional for from and to in Sounds? - [](Sound self, std::optional fromTime, std::optional toTime, kSound_windowShape windowShape, Positive relativeWidth, bool preserveTimes) { return Sound_extractPart(self, fromTime.value_or(self->xmin), toTime.value_or(self->xmax), windowShape, relativeWidth, preserveTimes); }, - "from_time"_a = std::nullopt, "to_time"_a = std::nullopt, "window_shape"_a = kSound_windowShape::RECTANGULAR, "relative_width"_a = 1.0, "preserve_times"_a = false); + [](Sound self, std::optional fromTime, std::optional toTime, kSound_windowShape windowShape, Positive relativeWidth, bool preserveTimes) { return Sound_extractPart(self, fromTime.value_or(self->xmin), toTime.value_or(self->xmax), windowShape, relativeWidth, preserveTimes); }, + "from_time"_a = std::nullopt, "to_time"_a = std::nullopt, "window_shape"_a = kSound_windowShape::RECTANGULAR, "relative_width"_a = 1.0, "preserve_times"_a = false); def("extract_part_for_overlap", - [](Sound self, std::optional fromTime, std::optional toTime, Positive overlap) { return Sound_extractPartForOverlap(self, fromTime.value_or(self->xmin), toTime.value_or(self->xmax), overlap); }, - "from_time"_a = std::nullopt, "to_time"_a = std::nullopt, "overlap"_a); + [](Sound self, std::optional fromTime, std::optional toTime, Positive overlap) { return Sound_extractPartForOverlap(self, fromTime.value_or(self->xmin), toTime.value_or(self->xmax), overlap); }, + "from_time"_a = std::nullopt, "to_time"_a = std::nullopt, "overlap"_a); def("resample", &Sound_resample, "new_frequency"_a, "precision"_a = 50); def("lengthen", // TODO Lengthen (Overlap-add) ? - [](Sound self, Positive minimumPitch, Positive maximumPitch, Positive factor) { - if (minimumPitch >= maximumPitch) + [](Sound self, Positive minimumPitch, Positive maximumPitch, Positive factor) { + if (minimumPitch >= maximumPitch) Melder_throw (U"Maximum pitch should be greater than minimum pitch."); - return Sound_lengthen_overlapAdd(self, minimumPitch, maximumPitch, factor); - }, - "minimum_pitch"_a = 75.0, "maximum_pitch"_a = 600.0, "factor"_a); + return Sound_lengthen_overlapAdd(self, minimumPitch, maximumPitch, factor); + }, + "minimum_pitch"_a = 75.0, "maximum_pitch"_a = 600.0, "factor"_a); def("deepen_band_modulation", args_cast<_, Positive<_>, Positive<_>, Positive<_>, Positive<_>, Positive<_>, Positive<_>>(Sound_deepenBandModulation), @@ -480,68 +482,68 @@ PRAAT_CLASS_BINDING(Sound) { // TODO Args cast for std::optional and std::optional ranges! def("to_pitch", - [](Sound self, std::optional> timeStep, Positive pitchFloor, Positive pitchCeiling) { return Sound_to_Pitch(self, timeStep ? static_cast(*timeStep) : 0.0, pitchFloor, pitchCeiling); }, - "time_step"_a = std::nullopt, "pitch_floor"_a = 75.0, "pitch_ceiling"_a = 600.0); + [](Sound self, std::optional> timeStep, Positive pitchFloor, Positive pitchCeiling) { return Sound_to_Pitch(self, timeStep ? static_cast(*timeStep) : 0.0, pitchFloor, pitchCeiling); }, + "time_step"_a = std::nullopt, "pitch_floor"_a = 75.0, "pitch_ceiling"_a = 600.0); def("to_pitch", - [](Sound self, ToPitchMethod method, py::args args, py::kwargs kwargs) -> py::object { - auto callMethod = [&](auto which) { return py::cast(self).attr(which)(*args, **kwargs); }; - switch (method) { - case ToPitchMethod::AC: - return callMethod("to_pitch_ac"); - case ToPitchMethod::CC: - return callMethod("to_pitch_cc"); - case ToPitchMethod::SPINET: - return callMethod("to_pitch_spinet"); - case ToPitchMethod::SHS: - return callMethod("to_pitch_shs"); - } + [](Sound self, ToPitchMethod method, py::args args, py::kwargs kwargs) -> py::object { + auto callMethod = [&](auto which) { return py::cast(self).attr(which)(*args, **kwargs); }; + switch (method) { + case ToPitchMethod::AC: + return callMethod("to_pitch_ac"); + case ToPitchMethod::CC: + return callMethod("to_pitch_cc"); + case ToPitchMethod::SPINET: + return callMethod("to_pitch_spinet"); + case ToPitchMethod::SHS: + return callMethod("to_pitch_shs"); + } return py::none(); // Unreachable - }, - "method"_a); + }, + "method"_a); def("to_pitch_ac", - [](Sound self, std::optional> timeStep, Positive pitchFloor, Positive maxNumberOfCandidates, bool veryAccurate, double silenceThreshold, double voicingThreshold, double octaveCost, double octaveJumpCost, double voicedUnvoicedCost, Positive pitchCeiling) { + [](Sound self, std::optional> timeStep, Positive pitchFloor, Positive maxNumberOfCandidates, bool veryAccurate, double silenceThreshold, double voicingThreshold, double octaveCost, double octaveJumpCost, double voicedUnvoicedCost, Positive pitchCeiling) { if (maxNumberOfCandidates <= 1) Melder_throw (U"Your maximum number of candidates should be greater than 1."); - return Sound_to_Pitch_ac(self, timeStep ? static_cast(*timeStep) : 0.0, pitchFloor, 3.0, maxNumberOfCandidates, veryAccurate, silenceThreshold, voicingThreshold, octaveCost, octaveJumpCost, voicedUnvoicedCost, pitchCeiling); - }, - "time_step"_a = std::nullopt, "pitch_floor"_a = 75.0, "max_number_of_candidates"_a = 15, "very_accurate"_a = false, "silence_threshold"_a = 0.03, "voicing_threshold"_a = 0.45, "octave_cost"_a = 0.01, "octave_jump_cost"_a = 0.35, "voiced_unvoiced_cost"_a = 0.14, "pitch_ceiling"_a = 600.0); + return Sound_to_Pitch_ac(self, timeStep ? static_cast(*timeStep) : 0.0, pitchFloor, 3.0, maxNumberOfCandidates, veryAccurate, silenceThreshold, voicingThreshold, octaveCost, octaveJumpCost, voicedUnvoicedCost, pitchCeiling); + }, + "time_step"_a = std::nullopt, "pitch_floor"_a = 75.0, "max_number_of_candidates"_a = 15, "very_accurate"_a = false, "silence_threshold"_a = 0.03, "voicing_threshold"_a = 0.45, "octave_cost"_a = 0.01, "octave_jump_cost"_a = 0.35, "voiced_unvoiced_cost"_a = 0.14, "pitch_ceiling"_a = 600.0); def("to_pitch_cc", - [](Sound self, std::optional> timeStep, Positive pitchFloor, Positive maxNumberOfCandidates, bool veryAccurate, double silenceThreshold, double voicingThreshold, double octaveCost, double octaveJumpCost, double voicedUnvoicedCost, Positive pitchCeiling) { + [](Sound self, std::optional> timeStep, Positive pitchFloor, Positive maxNumberOfCandidates, bool veryAccurate, double silenceThreshold, double voicingThreshold, double octaveCost, double octaveJumpCost, double voicedUnvoicedCost, Positive pitchCeiling) { if (maxNumberOfCandidates <= 1) Melder_throw (U"Your maximum number of candidates should be greater than 1."); - return Sound_to_Pitch_cc(self, timeStep ? static_cast(*timeStep) : 0.0, pitchFloor, 1.0, maxNumberOfCandidates, veryAccurate, silenceThreshold, voicingThreshold, octaveCost, octaveJumpCost, voicedUnvoicedCost, pitchCeiling); - }, - "time_step"_a = std::nullopt, "pitch_floor"_a = 75.0, "max_number_of_candidates"_a = 15, "very_accurate"_a = false, "silence_threshold"_a = 0.03, "voicing_threshold"_a = 0.45, "octave_cost"_a = 0.01, "octave_jump_cost"_a = 0.35, "voiced_unvoiced_cost"_a = 0.14, "pitch_ceiling"_a = 600.0); + return Sound_to_Pitch_cc(self, timeStep ? static_cast(*timeStep) : 0.0, pitchFloor, 1.0, maxNumberOfCandidates, veryAccurate, silenceThreshold, voicingThreshold, octaveCost, octaveJumpCost, voicedUnvoicedCost, pitchCeiling); + }, + "time_step"_a = std::nullopt, "pitch_floor"_a = 75.0, "max_number_of_candidates"_a = 15, "very_accurate"_a = false, "silence_threshold"_a = 0.03, "voicing_threshold"_a = 0.45, "octave_cost"_a = 0.01, "octave_jump_cost"_a = 0.35, "voiced_unvoiced_cost"_a = 0.14, "pitch_ceiling"_a = 600.0); def("to_pitch_spinet", - [](Sound self, Positive timeStep, Positive windowLength, Positive minimumFilterFrequency, Positive maximumFilterFrequency, Positive numberOfFilters, Positive ceiling, Positive maxNumberOfCandidates) { - if (minimumFilterFrequency >= maximumFilterFrequency) Melder_throw(U"Maximum frequency must be larger than minimum frequency."); - return Sound_to_Pitch_SPINET(self, timeStep, windowLength, minimumFilterFrequency, maximumFilterFrequency, numberOfFilters, ceiling, maxNumberOfCandidates); - }, - "time_step"_a = 0.005, "window_length"_a = 0.04, "minimum_filter_frequency"_a = 70.0, "maximum_filter_frequency"_a = 5000.0, "number_of_filters"_a = 250, "ceiling"_a = 500.0, "max_number_of_candidates"_a = 15); + [](Sound self, Positive timeStep, Positive windowLength, Positive minimumFilterFrequency, Positive maximumFilterFrequency, Positive numberOfFilters, Positive ceiling, Positive maxNumberOfCandidates) { + if (minimumFilterFrequency >= maximumFilterFrequency) Melder_throw(U"Maximum frequency must be larger than minimum frequency."); + return Sound_to_Pitch_SPINET(self, timeStep, windowLength, minimumFilterFrequency, maximumFilterFrequency, numberOfFilters, ceiling, maxNumberOfCandidates); + }, + "time_step"_a = 0.005, "window_length"_a = 0.04, "minimum_filter_frequency"_a = 70.0, "maximum_filter_frequency"_a = 5000.0, "number_of_filters"_a = 250, "ceiling"_a = 500.0, "max_number_of_candidates"_a = 15); def("to_pitch_shs", - [](Sound self, Positive timeStep, Positive minimumPitch, Positive maxNumberOfCandidates, Positive maximumFrequencyComponent, Positive maxNumberOfSubharmonics, Positive compressionFactor, Positive ceiling, Positive numberOfPointsPerOctave) { - if (minimumPitch >= ceiling) Melder_throw(U"Minimum pitch should be smaller than ceiling."); - if (ceiling > maximumFrequencyComponent) Melder_throw(U"Maximum frequency must be greater than or equal to ceiling."); - return Sound_to_Pitch_shs(self, timeStep, minimumPitch, maximumFrequencyComponent, ceiling, maxNumberOfSubharmonics, maxNumberOfCandidates, compressionFactor, numberOfPointsPerOctave); + [](Sound self, Positive timeStep, Positive minimumPitch, Positive maxNumberOfCandidates, Positive maximumFrequencyComponent, Positive maxNumberOfSubharmonics, Positive compressionFactor, Positive ceiling, Positive numberOfPointsPerOctave) { + if (minimumPitch >= ceiling) Melder_throw(U"Minimum pitch should be smaller than ceiling."); + if (ceiling > maximumFrequencyComponent) Melder_throw(U"Maximum frequency must be greater than or equal to ceiling."); + return Sound_to_Pitch_shs(self, timeStep, minimumPitch, maximumFrequencyComponent, ceiling, maxNumberOfSubharmonics, maxNumberOfCandidates, compressionFactor, numberOfPointsPerOctave); }, "time_step"_a = 0.01, "minimum_pitch"_a = 50.0, "max_number_of_candidates"_a = 15, "maximum_frequency_component"_a = 1250.0, "max_number_of_subharmonics"_a = 15, "compression_factor"_a = 0.84, "ceiling"_a = 600.0, "number_of_points_per_octave"_a = 48); def("to_harmonicity", - [](Sound self, ToHarmonicityMethod method, py::args args, py::kwargs kwargs) -> py::object { - auto callMethod = [&](auto which) { return py::cast(self).attr(which)(*args, **kwargs); }; - switch (method) { - case ToHarmonicityMethod::AC: - return callMethod("to_harmonicity_ac"); - case ToHarmonicityMethod::CC: - return callMethod("to_harmonicity_cc"); - case ToHarmonicityMethod::GNE: - return callMethod("to_harmonicity_gne"); - } + [](Sound self, ToHarmonicityMethod method, py::args args, py::kwargs kwargs) -> py::object { + auto callMethod = [&](auto which) { return py::cast(self).attr(which)(*args, **kwargs); }; + switch (method) { + case ToHarmonicityMethod::AC: + return callMethod("to_harmonicity_ac"); + case ToHarmonicityMethod::CC: + return callMethod("to_harmonicity_cc"); + case ToHarmonicityMethod::GNE: + return callMethod("to_harmonicity_gne"); + } return py::none(); // Unreachable - }, - "method"_a = ToHarmonicityMethod::CC); + }, + "method"_a = ToHarmonicityMethod::CC); def("to_harmonicity_cc", args_cast<_, Positive<_>, Positive<_>, _, Positive<_>>(Sound_to_Harmonicity_cc), @@ -564,34 +566,34 @@ PRAAT_CLASS_BINDING(Sound) { "fast"_a = true); def("to_spectrogram", - [](Sound self, Positive windowLength, Positive maximumFrequency, Positive timeStep, Positive frequencyStep, kSound_to_Spectrogram_windowShape windowShape) { return Sound_to_Spectrogram(self, windowLength, maximumFrequency, timeStep, frequencyStep, windowShape, 8.0, 8.0); }, - "window_length"_a = 0.005, "maximum_frequency"_a = 5000.0, "time_step"_a = 0.002, "frequency_step"_a = 20.0, "window_shape"_a = kSound_to_Spectrogram_windowShape::GAUSSIAN); + [](Sound self, Positive windowLength, Positive maximumFrequency, Positive timeStep, Positive frequencyStep, kSound_to_Spectrogram_windowShape windowShape) { return Sound_to_Spectrogram(self, windowLength, maximumFrequency, timeStep, frequencyStep, windowShape, 8.0, 8.0); }, + "window_length"_a = 0.005, "maximum_frequency"_a = 5000.0, "time_step"_a = 0.002, "frequency_step"_a = 20.0, "window_shape"_a = kSound_to_Spectrogram_windowShape::GAUSSIAN); def("to_formant_burg", // TODO Praat has Max. number of formants as REAL? What the hell? "Pi formants for me, please."? (I know, I know; see Praat documentation) - [](Sound self, std::optional> timeStep, Positive maxNumberOfFormants, double maximumFormant, Positive windowLength, Positive preEmphasisFrom) { return Sound_to_Formant_burg(self, timeStep ? static_cast(*timeStep) : 0.0, maxNumberOfFormants, maximumFormant, windowLength, preEmphasisFrom); }, - "time_step"_a = std::nullopt, "max_number_of_formants"_a = 5.0, "maximum_formant"_a = 5500.0, "window_length"_a = 0.025, "pre_emphasis_from"_a = 50.0); + [](Sound self, std::optional> timeStep, Positive maxNumberOfFormants, double maximumFormant, Positive windowLength, Positive preEmphasisFrom) { return Sound_to_Formant_burg(self, timeStep ? static_cast(*timeStep) : 0.0, maxNumberOfFormants, maximumFormant, windowLength, preEmphasisFrom); }, + "time_step"_a = std::nullopt, "max_number_of_formants"_a = 5.0, "maximum_formant"_a = 5500.0, "window_length"_a = 0.025, "pre_emphasis_from"_a = 50.0); // TODO To Formant... def("to_intensity", - [](Sound self, Positive minimumPitch, std::optional> timeStep, bool subtractMean) { return Sound_to_Intensity(self, minimumPitch, timeStep ? static_cast(*timeStep) : 0.0, subtractMean); }, - "minimum_pitch"_a = 100.0, "time_step"_a = std::nullopt, "subtract_mean"_a = true); + [](Sound self, Positive minimumPitch, std::optional> timeStep, bool subtractMean) { return Sound_to_Intensity(self, minimumPitch, timeStep ? static_cast(*timeStep) : 0.0, subtractMean); }, + "minimum_pitch"_a = 100.0, "time_step"_a = std::nullopt, "subtract_mean"_a = true); // TODO Filters // TODO Group different filters into enum/class/...? def_static("combine_to_stereo", - [](const std::vector> &sounds) { - auto ordered = referencesToOrderedOf(sounds); - return Sounds_combineToStereo(&ordered); - }, - "sounds"_a); + [](const std::vector> &sounds) { + auto ordered = referencesToOrderedOf(sounds); + return Sounds_combineToStereo(&ordered); + }, + "sounds"_a); def_static("concatenate", - [](const std::vector> &sounds, NonNegative overlap) { - auto ordered = referencesToOrderedOf(sounds); - return Sounds_concatenate(ordered, overlap); - }, - "sounds"_a, "overlap"_a = 0.0); + [](const std::vector> &sounds, NonNegative overlap) { + auto ordered = referencesToOrderedOf(sounds); + return Sounds_concatenate(ordered, overlap); + }, + "sounds"_a, "overlap"_a = 0.0); // TODO concatenate recoverably (dependends on having TextGrid) // TODO concatenate as member function? @@ -605,13 +607,47 @@ PRAAT_CLASS_BINDING(Sound) { // TODO Cross-correlate (short)? def("to_mfcc", // Watch out for different order of arguments in interface than in Sound_to_MFCC // TODO REQUIRE (numberOfCoefficients < 25, U"The number of coefficients should be less than 25.") - [](Sound self, Positive numberOfCoefficients, Positive windowLength, Positive timeStep, Positive firstFilterFrequency, Positive distanceBetweenFilters, std::optional> maximumFrequency) { - // if (numberOfCoefficients >= 25) Melder_throw(U"The number of coefficients should be less than 25."); // Might be wrong, but I see no reason to enforce this, in the actual code - return Sound_to_MFCC(self, numberOfCoefficients, windowLength, timeStep, firstFilterFrequency, maximumFrequency ? static_cast(*maximumFrequency) : 0.0, distanceBetweenFilters); - }, - "number_of_coefficients"_a = 12, "window_length"_a = 0.015, "time_step"_a = 0.005, "firstFilterFreqency"_a = 100.0, "distance_between_filters"_a = 100.0, "maximum_frequency"_a = std::nullopt); + [](Sound self, Positive numberOfCoefficients, Positive windowLength, Positive timeStep, Positive firstFilterFrequency, Positive distanceBetweenFilters, std::optional> maximumFrequency) { + // if (numberOfCoefficients >= 25) Melder_throw(U"The number of coefficients should be less than 25."); // Might be wrong, but I see no reason to enforce this, in the actual code + return Sound_to_MFCC(self, numberOfCoefficients, windowLength, timeStep, firstFilterFrequency, maximumFrequency ? static_cast(*maximumFrequency) : 0.0, distanceBetweenFilters); + }, + "number_of_coefficients"_a = 12, "window_length"_a = 0.015, "time_step"_a = 0.005, "firstFilterFreqency"_a = 100.0, "distance_between_filters"_a = 100.0, "maximum_frequency"_a = std::nullopt); // TODO For some reason praat_David_init.cpp also still contains Sound functionality + + // MAYBE-TODO (used internally by Sound_to_FormantPath_any() in praat/LPC/FormantPath.cpp) + + /* + def("to_lpc" [](Sound self) { + + autoLPC lpc = LPC_create (my xmin, my xmax, numberOfFrames, timeStep, t1, predictionOrder, resampled -> dx); + if (lpcType != kLPC_Analysis::ROBUST) { + Sound_into_LPC (resampled.get(), lpc.get(), analysisWidth, preemphasisFrequency, lpcType, marple_tol1, marple_tol2); + } else { + Sound_into_LPC (resampled.get(), lpc.get(), analysisWidth, preemphasisFrequency, kLPC_Analysis::AUTOCORRELATION, marple_tol1, marple_tol2); + lpc = LPC_Sound_to_LPC_robust (lpc.get(), resampled.get(), analysisWidth, preemphasisFrequency, huber_numberOfStdDev, huber_maximumNumberOfIterations, huber_tol, true); + } + return lpc; + }); + */ + + def("to_lpc_autocorrelation", &Sound_to_LPC_autocorrelation, + "prediction_order"_a = 16, "window_length"_a = 0.025, "time_step"_a = 0.005, + "preemphasis_frequency"_a = 50.0, TO_LPC_AUTOCORRELATION_DOCSTRING); + + def("to_lpc_covariance", &Sound_to_LPC_covariance, + "prediction_order"_a = 16, "window_length"_a = 0.025, "time_step"_a = 0.005, + "preemphasis_frequency"_a = 50.0, TO_LPC_COVARIANCE_DOCSTRING); + + def("to_lpc_burg", &Sound_to_LPC_burg, + "prediction_order"_a = 16, "window_length"_a = 0.025, "time_step"_a = 0.005, + "preemphasis_frequency"_a = 50.0, TO_LPC_BURG_DOCSTRING); + + def("to_lpc_marple", &Sound_to_LPC_marple, + "prediction_order"_a = 16, "window_length"_a = 0.025, "time_step"_a = 0.005, + "preemphasis_frequency"_a = 50.0, "tolerance1"_a = 1e-6, "tolerance2"_a = 1e-6, + TO_LPC_MARPLE_DOCSTRING); + // TODO Still a bunch of Sound in praat_LPC_init.cpp } diff --git a/src/parselmouth/Sound_docstrings.h b/src/parselmouth/Sound_docstrings.h new file mode 100644 index 00000000..4654b027 --- /dev/null +++ b/src/parselmouth/Sound_docstrings.h @@ -0,0 +1,176 @@ +#pragma once + +namespace parselmouth { + +constexpr auto TO_LPC_AUTOCORRELATION_DOCSTRING = R"(Create LPC using autocorrelation method. + +Run linear predictive coding (LPC) analysis with the autocorrelation +method and returns a new :obj:`~parselmouth.LPC` object containing the +analysis outcomes. + +The autocorrelation algorithm is decribed in Markel & Gray (1976). + +Warning +------- +You are advised not to use this command for formant analysis. For formant +analysis, instead use :func:`~parselmouth.Sound.to_formant_burg`, which +also works via LPC. + +Parameters +---------- +prediction_order : int, default=16 + Number of linear prediction coefficients, also called the number of + poles. Choose this number at least twice as large as the number of + spectral peaks that you want to detect. + +window_length : float, default=0.025 + Effective duration of each analysis frame in seconds. + +time_step : float, default=0.005 + Time step between two consecutive analysis frames in seconds. + +preemphasis_frequency : float, default=50.0 + +6dB / octave filtering will be applied above this frequency. If you do + not want pre-emphasis, choose a frequency greater than the Nyquist + frequency. + +See Also +-------- +:praat:`Sound: To LPC (autocorrelation)...` +:obj:`~parselmouth.LPC` +:func:`~parselmouth.Sound.to_lpc_covariance` +:func:`~parselmouth.Sound.to_lpc_burg` +:func:`~parselmouth.Sound.to_lpc_marple` +)"; + +constexpr auto TO_LPC_COVARIANCE_DOCSTRING = R"(Create LPC using covariance method. + +Run linear predictive coding (LPC) analysis with the covariance method and +returns a new :obj:`~parselmouth.LPC` object containing the analysis +outcomes. + +The covariance algorithm is decribed in Markel & Gray (1976). + +Warning +------- +You are advised not to use this command for formant analysis. For formant +analysis, instead use :func:`~parselmouth.Sound.to_formant_burg`, which +also works via LPC. + +Parameters +---------- +prediction_order : int, default=16 + Number of linear prediction coefficients, also called the number of + poles. Choose this number at least twice as large as the number of + spectral peaks that you want to detect. + +window_length : float, default=0.025 + Effective duration of each analysis frame in seconds. + +time_step : float, default=0.005 + Time step between two consecutive analysis frames in seconds. + +preemphasis_frequency : float, default=50.0 + +6dB / octave filtering will be applied above this frequency. + If you do not want pre-emphasis, choose a frequency greater than the + Nyquist frequency. + +See Also +-------- +:praat:`Sound: To LPC (covariance)...` +:obj:`~parselmouth.LPC` +:func:`~parselmouth.Sound.to_lpc_autocorrelation` +:func:`~parselmouth.Sound.to_lpc_burg` +:func:`~parselmouth.Sound.to_lpc_marple` +)"; + +constexpr auto TO_LPC_BURG_DOCSTRING = R"(Create LPC using Burg's method. + +Run linear predictive coding (LPC) analysis with the Burg's method and +returns a new :obj:`~parselmouth.LPC` object containing the analysis +outcomes. + +Burg's algorithm is described in Anderson (1978) + +Warning +------- +You are advised not to use this command for formant analysis. For formant +analysis, instead use :func:`~parselmouth.Sound.to_formant_burg`, which +also works via LPC. + +Parameters +---------- +prediction_order : int, default=16 + Number of linear prediction coefficients, also called the number of + poles. Choose this number at least twice as large as the number of + spectral peaks that you want to detect. + +window_length : float, default=0.025 + Effective duration of each analysis frame in seconds. + +time_step : float, default=0.005 + Time step between two consecutive analysis frames in seconds. + +preemphasis_frequency : float, default=50.0 + +6dB / octave filtering will be applied above this frequency. If you do + not want pre-emphasis, choose a frequency greater than the Nyquist + frequency. + +See Also +-------- +:praat:`Sound: To LPC (burg)...` +:obj:`~parselmouth.LPC` +:func:`~parselmouth.Sound.to_lpc_autocorrelation` +:func:`~parselmouth.Sound.to_lpc_covariance` +:func:`~parselmouth.Sound.to_lpc_marple` +)"; + +constexpr auto TO_LPC_MARPLE_DOCSTRING = R"(Create LPC using Marple's method. + +Run linear predictive coding (LPC) analysis with the Marple's method and +returns a new :obj:`~parselmouth.LPC` object containing the analysis +outcomes. + +The algorithm is described in Marple (1980). + +Warning +------- +You are advised not to use this command for formant analysis. For formant +analysis, instead use :func:`~parselmouth.Sound.to_formant_burg`, which +also works via LPC. + +Parameters +---------- +prediction_order : int, default=16 + Number of linear prediction coefficients, also called the number of + poles. Choose this number at least twice as large as the number of + spectral peaks that you want to detect. + +window_length : float, default=0.025 + Effective duration of each analysis frame in seconds. + +time_step : float, default=0.005 + Time step between two consecutive analysis frames in seconds. + +preemphasis_frequency : float, default=50.0 + +6dB / octave filtering will be applied above this frequency. If you do + not want pre-emphasis, choose a frequency greater than the Nyquist + frequency. + +tolerance1 : float, default=1e-6 + Stop the iteration when E(m) / E(0) < tolerance1, where E(m) is the + prediction error for order m. + +tolerance 2 : float, default=1e-6 + Stop the iteration when (E(m) - E(m-1)) / E(m-1) < tolerance2. + +See Also +-------- +:praat:`Sound: To LPC (marple)...` +:obj:`~parselmouth.LPC` +:func:`~parselmouth.Sound.to_lpc_autocorrelation` +:func:`~parselmouth.Sound.to_lpc_covariance` +:func:`~parselmouth.Sound.to_lpc_burg` +)"; + +}// namespace parselmouth diff --git a/src/parselmouth/common_docstring.h b/src/parselmouth/common_docstring.h new file mode 100644 index 00000000..5842320e --- /dev/null +++ b/src/parselmouth/common_docstring.h @@ -0,0 +1,8 @@ +#pragma once + +namespace parselmouth +{ +constexpr auto LEN_DOCSTRING = R"(Return len(self).)"; +constexpr auto GETITEM_DOCSTRING = R"(x.__getitem__(y) <==> x[y])"; +constexpr auto ITER_DOCSTRING = R"(Implement iter(self).)"; +} \ No newline at end of file diff --git a/tests/test_lpc.py b/tests/test_lpc.py new file mode 100644 index 00000000..9bd0437f --- /dev/null +++ b/tests/test_lpc.py @@ -0,0 +1,26 @@ +import parselmouth + +import numpy as np + + +def test_lpc(sound): + lpc = sound.to_lpc_autocorrelation() + lpc = sound.to_lpc_covariance() + lpc = sound.to_lpc_burg() + lpc = sound.to_lpc_marple() + print(lpc) + + print(f"time_step={lpc.dt} s") + print(f"time_frame_at={lpc.t1} s") + print(f"num_frames={lpc.nt}") + print(f"sampling_rate={1/lpc.sampling_period} samples/second") + print(f"max_n_coefficients={lpc.max_n_coefficients}") + lpc_frame = next(((frame) for frame in lpc)) + + print(f"frame.n_coefficients={lpc_frame.n_coefficients}") + print(f"frame.gain={lpc_frame.gain}") + print(f"frame.a({lpc_frame.a.size}, {lpc_frame.a.dtype})={lpc_frame.a}") + + print(np.array([np.pad(frame.a, (0, lpc.max_n_coefficients-frame.n_coefficients), 'constant') for frame in lpc]).shape) + + # assert False \ No newline at end of file