diff --git a/src/parselmouth.cpp b/src/parselmouth.cpp index b71c4480..d053011a 100644 --- a/src/parselmouth.cpp +++ b/src/parselmouth.cpp @@ -141,7 +141,7 @@ using PraatBindings = Bindingscandidates[1]; }, - [](Pitch_Frame self, Pitch_Candidate candidate) { - for (long j = 1; j <= self->nCandidates; j++) { - if (&self->candidates[j] == candidate) { - std::swap(self->candidates[1], self->candidates[j]); - return; - } - } - throw py::value_error("'candidate' is not a Pitch Candidate of this frame"); - }); + [](Pitch_Frame self) { return &self->candidates[1]; }, + [](Pitch_Frame self, Pitch_Candidate candidate) { + for (long j = 1; j <= self->nCandidates; j++) { + if (&self->candidates[j] == candidate) { + std::swap(self->candidates[1], self->candidates[j]); + return; + } + } + throw py::value_error("'candidate' is not a Pitch Candidate of this frame"); + }); def_property_readonly("candidates", [](Pitch_Frame self) { return std::vector(&self->candidates[1], &self->candidates[self->nCandidates + 1]); }); @@ -92,24 +92,24 @@ PRAAT_STRUCT_BINDING(Frame, Pitch_Frame) { }); def("select", - [](Pitch_Frame self, Pitch_Candidate candidate) { - for (long j = 1; j <= self->nCandidates; j++) { - if (self->candidates[j].frequency == candidate->frequency && self->candidates[j].strength == candidate->strength) { - std::swap(self->candidates[1], self->candidates[j]); - return; - } - } - throw py::value_error("'candidate' is not a Pitch Candidate of this frame"); - }, - "candidate"_a.none(false)); + [](Pitch_Frame self, Pitch_Candidate candidate) { + for (long j = 1; j <= self->nCandidates; j++) { + if (self->candidates[j].frequency == candidate->frequency && self->candidates[j].strength == candidate->strength) { + std::swap(self->candidates[1], self->candidates[j]); + return; + } + } + throw py::value_error("'candidate' is not a Pitch Candidate of this frame"); + }, + "candidate"_a.none(false)); def("select", - [](Pitch_Frame self, long i) { + [](Pitch_Frame self, long i) { if (i < 0) i += self->nCandidates; // Python-style negative indexing - if (i < 0 || i >= self->nCandidates) throw py::index_error("Pitch Frame index out of range"); + if (i < 0 || i >= self->nCandidates) throw py::index_error("Pitch Frame index out of range"); return std::swap(self->candidates[1], self->candidates[i+1]); - }, - "i"_a); + }, + "i"_a); def("__getitem__", [](Pitch_Frame self, long i) { @@ -139,69 +139,69 @@ PRAAT_CLASS_BINDING(Pitch) { // TODO Which constructors? From Sound? def("to_sound_pulses", - [](Pitch self, std::optional fromTime, std::optional toTime) { return Pitch_to_Sound(self, fromTime.value_or(self->xmin), toTime.value_or(self->xmax), false); }, - "from_time"_a = std::nullopt, "to_time"_a = std::nullopt); + [](Pitch self, std::optional fromTime, std::optional toTime) { return Pitch_to_Sound(self, fromTime.value_or(self->xmin), toTime.value_or(self->xmax), false); }, + "from_time"_a = std::nullopt, "to_time"_a = std::nullopt); def("to_sound_hum", - [](Pitch self, std::optional fromTime, std::optional toTime) { return Pitch_to_Sound(self, fromTime.value_or(self->xmin), toTime.value_or(self->xmax), true); }, - "from_time"_a = std::nullopt, "to_time"_a = std::nullopt); + [](Pitch self, std::optional fromTime, std::optional toTime) { return Pitch_to_Sound(self, fromTime.value_or(self->xmin), toTime.value_or(self->xmax), true); }, + "from_time"_a = std::nullopt, "to_time"_a = std::nullopt); def("to_sound_sine", - [](Pitch self, std::optional fromTime, std::optional toTime, Positive samplingFrequency, double roundToNearestZeroCrossing) { return Pitch_to_Sound_sine(self, fromTime.value_or(self->xmin), toTime.value_or(self->xmax), samplingFrequency, roundToNearestZeroCrossing); }, - "from_time"_a = std::nullopt, "to_time"_a = std::nullopt, "sampling_frequency"_a = 44100.0, "round_to_nearest_zero_crossing"_a = true); + [](Pitch self, std::optional fromTime, std::optional toTime, Positive samplingFrequency, double roundToNearestZeroCrossing) { return Pitch_to_Sound_sine(self, fromTime.value_or(self->xmin), toTime.value_or(self->xmax), samplingFrequency, roundToNearestZeroCrossing); }, + "from_time"_a = std::nullopt, "to_time"_a = std::nullopt, "sampling_frequency"_a = 44100.0, "round_to_nearest_zero_crossing"_a = true); def("count_voiced_frames", - &Pitch_countVoicedFrames); + &Pitch_countVoicedFrames); def("get_value_at_time", - [](Pitch self, double time, kPitch_unit unit, kVector_valueInterpolation interpolation) { - if (interpolation != kVector_valueInterpolation::NEAREST && interpolation != kVector_valueInterpolation::LINEAR) - Melder_throw(U"Pitch values can only be queried using NEAREST or LINEAR interpolation"); - auto value = Sampled_getValueAtX(self, time, Pitch_LEVEL_FREQUENCY, static_cast(unit), interpolation == kVector_valueInterpolation::LINEAR); - return Function_convertToNonlogarithmic(self, value, Pitch_LEVEL_FREQUENCY, static_cast(unit)); - }, - "time"_a, "unit"_a = kPitch_unit::HERTZ, "interpolation"_a = kVector_valueInterpolation::LINEAR); + [](Pitch self, double time, kPitch_unit unit, kVector_valueInterpolation interpolation) { + if (interpolation != kVector_valueInterpolation::NEAREST && interpolation != kVector_valueInterpolation::LINEAR) + Melder_throw(U"Pitch values can only be queried using NEAREST or LINEAR interpolation"); + auto value = Sampled_getValueAtX(self, time, Pitch_LEVEL_FREQUENCY, static_cast(unit), interpolation == kVector_valueInterpolation::LINEAR); + return Function_convertToNonlogarithmic(self, value, Pitch_LEVEL_FREQUENCY, static_cast(unit)); + }, + "time"_a, "unit"_a = kPitch_unit::HERTZ, "interpolation"_a = kVector_valueInterpolation::LINEAR); // TODO get_strength_at_time ? -> Pitch strength unit enum def("get_value_in_frame", - [](Pitch self, long frameNumber, kPitch_unit unit) { - auto value = Sampled_getValueAtSample(self, frameNumber, Pitch_LEVEL_FREQUENCY, static_cast(unit)); - return Function_convertToNonlogarithmic(self, value, Pitch_LEVEL_FREQUENCY, static_cast(unit)); - }, - "frame_number"_a, "unit"_a = kPitch_unit::HERTZ); + [](Pitch self, long frameNumber, kPitch_unit unit) { + auto value = Sampled_getValueAtSample(self, frameNumber, Pitch_LEVEL_FREQUENCY, static_cast(unit)); + return Function_convertToNonlogarithmic(self, value, Pitch_LEVEL_FREQUENCY, static_cast(unit)); + }, + "frame_number"_a, "unit"_a = kPitch_unit::HERTZ); // TODO Minimum, Time of minimum, Maximum, Time of maximum, ... def("get_mean_absolute_slope", - [](Pitch self, kPitch_unit unit) { - double slope; - long nVoiced = 0; - switch (unit) { - case kPitch_unit::HERTZ: - nVoiced = Pitch_getMeanAbsSlope_hertz(self, &slope); - break; - case kPitch_unit::MEL: - nVoiced = Pitch_getMeanAbsSlope_mel(self, &slope); - break; - case kPitch_unit::SEMITONES_1: - case kPitch_unit::SEMITONES_100: - case kPitch_unit::SEMITONES_200: - case kPitch_unit::SEMITONES_440: - nVoiced = Pitch_getMeanAbsSlope_semitones(self, &slope); - break; - case kPitch_unit::ERB: - nVoiced = Pitch_getMeanAbsSlope_erb(self, &slope); - break; - case kPitch_unit::HERTZ_LOGARITHMIC: - case kPitch_unit::LOG_HERTZ: - Melder_throw(U"The mean absolute slope of a Pitch object can only be calculated with units HERTZ, MEL, SEMITONES_1, SEMITONES_100, SEMITONES_200, SEMITONES_440, and ERB"); - case kPitch_unit::UNDEFINED: - Melder_throw(U"ERROR: PitchUnit should never be UNDEFINED!"); - } - if (nVoiced < 2) - return double{undefined}; - return slope; + [](Pitch self, kPitch_unit unit) { + double slope; + long nVoiced = 0; + switch (unit) { + case kPitch_unit::HERTZ: + nVoiced = Pitch_getMeanAbsSlope_hertz(self, &slope); + break; + case kPitch_unit::MEL: + nVoiced = Pitch_getMeanAbsSlope_mel(self, &slope); + break; + case kPitch_unit::SEMITONES_1: + case kPitch_unit::SEMITONES_100: + case kPitch_unit::SEMITONES_200: + case kPitch_unit::SEMITONES_440: + nVoiced = Pitch_getMeanAbsSlope_semitones(self, &slope); + break; + case kPitch_unit::ERB: + nVoiced = Pitch_getMeanAbsSlope_erb(self, &slope); + break; + case kPitch_unit::HERTZ_LOGARITHMIC: + case kPitch_unit::LOG_HERTZ: + Melder_throw(U"The mean absolute slope of a Pitch object can only be calculated with units HERTZ, MEL, SEMITONES_1, SEMITONES_100, SEMITONES_200, SEMITONES_440, and ERB"); + case kPitch_unit::UNDEFINED: + Melder_throw(U"ERROR: PitchUnit should never be UNDEFINED!"); + } + if (nVoiced < 2) + return double{undefined}; + return slope; }, "unit"_a = kPitch_unit::HERTZ); def("get_slope_without_octave_jumps", @@ -212,16 +212,16 @@ PRAAT_CLASS_BINDING(Pitch) { }); def("count_differences", - [](Pitch self, Pitch other) { - MelderInfoInterceptor info; - Pitch_difference(self, other); - return info.get(); - }, - "other"_a.none(false)); + [](Pitch self, Pitch other) { + MelderInfoInterceptor info; + Pitch_difference(self, other); + return info.get(); + }, + "other"_a.none(false)); def("formula", - [](Pitch self, const std::u32string &formula) { Pitch_formula(self, formula.c_str(), nullptr); }, - "formula"_a); + [](Pitch self, const std::u32string &formula) { Pitch_formula(self, formula.c_str(), nullptr); }, + "formula"_a); // TODO To TextGrid..., To TextTier, To IntervalTier: depends TextGrid and Tiers // TODO To PointProcess: depends on PointProcess @@ -230,8 +230,8 @@ PRAAT_CLASS_BINDING(Pitch) { &Pitch_interpolate); def("smooth", - args_cast<_, Positive<_>>(Pitch_smooth), - "bandwidth"_a = 10.0); + args_cast<_, Positive<_>>(Pitch_smooth), + "bandwidth"_a = 10.0); def("subtract_linear_fit", &Pitch_subtractLinearFit, @@ -281,37 +281,37 @@ PRAAT_CLASS_BINDING(Pitch) { def_readonly("max_n_candidates", &structPitch::maxnCandidates); def("get_frame", - [](Pitch self, Positive frameNumber) { - if (frameNumber > self->nx) Melder_throw(U"Frame number out of range"); - return &self->frames[frameNumber]; - }, - "frame_number"_a, py::return_value_policy::reference_internal); + [](Pitch self, Positive frameNumber) { + if (frameNumber > self->nx) Melder_throw(U"Frame number out of range"); + return &self->frames[frameNumber]; + }, + "frame_number"_a, py::return_value_policy::reference_internal); def("__getitem__", - [](Pitch self, long i) { + [](Pitch self, long i) { if (i < 0) i += self->nx; // Python-style negative indexing - if (i < 0 || i >= self->nx) throw py::index_error("Pitch index out of range"); + if (i < 0 || i >= self->nx) throw py::index_error("Pitch index out of range"); return &self->frames[i+1]; - }, - "i"_a, py::return_value_policy::reference_internal); + }, + "i"_a, py::return_value_policy::reference_internal); def("__getitem__", - [](Pitch self, std::tuple ij) { - auto &[i, j] = ij; + [](Pitch self, std::tuple ij) { + auto &[i, j] = ij; if (i < 0) i += self->nx; // Python-style negative indexing - if (i < 0 || i >= self->nx) throw py::index_error("Pitch index out of range"); + if (i < 0 || i >= self->nx) throw py::index_error("Pitch index out of range"); auto &frame = self->frames[i+1]; if (j < 0) j += frame.nCandidates; // Python-style negative indexing - if (j < 0 || j >= frame.nCandidates) throw py::index_error("Pitch Frame index out of range"); + if (j < 0 || j >= frame.nCandidates) throw py::index_error("Pitch Frame index out of range"); return frame.candidates[j+1]; - }, - "ij"_a); + }, + "ij"_a); // TODO __setitem__ def("__iter__", [](Pitch self) { return py::make_iterator(&self->frames[1], &self->frames[self->nx+1]); }, - py::keep_alive<0, 1>()); + py::keep_alive<0, 1>()); def("to_array", [](Pitch self) { @@ -354,52 +354,52 @@ PRAAT_CLASS_BINDING(Pitch) { "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, "ceiling"_a = 600.0, "pull_formants"_a = false); def("step", - [](Pitch self, double step, Positive precision, std::optional fromTime, std::optional toTime) { Pitch_step(self, step, precision, fromTime.value_or(self->xmin), toTime.value_or(self->xmax)); }, - "step"_a, "precision"_a = 0.1, "from_time"_a = std::nullopt, "to_time"_a = std::nullopt); + [](Pitch self, double step, Positive precision, std::optional fromTime, std::optional toTime) { Pitch_step(self, step, precision, fromTime.value_or(self->xmin), toTime.value_or(self->xmax)); }, + "step"_a, "precision"_a = 0.1, "from_time"_a = std::nullopt, "to_time"_a = std::nullopt); def("octave_up", - [](Pitch self, std::optional fromTime, std::optional toTime) { - Pitch_step(self, 2.0, 0.1, fromTime.value_or(self->xmin), toTime.value_or(self->xmax)); - }, - "from_time"_a = std::nullopt, "to_time"_a = std::nullopt); + [](Pitch self, std::optional fromTime, std::optional toTime) { + Pitch_step(self, 2.0, 0.1, fromTime.value_or(self->xmin), toTime.value_or(self->xmax)); + }, + "from_time"_a = std::nullopt, "to_time"_a = std::nullopt); def("fifth_up", - [](Pitch self, std::optional fromTime, std::optional toTime) { - Pitch_step(self, 1.5, 0.1, fromTime.value_or(self->xmin), toTime.value_or(self->xmax)); - }, - "from_time"_a = std::nullopt, "to_time"_a = std::nullopt); + [](Pitch self, std::optional fromTime, std::optional toTime) { + Pitch_step(self, 1.5, 0.1, fromTime.value_or(self->xmin), toTime.value_or(self->xmax)); + }, + "from_time"_a = std::nullopt, "to_time"_a = std::nullopt); def("fifth_down", - [](Pitch self, std::optional fromTime, std::optional toTime) { - Pitch_step(self, 1 / 1.5, 0.1, fromTime.value_or(self->xmin), toTime.value_or(self->xmax)); - }, - "from_time"_a = std::nullopt, "to_time"_a = std::nullopt); + [](Pitch self, std::optional fromTime, std::optional toTime) { + Pitch_step(self, 1 / 1.5, 0.1, fromTime.value_or(self->xmin), toTime.value_or(self->xmax)); + }, + "from_time"_a = std::nullopt, "to_time"_a = std::nullopt); def("octave_down", - [](Pitch self, std::optional fromTime, std::optional toTime) { - Pitch_step(self, 0.5, 0.1, fromTime.value_or(self->xmin), toTime.value_or(self->xmax)); - }, - "from_time"_a = std::nullopt, "to_time"_a = std::nullopt); + [](Pitch self, std::optional fromTime, std::optional toTime) { + Pitch_step(self, 0.5, 0.1, fromTime.value_or(self->xmin), toTime.value_or(self->xmax)); + }, + "from_time"_a = std::nullopt, "to_time"_a = std::nullopt); def("unvoice", - [](Pitch self, std::optional fromTime, std::optional toTime) { - long ileft = Sampled_xToHighIndex(self, fromTime.value_or(self->xmin)); - long iright = Sampled_xToLowIndex(self, toTime.value_or(self->xmax)); + [](Pitch self, std::optional fromTime, std::optional toTime) { + long ileft = Sampled_xToHighIndex(self, fromTime.value_or(self->xmin)); + long iright = Sampled_xToLowIndex(self, toTime.value_or(self->xmax)); - if (ileft < 1) ileft = 1; + if (ileft < 1) ileft = 1; if (iright > self->nx) iright = self-> nx; for (auto i = ileft; i <= iright; i ++) { - auto &frame = self->frames[i]; - for (long j = 1; j <= frame.nCandidates; j++) { - if (frame.candidates[j].frequency == 0.0) { - std::swap(frame.candidates[1], frame.candidates[j]); - break; - } - } - } - }, - "from_time"_a = std::nullopt, "to_time"_a = std::nullopt); + auto &frame = self->frames[i]; + for (long j = 1; j <= frame.nCandidates; j++) { + if (frame.candidates[j].frequency == 0.0) { + std::swap(frame.candidates[1], frame.candidates[j]); + break; + } + } + } + }, + "from_time"_a = std::nullopt, "to_time"_a = std::nullopt); // TODO Pitch_Intensity_getMean & Pitch_Intensity_getMeanAbsoluteSlope ? (cfr. Intensity) } diff --git a/src/parselmouth/Pitch_docstrings.h b/src/parselmouth/Pitch_docstrings.h index aaac52ab..d4c52678 100644 --- a/src/parselmouth/Pitch_docstrings.h +++ b/src/parselmouth/Pitch_docstrings.h @@ -1,6 +1,7 @@ namespace parselmouth { -constexpr auto TO_POINT_PROCESS_DOCSTRING = R"(Create PointProcess from Pitch object. +constexpr auto TO_POINT_PROCESS_DOCSTRING = +R"(Create PointProcess from Pitch object. Returns a new PointProcess instance which is generated from the specified Pitch object. The acoustic periodicity contour stored in the Pitch object @@ -59,7 +60,8 @@ See Also :praat:`Sound & Pitch: To PointProcess (peaks)...` )"; -constexpr auto TO_POINT_PROCESS_CC_DOCSTRING = R"(Create PointProcess from Sound and Pitch objects using crosscorrelation. +constexpr auto TO_POINT_PROCESS_CC_DOCSTRING = +R"(Create PointProcess from Sound and Pitch objects using crosscorrelation. Returns a new PointProcess instance, generated from the specified Sound and Pitch instances using the cross-correlation method. The resulting @@ -80,7 +82,8 @@ See Also :praat:`Sound & Pitch: To PointProcess (cc)` )"; -constexpr auto TO_POINT_PROCESS_PEAKS_DOCSTRING = R"(Create PointProcess from Sound and Pitch objects using peak-picking. +constexpr auto TO_POINT_PROCESS_PEAKS_DOCSTRING = +R"(Create PointProcess from Sound and Pitch objects using peak-picking. Returns a new PointProcess instance, generated from the specified Sound and Pitch instances using the peak-picking method. The resulting diff --git a/src/parselmouth/PointProcess.cpp b/src/parselmouth/PointProcess.cpp index edfc98e3..38928daa 100644 --- a/src/parselmouth/PointProcess.cpp +++ b/src/parselmouth/PointProcess.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019-2021 Yannick Jadoul + * Copyright (C) 2021 Yannick Jadoul * * This file is part of Parselmouth. * @@ -17,8 +17,6 @@ * along with Parselmouth. If not, see */ -#include - #include "Parselmouth.h" #include "PointProcess_docstrings.h" @@ -35,6 +33,7 @@ #include #include +#include #include #include diff --git a/tests/resource_fixtures.py b/tests/resource_fixtures.py index e03fd8ba..9afd56a3 100644 --- a/tests/resource_fixtures.py +++ b/tests/resource_fixtures.py @@ -45,12 +45,17 @@ def pitch(sound): yield sound.to_pitch() +@pytest.fixture +def point_process(pitch): + yield pitch.to_point_process() + + @pytest.fixture def spectrogram(sound): yield sound.to_spectrogram() -@combined_fixture('intensity', 'pitch', 'spectrogram', 'sound') +@combined_fixture('sound', 'intensity', 'pitch', 'point_process', 'spectrogram') def sampled(request): yield request.param @@ -73,8 +78,3 @@ def text_grid(text_grid_path): @pytest.fixture def script_path(resources): yield resources["script.praat"] - - -@pytest.fixture -def point_process(pitch): - yield pitch.to_point_process()