From 3ce257c1d314649536ec7ad2e406cd879b606c00 Mon Sep 17 00:00:00 2001 From: takenori-y Date: Wed, 27 Sep 2023 16:18:41 +0900 Subject: [PATCH] add pitch2sin --- CMakeLists.txt | 2 + .../SPTK/generation/sinusoidal_generation.h | 73 ++++++ src/generation/sinusoidal_generation.cc | 80 +++++++ src/main/gmm.cc | 2 +- src/main/pitch2sin.cc | 220 ++++++++++++++++++ src/main/sin.cc | 57 +++-- test/test_pitch2sin.bats | 34 +++ 7 files changed, 453 insertions(+), 15 deletions(-) create mode 100644 include/SPTK/generation/sinusoidal_generation.h create mode 100644 src/generation/sinusoidal_generation.cc create mode 100644 src/main/pitch2sin.cc create mode 100644 test/test_pitch2sin.bats diff --git a/CMakeLists.txt b/CMakeLists.txt index 1582974..d19492f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -154,6 +154,7 @@ set(CC_SOURCES ${SOURCE_DIR}/generation/nonrecursive_maximum_likelihood_parameter_generation.cc ${SOURCE_DIR}/generation/normal_distributed_random_value_generation.cc ${SOURCE_DIR}/generation/recursive_maximum_likelihood_parameter_generation.cc + ${SOURCE_DIR}/generation/sinusoidal_generation.cc ${SOURCE_DIR}/input/input_source_delay.cc ${SOURCE_DIR}/input/input_source_filling_magic_number.cc ${SOURCE_DIR}/input/input_source_from_array.cc @@ -352,6 +353,7 @@ set(MAIN_SOURCES ${SOURCE_DIR}/main/pcas.cc ${SOURCE_DIR}/main/phase.cc ${SOURCE_DIR}/main/pitch.cc + ${SOURCE_DIR}/main/pitch2sin.cc ${SOURCE_DIR}/main/pitch_mark.cc ${SOURCE_DIR}/main/pitch_spec.cc ${SOURCE_DIR}/main/poledf.cc diff --git a/include/SPTK/generation/sinusoidal_generation.h b/include/SPTK/generation/sinusoidal_generation.h new file mode 100644 index 0000000..e395ef5 --- /dev/null +++ b/include/SPTK/generation/sinusoidal_generation.h @@ -0,0 +1,73 @@ +// ------------------------------------------------------------------------ // +// Copyright 2021 SPTK Working Group // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// ------------------------------------------------------------------------ // + +#ifndef SPTK_GENERATION_SINUSOIDAL_GENERATION_H_ +#define SPTK_GENERATION_SINUSOIDAL_GENERATION_H_ + +#include "SPTK/input/input_source_interpolation_with_magic_number.h" +#include "SPTK/utils/sptk_utils.h" + +namespace sptk { + +/** + * Generate sinusoidal sequence. + * + * The input is a sequence of pitch value which can be either a continuous value + * or a magic number. The output is the sinusoidal signal given the input + * sequence. + */ +class SinusoidalGeneration { + public: + /** + * @param[in] input_source Input source. + */ + explicit SinusoidalGeneration( + InputSourceInterpolationWithMagicNumber* input_source); + + virtual ~SinusoidalGeneration() { + } + + /** + * @return True if this object is valid. + */ + bool IsValid() const { + return is_valid_; + } + + /** + * Get sinusoidal signal. + * + * @param[out] sin Sine waveform (optional). + * @param[out] cos Cosine waveform (optional). + * @param[out] pitch Pitch (optional). + * @return True on success, false on failure. + */ + bool Get(double* sin, double* cos, double* pitch); + + private: + InputSourceInterpolationWithMagicNumber* input_source_; + + bool is_valid_; + + // Phase value ranging from 0.0 to 2 x pi + double phase_; + + DISALLOW_COPY_AND_ASSIGN(SinusoidalGeneration); +}; + +} // namespace sptk + +#endif // SPTK_GENERATION_SINUSOIDAL_GENERATION_H_ diff --git a/src/generation/sinusoidal_generation.cc b/src/generation/sinusoidal_generation.cc new file mode 100644 index 0000000..69106c8 --- /dev/null +++ b/src/generation/sinusoidal_generation.cc @@ -0,0 +1,80 @@ +// ------------------------------------------------------------------------ // +// Copyright 2021 SPTK Working Group // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// ------------------------------------------------------------------------ // + +#include "SPTK/generation/sinusoidal_generation.h" + +#include // std::cos, std::sin +#include // std::vector + +namespace sptk { + +SinusoidalGeneration::SinusoidalGeneration( + InputSourceInterpolationWithMagicNumber* input_source) + : input_source_(input_source), is_valid_(true), phase_(0.0) { + if (NULL == input_source_ || !input_source_->IsValid()) { + is_valid_ = false; + return; + } +} + +bool SinusoidalGeneration::Get(double* sin, double* cos, double* pitch) { + if (!is_valid_) { + return false; + } + + // Get pitch. + double pitch_in_current_point; + { + std::vector tmp; + if (!input_source_->Get(&tmp) || tmp[0] < 0.0) { + return false; + } + pitch_in_current_point = tmp[0]; + } + + if (pitch) { + *pitch = pitch_in_current_point; + } + + // If unvoiced point, return zero. + if (input_source_->GetMagicNumber() == pitch_in_current_point) { + phase_ = 0.0; + if (sin) { + *sin = 0.0; + } + if (cos) { + *cos = 0.0; + } + return true; + } + + if (sin) { + *sin = std::sin(phase_); + } + if (cos) { + *cos = std::cos(phase_); + } + + // Proceed phase. + phase_ += sptk::kTwoPi / pitch_in_current_point; + if (sptk::kTwoPi < phase_) { + phase_ -= sptk::kTwoPi; + } + + return true; +} + +} // namespace sptk diff --git a/src/main/gmm.cc b/src/main/gmm.cc index cd98865..ad48f27 100644 --- a/src/main/gmm.cc +++ b/src/main/gmm.cc @@ -14,7 +14,7 @@ // limitations under the License. // // ------------------------------------------------------------------------ // -#include // std::ifstream +#include // std::ifstream, std::ofstream #include // std::setw #include // std::cerr, std::cin, std::cout, std::endl, etc. #include // std::ostringstream diff --git a/src/main/pitch2sin.cc b/src/main/pitch2sin.cc new file mode 100644 index 0000000..f220811 --- /dev/null +++ b/src/main/pitch2sin.cc @@ -0,0 +1,220 @@ +// ------------------------------------------------------------------------ // +// Copyright 2021 SPTK Working Group // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// ------------------------------------------------------------------------ // + +#include // std::ifstream +#include // std::setw +#include // std::cerr, std::cin, std::cout, std::endl, etc. +#include // std::ostringstream + +#include "GETOPT/ya_getopt.h" +#include "SPTK/generation/sinusoidal_generation.h" +#include "SPTK/input/input_source_from_stream.h" +#include "SPTK/input/input_source_interpolation_with_magic_number.h" +#include "SPTK/utils/sptk_utils.h" + +namespace { + +enum OutputFormats { kSine = 0, kCosine, kNumOutputFormats }; + +const int kDefaultFramePeriod(100); +const int kDefaultInterpolationPeriod(1); +const OutputFormats kDefaultOutputFormat(kSine); +const double kMagicNumberForUnvoicedFrame(0.0); + +void PrintUsage(std::ostream* stream) { + // clang-format off + *stream << std::endl; + *stream << " pitch2sin - convert pitch to sinusoidal sequence" << std::endl; + *stream << std::endl; + *stream << " usage:" << std::endl; + *stream << " pitch2sin [ options ] [ infile ] > stdout" << std::endl; + *stream << " options:" << std::endl; + *stream << " -p p : frame period ( int)[" << std::setw(5) << std::right << kDefaultFramePeriod << "][ 1 <= p <= ]" << std::endl; // NOLINT + *stream << " -i i : interpolation period ( int)[" << std::setw(5) << std::right << kDefaultInterpolationPeriod << "][ 0 <= i <= p/2 ]" << std::endl; // NOLINT + *stream << " -o o : output format ( int)[" << std::setw(5) << std::right << kDefaultOutputFormat << "][ 0 <= o <= 1 ]" << std::endl; // NOLINT + *stream << " 0 (sine)" << std::endl; + *stream << " 1 (cosine)" << std::endl; + *stream << " -h : print this message" << std::endl; + *stream << " infile:" << std::endl; + *stream << " pitch period (double)[stdin]" << std::endl; + *stream << " stdout:" << std::endl; + *stream << " sinusoidal sequence (double)" << std::endl; + *stream << " notice:" << std::endl; + *stream << " if i = 0, don't interpolate pitch" << std::endl; + *stream << " magic number for unvoiced frame is " << kMagicNumberForUnvoicedFrame << std::endl; // NOLINT + *stream << std::endl; + *stream << " SPTK: version " << sptk::kVersion << std::endl; + *stream << std::endl; + // clang-format on +} + +} // namespace + +/** + * @a pitch2sin [ @e option ] [ @e infile ] + * + * - @b -p @e int + * - frame_period @f$(1 \le P)@f$ + * - @b -i @e int + * - interpolation period @f$(0 \le I \le P/2)@f$ + * - @b -o @e int + * - output format + * \arg @c 0 sine + * \arg @c 1 cosine + * - @b infile @e str + * - pitch period + * - @b stdout + * - sinudoisal sequence + * + * The below is a simple example to generate sinusoidal from @c data.d + * + * @code{.sh} + * pitch -s 16 -p 80 -o 0 < data.d | pitch2sin -p 80 > data.sin + * @endcode + * + * @param[in] argc Number of arguments. + * @param[in] argv Argument vector. + * @return 0 on success, 1 on failure. + */ +int main(int argc, char* argv[]) { + int frame_period(kDefaultFramePeriod); + int interpolation_period(kDefaultInterpolationPeriod); + OutputFormats output_format(kDefaultOutputFormat); + + for (;;) { + const int option_char(getopt_long(argc, argv, "p:i:o:h", NULL, NULL)); + if (-1 == option_char) break; + + switch (option_char) { + case 'p': { + if (!sptk::ConvertStringToInteger(optarg, &frame_period) || + frame_period <= 0) { + std::ostringstream error_message; + error_message + << "The argument for the -p option must be a positive integer"; + sptk::PrintErrorMessage("pitch2sin", error_message); + return 1; + } + break; + } + case 'i': { + if (!sptk::ConvertStringToInteger(optarg, &interpolation_period) || + interpolation_period < 0) { + std::ostringstream error_message; + error_message << "The argument for the -i option must be a " + << "non-negative integer"; + sptk::PrintErrorMessage("pitch2sin", error_message); + return 1; + } + break; + } + case 'o': { + const int min(0); + const int max(static_cast(kNumOutputFormats) - 1); + int tmp; + if (!sptk::ConvertStringToInteger(optarg, &tmp) || + !sptk::IsInRange(tmp, min, max)) { + std::ostringstream error_message; + error_message << "The argument for the -o option must be an integer " + << "in the range of " << min << " to " << max; + sptk::PrintErrorMessage("pitch2sin", error_message); + return 1; + } + output_format = static_cast(tmp); + break; + } + case 'h': { + PrintUsage(&std::cout); + return 0; + } + default: { + PrintUsage(&std::cerr); + return 1; + } + } + } + + if (frame_period / 2 < interpolation_period) { + std::ostringstream error_message; + error_message << "Interpolation period must be equal to or less than half " + << "frame period"; + sptk::PrintErrorMessage("pitch2sin", error_message); + return 1; + } + + const int num_input_files(argc - optind); + if (1 < num_input_files) { + std::ostringstream error_message; + error_message << "Too many input files"; + sptk::PrintErrorMessage("pitch2sin", error_message); + return 1; + } + const char* input_file(0 == num_input_files ? NULL : argv[optind]); + + if (!sptk::SetBinaryMode()) { + std::ostringstream error_message; + error_message << "Cannot set translation mode"; + sptk::PrintErrorMessage("pitch2sin", error_message); + return 1; + } + + std::ifstream ifs; + if (NULL != input_file) { + ifs.open(input_file, std::ios::in | std::ios::binary); + if (ifs.fail()) { + std::ostringstream error_message; + error_message << "Cannot open file " << input_file; + sptk::PrintErrorMessage("pitch2sin", error_message); + return 1; + } + } + std::istream& input_stream(ifs.is_open() ? ifs : std::cin); + + sptk::InputSourceFromStream input_source_from_stream(false, 1, &input_stream); + sptk::InputSourceInterpolationWithMagicNumber + input_source_interpolation_with_magic_number( + frame_period, interpolation_period, false, + kMagicNumberForUnvoicedFrame, &input_source_from_stream); + if (!input_source_interpolation_with_magic_number.IsValid()) { + std::ostringstream error_message; + error_message << "Failed to initialize InputSourceFromStream"; + sptk::PrintErrorMessage("pitch2sin", error_message); + return 1; + } + + sptk::SinusoidalGeneration sinusoidal_generation( + &input_source_interpolation_with_magic_number); + if (!sinusoidal_generation.IsValid()) { + std::ostringstream error_message; + error_message << "Failed to initialize SinusoidalGeneration"; + sptk::PrintErrorMessage("pitch2sin", error_message); + return 1; + } + + double sinusoidal; + while (sinusoidal_generation.Get( + kSine == output_format ? &sinusoidal : NULL, + kCosine == output_format ? &sinusoidal : NULL, NULL)) { + if (!sptk::WriteStream(sinusoidal, &std::cout)) { + std::ostringstream error_message; + error_message << "Failed to write sinusoidal"; + sptk::PrintErrorMessage("pitch2sin", error_message); + return 1; + } + } + + return 0; +} diff --git a/src/main/sin.cc b/src/main/sin.cc index 5ed8e20..f960449 100644 --- a/src/main/sin.cc +++ b/src/main/sin.cc @@ -24,10 +24,12 @@ namespace { +enum OutputFormats { kSine = 0, kCosine, kNumOutputFormats }; + const int kMagicNumberForInfinity(-1); const double kDefaultPeriod(10.0); const double kDefaultAmplitude(1.0); -const bool kDefaultCosineWaveFlag(false); +const OutputFormats kDefaultOutputFormat(kSine); void PrintUsage(std::ostream* stream) { // clang-format off @@ -37,11 +39,13 @@ void PrintUsage(std::ostream* stream) { *stream << " usage:" << std::endl; *stream << " sin [ options ] > stdout" << std::endl; *stream << " options:" << std::endl; - *stream << " -l l : output length ( int)[" << std::setw(5) << std::right << "INF" << "][ 1 <= l <= ]" << std::endl; // NOLINT - *stream << " -m m : output order ( int)[" << std::setw(5) << std::right << "l-1" << "][ 0 <= m <= ]" << std::endl; // NOLINT - *stream << " -p p : period (double)[" << std::setw(5) << std::right << kDefaultPeriod << "][ 0.0 < p <= ]" << std::endl; // NOLINT - *stream << " -a a : amplitude (double)[" << std::setw(5) << std::right << kDefaultAmplitude << "][ <= a <= ]" << std::endl; // NOLINT - *stream << " -C : cosine wave ( bool)[" << std::setw(5) << std::right << sptk::ConvertBooleanToString(kDefaultCosineWaveFlag) << "]" << std::endl; // NOLINT + *stream << " -l l : output length ( int)[" << std::setw(5) << std::right << "INF" << "][ 1 <= l <= ]" << std::endl; // NOLINT + *stream << " -m m : output order ( int)[" << std::setw(5) << std::right << "l-1" << "][ 0 <= m <= ]" << std::endl; // NOLINT + *stream << " -p p : period (double)[" << std::setw(5) << std::right << kDefaultPeriod << "][ 0.0 < p <= ]" << std::endl; // NOLINT + *stream << " -a a : amplitude (double)[" << std::setw(5) << std::right << kDefaultAmplitude << "][ <= a <= ]" << std::endl; // NOLINT + *stream << " -o o : output format ( int)[" << std::setw(5) << std::right << kDefaultOutputFormat << "][ 0 <= o <= 1 ]" << std::endl; // NOLINT + *stream << " 0 (sine)" << std::endl; + *stream << " 1 (cosine)" << std::endl; *stream << " -h : print this message" << std::endl; *stream << " stdout:" << std::endl; *stream << " sinusoidal sequence (double)" << std::endl; @@ -64,8 +68,10 @@ void PrintUsage(std::ostream* stream) { * - period @f$(0 < P)@f$ * - @b -a @e double * - amplitude @f$(A)@f$ - * - @b -C - * - generate cosine wave + * - @b -o @e int + * - output format + * \arg @c 0 sine + * \arg @c 1 cosine * - @b stdout * - double-type sinusoidal sequence * @@ -89,10 +95,10 @@ int main(int argc, char* argv[]) { int output_length(kMagicNumberForInfinity); double period(kDefaultPeriod); double amplitude(kDefaultAmplitude); - bool cosine_wave(kDefaultCosineWaveFlag); + OutputFormats output_format(kDefaultOutputFormat); for (;;) { - const int option_char(getopt_long(argc, argv, "l:m:p:a:Ch", NULL, NULL)); + const int option_char(getopt_long(argc, argv, "l:m:p:a:o:h", NULL, NULL)); if (-1 == option_char) break; switch (option_char) { @@ -138,8 +144,19 @@ int main(int argc, char* argv[]) { } break; } - case 'C': { - cosine_wave = true; + case 'o': { + const int min(0); + const int max(static_cast(kNumOutputFormats) - 1); + int tmp; + if (!sptk::ConvertStringToInteger(optarg, &tmp) || + !sptk::IsInRange(tmp, min, max)) { + std::ostringstream error_message; + error_message << "The argument for the -o option must be an integer " + << "in the range of " << min << " to " << max; + sptk::PrintErrorMessage("sin", error_message); + return 1; + } + output_format = static_cast(tmp); break; } case 'h': { @@ -170,8 +187,20 @@ int main(int argc, char* argv[]) { const double omega(sptk::kTwoPi / period); for (int i(0); kMagicNumberForInfinity == output_length || i < output_length; ++i) { - const double output(cosine_wave ? amplitude * std::cos(omega * i) - : amplitude * std::sin(omega * i)); + double output; + switch (output_format) { + case kSine: { + output = amplitude * std::sin(omega * i); + break; + } + case kCosine: { + output = amplitude * std::cos(omega * i); + break; + } + default: { + return 1; + } + } if (!sptk::WriteStream(output, &std::cout)) { std::ostringstream error_message; error_message << "Failed to write sinusoidal sequence"; diff --git a/test/test_pitch2sin.bats b/test/test_pitch2sin.bats new file mode 100644 index 0000000..68e72d8 --- /dev/null +++ b/test/test_pitch2sin.bats @@ -0,0 +1,34 @@ +#!/usr/bin/env bats +# ------------------------------------------------------------------------ # +# Copyright 2021 SPTK Working Group # +# # +# Licensed under the Apache License, Version 2.0 (the "License"); # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http://www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +# ------------------------------------------------------------------------ # + +sptk3=tools/sptk/bin +sptk4=bin +tmp=test_pitch2sin + +setup() { + mkdir -p $tmp +} + +teardown() { + rm -rf $tmp +} + +@test "pitch2sin: valgrind" { + $sptk3/step -l 10 -v 100 > $tmp/1 + run valgrind $sptk4/pitch2sin -p 10 $tmp/1 + [ "$(echo "${lines[-1]}" | sed -r 's/.*SUMMARY: ([0-9]*) .*/\1/')" -eq 0 ] +}