From d5635eee7d12277372c19fa9e69ab4f428469e14 Mon Sep 17 00:00:00 2001 From: Joseph Bellahcen Date: Thu, 6 Jul 2023 00:25:43 -0700 Subject: [PATCH 01/30] Refactored Autocorrelation per Google C++ style --- .gitignore | 1 + CMakeLists.txt | 2 +- inc/LPC_Analysis/Autocorrelation.h | 18 +++++++++++ inc/LPC_Analysis/Autocorrelator.h | 13 -------- .../BitstreamGenerator.cpp | 6 ++-- src/LPC_Analysis/Autocorrelation.cpp | 25 ++++++++++++++++ src/LPC_Analysis/Autocorrelator.cpp | 30 ------------------- src/User_Interfaces/MainWindow.cpp | 6 ++-- test/AutocorrelatorTests.cpp | 4 +-- test/TmsTest.cmake | 2 +- 10 files changed, 54 insertions(+), 53 deletions(-) create mode 100644 inc/LPC_Analysis/Autocorrelation.h delete mode 100644 inc/LPC_Analysis/Autocorrelator.h create mode 100644 src/LPC_Analysis/Autocorrelation.cpp delete mode 100644 src/LPC_Analysis/Autocorrelator.cpp diff --git a/.gitignore b/.gitignore index f945b40..ba33631 100644 --- a/.gitignore +++ b/.gitignore @@ -39,6 +39,7 @@ build/ .DS_Store # IDEs +.cache/ .idea/ .vscode/ *.user diff --git a/CMakeLists.txt b/CMakeLists.txt index e31590e..a9f68cf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,7 +27,7 @@ add_executable(${PROJECT_NAME} src/Audio/AudioBuffer.cpp src/Audio/AudioFilter.cpp - src/LPC_Analysis/Autocorrelator.cpp + src/LPC_Analysis/Autocorrelation.cpp src/LPC_Analysis/PitchEstimator.cpp src/LPC_Analysis/LinearPredictor.cpp diff --git a/inc/LPC_Analysis/Autocorrelation.h b/inc/LPC_Analysis/Autocorrelation.h new file mode 100644 index 0000000..557d821 --- /dev/null +++ b/inc/LPC_Analysis/Autocorrelation.h @@ -0,0 +1,18 @@ +// Copyright 2023 Joseph Bellahcen + +#ifndef LPC_ANALYSIS_AUTOCORRELATION_H_ +#define LPC_ANALYSIS_AUTOCORRELATION_H_ + +#include + +namespace tms_express { + +/// @brief Compute biased autocorrelation of segment +/// +/// @param segment Segment from which to compute autocorrelation +/// @return Biased autocorrelation of segment +std::vector Autocorrelation(const std::vector &segment); + +}; // namespace tms_express + +#endif // LPC_ANALYSIS_AUTOCORRELATION_H_ diff --git a/inc/LPC_Analysis/Autocorrelator.h b/inc/LPC_Analysis/Autocorrelator.h deleted file mode 100644 index 231b9b0..0000000 --- a/inc/LPC_Analysis/Autocorrelator.h +++ /dev/null @@ -1,13 +0,0 @@ -// Author: Joseph Bellahcen - -#ifndef TMS_EXPRESS_AUTOCORRELATOR_H -#define TMS_EXPRESS_AUTOCORRELATOR_H - -#include - -class Autocorrelator { -public: - static std::vector process(const std::vector& segment); -}; - -#endif //TMS_EXPRESS_AUTOCORRELATOR_H diff --git a/src/Bitstream_Generation/BitstreamGenerator.cpp b/src/Bitstream_Generation/BitstreamGenerator.cpp index b5948f4..e5bf739 100644 --- a/src/Bitstream_Generation/BitstreamGenerator.cpp +++ b/src/Bitstream_Generation/BitstreamGenerator.cpp @@ -14,7 +14,7 @@ #include "Frame_Encoding/Frame.h" #include "Frame_Encoding/FrameEncoder.h" #include "Frame_Encoding/FramePostprocessor.h" -#include "LPC_Analysis/Autocorrelator.h" +#include "LPC_Analysis/Autocorrelation.h" #include "LPC_Analysis/LinearPredictor.h" #include "LPC_Analysis/PitchEstimator.h" @@ -122,8 +122,8 @@ std::vector BitstreamGenerator::generateFrames(const std::string &inputPa preprocessor.hammingWindow(lpcSegment); // Compute the autocorrelation of each segment, which serves as the basis of all analysis - auto lpcAcf = Autocorrelator::process(lpcSegment); - auto pitchAcf = Autocorrelator::process(pitchSegment); + auto lpcAcf = tms_express::Autocorrelation(lpcSegment); + auto pitchAcf = tms_express::Autocorrelation(pitchSegment); // Extract LPC reflector coefficients and compute the predictor gain auto coeffs = linearPredictor.reflectorCoefficients(lpcAcf); diff --git a/src/LPC_Analysis/Autocorrelation.cpp b/src/LPC_Analysis/Autocorrelation.cpp new file mode 100644 index 0000000..b13c8a7 --- /dev/null +++ b/src/LPC_Analysis/Autocorrelation.cpp @@ -0,0 +1,25 @@ +// Copyright 2023 Joseph Bellahcen + +#include "LPC_Analysis/Autocorrelation.h" +#include + +namespace tms_express { + +std::vector Autocorrelation(const std::vector &segment) { + auto size = segment.size(); + auto acf = std::vector(size); + + for (int i = 0; i < size; i++) { + float sum = 0.0f; + + for (int j = 0; j < size - i; j++) { + sum += segment[j] * segment[j + i]; + } + + acf[i] = (sum / static_cast(size)); + } + + return acf; +} + +}; // namespace tms_express diff --git a/src/LPC_Analysis/Autocorrelator.cpp b/src/LPC_Analysis/Autocorrelator.cpp deleted file mode 100644 index f8f5d2d..0000000 --- a/src/LPC_Analysis/Autocorrelator.cpp +++ /dev/null @@ -1,30 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// Class: Autocorrelator -// -// Description: At the heart of many speech analysis applications is the autocorrelation function (AFC), which -// represents the FFT of the energy spectrum of the signal. The Autocorrelator provides a single static -// method for computing the biased ACF, which is scaled by the number of samples in the segment -// -// Author: Joseph Bellahcen -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -#include "LPC_Analysis/Autocorrelator.h" -#include - -/// Compute the biased autocorrelation of the segment -std::vector Autocorrelator::process(const std::vector &segment) { - auto size = segment.size(); - auto acf = std::vector(size); - - for (int i = 0; i < size; i++) { - float sum = 0.0f; - - for (int j = 0; j < size - i; j++) { - sum += segment[j] * segment[j + i]; - } - - acf[i] = (sum / float(size)); - } - - return acf; -} diff --git a/src/User_Interfaces/MainWindow.cpp b/src/User_Interfaces/MainWindow.cpp index 8eaa5cf..0e7ad1a 100644 --- a/src/User_Interfaces/MainWindow.cpp +++ b/src/User_Interfaces/MainWindow.cpp @@ -8,7 +8,7 @@ #include "Audio/AudioBuffer.h" #include "Frame_Encoding/FramePostprocessor.h" -#include "LPC_Analysis/Autocorrelator.h" +#include "LPC_Analysis/Autocorrelation.h" #include "User_Interfaces/Audio_Waveform/AudioWaveformView.h" #include "User_Interfaces/MainWindow.h" #include "User_Interfaces/Control_Panels/ControlPanelPitchView.h" @@ -410,7 +410,7 @@ void MainWindow::performPitchAnalysis() { pitchEstimator.setMinPeriod(pitchControl->maxPitchFrq()); for (const auto &segment : inputBuffer.segments()) { - auto acf = Autocorrelator::process(segment); + auto acf = tms_express::Autocorrelation(segment); auto pitchPeriod = pitchEstimator.estimatePeriod(acf); // TODO: Parameterize auto pitchFrq = pitchEstimator.estimateFrequency(acf) / float(pitchEstimator.getMaxFrq()); @@ -456,7 +456,7 @@ void MainWindow::performLpcAnalysis() { for (int i = 0; i < lpcBuffer.size(); i++) { auto segment = lpcBuffer.segment(i); - auto acf = Autocorrelator::process(segment); + auto acf = tms_express::Autocorrelation(segment); auto coeffs = linearPredictor.reflectorCoefficients(acf); auto gain = linearPredictor.gain(); diff --git a/test/AutocorrelatorTests.cpp b/test/AutocorrelatorTests.cpp index 2f3d582..35b96f9 100644 --- a/test/AutocorrelatorTests.cpp +++ b/test/AutocorrelatorTests.cpp @@ -2,7 +2,7 @@ // Created by Joseph Bellahcen on 6/1/22. // -#include "LPC_Analysis/Autocorrelator.h" +#include "LPC_Analysis/Autocorrelation.h" #include #include #include @@ -16,7 +16,7 @@ std::vector acfTestSubject() { signal.push_back(sample); } - auto acf = Autocorrelator::process(signal); + auto acf = tms_express::Autocorrelation(signal); return acf; } diff --git a/test/TmsTest.cmake b/test/TmsTest.cmake index ca9480a..a9f34b9 100644 --- a/test/TmsTest.cmake +++ b/test/TmsTest.cmake @@ -23,7 +23,7 @@ include_directories( ) add_executable(TMS-Test - src/LPC_Analysis/Autocorrelator.cpp + src/LPC_Analysis/Autocorrelation.cpp test/AutocorrelatorTests.cpp src/Frame_Encoding/Frame.cpp From 1d8d5db4f3cbbe697b07a16354d222f07d8c8f46 Mon Sep 17 00:00:00 2001 From: Joseph Bellahcen Date: Fri, 7 Jul 2023 20:55:21 -0700 Subject: [PATCH 02/30] Merged headers and sources per Canonical Project Structure --- {src => tms_express}/Audio/AudioBuffer.cpp | 0 {inc => tms_express}/Audio/AudioBuffer.h | 0 {src => tms_express}/Audio/AudioFilter.cpp | 0 {inc => tms_express}/Audio/AudioFilter.h | 0 {src => tms_express}/Bitstream_Generation/BitstreamGenerator.cpp | 0 {inc => tms_express}/Bitstream_Generation/BitstreamGenerator.h | 0 {src => tms_express}/Bitstream_Generation/PathUtils.cpp | 0 {inc => tms_express}/Bitstream_Generation/PathUtils.h | 0 {src => tms_express}/Frame_Encoding/Frame.cpp | 0 {inc => tms_express}/Frame_Encoding/Frame.h | 0 {src => tms_express}/Frame_Encoding/FrameEncoder.cpp | 0 {inc => tms_express}/Frame_Encoding/FrameEncoder.h | 0 {src => tms_express}/Frame_Encoding/FramePostprocessor.cpp | 0 {inc => tms_express}/Frame_Encoding/FramePostprocessor.h | 0 {src => tms_express}/Frame_Encoding/Synthesizer.cpp | 0 {inc => tms_express}/Frame_Encoding/Synthesizer.h | 0 {inc => tms_express}/Frame_Encoding/Tms5220CodingTable.h | 0 {src => tms_express}/LPC_Analysis/Autocorrelation.cpp | 0 {inc => tms_express}/LPC_Analysis/Autocorrelation.h | 0 {src => tms_express}/LPC_Analysis/LinearPredictor.cpp | 0 {inc => tms_express}/LPC_Analysis/LinearPredictor.h | 0 {src => tms_express}/LPC_Analysis/PitchEstimator.cpp | 0 {inc => tms_express}/LPC_Analysis/PitchEstimator.h | 0 .../User_Interfaces/Audio_Waveform/AudioWaveform.cpp | 0 .../User_Interfaces/Audio_Waveform/AudioWaveform.h | 0 .../User_Interfaces/Audio_Waveform/AudioWaveformView.cpp | 0 .../User_Interfaces/Audio_Waveform/AudioWaveformView.h | 0 {src => tms_express}/User_Interfaces/CommandLineApp.cpp | 0 {inc => tms_express}/User_Interfaces/CommandLineApp.h | 0 .../User_Interfaces/Control_Panels/ControlPanelLpcView.cpp | 0 .../User_Interfaces/Control_Panels/ControlPanelLpcView.h | 0 .../User_Interfaces/Control_Panels/ControlPanelPitchView.cpp | 0 .../User_Interfaces/Control_Panels/ControlPanelPitchView.h | 0 .../User_Interfaces/Control_Panels/ControlPanelPostView.cpp | 0 .../User_Interfaces/Control_Panels/ControlPanelPostView.h | 0 .../User_Interfaces/Control_Panels/ControlPanelView.cpp | 0 .../User_Interfaces/Control_Panels/ControlPanelView.h | 0 {src => tms_express}/User_Interfaces/MainWindow.cpp | 0 {inc => tms_express}/User_Interfaces/MainWindow.h | 0 {src => tms_express}/main.cpp | 0 40 files changed, 0 insertions(+), 0 deletions(-) rename {src => tms_express}/Audio/AudioBuffer.cpp (100%) rename {inc => tms_express}/Audio/AudioBuffer.h (100%) rename {src => tms_express}/Audio/AudioFilter.cpp (100%) rename {inc => tms_express}/Audio/AudioFilter.h (100%) rename {src => tms_express}/Bitstream_Generation/BitstreamGenerator.cpp (100%) rename {inc => tms_express}/Bitstream_Generation/BitstreamGenerator.h (100%) rename {src => tms_express}/Bitstream_Generation/PathUtils.cpp (100%) rename {inc => tms_express}/Bitstream_Generation/PathUtils.h (100%) rename {src => tms_express}/Frame_Encoding/Frame.cpp (100%) rename {inc => tms_express}/Frame_Encoding/Frame.h (100%) rename {src => tms_express}/Frame_Encoding/FrameEncoder.cpp (100%) rename {inc => tms_express}/Frame_Encoding/FrameEncoder.h (100%) rename {src => tms_express}/Frame_Encoding/FramePostprocessor.cpp (100%) rename {inc => tms_express}/Frame_Encoding/FramePostprocessor.h (100%) rename {src => tms_express}/Frame_Encoding/Synthesizer.cpp (100%) rename {inc => tms_express}/Frame_Encoding/Synthesizer.h (100%) rename {inc => tms_express}/Frame_Encoding/Tms5220CodingTable.h (100%) rename {src => tms_express}/LPC_Analysis/Autocorrelation.cpp (100%) rename {inc => tms_express}/LPC_Analysis/Autocorrelation.h (100%) rename {src => tms_express}/LPC_Analysis/LinearPredictor.cpp (100%) rename {inc => tms_express}/LPC_Analysis/LinearPredictor.h (100%) rename {src => tms_express}/LPC_Analysis/PitchEstimator.cpp (100%) rename {inc => tms_express}/LPC_Analysis/PitchEstimator.h (100%) rename {src => tms_express}/User_Interfaces/Audio_Waveform/AudioWaveform.cpp (100%) rename {inc => tms_express}/User_Interfaces/Audio_Waveform/AudioWaveform.h (100%) rename {src => tms_express}/User_Interfaces/Audio_Waveform/AudioWaveformView.cpp (100%) rename {inc => tms_express}/User_Interfaces/Audio_Waveform/AudioWaveformView.h (100%) rename {src => tms_express}/User_Interfaces/CommandLineApp.cpp (100%) rename {inc => tms_express}/User_Interfaces/CommandLineApp.h (100%) rename {src => tms_express}/User_Interfaces/Control_Panels/ControlPanelLpcView.cpp (100%) rename {inc => tms_express}/User_Interfaces/Control_Panels/ControlPanelLpcView.h (100%) rename {src => tms_express}/User_Interfaces/Control_Panels/ControlPanelPitchView.cpp (100%) rename {inc => tms_express}/User_Interfaces/Control_Panels/ControlPanelPitchView.h (100%) rename {src => tms_express}/User_Interfaces/Control_Panels/ControlPanelPostView.cpp (100%) rename {inc => tms_express}/User_Interfaces/Control_Panels/ControlPanelPostView.h (100%) rename {src => tms_express}/User_Interfaces/Control_Panels/ControlPanelView.cpp (100%) rename {inc => tms_express}/User_Interfaces/Control_Panels/ControlPanelView.h (100%) rename {src => tms_express}/User_Interfaces/MainWindow.cpp (100%) rename {inc => tms_express}/User_Interfaces/MainWindow.h (100%) rename {src => tms_express}/main.cpp (100%) diff --git a/src/Audio/AudioBuffer.cpp b/tms_express/Audio/AudioBuffer.cpp similarity index 100% rename from src/Audio/AudioBuffer.cpp rename to tms_express/Audio/AudioBuffer.cpp diff --git a/inc/Audio/AudioBuffer.h b/tms_express/Audio/AudioBuffer.h similarity index 100% rename from inc/Audio/AudioBuffer.h rename to tms_express/Audio/AudioBuffer.h diff --git a/src/Audio/AudioFilter.cpp b/tms_express/Audio/AudioFilter.cpp similarity index 100% rename from src/Audio/AudioFilter.cpp rename to tms_express/Audio/AudioFilter.cpp diff --git a/inc/Audio/AudioFilter.h b/tms_express/Audio/AudioFilter.h similarity index 100% rename from inc/Audio/AudioFilter.h rename to tms_express/Audio/AudioFilter.h diff --git a/src/Bitstream_Generation/BitstreamGenerator.cpp b/tms_express/Bitstream_Generation/BitstreamGenerator.cpp similarity index 100% rename from src/Bitstream_Generation/BitstreamGenerator.cpp rename to tms_express/Bitstream_Generation/BitstreamGenerator.cpp diff --git a/inc/Bitstream_Generation/BitstreamGenerator.h b/tms_express/Bitstream_Generation/BitstreamGenerator.h similarity index 100% rename from inc/Bitstream_Generation/BitstreamGenerator.h rename to tms_express/Bitstream_Generation/BitstreamGenerator.h diff --git a/src/Bitstream_Generation/PathUtils.cpp b/tms_express/Bitstream_Generation/PathUtils.cpp similarity index 100% rename from src/Bitstream_Generation/PathUtils.cpp rename to tms_express/Bitstream_Generation/PathUtils.cpp diff --git a/inc/Bitstream_Generation/PathUtils.h b/tms_express/Bitstream_Generation/PathUtils.h similarity index 100% rename from inc/Bitstream_Generation/PathUtils.h rename to tms_express/Bitstream_Generation/PathUtils.h diff --git a/src/Frame_Encoding/Frame.cpp b/tms_express/Frame_Encoding/Frame.cpp similarity index 100% rename from src/Frame_Encoding/Frame.cpp rename to tms_express/Frame_Encoding/Frame.cpp diff --git a/inc/Frame_Encoding/Frame.h b/tms_express/Frame_Encoding/Frame.h similarity index 100% rename from inc/Frame_Encoding/Frame.h rename to tms_express/Frame_Encoding/Frame.h diff --git a/src/Frame_Encoding/FrameEncoder.cpp b/tms_express/Frame_Encoding/FrameEncoder.cpp similarity index 100% rename from src/Frame_Encoding/FrameEncoder.cpp rename to tms_express/Frame_Encoding/FrameEncoder.cpp diff --git a/inc/Frame_Encoding/FrameEncoder.h b/tms_express/Frame_Encoding/FrameEncoder.h similarity index 100% rename from inc/Frame_Encoding/FrameEncoder.h rename to tms_express/Frame_Encoding/FrameEncoder.h diff --git a/src/Frame_Encoding/FramePostprocessor.cpp b/tms_express/Frame_Encoding/FramePostprocessor.cpp similarity index 100% rename from src/Frame_Encoding/FramePostprocessor.cpp rename to tms_express/Frame_Encoding/FramePostprocessor.cpp diff --git a/inc/Frame_Encoding/FramePostprocessor.h b/tms_express/Frame_Encoding/FramePostprocessor.h similarity index 100% rename from inc/Frame_Encoding/FramePostprocessor.h rename to tms_express/Frame_Encoding/FramePostprocessor.h diff --git a/src/Frame_Encoding/Synthesizer.cpp b/tms_express/Frame_Encoding/Synthesizer.cpp similarity index 100% rename from src/Frame_Encoding/Synthesizer.cpp rename to tms_express/Frame_Encoding/Synthesizer.cpp diff --git a/inc/Frame_Encoding/Synthesizer.h b/tms_express/Frame_Encoding/Synthesizer.h similarity index 100% rename from inc/Frame_Encoding/Synthesizer.h rename to tms_express/Frame_Encoding/Synthesizer.h diff --git a/inc/Frame_Encoding/Tms5220CodingTable.h b/tms_express/Frame_Encoding/Tms5220CodingTable.h similarity index 100% rename from inc/Frame_Encoding/Tms5220CodingTable.h rename to tms_express/Frame_Encoding/Tms5220CodingTable.h diff --git a/src/LPC_Analysis/Autocorrelation.cpp b/tms_express/LPC_Analysis/Autocorrelation.cpp similarity index 100% rename from src/LPC_Analysis/Autocorrelation.cpp rename to tms_express/LPC_Analysis/Autocorrelation.cpp diff --git a/inc/LPC_Analysis/Autocorrelation.h b/tms_express/LPC_Analysis/Autocorrelation.h similarity index 100% rename from inc/LPC_Analysis/Autocorrelation.h rename to tms_express/LPC_Analysis/Autocorrelation.h diff --git a/src/LPC_Analysis/LinearPredictor.cpp b/tms_express/LPC_Analysis/LinearPredictor.cpp similarity index 100% rename from src/LPC_Analysis/LinearPredictor.cpp rename to tms_express/LPC_Analysis/LinearPredictor.cpp diff --git a/inc/LPC_Analysis/LinearPredictor.h b/tms_express/LPC_Analysis/LinearPredictor.h similarity index 100% rename from inc/LPC_Analysis/LinearPredictor.h rename to tms_express/LPC_Analysis/LinearPredictor.h diff --git a/src/LPC_Analysis/PitchEstimator.cpp b/tms_express/LPC_Analysis/PitchEstimator.cpp similarity index 100% rename from src/LPC_Analysis/PitchEstimator.cpp rename to tms_express/LPC_Analysis/PitchEstimator.cpp diff --git a/inc/LPC_Analysis/PitchEstimator.h b/tms_express/LPC_Analysis/PitchEstimator.h similarity index 100% rename from inc/LPC_Analysis/PitchEstimator.h rename to tms_express/LPC_Analysis/PitchEstimator.h diff --git a/src/User_Interfaces/Audio_Waveform/AudioWaveform.cpp b/tms_express/User_Interfaces/Audio_Waveform/AudioWaveform.cpp similarity index 100% rename from src/User_Interfaces/Audio_Waveform/AudioWaveform.cpp rename to tms_express/User_Interfaces/Audio_Waveform/AudioWaveform.cpp diff --git a/inc/User_Interfaces/Audio_Waveform/AudioWaveform.h b/tms_express/User_Interfaces/Audio_Waveform/AudioWaveform.h similarity index 100% rename from inc/User_Interfaces/Audio_Waveform/AudioWaveform.h rename to tms_express/User_Interfaces/Audio_Waveform/AudioWaveform.h diff --git a/src/User_Interfaces/Audio_Waveform/AudioWaveformView.cpp b/tms_express/User_Interfaces/Audio_Waveform/AudioWaveformView.cpp similarity index 100% rename from src/User_Interfaces/Audio_Waveform/AudioWaveformView.cpp rename to tms_express/User_Interfaces/Audio_Waveform/AudioWaveformView.cpp diff --git a/inc/User_Interfaces/Audio_Waveform/AudioWaveformView.h b/tms_express/User_Interfaces/Audio_Waveform/AudioWaveformView.h similarity index 100% rename from inc/User_Interfaces/Audio_Waveform/AudioWaveformView.h rename to tms_express/User_Interfaces/Audio_Waveform/AudioWaveformView.h diff --git a/src/User_Interfaces/CommandLineApp.cpp b/tms_express/User_Interfaces/CommandLineApp.cpp similarity index 100% rename from src/User_Interfaces/CommandLineApp.cpp rename to tms_express/User_Interfaces/CommandLineApp.cpp diff --git a/inc/User_Interfaces/CommandLineApp.h b/tms_express/User_Interfaces/CommandLineApp.h similarity index 100% rename from inc/User_Interfaces/CommandLineApp.h rename to tms_express/User_Interfaces/CommandLineApp.h diff --git a/src/User_Interfaces/Control_Panels/ControlPanelLpcView.cpp b/tms_express/User_Interfaces/Control_Panels/ControlPanelLpcView.cpp similarity index 100% rename from src/User_Interfaces/Control_Panels/ControlPanelLpcView.cpp rename to tms_express/User_Interfaces/Control_Panels/ControlPanelLpcView.cpp diff --git a/inc/User_Interfaces/Control_Panels/ControlPanelLpcView.h b/tms_express/User_Interfaces/Control_Panels/ControlPanelLpcView.h similarity index 100% rename from inc/User_Interfaces/Control_Panels/ControlPanelLpcView.h rename to tms_express/User_Interfaces/Control_Panels/ControlPanelLpcView.h diff --git a/src/User_Interfaces/Control_Panels/ControlPanelPitchView.cpp b/tms_express/User_Interfaces/Control_Panels/ControlPanelPitchView.cpp similarity index 100% rename from src/User_Interfaces/Control_Panels/ControlPanelPitchView.cpp rename to tms_express/User_Interfaces/Control_Panels/ControlPanelPitchView.cpp diff --git a/inc/User_Interfaces/Control_Panels/ControlPanelPitchView.h b/tms_express/User_Interfaces/Control_Panels/ControlPanelPitchView.h similarity index 100% rename from inc/User_Interfaces/Control_Panels/ControlPanelPitchView.h rename to tms_express/User_Interfaces/Control_Panels/ControlPanelPitchView.h diff --git a/src/User_Interfaces/Control_Panels/ControlPanelPostView.cpp b/tms_express/User_Interfaces/Control_Panels/ControlPanelPostView.cpp similarity index 100% rename from src/User_Interfaces/Control_Panels/ControlPanelPostView.cpp rename to tms_express/User_Interfaces/Control_Panels/ControlPanelPostView.cpp diff --git a/inc/User_Interfaces/Control_Panels/ControlPanelPostView.h b/tms_express/User_Interfaces/Control_Panels/ControlPanelPostView.h similarity index 100% rename from inc/User_Interfaces/Control_Panels/ControlPanelPostView.h rename to tms_express/User_Interfaces/Control_Panels/ControlPanelPostView.h diff --git a/src/User_Interfaces/Control_Panels/ControlPanelView.cpp b/tms_express/User_Interfaces/Control_Panels/ControlPanelView.cpp similarity index 100% rename from src/User_Interfaces/Control_Panels/ControlPanelView.cpp rename to tms_express/User_Interfaces/Control_Panels/ControlPanelView.cpp diff --git a/inc/User_Interfaces/Control_Panels/ControlPanelView.h b/tms_express/User_Interfaces/Control_Panels/ControlPanelView.h similarity index 100% rename from inc/User_Interfaces/Control_Panels/ControlPanelView.h rename to tms_express/User_Interfaces/Control_Panels/ControlPanelView.h diff --git a/src/User_Interfaces/MainWindow.cpp b/tms_express/User_Interfaces/MainWindow.cpp similarity index 100% rename from src/User_Interfaces/MainWindow.cpp rename to tms_express/User_Interfaces/MainWindow.cpp diff --git a/inc/User_Interfaces/MainWindow.h b/tms_express/User_Interfaces/MainWindow.h similarity index 100% rename from inc/User_Interfaces/MainWindow.h rename to tms_express/User_Interfaces/MainWindow.h diff --git a/src/main.cpp b/tms_express/main.cpp similarity index 100% rename from src/main.cpp rename to tms_express/main.cpp From 0288844d03d0905fcab20a4cd55c17088b7f8b80 Mon Sep 17 00:00:00 2001 From: Joseph Bellahcen Date: Mon, 17 Jul 2023 19:35:11 -0700 Subject: [PATCH 03/30] Refactored AudioBuffer --- CMakeLists.txt | 88 ++--- test/AutocorrelatorTests.cpp | 4 + test/FrameEncoderTests.cpp | 6 +- test/FrameTests.cpp | 5 +- test/TmsTest.cmake | 6 +- tms_express/Audio/AudioBuffer.cpp | 347 ++++++++---------- tms_express/Audio/AudioBuffer.h | 46 --- tms_express/Audio/AudioBuffer.hpp | 144 ++++++++ tms_express/Audio/AudioFilter.cpp | 6 +- tms_express/Audio/AudioFilter.h | 6 +- .../BitstreamGenerator.cpp | 16 +- .../Bitstream_Generation/BitstreamGenerator.h | 4 + .../Bitstream_Generation/PathUtils.cpp | 4 + tms_express/Bitstream_Generation/PathUtils.h | 3 + tms_express/Frame_Encoding/Frame.cpp | 4 + tms_express/Frame_Encoding/Frame.h | 4 + tms_express/Frame_Encoding/FrameEncoder.cpp | 4 + tms_express/Frame_Encoding/FrameEncoder.h | 4 + .../Frame_Encoding/FramePostprocessor.cpp | 6 +- .../Frame_Encoding/FramePostprocessor.h | 4 + tms_express/Frame_Encoding/Synthesizer.cpp | 6 +- tms_express/Frame_Encoding/Synthesizer.h | 4 + .../Frame_Encoding/Tms5220CodingTable.h | 4 + tms_express/LPC_Analysis/LinearPredictor.cpp | 4 + tms_express/LPC_Analysis/LinearPredictor.h | 4 + tms_express/LPC_Analysis/PitchEstimator.cpp | 4 + tms_express/LPC_Analysis/PitchEstimator.h | 6 +- .../Audio_Waveform/AudioWaveform.cpp | 4 + .../Audio_Waveform/AudioWaveform.h | 4 + .../Audio_Waveform/AudioWaveformView.cpp | 4 + .../Audio_Waveform/AudioWaveformView.h | 4 + .../User_Interfaces/CommandLineApp.cpp | 4 + tms_express/User_Interfaces/CommandLineApp.h | 4 + .../Control_Panels/ControlPanelLpcView.cpp | 4 + .../Control_Panels/ControlPanelLpcView.h | 4 + .../Control_Panels/ControlPanelPitchView.cpp | 4 + .../Control_Panels/ControlPanelPitchView.h | 4 + .../Control_Panels/ControlPanelPostView.cpp | 4 + .../Control_Panels/ControlPanelPostView.h | 4 + .../Control_Panels/ControlPanelView.cpp | 4 + .../Control_Panels/ControlPanelView.h | 4 + tms_express/User_Interfaces/MainWindow.cpp | 44 ++- tms_express/User_Interfaces/MainWindow.h | 6 +- tms_express/main.cpp | 4 +- 44 files changed, 539 insertions(+), 314 deletions(-) delete mode 100644 tms_express/Audio/AudioBuffer.h create mode 100644 tms_express/Audio/AudioBuffer.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index a9f68cf..e862bf0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,6 +3,7 @@ cmake_minimum_required(VERSION 3.14) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) +add_compile_options(-Wall -Wextra) # Run Qt's moc, rcc, and uic tools set(CMAKE_AUTOUIC ON) @@ -16,57 +17,46 @@ option(TMSEXPRESS_BUILD_TESTS "Build test programs" ON) # PROGRAM FILES # ==================================== include_directories( - inc - lib + tms_express + lib ) add_executable(${PROJECT_NAME} - # ==================================== - # BACKEND SOURCES - # ==================================== - src/Audio/AudioBuffer.cpp - src/Audio/AudioFilter.cpp + # ==================================== + # BACKEND SOURCES + # ==================================== + tms_express/Audio/AudioBuffer.cpp + tms_express/Audio/AudioFilter.cpp - src/LPC_Analysis/Autocorrelation.cpp - src/LPC_Analysis/PitchEstimator.cpp - src/LPC_Analysis/LinearPredictor.cpp + tms_express/LPC_Analysis/Autocorrelation.cpp + tms_express/LPC_Analysis/PitchEstimator.cpp + tms_express/LPC_Analysis/LinearPredictor.cpp - src/Frame_Encoding/Frame.cpp - src/Frame_Encoding/FrameEncoder.cpp - src/Frame_Encoding/FramePostprocessor.cpp - src/Frame_Encoding/Synthesizer.cpp + tms_express/Frame_Encoding/Frame.cpp + tms_express/Frame_Encoding/FrameEncoder.cpp + tms_express/Frame_Encoding/FramePostprocessor.cpp + tms_express/Frame_Encoding/Synthesizer.cpp - src/Bitstream_Generation/BitstreamGenerator.cpp - src/Bitstream_Generation/PathUtils.cpp + tms_express/Bitstream_Generation/BitstreamGenerator.cpp + tms_express/Bitstream_Generation/PathUtils.cpp - # ==================================== - # FRONTEND SOURCES - # ==================================== - src/User_Interfaces/CommandLineApp.cpp + # ==================================== + # FRONTEND SOURCES + # ==================================== + tms_express/User_Interfaces/CommandLineApp.cpp - inc/User_Interfaces/MainWindow.h - src/User_Interfaces/MainWindow.cpp + tms_express/User_Interfaces/MainWindow.cpp - inc/User_Interfaces/Audio_Waveform/AudioWaveform.h - src/User_Interfaces/Audio_Waveform/AudioWaveform.cpp + tms_express/User_Interfaces/Audio_Waveform/AudioWaveform.cpp + tms_express/User_Interfaces/Audio_Waveform/AudioWaveformView.cpp - inc/User_Interfaces/Audio_Waveform/AudioWaveformView.h - src/User_Interfaces/Audio_Waveform/AudioWaveformView.cpp + tms_express/User_Interfaces/Control_Panels/ControlPanelView.cpp + tms_express/User_Interfaces/Control_Panels/ControlPanelPitchView.cpp + tms_express/User_Interfaces/Control_Panels/ControlPanelLpcView.cpp + tms_express/User_Interfaces/Control_Panels/ControlPanelPostView.cpp - inc/User_Interfaces/Control_Panels/ControlPanelView.h - src/User_Interfaces/Control_Panels/ControlPanelView.cpp - - inc/User_Interfaces/Control_Panels/ControlPanelPitchView.h - src/User_Interfaces/Control_Panels/ControlPanelPitchView.cpp - - inc/User_Interfaces/Control_Panels/ControlPanelLpcView.h - src/User_Interfaces/Control_Panels/ControlPanelLpcView.cpp - - inc/User_Interfaces/Control_Panels/ControlPanelPostView.h - src/User_Interfaces/Control_Panels/ControlPanelPostView.cpp - - src/main.cpp - ) + tms_express/main.cpp +) # ==================================== # DYNAMIC LIBS @@ -78,15 +68,15 @@ pkg_check_modules(SndFile REQUIRED IMPORTED_TARGET sndfile) pkg_check_modules(SampleRate REQUIRED IMPORTED_TARGET samplerate) target_link_libraries(${PROJECT_NAME} - PRIVATE - PkgConfig::SndFile - PkgConfig::SampleRate - Qt::Core - Qt::Gui - Qt::Widgets - Qt::PrintSupport - Qt::Multimedia - ) + PRIVATE + PkgConfig::SndFile + PkgConfig::SampleRate + Qt::Core + Qt::Gui + Qt::Widgets + Qt::PrintSupport + Qt::Multimedia +) # Rename executable set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME tmsexpress) diff --git a/test/AutocorrelatorTests.cpp b/test/AutocorrelatorTests.cpp index 35b96f9..833bbc0 100644 --- a/test/AutocorrelatorTests.cpp +++ b/test/AutocorrelatorTests.cpp @@ -8,6 +8,8 @@ #include #include +namespace tms_express { + // Compute the autocorrelation of a decaying cosine signal with amplitude 20 and period 50 std::vector acfTestSubject() { auto signal = std::vector(); @@ -42,3 +44,5 @@ TEST(AutocorrelatorTests, AutocorrelationHasLocalMaxAtOriginalSignalPeriod) { auto periodIdx = std::distance(acf.begin(), nextMaxElement); EXPECT_NEAR(periodIdx, 50, 2); } + +}; // namespace tms_express diff --git a/test/FrameEncoderTests.cpp b/test/FrameEncoderTests.cpp index 424d7fa..af51b94 100644 --- a/test/FrameEncoderTests.cpp +++ b/test/FrameEncoderTests.cpp @@ -6,6 +6,8 @@ #include #include +namespace tms_express { + // The hex stream should, at minimum, contain a StopFrame TEST(FrameEncoderTests, StopFrame) { auto frameEncoder = FrameEncoder(); @@ -122,4 +124,6 @@ TEST(FrameEncoderTests, AsciiMixtureOfFrames) { auto bin = frameEncoder.toHex(); EXPECT_EQ(bin, "c0,8c,a4,5b,e2,bc,0a,33,92,6e,89,f3,2a,08,88,4f,e5,01"); -} \ No newline at end of file +} + +}; // namespace tms_express diff --git a/test/FrameTests.cpp b/test/FrameTests.cpp index 4558e0b..d286d27 100644 --- a/test/FrameTests.cpp +++ b/test/FrameTests.cpp @@ -2,6 +2,7 @@ #include #include +namespace tms_express { Frame frameTestSubject() { return Frame(38, true, 56.850773, {-0.753234, 0.939525, -0.342255, -0.172317, @@ -122,4 +123,6 @@ TEST(FrameTests, VoicedFrame) { // K10 = 0.17028 ~= 0.17143 @ [4] auto k10Bin = bin.substr(47, 3); EXPECT_EQ(k10Bin, "100"); -} \ No newline at end of file +} + +}; // namespace tms_express diff --git a/test/TmsTest.cmake b/test/TmsTest.cmake index a9f34b9..6ecd02d 100644 --- a/test/TmsTest.cmake +++ b/test/TmsTest.cmake @@ -23,13 +23,13 @@ include_directories( ) add_executable(TMS-Test - src/LPC_Analysis/Autocorrelation.cpp + tms_express/LPC_Analysis/Autocorrelation.cpp test/AutocorrelatorTests.cpp - src/Frame_Encoding/Frame.cpp + tms_express/Frame_Encoding/Frame.cpp test/FrameTests.cpp - src/Frame_Encoding/FrameEncoder.cpp + tms_express/Frame_Encoding/FrameEncoder.cpp test/FrameEncoderTests.cpp ) diff --git a/tms_express/Audio/AudioBuffer.cpp b/tms_express/Audio/AudioBuffer.cpp index 3425309..a9572b0 100644 --- a/tms_express/Audio/AudioBuffer.cpp +++ b/tms_express/Audio/AudioBuffer.cpp @@ -1,277 +1,256 @@ -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// Class: AudioBuffer -// -// Description: The AudioBuffer stores mono audio samples and provides and interface for segmentation. Audio data -// may be imported from any format supported by libsndfile and will be resampled during initialization -// -// Author: Joseph Bellahcen -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2023 Joseph Bellahcen -#include "Audio/AudioBuffer.h" +#include "Audio/AudioBuffer.hpp" -#include -#include - -#include +#include #include #include -/// Create an audio buffer from an existing audio file -/// -/// \param path Path to audio file -/// \param targetSampleRateHz Rate at which to sample audio (in Hertz) -/// \param windowWidthMs Segmentation window width (in milliseconds) -AudioBuffer::AudioBuffer(const std::string &path, int targetSampleRateHz, float windowWidthMs) { - // Import audio file via libsndfile - auto sndfile = SndfileHandle(path); - - // TODO: Catch in GUI - if (sndfile.error()) { - throw std::runtime_error("Unsupported audio format"); +#include +#include + +namespace tms_express { + +/////////////////////////////////////////////////////////////////////////////// +// Factory Functions ////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +std::shared_ptr AudioBuffer::Create(const std::string &path, + int sample_rate_hz, float window_width_ms) { + // Attempt to open an audio file via libsndfile, aborting initialization if + // the given path does not exist, is invalid, or is not a suported format + auto audio_file = SndfileHandle(path); + + if (audio_file.error()) { + return nullptr; } - // Extract audio metadata - sampleRateHz = sndfile.samplerate(); - auto nFrames = sndfile.frames(); - auto nChannels = sndfile.channels(); + // Import samples from the audio file, then resample and mix + auto src_sample_rate_hz = audio_file.samplerate(); + auto n_frames = audio_file.frames(); + auto n_channels = audio_file.channels(); + auto buffer_size = n_frames * n_channels; - // Store floating-point samples - auto bufferSize = nFrames * nChannels; - samples = std::vector(bufferSize); - sndfile.read(samples.data(), bufferSize); + std::vector samples(buffer_size); + audio_file.read(samples.data(), buffer_size); - // Mix to mono - if (nChannels != 1) { - mixToMono(nChannels); + // Resample if needed, and mix to mono + if (n_channels != 1) { + samples = mixToMono(samples, n_channels); } - // Resample at target rate - // TODO: Implement upsampling safeguards - if (sndfile.samplerate() != targetSampleRateHz) { - resample(targetSampleRateHz); + if (src_sample_rate_hz != sample_rate_hz) { + samples = resample(samples, src_sample_rate_hz, sample_rate_hz); } - // Initialize parameters which depend on window width - nSegments = 0; - originalSamples = samples; - samplesPerSegment = 0; + auto ptr = std::make_shared( + AudioBuffer(samples, sample_rate_hz, window_width_ms)); - setWindowWidth(windowWidthMs); + return ptr; } -/// Create an audio buffer from raw samples -/// -/// \param pcmSamples Floating point audio samples -/// \param sampleRate Sample rate of buffer (in Hertz) -/// \param windowWidthMs Segmentation window width (in milliseconds) -AudioBuffer::AudioBuffer(std::vector pcmSamples, int sampleRate, float windowWidthMs) { - sampleRateHz = sampleRate; - samples = std::move(pcmSamples); +/////////////////////////////////////////////////////////////////////////////// +// Initializers /////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// - nSegments = 0; - originalSamples = samples; - samplesPerSegment = 0; +AudioBuffer::AudioBuffer(std::vector samples, int sample_rate_hz, + float window_width_ms) { + n_segments_ = 0; + n_samples_per_segment_ = 0; + sample_rate_hz_ = sample_rate_hz; - setWindowWidth(windowWidthMs); -} + samples_ = samples; + original_samples_ = samples_; -/// Initialize an empty Audio Buffer -/// -/// \param sampleRate Target sample rate of buffer (in Hertz) -/// \param windowWidthMs Segmentation window width (in milliseconds) -AudioBuffer::AudioBuffer(int sampleRate, float windowWidthMs) { - nSegments = 0; - originalSamples = samples = {}; - sampleRateHz = sampleRate; - samplesPerSegment = 0; - - setWindowWidth(windowWidthMs); + setWindowWidthMs(window_width_ms); } -/// Create a deep copy of an existing audio buffer -/// -/// \param buffer Original AudioBuffer to copy -AudioBuffer::AudioBuffer(const AudioBuffer &buffer) { - sampleRateHz = buffer.sampleRateHz; - samplesPerSegment = buffer.samplesPerSegment; - nSegments = buffer.nSegments; - samples = std::vector(buffer.samples); +AudioBuffer::AudioBuffer(int sample_rate_hz, float window_width_ms) { + n_segments_ = 0; + n_samples_per_segment_ = 0; + sample_rate_hz_ = sample_rate_hz; + + samples_ = original_samples_ = {}; + + setWindowWidthMs(window_width_ms); } /////////////////////////////////////////////////////////////////////////////// -// Getters & Setters +// Accessors ////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// -/// Return flat, unsegmented array of samples -std::vector AudioBuffer::getSamples() { - return samples; +std::vector AudioBuffer::getSamples() const { + return samples_; } -/// Replace samples in buffer -/// -/// \note Size of newSamples must equal size of buffer -/// -/// \param newSamples Samples to be copied into buffer -void AudioBuffer::setSamples(const std::vector &newSamples) { - // If the Audio Buffer is empty, allow the user to set the samples with a - // vector of any size. Otherwise, the new samples must be the same size as - // the original samples - if (!isEmpty() && (newSamples.size() != samples.size())) { - throw std::length_error("Unsupported buffer write: new size must equal old size"); - } +void AudioBuffer::setSamples(const std::vector &samples) { + // If, for some reason, the passed vector is empty, simply clear the buffer + if (samples.empty()) { + n_segments_ = 0; + n_samples_per_segment_ = 0; + samples_.clear(); - samples = newSamples; - - // If the Audio Buffer was empty, determine the segmentation bounds - if (isEmpty()) { - setWindowWidth(getWindowWidth()); + return; } + + samples_ = samples; + setWindowWidthMs(getWindowWidthMs()); } -/// Return window width (in number of samples) -float AudioBuffer::getWindowWidth() const { - return float(samplesPerSegment) / float(sampleRateHz * 1.0e-3); +float AudioBuffer::getWindowWidthMs() const { + float numerator = n_samples_per_segment_; + float denominator = sample_rate_hz_ * 1.0e-3; + + return numerator / denominator; } -/// Update the window width, modifying segment bounds -/// -/// \param windowWidthMs Desired window width (in milliseconds) -void AudioBuffer::setWindowWidth(float windowWidthMs) { - // Re-compute segment bounds - samplesPerSegment = int(float(sampleRateHz) * windowWidthMs * 1e-3); - nSegments = samples.size() / samplesPerSegment; - - // Effectively pad final segment with zeros - unsigned int paddedSize = samplesPerSegment * nSegments; - if (samples.size() < paddedSize) { - samples.resize(paddedSize, 0); - } else if (samples.size() > paddedSize) { - samples.resize(paddedSize + samplesPerSegment, 0); +void AudioBuffer::setWindowWidthMs(float window_width_ms) { + if (window_width_ms == 0) { + n_samples_per_segment_ = 1; + n_segments_ = samples_.size(); + return; } -} -/////////////////////////////////////////////////////////////////////////////// -// Const Getters -/////////////////////////////////////////////////////////////////////////////// + // Re-compute segment bounds + n_samples_per_segment_ = sample_rate_hz_ * window_width_ms * 1e-3; + n_segments_ = samples_.size() / n_samples_per_segment_; -/// Return sampling rate of audio -int AudioBuffer::sampleRate() const { - return sampleRateHz; -} + // Pad final segment with zeros + std::vector::size_type padded_size = + n_samples_per_segment_ * n_segments_; + + if (samples_.size() < padded_size) { + samples_.resize(padded_size, 0); -/// Return ith audio segment -std::vector AudioBuffer::segment(int i) { - if (i >= nSegments) { - throw std::range_error("Segment index out of bounds"); + } else if (samples_.size() > padded_size) { + samples_.resize(padded_size + n_samples_per_segment_, 0); } +} - if (isEmpty()) { - throw std::runtime_error("Cannot segment empty buffer"); +int AudioBuffer::getSampleRateHz() const { + return sample_rate_hz_; +} + +std::vector AudioBuffer::getSegment(int i) const { + if (i >= n_segments_ || empty()) { + return {}; } - auto offset = int(samplesPerSegment); - auto start = samples.begin() + (i * offset); + auto offset = n_samples_per_segment_; + auto start = samples_.begin() + (i * offset); auto end = start + offset; return {start, end}; } -/// Return 2-dimensional array of all segments -std::vector> AudioBuffer::segments() { - auto _segments = std::vector>(); - for (int i = 0; i < nSegments; i++) { - _segments.push_back(segment(i)); +std::vector> AudioBuffer::getAllSegments() const { + if (empty()) { + return {}; } - return _segments; -} + auto segments = std::vector>(); + for (int i = 0; i < n_segments_; i++) { + segments.push_back(getSegment(i)); + } -/// Return number of audio samples in each segment -size_t AudioBuffer::segmentSize() const { - return samplesPerSegment; + return segments; } -/// Return number of total audio segments -size_t AudioBuffer::size() const { - return nSegments; +int AudioBuffer::getNSamplesPerSegment() const { + return n_samples_per_segment_; } -/// Return whether or not the Audio Buffer contains samples -bool AudioBuffer::isEmpty() const { - return samples.empty(); +int AudioBuffer::getNSegments() const { + return n_segments_; } /////////////////////////////////////////////////////////////////////////////// -// Public Utility Functions +// Metadata /////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// -/// Export buffer contents to WAV audio file -/// -/// \param path Path to new audio file -void AudioBuffer::render(const std::string &path) { +bool AudioBuffer::empty() const { + return samples_.empty(); +} + +/////////////////////////////////////////////////////////////////////////// +// Utility //////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////// + +AudioBuffer AudioBuffer::copy() const { + return AudioBuffer(samples_, sample_rate_hz_, getWindowWidthMs()); +} + +bool AudioBuffer::render(const std::string &path) const { // Throw error if buffer is empty - if (isEmpty()) { - throw std::runtime_error("Cannot render empty buffer"); + if (empty()) { + return false; } - - // Construct 8kHz mono audio file handle - auto metadata = SF_INFO(); + // Construct WAV audio file handle + auto metadata = SF_INFO(); metadata.channels = 1; - metadata.frames = sf_count_t(samples.size()); + metadata.frames = sf_count_t(samples_.size()); metadata.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16; - metadata.samplerate = int(sampleRateHz); + metadata.samplerate = sample_rate_hz_; // Open file - auto sndfile = sf_open(path.c_str(), SFM_WRITE, &metadata); - sf_writef_float(sndfile, samples.data(), sf_count_t(samples.size())); - sf_close(sndfile); + // TODO(Joseph Bellahcen) Track number of samples written and return error + // if number does not match original buffer size + auto audio_file = sf_open(path.c_str(), SFM_WRITE, &metadata); + sf_writef_float(audio_file, samples_.data(), sf_count_t(samples_.size())); + sf_close(audio_file); + + return true; } -/// Restore buffer to its initialization state -/// -/// \note This function will NOT reset the window width void AudioBuffer::reset() { - samples = originalSamples; + samples_ = original_samples_; } -// Mix audio buffer to mono by summing each frame across channels and averaging the result -void AudioBuffer::mixToMono(int nOriginalChannels) { - auto monoSize = samples.size() / nOriginalChannels; - auto monoSamples = std::vector(monoSize, 0); +/////////////////////////////////////////////////////////////////////////////// +// Static Initialization Utilities //////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// - for (int frame = 0; frame < monoSize; frame++) { - for (int channel = 0; channel < nOriginalChannels; channel++) { - monoSamples[frame] += samples[frame * nOriginalChannels + channel]; +std::vector AudioBuffer::mixToMono(std::vector samples, + int n_channels) { + int mono_size = samples.size() / n_channels; + auto mono_samples = std::vector(mono_size, 0); + + for (int frame = 0; frame < mono_size; frame++) { + for (int channel = 0; channel < n_channels; channel++) { + mono_samples[frame] += samples[frame * n_channels + channel]; } - monoSamples[frame] /= float(nOriginalChannels); + mono_samples[frame] /= static_cast(n_channels); } - samples = monoSamples; + return mono_samples; } // Resample the audio buffer to the target sample rate -void AudioBuffer::resample(int targetSampleRateHz) { +std::vector AudioBuffer::resample(std::vector samples, + int src_sample_rate_hz, int target_sample_rate_hz) { // Resampler parameters // NOTE: If a future version of this codebase requires // compatibility with stereo audio, compute the // number of frames as: size / (channels * ratio) // and the number of samples as: (frames * channels) - double ratio = double(targetSampleRateHz) / double(sampleRateHz); - int nFrames = int(double(samples.size()) * ratio); - auto resampledBuffer = std::vector(nFrames); + double ratio = target_sample_rate_hz / src_sample_rate_hz; + auto n_frames = samples.size() * ratio; + auto resampled_buffer = std::vector(n_frames); // Initialize resampler auto resampler = SRC_DATA(); resampler.data_in = samples.data(); - resampler.input_frames = long(samples.size()); - resampler.data_out = resampledBuffer.data(); - resampler.output_frames = nFrames; + resampler.input_frames = samples.size(); + resampler.data_out = resampled_buffer.data(); + resampler.output_frames = n_frames; resampler.src_ratio = ratio; // Store resampled audio + // TODO(Joseph Bellahcen): Check for errors and return empty vector if occur src_simple(&resampler, SRC_SINC_BEST_QUALITY, 1); - sampleRateHz = targetSampleRateHz; - samples = resampledBuffer; + return resampled_buffer; } + +}; // namespace tms_express diff --git a/tms_express/Audio/AudioBuffer.h b/tms_express/Audio/AudioBuffer.h deleted file mode 100644 index 098e1dd..0000000 --- a/tms_express/Audio/AudioBuffer.h +++ /dev/null @@ -1,46 +0,0 @@ -// Author: Joseph Bellahcen - -#ifndef TMS_EXPRESS_AUDIOBUFFER_H -#define TMS_EXPRESS_AUDIOBUFFER_H - -#include -#include - -class AudioBuffer { -public: - explicit AudioBuffer(const std::string &path, int targetSampleRateHz = 8000, float windowWidthMs = 25.0f); - explicit AudioBuffer(std::vector pcmSamples, int sampleRate = 8000, float windowWidthMs = 25.0f); - AudioBuffer(int sampleRate = 8000, float windowWidthMs = 25.0f); - AudioBuffer(const AudioBuffer &buffer); - - // Getters & setters - std::vector getSamples(); - void setSamples(const std::vector &newSamples); - - [[nodiscard]] float getWindowWidth() const; - void setWindowWidth(float windowWidthMs); - - // Const getters - [[nodiscard]] int sampleRate() const; - std::vector segment(int i); - std::vector> segments(); - [[nodiscard]] size_t segmentSize() const; - [[nodiscard]] size_t size() const; - bool isEmpty() const; - - // Utility - void render(const std::string &path); - void reset(); - -private: - size_t nSegments; - std::vector originalSamples; - int sampleRateHz; - std::vector samples; - size_t samplesPerSegment; - - void mixToMono(int nOriginalChannels); - void resample(int targetSampleRateHz); -}; - -#endif //TMS_EXPRESS_AUDIOBUFFER_H diff --git a/tms_express/Audio/AudioBuffer.hpp b/tms_express/Audio/AudioBuffer.hpp new file mode 100644 index 0000000..b08e408 --- /dev/null +++ b/tms_express/Audio/AudioBuffer.hpp @@ -0,0 +1,144 @@ +// Copyright (C) 2023 Joseph Bellahcen + +#ifndef TMS_EXPRESS_AUDIO_AUDIOBUFFER_HPP_ +#define TMS_EXPRESS_AUDIO_AUDIOBUFFER_HPP_ + +#include +#include +#include + +namespace tms_express { + +/// @brief Stores mono audio samples and provides interface for +/// segment-based analysis +class AudioBuffer { + public: + /////////////////////////////////////////////////////////////////////////// + // Factory Functions ////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @brief Creates new Audio Buffer from audio file + /// @param path Path to audio file + /// @param sample_rate_hz Rate at which to sample/resample audio, in Hertz + /// @param window_width_ms Segmentation window with, in milliseconds + /// @return Pointer to a valid Audio Buffer if path points to valid file, + /// nullptr otherwise + static std::shared_ptr Create(const std::string &path, + int sample_rate_hz = 8000, float window_width_ms = 25.0f); + + /////////////////////////////////////////////////////////////////////////// + // Initializers /////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @brief Initializes new Audio Buffer from given samples + /// @param samples Floating-point PCM samples + /// @param sample_rate_hz Sampling rate used to generate samples, in Hertz + /// @param window_width_ms Segmentation window with, in milliseconds + explicit AudioBuffer(std::vector samples, int sample_rate_hz, + float window_width_ms); + + /// @brief Initializes a new empty Audio Buffer + /// @param sample_rate_hz Sample rate, in Hertz, of data which Audio Buffer + /// may eventually hold + /// @param window_width_ms Segmentation window with, in milliseconds + explicit AudioBuffer(int sample_rate_hz = 8000, + float window_width_ms = 25.0f); + + /////////////////////////////////////////////////////////////////////////// + // Accessors ////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @brief Accesses unsegmented array of samples + /// @return Samples vector + std::vector getSamples() const; + + /// @brief Replaces Audio Buffer samples with given vector + /// @param samples New samples vector + void setSamples(const std::vector &samples); + + /// @brief Accesses segmentation window width + /// @return Segmentation widnow width, in milliseconds + float getWindowWidthMs() const; + + /// @brief Recomputes analysis segment bounds from given window width + /// @param window_width_ms New segmentation window width, in milliseconds + void setWindowWidthMs(float window_width_ms); + + /// @brief Accesses audio sampling rate + /// @return Sampling rate, in Hertz + int getSampleRateHz() const; + + /// @brief Accesses ith segment of Audio Buffer + /// @param i Index of the desired segment + /// @return The ith segment if index in range, empty vector if index out of + /// range or Audio Buffer empty + std::vector getSegment(int i) const; + + /// @brief Accesses all segments, as a 2D vector + /// @return Vector of segments, or empty vector if Audio Buffer empty + std::vector> getAllSegments() const; + + /// @brief Accesses number of samples each segment + /// @return Samples per segment + int getNSamplesPerSegment() const; + + /// @brief Accesses number of segments in Audio Buffer + /// @return Segments in Audio Buffer + int getNSegments() const; + + /////////////////////////////////////////////////////////////////////////// + // Metadata /////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @brief Reports whether Audio Buffer contains samples + /// @return false if Audio Buffer contains no samples, true otherwise + bool empty() const; + + /////////////////////////////////////////////////////////////////////////// + // Utility //////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @brief Creates an independent copy of the Audio Buffer + /// @return New Audio Buffer which is separate from but identical to its + /// parent at the time of creation + AudioBuffer copy() const; + + /// @brief Exports Audio Buffer to audio file for playback + /// @param path Path to new audio file + /// @return true if render successful, false otherwise + bool render(const std::string &path) const; + + /// @brief Restore Audio Buffer samples to initialization state + [[deprecated]] void reset(); + + private: + int n_segments_; + int n_samples_per_segment_; + int sample_rate_hz_; + std::vector samples_; + + std::vector original_samples_; + + /////////////////////////////////////////////////////////////////////////// + // Static Initialization Utilities //////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @brief Mixes multi-channel audio to 1D mono samples + /// @param samples Original multi-channel samples + /// @param n_channels Original number of channels + /// @return Mono samples, as a 1D vector + static std::vector mixToMono(std::vector samples, + int n_channels); + + /// @brief Resamples samples to the target sample rate + /// @param samples Original samples + /// @param src_sample_rate_hz Original sample rate, in Hertz + /// @param target_sample_rate_hz Target sample rate, in Hertz + /// @return Resampled vectors at the target sample rate + static std::vector resample(std::vector samples, + int src_sample_rate_hz, int target_sample_rate_hz); +}; + +}; // namespace tms_express + +#endif // TMS_EXPRESS_AUDIO_AUDIOBUFFER_HPP_ diff --git a/tms_express/Audio/AudioFilter.cpp b/tms_express/Audio/AudioFilter.cpp index 8136f06..9c4c52b 100644 --- a/tms_express/Audio/AudioFilter.cpp +++ b/tms_express/Audio/AudioFilter.cpp @@ -16,13 +16,15 @@ #include #include +namespace tms_express { + AudioFilter::AudioFilter() = default; /// Apply Hamming window to buffer /// /// \param buffer Buffer on which to apply Hamming window void AudioFilter::hammingWindow(AudioBuffer &buffer) { - for (auto &segment : buffer.segments()) { + for (auto &segment : buffer.getAllSegments()) { hammingWindow(segment); } } @@ -173,3 +175,5 @@ void AudioFilter::computeCoeffs(AudioFilter::FilterMode mode, int cutoffHz) { // Normalization coefficient coeffs[5] = aCoeff[0]; } + +}; // namespace tms_express diff --git a/tms_express/Audio/AudioFilter.h b/tms_express/Audio/AudioFilter.h index 0c0c806..49a584a 100644 --- a/tms_express/Audio/AudioFilter.h +++ b/tms_express/Audio/AudioFilter.h @@ -3,11 +3,13 @@ #ifndef TMS_EXPRESS_AUDIOFILTER_H #define TMS_EXPRESS_AUDIOFILTER_H -#include "AudioBuffer.h" +#include "AudioBuffer.hpp" #include #include #include +namespace tms_express { + class AudioFilter { public: AudioFilter(); @@ -26,4 +28,6 @@ class AudioFilter { void computeCoeffs(FilterMode mode, int cutoffHz); }; +}; // namespace tms_express + #endif //TMS_EXPRESS_AUDIOFILTER_H diff --git a/tms_express/Bitstream_Generation/BitstreamGenerator.cpp b/tms_express/Bitstream_Generation/BitstreamGenerator.cpp index e5bf739..760cb4a 100644 --- a/tms_express/Bitstream_Generation/BitstreamGenerator.cpp +++ b/tms_express/Bitstream_Generation/BitstreamGenerator.cpp @@ -8,7 +8,7 @@ // Author: Joseph Bellahcen /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#include "Audio/AudioBuffer.h" +#include "Audio/AudioBuffer.hpp" #include "Audio/AudioFilter.h" #include "Bitstream_Generation/BitstreamGenerator.h" #include "Frame_Encoding/Frame.h" @@ -24,6 +24,8 @@ #include #include +namespace tms_express { + BitstreamGenerator::BitstreamGenerator(float windowMs, int highpassHz, int lowpassHz, float preemphasis, EncoderStyle style, bool includeStopFrame, int gainShift, float maxVoicedDb, float maxUnvoicedDb, bool detectRepeats, int maxHz, int minHz) : windowMs(windowMs), highpassHz(highpassHz), lowpassHz(lowpassHz), @@ -83,7 +85,7 @@ void BitstreamGenerator::encodeBatch(const std::vector &inputPaths, // Generate a bitstream from a single audio file std::vector BitstreamGenerator::generateFrames(const std::string &inputPath) const { // Mix audio to 8kHz mono and store in a segmented buffer - auto lpcBuffer = AudioBuffer(inputPath, 8000, windowMs); + auto lpcBuffer = *AudioBuffer::Create(inputPath, 8000, windowMs); // Copy the buffer so that upper and lower vocal tract analysis may occur separately auto pitchBuffer = AudioBuffer(lpcBuffer); @@ -102,8 +104,8 @@ std::vector BitstreamGenerator::generateFrames(const std::string &inputPa // Only the LPC buffer is queried for metadata, since it will have the same number of samples as the pitch buffer. // The sample rate of the buffer is extracted despite being known, as future iterations of TMS Express may support // encoding 10kHz/variable sample rate audio for the TMS5200C - auto nSegments = lpcBuffer.size(); - auto sampleRate = lpcBuffer.sampleRate(); + auto nSegments = lpcBuffer.getNSegments(); + auto sampleRate = lpcBuffer.getSampleRateHz(); // Initialize analysis objects and data structures auto linearPredictor = LinearPredictor(); @@ -112,8 +114,8 @@ std::vector BitstreamGenerator::generateFrames(const std::string &inputPa for (int i = 0; i < nSegments; i++) { // Get segment for frame - auto pitchSegment = pitchBuffer.segment(i); - auto lpcSegment = lpcBuffer.segment(i); + auto pitchSegment = pitchBuffer.getSegment(i); + auto lpcSegment = lpcBuffer.getSegment(i); // Apply a window function to the segment to smoothen its boundaries // @@ -182,3 +184,5 @@ std::string BitstreamGenerator::formatBitstream(const std::vector& frames return bitstream; } + +}; // namespace tms_express diff --git a/tms_express/Bitstream_Generation/BitstreamGenerator.h b/tms_express/Bitstream_Generation/BitstreamGenerator.h index 61a7ee3..3bc2867 100644 --- a/tms_express/Bitstream_Generation/BitstreamGenerator.h +++ b/tms_express/Bitstream_Generation/BitstreamGenerator.h @@ -6,6 +6,8 @@ #include "Frame_Encoding/Frame.h" #include +namespace tms_express { + class BitstreamGenerator { public: typedef enum {ENCODERSTYLE_ASCII, ENCODERSTYLE_C, ENCODERSTYLE_ARDUINO, ENCODERSTYLE_JSON} EncoderStyle; @@ -35,4 +37,6 @@ class BitstreamGenerator { std::string formatBitstream(const std::vector& frames, const std::string &filename); }; +}; // namespace tms_express + #endif //TMS_EXPRESS_BITSTREAMGENERATOR_H diff --git a/tms_express/Bitstream_Generation/PathUtils.cpp b/tms_express/Bitstream_Generation/PathUtils.cpp index eee3c3f..611891e 100644 --- a/tms_express/Bitstream_Generation/PathUtils.cpp +++ b/tms_express/Bitstream_Generation/PathUtils.cpp @@ -13,6 +13,8 @@ #include #include +namespace tms_express { + namespace fs = std::filesystem; PathUtils::PathUtils(std::string filepath) { @@ -98,3 +100,5 @@ std::vector PathUtils::splitString(const std::string& str, const st return result; } + +}; // namespace tms_express diff --git a/tms_express/Bitstream_Generation/PathUtils.h b/tms_express/Bitstream_Generation/PathUtils.h index 3236dbb..6d9d10a 100644 --- a/tms_express/Bitstream_Generation/PathUtils.h +++ b/tms_express/Bitstream_Generation/PathUtils.h @@ -6,6 +6,8 @@ #include #include +namespace tms_express { + class PathUtils { public: explicit PathUtils(std::string filepath); @@ -27,5 +29,6 @@ class PathUtils { static std::vector splitString(const std::string& str, const std::string& delim); }; +}; // namespace tms_express #endif //TMS_EXPRESS_PATHUTILS_H diff --git a/tms_express/Frame_Encoding/Frame.cpp b/tms_express/Frame_Encoding/Frame.cpp index 5ef5b60..dcc1c59 100644 --- a/tms_express/Frame_Encoding/Frame.cpp +++ b/tms_express/Frame_Encoding/Frame.cpp @@ -18,6 +18,8 @@ #include #include +namespace tms_express { + using namespace Tms5220CodingTable; /// Create a new frame @@ -295,3 +297,5 @@ std::string Frame::valueToBinary(int value, int bitWidth) { return bin; } + +}; // namespace tms_express diff --git a/tms_express/Frame_Encoding/Frame.h b/tms_express/Frame_Encoding/Frame.h index fdebf30..7351479 100644 --- a/tms_express/Frame_Encoding/Frame.h +++ b/tms_express/Frame_Encoding/Frame.h @@ -7,6 +7,8 @@ #include #include +namespace tms_express { + class Frame { public: Frame(int pitchPeriod, bool isVoiced, float gainDB, std::vector coeffs); @@ -54,4 +56,6 @@ class Frame { static std::string valueToBinary(int value, int bitWidth); }; +}; // namespace tms_express + #endif //TMS_EXPRESS_FRAME_H diff --git a/tms_express/Frame_Encoding/FrameEncoder.cpp b/tms_express/Frame_Encoding/FrameEncoder.cpp index de0c0bd..64dfbc7 100644 --- a/tms_express/Frame_Encoding/FrameEncoder.cpp +++ b/tms_express/Frame_Encoding/FrameEncoder.cpp @@ -21,6 +21,8 @@ #include #include +namespace tms_express { + /// Create a new Frame Encoder with an empty frame buffer /// /// \param includeHexPrefix Whether or not to include '0x' before hex bytes @@ -315,3 +317,5 @@ size_t FrameEncoder::parseAsciiBitstream(std::string flatBitstream) { return frames.size(); } + +}; // namespace tms_express diff --git a/tms_express/Frame_Encoding/FrameEncoder.h b/tms_express/Frame_Encoding/FrameEncoder.h index 1d2d1de..8f6146a 100644 --- a/tms_express/Frame_Encoding/FrameEncoder.h +++ b/tms_express/Frame_Encoding/FrameEncoder.h @@ -8,6 +8,8 @@ #include #include +namespace tms_express { + class FrameEncoder { public: explicit FrameEncoder(bool includeHexPrefix = false, char separator = ','); @@ -43,4 +45,6 @@ class FrameEncoder { [[nodiscard]] std::string byteToHex(const std::string &byte) const; }; +}; // namespace tms_express + #endif //TMS_EXPRESS_FRAMEENCODER_H diff --git a/tms_express/Frame_Encoding/FramePostprocessor.cpp b/tms_express/Frame_Encoding/FramePostprocessor.cpp index 7274826..25325d3 100644 --- a/tms_express/Frame_Encoding/FramePostprocessor.cpp +++ b/tms_express/Frame_Encoding/FramePostprocessor.cpp @@ -13,6 +13,8 @@ #include +namespace tms_express { + /// Create a new Frame Postprocessor /// /// \param frames Frames to modify @@ -203,4 +205,6 @@ void FramePostprocessor::reset() { frame = originalFrame; } -} \ No newline at end of file +} + +}; // namespace tms_express diff --git a/tms_express/Frame_Encoding/FramePostprocessor.h b/tms_express/Frame_Encoding/FramePostprocessor.h index a45ad8f..3fc95bc 100644 --- a/tms_express/Frame_Encoding/FramePostprocessor.h +++ b/tms_express/Frame_Encoding/FramePostprocessor.h @@ -6,6 +6,8 @@ #include "Frame.h" #include +namespace tms_express { + class FramePostprocessor { public: explicit FramePostprocessor(std::vector *frames, float maxVoicedGainDB = 37.5, float maxUnvoicedGainDB = 37.5); @@ -37,4 +39,6 @@ class FramePostprocessor { void normalizeGain(bool normalizeVoicedFrames); }; +}; // namespace tms_express + #endif //TMS_EXPRESS_FRAMEPOSTPROCESSOR_H diff --git a/tms_express/Frame_Encoding/Synthesizer.cpp b/tms_express/Frame_Encoding/Synthesizer.cpp index 6d4a45a..e076420 100644 --- a/tms_express/Frame_Encoding/Synthesizer.cpp +++ b/tms_express/Frame_Encoding/Synthesizer.cpp @@ -13,7 +13,7 @@ // https://github.com/tocisz/talkie.love /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#include "Audio/AudioBuffer.h" +#include "Audio/AudioBuffer.hpp" #include "Frame_Encoding/Synthesizer.h" #include "Frame_Encoding/Frame.h" #include "Frame_Encoding/Tms5220CodingTable.h" @@ -22,6 +22,8 @@ #include #include +namespace tms_express { + using namespace Tms5220CodingTable; /// Create a new synthesizer @@ -192,3 +194,5 @@ float Synthesizer::updateLatticeFilter() { std::vector Synthesizer::samples() { return synthesizedSamples; } + +}; // namespace tms_express diff --git a/tms_express/Frame_Encoding/Synthesizer.h b/tms_express/Frame_Encoding/Synthesizer.h index fb303af..b1d7473 100644 --- a/tms_express/Frame_Encoding/Synthesizer.h +++ b/tms_express/Frame_Encoding/Synthesizer.h @@ -8,6 +8,8 @@ #include #include +namespace tms_express { + class Synthesizer { public: explicit Synthesizer(int sampleRateHz = 8000, float frameRateMs = 25.0f); @@ -35,4 +37,6 @@ class Synthesizer { float updateLatticeFilter(); }; +}; // namespace tms_express + #endif //TMS_EXPRESS_SYNTHESIZER_H diff --git a/tms_express/Frame_Encoding/Tms5220CodingTable.h b/tms_express/Frame_Encoding/Tms5220CodingTable.h index 9471118..710ce8e 100644 --- a/tms_express/Frame_Encoding/Tms5220CodingTable.h +++ b/tms_express/Frame_Encoding/Tms5220CodingTable.h @@ -7,6 +7,8 @@ #include #include +namespace tms_express { + using std::vector; using std::array; @@ -115,4 +117,6 @@ namespace Tms5220CodingTable { } }; +}; // namespace tms_express + #endif //TMS_EXPRESS_TMS5220CODINGTABLE_H diff --git a/tms_express/LPC_Analysis/LinearPredictor.cpp b/tms_express/LPC_Analysis/LinearPredictor.cpp index 36e3952..35df1cf 100644 --- a/tms_express/LPC_Analysis/LinearPredictor.cpp +++ b/tms_express/LPC_Analysis/LinearPredictor.cpp @@ -14,6 +14,8 @@ #include #include +namespace tms_express { + /// Create a new LPC solver of the given order /// /// \param modelOrder Order of the LPC model for which to solve (typically 10) @@ -76,3 +78,5 @@ float LinearPredictor::gain() const { float gain = 10.0f * log10f(error / 1e-12f); return abs(gain); } + +}; // namespace tms_express diff --git a/tms_express/LPC_Analysis/LinearPredictor.h b/tms_express/LPC_Analysis/LinearPredictor.h index bfacb05..3b64c77 100644 --- a/tms_express/LPC_Analysis/LinearPredictor.h +++ b/tms_express/LPC_Analysis/LinearPredictor.h @@ -5,6 +5,8 @@ #include +namespace tms_express { + class LinearPredictor { public: explicit LinearPredictor(int modelOrder = 10); @@ -17,4 +19,6 @@ class LinearPredictor { float error; }; +}; // namespace tms_express + #endif //TMS_EXPRESS_LINEARPREDICTOR_H \ No newline at end of file diff --git a/tms_express/LPC_Analysis/PitchEstimator.cpp b/tms_express/LPC_Analysis/PitchEstimator.cpp index 9239e57..2f27fee 100644 --- a/tms_express/LPC_Analysis/PitchEstimator.cpp +++ b/tms_express/LPC_Analysis/PitchEstimator.cpp @@ -12,6 +12,8 @@ #include #include +namespace tms_express { + /// Create a new pitch estimator, bounded by the min and max frequencies /// /// \param sampleRateHz Sample rate of the audio samples @@ -97,3 +99,5 @@ int PitchEstimator::estimatePeriod(const std::vector &acf) const { return period; } } + +}; // namespace tms_express diff --git a/tms_express/LPC_Analysis/PitchEstimator.h b/tms_express/LPC_Analysis/PitchEstimator.h index e58a323..a5c5ebe 100644 --- a/tms_express/LPC_Analysis/PitchEstimator.h +++ b/tms_express/LPC_Analysis/PitchEstimator.h @@ -3,9 +3,11 @@ #ifndef TMS_EXPRESS_PITCHESTIMATOR_H #define TMS_EXPRESS_PITCHESTIMATOR_H -#include "Audio/AudioBuffer.h" +#include "Audio/AudioBuffer.hpp" #include +namespace tms_express { + class PitchEstimator { public: explicit PitchEstimator(int sampleRateHz, int minFrqHz = 50, int maxFrqHz = 500); @@ -29,4 +31,6 @@ class PitchEstimator { int sampleRate; }; +}; // namespace tms_express + #endif //TMS_EXPRESS_PITCHESTIMATOR_H \ No newline at end of file diff --git a/tms_express/User_Interfaces/Audio_Waveform/AudioWaveform.cpp b/tms_express/User_Interfaces/Audio_Waveform/AudioWaveform.cpp index 078f510..dd3e15f 100644 --- a/tms_express/User_Interfaces/Audio_Waveform/AudioWaveform.cpp +++ b/tms_express/User_Interfaces/Audio_Waveform/AudioWaveform.cpp @@ -16,6 +16,8 @@ #include #include +namespace tms_express { + /// Create a new Audio Waveform plot AudioWaveform::AudioWaveform(QWidget *parent) : QWidget(parent) { // Set the plot background to true black @@ -87,3 +89,5 @@ void AudioWaveform::paintEvent(QPaintEvent * event) { } } } + +}; // namespace tms_express diff --git a/tms_express/User_Interfaces/Audio_Waveform/AudioWaveform.h b/tms_express/User_Interfaces/Audio_Waveform/AudioWaveform.h index 52e5cfc..0f49a9d 100644 --- a/tms_express/User_Interfaces/Audio_Waveform/AudioWaveform.h +++ b/tms_express/User_Interfaces/Audio_Waveform/AudioWaveform.h @@ -9,6 +9,8 @@ #include #include +namespace tms_express { + class AudioWaveform : public QWidget { Q_OBJECT public: @@ -25,4 +27,6 @@ class AudioWaveform : public QWidget { std::vector samples; }; +}; // namespace tms_express + #endif //TMS_EXPRESS_AUDIOWAVEFORM_H diff --git a/tms_express/User_Interfaces/Audio_Waveform/AudioWaveformView.cpp b/tms_express/User_Interfaces/Audio_Waveform/AudioWaveformView.cpp index 35e8ef1..c9111d8 100644 --- a/tms_express/User_Interfaces/Audio_Waveform/AudioWaveformView.cpp +++ b/tms_express/User_Interfaces/Audio_Waveform/AudioWaveformView.cpp @@ -6,6 +6,8 @@ #include #include +namespace tms_express { + AudioWaveformView::AudioWaveformView(std::string title, uint baseWidth, uint baseHeight, QWidget *parent): QWidget(parent) { setMinimumSize(baseWidth, baseHeight); @@ -42,3 +44,5 @@ void AudioWaveformView::plotSamples(const std::vector &_samples) { void AudioWaveformView::onPlayButtonPressed() { emit signalPlayButtonPressed(true); } + +}; // namespace tms_express diff --git a/tms_express/User_Interfaces/Audio_Waveform/AudioWaveformView.h b/tms_express/User_Interfaces/Audio_Waveform/AudioWaveformView.h index e3c2aa6..414f2ea 100644 --- a/tms_express/User_Interfaces/Audio_Waveform/AudioWaveformView.h +++ b/tms_express/User_Interfaces/Audio_Waveform/AudioWaveformView.h @@ -10,6 +10,8 @@ #include +namespace tms_express { + class AudioWaveformView: public QWidget { Q_OBJECT public: @@ -30,4 +32,6 @@ public slots: QPushButton *playButton; }; +}; // namespace tms_express + #endif //TMS_EXPRESS_AUDIOWAVEFORMVIEW_H diff --git a/tms_express/User_Interfaces/CommandLineApp.cpp b/tms_express/User_Interfaces/CommandLineApp.cpp index d31d89c..391014f 100644 --- a/tms_express/User_Interfaces/CommandLineApp.cpp +++ b/tms_express/User_Interfaces/CommandLineApp.cpp @@ -12,6 +12,8 @@ #include "CLI11.hpp" +namespace tms_express { + /// Setup a CLI11 interface for accessing TMS Express from the command line CommandLineApp::CommandLineApp() { encoder = add_subcommand("encode", "Convert audio file(s) to TMS5220 bitstream(s)"); @@ -89,3 +91,5 @@ void CommandLineApp::setupEncoder() { encoder->add_option("-m,--min-pitch", minPitchFrq, "Min pitch frequency (Hz)"); encoder->add_option("-o,--output,output", outputPath, "Path to output file")->required(); } + +}; // namespace tms_express diff --git a/tms_express/User_Interfaces/CommandLineApp.h b/tms_express/User_Interfaces/CommandLineApp.h index f87bdf4..6c1233f 100644 --- a/tms_express/User_Interfaces/CommandLineApp.h +++ b/tms_express/User_Interfaces/CommandLineApp.h @@ -6,6 +6,8 @@ #include "Bitstream_Generation/BitstreamGenerator.h" #include "CLI11.hpp" +namespace tms_express { + class CommandLineApp : public CLI::App { public: CommandLineApp(); @@ -33,4 +35,6 @@ class CommandLineApp : public CLI::App { void setupEncoder(); }; +}; // namespace tms_express + #endif //TMS_EXPRESS_COMMANDLINEAPP_H diff --git a/tms_express/User_Interfaces/Control_Panels/ControlPanelLpcView.cpp b/tms_express/User_Interfaces/Control_Panels/ControlPanelLpcView.cpp index 5725de3..732660e 100644 --- a/tms_express/User_Interfaces/Control_Panels/ControlPanelLpcView.cpp +++ b/tms_express/User_Interfaces/Control_Panels/ControlPanelLpcView.cpp @@ -12,6 +12,8 @@ #include #include +namespace tms_express { + ControlPanelLpcView::ControlPanelLpcView(QWidget *parent): ControlPanelView("LPC Analysis", parent) { auto analysisWindowLabel = new QLabel("Analysis window (ms)", this); analysisWindowLine = new QLineEdit("25.0", this); @@ -84,3 +86,5 @@ bool ControlPanelLpcView::preemphEnabled() { float ControlPanelLpcView::preemphAlpha() { return preemphLine->text().toFloat(); } + +}; // namespace tms_express diff --git a/tms_express/User_Interfaces/Control_Panels/ControlPanelLpcView.h b/tms_express/User_Interfaces/Control_Panels/ControlPanelLpcView.h index a993181..db78864 100644 --- a/tms_express/User_Interfaces/Control_Panels/ControlPanelLpcView.h +++ b/tms_express/User_Interfaces/Control_Panels/ControlPanelLpcView.h @@ -8,6 +8,8 @@ #include #include +namespace tms_express { + class ControlPanelLpcView: public ControlPanelView { Q_OBJECT public: @@ -35,4 +37,6 @@ Q_OBJECT QLineEdit *preemphLine; }; +}; // namespace tms_express + #endif //TMS_EXPRESS_CONTROLPANELLPCVIEW_H diff --git a/tms_express/User_Interfaces/Control_Panels/ControlPanelPitchView.cpp b/tms_express/User_Interfaces/Control_Panels/ControlPanelPitchView.cpp index 95932a2..d5a782a 100644 --- a/tms_express/User_Interfaces/Control_Panels/ControlPanelPitchView.cpp +++ b/tms_express/User_Interfaces/Control_Panels/ControlPanelPitchView.cpp @@ -11,6 +11,8 @@ #include #include +namespace tms_express { + ControlPanelPitchView::ControlPanelPitchView(QWidget *parent): ControlPanelView("Pitch Analysis", parent) { auto line2 = new QFrame(this); line2->setFrameShape(QFrame::HLine); @@ -102,3 +104,5 @@ int ControlPanelPitchView::maxPitchFrq() { int ControlPanelPitchView::minPitchFrq() { return minPitchLine->text().toInt(); } + +}; // namespace tms_express diff --git a/tms_express/User_Interfaces/Control_Panels/ControlPanelPitchView.h b/tms_express/User_Interfaces/Control_Panels/ControlPanelPitchView.h index 511ec2e..e0e7e9a 100644 --- a/tms_express/User_Interfaces/Control_Panels/ControlPanelPitchView.h +++ b/tms_express/User_Interfaces/Control_Panels/ControlPanelPitchView.h @@ -9,6 +9,8 @@ #include #include +namespace tms_express { + class ControlPanelPitchView: public ControlPanelView { Q_OBJECT public: @@ -40,4 +42,6 @@ Q_OBJECT QLineEdit *minPitchLine; }; +}; // namespace tms_express + #endif //TMS_EXPRESS_CONTROLPANELPITCHVIEW_H diff --git a/tms_express/User_Interfaces/Control_Panels/ControlPanelPostView.cpp b/tms_express/User_Interfaces/Control_Panels/ControlPanelPostView.cpp index 7a6a87b..2999e17 100644 --- a/tms_express/User_Interfaces/Control_Panels/ControlPanelPostView.cpp +++ b/tms_express/User_Interfaces/Control_Panels/ControlPanelPostView.cpp @@ -12,6 +12,8 @@ #include #include +namespace tms_express { + using namespace Tms5220CodingTable; ControlPanelPostView::ControlPanelPostView(QWidget *parent): ControlPanelView("Post-Processing", parent) { @@ -137,3 +139,5 @@ float ControlPanelPostView::maxUnvoicedGain() { float ControlPanelPostView::maxVoicedGain() { return maxVoicedGainLine->text().toFloat(); } + +}; // namespace tms_express diff --git a/tms_express/User_Interfaces/Control_Panels/ControlPanelPostView.h b/tms_express/User_Interfaces/Control_Panels/ControlPanelPostView.h index a20173e..ce2deff 100644 --- a/tms_express/User_Interfaces/Control_Panels/ControlPanelPostView.h +++ b/tms_express/User_Interfaces/Control_Panels/ControlPanelPostView.h @@ -8,6 +8,8 @@ #include #include +namespace tms_express { + class ControlPanelPostView: public ControlPanelView { Q_OBJECT public: @@ -41,4 +43,6 @@ Q_OBJECT QLineEdit *maxVoicedGainLine; }; +}; // namespace tms_express + #endif //TMS_EXPRESS_CONTROLPANELPOSTVIEW_H diff --git a/tms_express/User_Interfaces/Control_Panels/ControlPanelView.cpp b/tms_express/User_Interfaces/Control_Panels/ControlPanelView.cpp index 0298f3f..bd483f9 100644 --- a/tms_express/User_Interfaces/Control_Panels/ControlPanelView.cpp +++ b/tms_express/User_Interfaces/Control_Panels/ControlPanelView.cpp @@ -10,6 +10,8 @@ #include "User_Interfaces/Control_Panels/ControlPanelView.h" +namespace tms_express { + /// Initialize a new Control Panel View /// /// At minimum, the Control Panel View will have a title with a line below it @@ -33,3 +35,5 @@ ControlPanelView::ControlPanelView(const std::string &panelTitle, QWidget *paren void ControlPanelView::stateChangeSlot() { emit stateChangeSignal(); } + +}; // namespace tms_express diff --git a/tms_express/User_Interfaces/Control_Panels/ControlPanelView.h b/tms_express/User_Interfaces/Control_Panels/ControlPanelView.h index 1efd526..4250e47 100644 --- a/tms_express/User_Interfaces/Control_Panels/ControlPanelView.h +++ b/tms_express/User_Interfaces/Control_Panels/ControlPanelView.h @@ -8,6 +8,8 @@ #include +namespace tms_express { + class ControlPanelView: public QWidget { Q_OBJECT public: @@ -26,4 +28,6 @@ public slots: QGridLayout *grid; }; +}; // namespace tms_express + #endif //TMS_EXPRESS_CONTROLPANELVIEW_H diff --git a/tms_express/User_Interfaces/MainWindow.cpp b/tms_express/User_Interfaces/MainWindow.cpp index 0e7ad1a..560c98a 100644 --- a/tms_express/User_Interfaces/MainWindow.cpp +++ b/tms_express/User_Interfaces/MainWindow.cpp @@ -6,7 +6,7 @@ // Author: Joseph Bellahcen /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#include "Audio/AudioBuffer.h" +#include "Audio/AudioBuffer.hpp" #include "Frame_Encoding/FramePostprocessor.h" #include "LPC_Analysis/Autocorrelation.h" #include "User_Interfaces/Audio_Waveform/AudioWaveformView.h" @@ -24,6 +24,8 @@ #include #include +namespace tms_express { + /// Setup the main window of the application MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { // Set the minimum requirements for window dimensions and margins @@ -137,7 +139,7 @@ void MainWindow::configureUiSlots() { /// Toggle UI elements based on the state of the application void MainWindow::configureUiState() { // Get UI state - auto disableAudioDependentObject = (inputBuffer.isEmpty()); + auto disableAudioDependentObject = (inputBuffer.empty()); auto disableBitstreamDependentObject = frameTable.empty(); // Menu bar @@ -188,11 +190,11 @@ void MainWindow::onOpenFile() { return; } - if (!inputBuffer.isEmpty()) { + if (!inputBuffer.empty()) { inputBuffer = AudioBuffer(); } - if (!lpcBuffer.isEmpty()) { + if (!lpcBuffer.empty()) { lpcBuffer = AudioBuffer(); } @@ -205,8 +207,15 @@ void MainWindow::onOpenFile() { // Enable gain normalization by default //ui->postGainNormalizeEnable->setChecked(true); - inputBuffer = AudioBuffer(filePath.toStdString(), 8000, lpcControl->analysisWindowWidth()); - lpcBuffer = AudioBuffer(filePath.toStdString(), 8000, lpcControl->analysisWindowWidth()); + auto input_buffer_ptr = AudioBuffer::Create(filePath.toStdString(), 8000, lpcControl->analysisWindowWidth()); + + if (input_buffer_ptr == nullptr) { + qDebug() << "NULL"; + return; + } + + inputBuffer = input_buffer_ptr->copy(); + lpcBuffer = input_buffer_ptr->copy(); performPitchAnalysis(); performLpcAnalysis(); @@ -266,7 +275,7 @@ void MainWindow::onExportAudio() { /// Play contents of input buffer void MainWindow::onInputAudioPlay() { - if (inputBuffer.isEmpty()) { + if (inputBuffer.empty()) { qDebug() << "Requested play, but input buffer is empty"; return; } @@ -306,7 +315,7 @@ void MainWindow::onLpcAudioPlay() { // be significant enough to modify the buffer checksum alone char filename[35]; - uint checksum = (!lpcBuffer.isEmpty()) ? samplesChecksum(lpcBuffer.getSamples()) : samplesChecksum(synthesizer.samples()); + uint checksum = (!lpcBuffer.empty()) ? samplesChecksum(lpcBuffer.getSamples()) : samplesChecksum(synthesizer.samples()); snprintf(filename, 35, "tmsexpress_lpc_render_%x.wav", checksum); // Only render audio if this particular buffer does not exist @@ -326,7 +335,7 @@ void MainWindow::onLpcAudioPlay() { void MainWindow::onPitchParamEdit() { configureUiState(); - if (!inputBuffer.isEmpty()) { + if (!inputBuffer.empty()) { performPitchAnalysis(); performLpcAnalysis(); @@ -338,7 +347,7 @@ void MainWindow::onPitchParamEdit() { void MainWindow::onLpcParamEdit() { configureUiState(); - if (!lpcBuffer.isEmpty()) { + if (!lpcBuffer.empty()) { performLpcAnalysis(); performPostProc(); @@ -409,7 +418,7 @@ void MainWindow::performPitchAnalysis() { pitchEstimator.setMaxPeriod(pitchControl->minPitchFrq()); pitchEstimator.setMinPeriod(pitchControl->maxPitchFrq()); - for (const auto &segment : inputBuffer.segments()) { + for (const auto &segment : inputBuffer.getAllSegments()) { auto acf = tms_express::Autocorrelation(segment); auto pitchPeriod = pitchEstimator.estimatePeriod(acf); // TODO: Parameterize @@ -430,9 +439,9 @@ void MainWindow::performLpcAnalysis() { frameTable.clear(); // Re-trigger pitch analysis if window width has changed - if (lpcControl->analysisWindowWidth() != inputBuffer.getWindowWidth()) { - inputBuffer.setWindowWidth(lpcControl->analysisWindowWidth()); - lpcBuffer.setWindowWidth(lpcControl->analysisWindowWidth()); + if (lpcControl->analysisWindowWidth() != inputBuffer.getWindowWidthMs()) { + inputBuffer.setWindowWidthMs(lpcControl->analysisWindowWidth()); + lpcBuffer.setWindowWidthMs(lpcControl->analysisWindowWidth()); qDebug() << "Adjusting window width for pitch and LPC buffers"; performPitchAnalysis(); @@ -451,11 +460,12 @@ void MainWindow::performLpcAnalysis() { if (lpcControl->preemphEnabled()) { qDebug() << "PEF"; + qDebug() << (lpcBuffer.empty()); filter.preEmphasis(lpcBuffer, lpcControl->preemphAlpha()); } - for (int i = 0; i < lpcBuffer.size(); i++) { - auto segment = lpcBuffer.segment(i); + for (int i = 0; i < lpcBuffer.getNSegments(); i++) { + auto segment = lpcBuffer.getSegment(i); auto acf = tms_express::Autocorrelation(segment); auto coeffs = linearPredictor.reflectorCoefficients(acf); @@ -539,3 +549,5 @@ void MainWindow::exportBitstream(const std::string& path) { binOut.write((char *)(bin.data()), long(bin.size())); } } + +}; // namespace tms_express diff --git a/tms_express/User_Interfaces/MainWindow.h b/tms_express/User_Interfaces/MainWindow.h index 385e27b..e14a6d0 100644 --- a/tms_express/User_Interfaces/MainWindow.h +++ b/tms_express/User_Interfaces/MainWindow.h @@ -3,7 +3,7 @@ #ifndef TMS_EXPRESS_MAINWINDOW_H #define TMS_EXPRESS_MAINWINDOW_H -#include "Audio/AudioBuffer.h" +#include "Audio/AudioBuffer.hpp" #include "Audio/AudioFilter.h" #include "Bitstream_Generation/BitstreamGenerator.h" #include "Frame_Encoding/Frame.h" @@ -28,6 +28,8 @@ #define TMS_EXPRESS_WINDOW_MARGINS 5 #define TMS_EXPRESS_AUDIO_SAMPLE_RATE 8000 +namespace tms_express { + class MainWindow : public QMainWindow { Q_OBJECT @@ -108,4 +110,6 @@ public slots: unsigned int samplesChecksum(std::vector samples); }; +}; // namespace tms_express + #endif //TMS_EXPRESS_MAINWINDOW_H diff --git a/tms_express/main.cpp b/tms_express/main.cpp index c589c79..baee6e8 100644 --- a/tms_express/main.cpp +++ b/tms_express/main.cpp @@ -9,7 +9,7 @@ int main(int argc, char **argv) { if (argc == 1) { QApplication app(argc, argv); - MainWindow w; + tms_express::MainWindow w; w.show(); return app.exec(); @@ -17,7 +17,7 @@ int main(int argc, char **argv) { // Otherwise, use command-line interface if (argc > 1) { - CommandLineApp cli = CommandLineApp(); + auto cli = tms_express::CommandLineApp(); int status = cli.run(argc, argv); exit(status); } From b693f43874a1ca5875539484eabf2af080d823f6 Mon Sep 17 00:00:00 2001 From: Joseph Bellahcen Date: Fri, 21 Jul 2023 17:13:18 -0700 Subject: [PATCH 04/30] Refactored AudioFilter --- tms_express/Audio/AudioBuffer.hpp | 17 +++-- tms_express/Audio/AudioFilter.cpp | 75 +++++-------------- tms_express/Audio/AudioFilter.h | 33 -------- tms_express/Audio/AudioFilter.hpp | 61 +++++++++++++++ .../BitstreamGenerator.cpp | 10 +-- tms_express/User_Interfaces/MainWindow.cpp | 12 +-- tms_express/User_Interfaces/MainWindow.h | 2 +- 7 files changed, 103 insertions(+), 107 deletions(-) delete mode 100644 tms_express/Audio/AudioFilter.h create mode 100644 tms_express/Audio/AudioFilter.hpp diff --git a/tms_express/Audio/AudioBuffer.hpp b/tms_express/Audio/AudioBuffer.hpp index b08e408..5fee425 100644 --- a/tms_express/Audio/AudioBuffer.hpp +++ b/tms_express/Audio/AudioBuffer.hpp @@ -112,13 +112,6 @@ class AudioBuffer { [[deprecated]] void reset(); private: - int n_segments_; - int n_samples_per_segment_; - int sample_rate_hz_; - std::vector samples_; - - std::vector original_samples_; - /////////////////////////////////////////////////////////////////////////// // Static Initialization Utilities //////////////////////////////////////// /////////////////////////////////////////////////////////////////////////// @@ -137,6 +130,16 @@ class AudioBuffer { /// @return Resampled vectors at the target sample rate static std::vector resample(std::vector samples, int src_sample_rate_hz, int target_sample_rate_hz); + + /////////////////////////////////////////////////////////////////////////// + // Members //////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + int n_segments_; + int n_samples_per_segment_; + int sample_rate_hz_; + std::vector samples_; + std::vector original_samples_; }; }; // namespace tms_express diff --git a/tms_express/Audio/AudioFilter.cpp b/tms_express/Audio/AudioFilter.cpp index 9c4c52b..d8f983a 100644 --- a/tms_express/Audio/AudioFilter.cpp +++ b/tms_express/Audio/AudioFilter.cpp @@ -12,71 +12,44 @@ // https://webaudio.github.io/Audio-EQ-Cookbook/audio-eq-cookbook.html /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#include "Audio/AudioFilter.h" +#include "Audio/AudioFilter.hpp" + #include #include namespace tms_express { -AudioFilter::AudioFilter() = default; - -/// Apply Hamming window to buffer -/// -/// \param buffer Buffer on which to apply Hamming window -void AudioFilter::hammingWindow(AudioBuffer &buffer) { +void AudioFilter::applyHammingWindow(AudioBuffer &buffer) { for (auto &segment : buffer.getAllSegments()) { - hammingWindow(segment); + applyHammingWindow(segment); } } -/// Apply Hamming window to audio segment -/// -/// \note Because speech segments in isolation cannot convey information about the transition between each other, -/// a windowing function is necessary to smooth their boundaries. This is accomplished by "smearing" the -/// spectrum of the segments by a known alpha value. For the Hamming Window, which is preferred for speech -/// signal analysis, this smearing coefficient alpha=0.54 -/// -/// \param segment Segment on which to apply Hamming window -void AudioFilter::hammingWindow(std::vector &segment) { +void AudioFilter::applyHammingWindow(std::vector &segment) { auto size = segment.size(); for (int i = 0; i < size; i++) { - float theta = 2.0f * float(M_PI) * float(i) / float(size); - // TODO: Make smearing coefficient alpha configurable + float theta = 2.0f * M_PI * i / size; + // TODO(Joseph Bellahcen): Make smearing coefficient alpha configurable float window = 0.54f - 0.46f * cosf(theta); segment[i] *= window; } } -/// Apply time-domain highpass filter to buffer -/// -/// \param buffer Buffer to filter -/// \param cutoffHz Highpass filter cutoff (in Hertz) -void AudioFilter::highpass(AudioBuffer &buffer, int cutoffHz) { - computeCoeffs(HPF, cutoffHz); +void AudioFilter::applyHighpass(AudioBuffer &buffer, int cutoff_hz) { + computeCoeffs(HPF, cutoff_hz); applyBiquad(buffer); } -/// Apply time-domain lowpass filter to buffer -/// -/// \param buffer Buffer to filter -/// \param cutoffHz Lowpass filter cutoff (in Hertz) -void AudioFilter::lowpass(AudioBuffer &buffer, int cutoffHz) { - computeCoeffs(LPF, cutoffHz); +void AudioFilter::applyLowpass(AudioBuffer &buffer, int cutoff_hz) { + computeCoeffs(LPF, cutoff_hz); applyBiquad(buffer); } -/// Apply a time-domain pre-emphasis filter to the samples -/// -/// \note During LPC analysis, a pre-emphasis filter balances the signal spectrum by augmenting high-frequencies. -/// This is undesirable for pitch estimation, but may improve the accuracy of linear prediction -/// -/// \param buffer Buffer to filter -/// \param alpha Pre-emphasis coefficient (typically, alpha = 15/16) -void AudioFilter::preEmphasis(AudioBuffer &buffer, float alpha) { +void AudioFilter::applyPreEmphasis(AudioBuffer &buffer, float alpha) { // Initialize filtered buffer - std::vector samples = buffer.getSamples(); - std::vector filteredSamples = std::vector(); + auto samples = buffer.getSamples(); + auto filteredSamples = std::vector(); float previous = samples[0]; filteredSamples.push_back(previous); @@ -84,17 +57,14 @@ void AudioFilter::preEmphasis(AudioBuffer &buffer, float alpha) { // Apply filter // y(t) = x(t) - a * x(t-1) for (int i = 1; i < samples.size(); i++) { - float preEmphasized = samples[i] - alpha * samples[i - 1]; - filteredSamples.push_back(preEmphasized); + auto new_sample = samples[i] - alpha * samples[i - 1]; + filteredSamples.push_back(new_sample); } // Store filtered samples buffer.setSamples(filteredSamples); } -/// Apply biquad filter coefficients to buffer -/// -/// \param buffer Buffer to filter void AudioFilter::applyBiquad(AudioBuffer &buffer) { // Rename coefficients for readability float k0 = coeffs[0]; @@ -111,7 +81,8 @@ void AudioFilter::applyBiquad(AudioBuffer &buffer) { // Apply filter for (float &sample : samples) { - float result = (k0 * sample) + (k1 * x1) + (k2 * x2) - (k3 * y1) - (k4 * y2); + float result = (k0 * sample) + (k1 * x1) + + (k2 * x2) - (k3 * y1)- (k4 * y2); result /= normalizationCoeff; x2 = x1; @@ -126,15 +97,9 @@ void AudioFilter::applyBiquad(AudioBuffer &buffer) { buffer.setSamples(samples); } -/// Determine the biquadratic filter coefficients -/// -/// \param coeffs Destination array (5-wide) to store coefficients -/// \param mode Filter mode (HPF or LPF) -/// \param cutoffHz Filter cutoff (in Hertz) -/// \return Normalization coefficient -void AudioFilter::computeCoeffs(AudioFilter::FilterMode mode, int cutoffHz) { +void AudioFilter::computeCoeffs(AudioFilter::FilterMode mode, int cutoff_hz) { // Filter-agnostic parameters - float omega = 2.0f * float(M_PI) * float(cutoffHz) / 8000.0f; + float omega = 2.0f * M_PI * cutoff_hz / 8000.0f; float cs = cosf(omega); float sn = sinf(omega); float alpha = sn / (2.0f * 0.707f); diff --git a/tms_express/Audio/AudioFilter.h b/tms_express/Audio/AudioFilter.h deleted file mode 100644 index 49a584a..0000000 --- a/tms_express/Audio/AudioFilter.h +++ /dev/null @@ -1,33 +0,0 @@ -// Author: Joseph Bellahcen - -#ifndef TMS_EXPRESS_AUDIOFILTER_H -#define TMS_EXPRESS_AUDIOFILTER_H - -#include "AudioBuffer.hpp" -#include -#include -#include - -namespace tms_express { - -class AudioFilter { -public: - AudioFilter(); - - void hammingWindow(AudioBuffer &buffer); - void hammingWindow(std::vector &segment); - void highpass(AudioBuffer &buffer, int cutoffHz); - void lowpass(AudioBuffer &buffer, int cutoffHz); - void preEmphasis(AudioBuffer &buffer, float alpha = 0.9375); - -private: - typedef enum FilterMode {HPF, LPF} FilterMode; - std::array coeffs{0, 0, 0, 0, 0, 0}; - - void applyBiquad(AudioBuffer &buffer); - void computeCoeffs(FilterMode mode, int cutoffHz); -}; - -}; // namespace tms_express - -#endif //TMS_EXPRESS_AUDIOFILTER_H diff --git a/tms_express/Audio/AudioFilter.hpp b/tms_express/Audio/AudioFilter.hpp new file mode 100644 index 0000000..6f7ad09 --- /dev/null +++ b/tms_express/Audio/AudioFilter.hpp @@ -0,0 +1,61 @@ +// Copyright (C) 2023 Joseph Bellahcen + +#ifndef TMS_EXPRESS_AUDIO_AUDIOFILTER_HPP_ +#define TMS_EXPRESS_AUDIO_AUDIOFILTER_HPP_ + +#include +#include +#include + +#include "Audio/AudioBuffer.hpp" + +namespace tms_express { + +class AudioFilter { + public: + AudioFilter() = default; + + /// @brief Applies Hamming window to entire buffer + /// @param buffer Audio Buffer to apply window to + void applyHammingWindow(AudioBuffer &buffer); + + /// @brief Applies Hamming window to segment of samples + /// @param buffer Segment vector to apply window to + void applyHammingWindow(std::vector &segment); + + /// @brief Applies highpass filter to entire buffer + /// @param buffer Audio Buffer to apply filter to + /// @param cutoffHz Highpass cutoff frequency, in Hertz + void applyHighpass(AudioBuffer &buffer, int cutoff_hz); + + /// @brief Applies lowpass filter to entire buffer + /// @param buffer Audio Buffer to apply filter to + /// @param cutoffHz Lowpass cutoff frequency, in Hertz + void applyLowpass(AudioBuffer &buffer, int cutoff_hz); + + /// @brief Applies pre-emphasis filter to entire buffer + /// @param buffer Audio Buffer to apply filter to + /// @param alpha Pre-emphasis coefficient (usually 0.9375) + void applyPreEmphasis(AudioBuffer &buffer, float alpha = 0.9375); + + private: + /// @brief Last-used filter mode + /// @note The Filter Mode is stored to prevent unnecessary re-calculation + /// of filter coefficients + typedef enum FilterMode {HPF, LPF} FilterMode; + + /// @brief Bi-quadratic filter coefficients + std::array coeffs{0, 0, 0, 0, 0, 0}; + + /// @brief Applies bi-quadratic filter coefficients to entire Buffer + /// @param buffer Audio Buffer to apply filter to + void applyBiquad(AudioBuffer &buffer); + + /// @brief Computes bi-quadratic filter coefficients for a highpass or + /// lowpass filter + void computeCoeffs(FilterMode mode, int cutoff_hz); +}; + +}; // namespace tms_express + +#endif // TMS_EXPRESS_AUDIO_AUDIOFILTER_HPP_ diff --git a/tms_express/Bitstream_Generation/BitstreamGenerator.cpp b/tms_express/Bitstream_Generation/BitstreamGenerator.cpp index 760cb4a..36409ca 100644 --- a/tms_express/Bitstream_Generation/BitstreamGenerator.cpp +++ b/tms_express/Bitstream_Generation/BitstreamGenerator.cpp @@ -9,7 +9,7 @@ /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #include "Audio/AudioBuffer.hpp" -#include "Audio/AudioFilter.h" +#include "Audio/AudioFilter.hpp" #include "Bitstream_Generation/BitstreamGenerator.h" #include "Frame_Encoding/Frame.h" #include "Frame_Encoding/FrameEncoder.h" @@ -95,9 +95,9 @@ std::vector BitstreamGenerator::generateFrames(const std::string &inputPa // The pitch buffer will ONLY be lowpass-filtered, as pitch is a low-frequency component of the signal. Neither // highpass filtering nor preemphasis, which exaggerate high-frequency components, will improve pitch estimation auto preprocessor = AudioFilter(); - preprocessor.preEmphasis(lpcBuffer, preemphasisAlpha); - preprocessor.highpass(lpcBuffer, highpassHz); - preprocessor.lowpass(pitchBuffer, lowpassHz); + preprocessor.applyPreEmphasis(lpcBuffer, preemphasisAlpha); + preprocessor.applyHighpass(lpcBuffer, highpassHz); + preprocessor.applyLowpass(pitchBuffer, lowpassHz); // Extract buffer metadata // @@ -121,7 +121,7 @@ std::vector BitstreamGenerator::generateFrames(const std::string &inputPa // // Because information about the transition between adjacent frames is lost during segmentation, a window will // help produce smoother results - preprocessor.hammingWindow(lpcSegment); + preprocessor.applyHammingWindow(lpcSegment); // Compute the autocorrelation of each segment, which serves as the basis of all analysis auto lpcAcf = tms_express::Autocorrelation(lpcSegment); diff --git a/tms_express/User_Interfaces/MainWindow.cpp b/tms_express/User_Interfaces/MainWindow.cpp index 560c98a..f8a1b5a 100644 --- a/tms_express/User_Interfaces/MainWindow.cpp +++ b/tms_express/User_Interfaces/MainWindow.cpp @@ -404,15 +404,15 @@ void MainWindow::performPitchAnalysis() { // Pre-process if (pitchControl->hpfEnabled()) { - filter.highpass(inputBuffer, pitchControl->hpfCutoff()); + filter.applyHighpass(inputBuffer, pitchControl->hpfCutoff()); } if (pitchControl->lpfEnabled()) { - filter.lowpass(inputBuffer, pitchControl->lpfCutoff()); + filter.applyLowpass(inputBuffer, pitchControl->lpfCutoff()); } if (pitchControl->preemphEnabled()) { - filter.preEmphasis(inputBuffer, pitchControl->preemphAlpha()); + filter.applyPreEmphasis(inputBuffer, pitchControl->preemphAlpha()); } pitchEstimator.setMaxPeriod(pitchControl->minPitchFrq()); @@ -450,18 +450,18 @@ void MainWindow::performLpcAnalysis() { // Pre-process if (lpcControl->hpfEnabled()) { qDebug() << "HPF"; - filter.highpass(lpcBuffer, lpcControl->hpfCutoff()); + filter.applyHighpass(lpcBuffer, lpcControl->hpfCutoff()); } if (lpcControl->lpfEnabled()) { qDebug() << "LPF"; - filter.lowpass(lpcBuffer, lpcControl->lpfCutoff()); + filter.applyLowpass(lpcBuffer, lpcControl->lpfCutoff()); } if (lpcControl->preemphEnabled()) { qDebug() << "PEF"; qDebug() << (lpcBuffer.empty()); - filter.preEmphasis(lpcBuffer, lpcControl->preemphAlpha()); + filter.applyPreEmphasis(lpcBuffer, lpcControl->preemphAlpha()); } for (int i = 0; i < lpcBuffer.getNSegments(); i++) { diff --git a/tms_express/User_Interfaces/MainWindow.h b/tms_express/User_Interfaces/MainWindow.h index e14a6d0..29c2f72 100644 --- a/tms_express/User_Interfaces/MainWindow.h +++ b/tms_express/User_Interfaces/MainWindow.h @@ -4,7 +4,7 @@ #define TMS_EXPRESS_MAINWINDOW_H #include "Audio/AudioBuffer.hpp" -#include "Audio/AudioFilter.h" +#include "Audio/AudioFilter.hpp" #include "Bitstream_Generation/BitstreamGenerator.h" #include "Frame_Encoding/Frame.h" #include "Frame_Encoding/FrameEncoder.h" From 43636d822a114b48c8413c700b1c621942d3a432 Mon Sep 17 00:00:00 2001 From: Joseph Bellahcen Date: Fri, 21 Jul 2023 17:24:46 -0700 Subject: [PATCH 05/30] Replaced AudioFilter source header with copyright --- tms_express/Audio/AudioFilter.cpp | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/tms_express/Audio/AudioFilter.cpp b/tms_express/Audio/AudioFilter.cpp index d8f983a..cd20d6f 100644 --- a/tms_express/Audio/AudioFilter.cpp +++ b/tms_express/Audio/AudioFilter.cpp @@ -1,16 +1,4 @@ -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// Class: AudioFilter -// -// Description: The Filterer applies filters to audio data to reduce the impact of unnecessary frequency components -// on speech signal analysis. The class implements biquadratic highpass and lowpass filters, as well as -// a pre-emphasis filter. All filters operate in the time-domain -// -// Author: Joseph Bellahcen -// -// Acknowledgement: The bi-quadratic filter algorithms come from Robert Bristow-Johnson . -// Additional information about digital biquad filters may be found at -// https://webaudio.github.io/Audio-EQ-Cookbook/audio-eq-cookbook.html -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2023 Joseph Bellahcen #include "Audio/AudioFilter.hpp" @@ -98,6 +86,11 @@ void AudioFilter::applyBiquad(AudioBuffer &buffer) { } void AudioFilter::computeCoeffs(AudioFilter::FilterMode mode, int cutoff_hz) { + // Acknowledgement: The bi-quadratic filter algorithms come from + // Robert Bristow-Johnson . + // Additional information about digital biquad filters may be found at + // https://webaudio.github.io/Audio-EQ-Cookbook/audio-eq-cookbook.html + // Filter-agnostic parameters float omega = 2.0f * M_PI * cutoff_hz / 8000.0f; float cs = cosf(omega); From c90b831579dcb994cacdf0f136861efef505f23a Mon Sep 17 00:00:00 2001 From: Joseph Bellahcen Date: Fri, 21 Jul 2023 17:31:37 -0700 Subject: [PATCH 06/30] Removed trivial AudioFilter constructor --- tms_express/Audio/AudioFilter.hpp | 3 +-- .../{BitstreamGenerator.h => BitstreamGenerator.hpp} | 0 2 files changed, 1 insertion(+), 2 deletions(-) rename tms_express/Bitstream_Generation/{BitstreamGenerator.h => BitstreamGenerator.hpp} (100%) diff --git a/tms_express/Audio/AudioFilter.hpp b/tms_express/Audio/AudioFilter.hpp index 6f7ad09..849c091 100644 --- a/tms_express/Audio/AudioFilter.hpp +++ b/tms_express/Audio/AudioFilter.hpp @@ -11,10 +11,9 @@ namespace tms_express { +/// @brief Implements various digital filters for processing audio samples class AudioFilter { public: - AudioFilter() = default; - /// @brief Applies Hamming window to entire buffer /// @param buffer Audio Buffer to apply window to void applyHammingWindow(AudioBuffer &buffer); diff --git a/tms_express/Bitstream_Generation/BitstreamGenerator.h b/tms_express/Bitstream_Generation/BitstreamGenerator.hpp similarity index 100% rename from tms_express/Bitstream_Generation/BitstreamGenerator.h rename to tms_express/Bitstream_Generation/BitstreamGenerator.hpp From 8995dbf2cff3bd0bc3b262ac82e03399e6c0d6fb Mon Sep 17 00:00:00 2001 From: Joseph Bellahcen Date: Fri, 21 Jul 2023 18:42:29 -0700 Subject: [PATCH 07/30] Refactored BitstreamGenerator --- CMakeLists.txt | 1 - tms_express/Audio/AudioFilter.cpp | 30 +-- tms_express/Audio/AudioFilter.hpp | 10 +- .../BitstreamGenerator.cpp | 210 ++++++++++-------- .../BitstreamGenerator.hpp | 126 ++++++++--- .../User_Interfaces/CommandLineApp.cpp | 2 +- tms_express/User_Interfaces/CommandLineApp.h | 2 +- tms_express/User_Interfaces/MainWindow.h | 2 +- 8 files changed, 233 insertions(+), 150 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e862bf0..f48b908 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,7 +3,6 @@ cmake_minimum_required(VERSION 3.14) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) -add_compile_options(-Wall -Wextra) # Run Qt's moc, rcc, and uic tools set(CMAKE_AUTOUIC ON) diff --git a/tms_express/Audio/AudioFilter.cpp b/tms_express/Audio/AudioFilter.cpp index cd20d6f..e58e07c 100644 --- a/tms_express/Audio/AudioFilter.cpp +++ b/tms_express/Audio/AudioFilter.cpp @@ -7,13 +7,13 @@ namespace tms_express { -void AudioFilter::applyHammingWindow(AudioBuffer &buffer) { +void AudioFilter::applyHammingWindow(AudioBuffer &buffer) const { for (auto &segment : buffer.getAllSegments()) { applyHammingWindow(segment); } } -void AudioFilter::applyHammingWindow(std::vector &segment) { +void AudioFilter::applyHammingWindow(std::vector &segment) const { auto size = segment.size(); for (int i = 0; i < size; i++) { @@ -34,7 +34,7 @@ void AudioFilter::applyLowpass(AudioBuffer &buffer, int cutoff_hz) { applyBiquad(buffer); } -void AudioFilter::applyPreEmphasis(AudioBuffer &buffer, float alpha) { +void AudioFilter::applyPreEmphasis(AudioBuffer &buffer, float alpha) const { // Initialize filtered buffer auto samples = buffer.getSamples(); auto filteredSamples = std::vector(); @@ -55,12 +55,12 @@ void AudioFilter::applyPreEmphasis(AudioBuffer &buffer, float alpha) { void AudioFilter::applyBiquad(AudioBuffer &buffer) { // Rename coefficients for readability - float k0 = coeffs[0]; - float k1 = coeffs[1]; - float k2 = coeffs[2]; - float k3 = coeffs[3]; - float k4 = coeffs[4]; - float normalizationCoeff = coeffs[5]; + float k0 = coeffs_[0]; + float k1 = coeffs_[1]; + float k2 = coeffs_[2]; + float k3 = coeffs_[3]; + float k4 = coeffs_[4]; + float normalizationCoeff = coeffs_[5]; // Initialize filtered buffer std::vector samples = buffer.getSamples(); @@ -124,14 +124,14 @@ void AudioFilter::computeCoeffs(AudioFilter::FilterMode mode, int cutoff_hz) { } // Filter coefficients - coeffs[0] = bCoeff[0]; - coeffs[1] = bCoeff[1]; - coeffs[2] = bCoeff[2]; - coeffs[3] = aCoeff[1]; - coeffs[4] = aCoeff[2]; + coeffs_[0] = bCoeff[0]; + coeffs_[1] = bCoeff[1]; + coeffs_[2] = bCoeff[2]; + coeffs_[3] = aCoeff[1]; + coeffs_[4] = aCoeff[2]; // Normalization coefficient - coeffs[5] = aCoeff[0]; + coeffs_[5] = aCoeff[0]; } }; // namespace tms_express diff --git a/tms_express/Audio/AudioFilter.hpp b/tms_express/Audio/AudioFilter.hpp index 849c091..fc5f6bb 100644 --- a/tms_express/Audio/AudioFilter.hpp +++ b/tms_express/Audio/AudioFilter.hpp @@ -16,11 +16,11 @@ class AudioFilter { public: /// @brief Applies Hamming window to entire buffer /// @param buffer Audio Buffer to apply window to - void applyHammingWindow(AudioBuffer &buffer); + void applyHammingWindow(AudioBuffer &buffer) const; /// @brief Applies Hamming window to segment of samples /// @param buffer Segment vector to apply window to - void applyHammingWindow(std::vector &segment); + void applyHammingWindow(std::vector &segment) const; /// @brief Applies highpass filter to entire buffer /// @param buffer Audio Buffer to apply filter to @@ -35,16 +35,16 @@ class AudioFilter { /// @brief Applies pre-emphasis filter to entire buffer /// @param buffer Audio Buffer to apply filter to /// @param alpha Pre-emphasis coefficient (usually 0.9375) - void applyPreEmphasis(AudioBuffer &buffer, float alpha = 0.9375); + void applyPreEmphasis(AudioBuffer &buffer, float alpha = 0.9375) const; private: /// @brief Last-used filter mode /// @note The Filter Mode is stored to prevent unnecessary re-calculation /// of filter coefficients - typedef enum FilterMode {HPF, LPF} FilterMode; + enum FilterMode {HPF, LPF}; /// @brief Bi-quadratic filter coefficients - std::array coeffs{0, 0, 0, 0, 0, 0}; + std::array coeffs_{0, 0, 0, 0, 0, 0}; /// @brief Applies bi-quadratic filter coefficients to entire Buffer /// @param buffer Audio Buffer to apply filter to diff --git a/tms_express/Bitstream_Generation/BitstreamGenerator.cpp b/tms_express/Bitstream_Generation/BitstreamGenerator.cpp index 36409ca..2bc2698 100644 --- a/tms_express/Bitstream_Generation/BitstreamGenerator.cpp +++ b/tms_express/Bitstream_Generation/BitstreamGenerator.cpp @@ -1,16 +1,15 @@ -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// Class: BitstreamGenerator -// -// Description: The BitstreamGenerator encapsulates the core functionality of TMS Express in a user-accessible -// interface. Given an audio file or directory thereof, it will perform LPC analysis and create a bitstream -// file in the desired format -// -// Author: Joseph Bellahcen -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2023 Joseph Bellahcen + +#include "Bitstream_Generation/BitstreamGenerator.hpp" + +#include +#include +#include +#include +#include #include "Audio/AudioBuffer.hpp" #include "Audio/AudioFilter.hpp" -#include "Bitstream_Generation/BitstreamGenerator.h" #include "Frame_Encoding/Frame.h" #include "Frame_Encoding/FrameEncoder.h" #include "Frame_Encoding/FramePostprocessor.h" @@ -18,62 +17,70 @@ #include "LPC_Analysis/LinearPredictor.h" #include "LPC_Analysis/PitchEstimator.h" -#include -#include -#include -#include -#include - namespace tms_express { -BitstreamGenerator::BitstreamGenerator(float windowMs, int highpassHz, int lowpassHz, float preemphasis, EncoderStyle style, - bool includeStopFrame, int gainShift, float maxVoicedDb, float maxUnvoicedDb, bool detectRepeats, - int maxHz, int minHz) : windowMs(windowMs), highpassHz(highpassHz), lowpassHz(lowpassHz), - preemphasisAlpha(preemphasis), style(style), - includeStopFrame(includeStopFrame), gainShift(gainShift), - maxVoicedDB(maxVoicedDb), maxUnvoicedDB(maxUnvoicedDb), - detectRepeats(detectRepeats), maxHz(maxHz), minHz(minHz) {} - -// Encode a single audio file -void BitstreamGenerator::encode(const std::string &inputPath, const std::string &inputFilename, - const std::string &outputPath) { +BitstreamGenerator::BitstreamGenerator(float window_width_ms, + int highpass_cutoff_hz, int lowpass_cutoff_hz, float pre_emphasis_alpha, + EncoderStyle style, bool include_stop_frame, int gain_shift, + float max_voiced_gain_db, float max_unvoiced_gain_db, + bool detect_repeat_frames, int max_pitch_hz, int min_pitch_hz) { + + window_width_ms_ = window_width_ms; + highpass_cutoff_hz_ = highpass_cutoff_hz; + lowpass_cutoff_hz_ = lowpass_cutoff_hz; + pre_emphasis_alpha_ = pre_emphasis_alpha; + style_ = style; + include_stop_frame_ = include_stop_frame; + gain_shift_ = gain_shift; + main_voiced_gain_db_ = max_voiced_gain_db; + max_unvoiced_gain_db_ = max_unvoiced_gain_db; + detect_repeat_frames_ = detect_repeat_frames; + max_pitch_hz_ = max_pitch_hz; + min_pitch_hz_ = min_pitch_hz; +} + +void BitstreamGenerator::encode(const std::string &audio_input_path, + const std::string &bitstream_name, const std::string &output_path) const { // Perform LPC analysis and convert audio data to a bitstream - auto frames = generateFrames(inputPath); - auto bitstream = formatBitstream(frames, inputFilename); + auto frames = generateFrames(audio_input_path); + auto bitstream = serializeFrames(frames, bitstream_name); // Write bitstream to disk std::ofstream lpcOut; - lpcOut.open(outputPath); + lpcOut.open(output_path); lpcOut << bitstream; lpcOut.close(); } -// Encode a batch job into either a single file or a collection of files -void BitstreamGenerator::encodeBatch(const std::vector &inputPaths, - const std::vector &inputFilenames, const std::string &outputPath) { - if (style == ENCODERSTYLE_ASCII) { +void BitstreamGenerator::encodeBatch( + const std::vector &audio_input_paths, + const std::vector &bitstream_names, + const std::string &output_path) const { + std::string in_path, filename; + + if (style_ == ENCODERSTYLE_ASCII) { // Create directory to populate with encoded files - std::filesystem::create_directory(outputPath); + std::filesystem::create_directory(output_path); - for (int i = 0; i < inputPaths.size(); i++) { - const auto& inPath = inputPaths[i]; - const auto& filename = inputFilenames[i]; + for (int i = 0; i < audio_input_paths.size(); i++) { + in_path = audio_input_paths[i]; + filename = bitstream_names[i]; - std::filesystem::path outPath = outputPath; - outPath /= (filename + ".lpc"); + std::filesystem::path out_path = output_path; + out_path /= (filename + ".lpc"); - encode(inPath, filename, outPath.string()); + encode(in_path, filename, out_path.string()); } } else { std::ofstream lpcOut; - lpcOut.open(outputPath); + lpcOut.open(output_path); - for (int i = 0; i < inputPaths.size(); i++) { - const auto& inPath = inputPaths[i]; - const auto& filename = inputFilenames[i]; + for (int i = 0; i < audio_input_paths.size(); i++) { + in_path = audio_input_paths[i]; + filename = bitstream_names[i]; - auto frames = generateFrames(inPath); - auto bitstream = formatBitstream(frames, filename); + auto frames = generateFrames(in_path); + auto bitstream = serializeFrames(frames, filename); lpcOut << bitstream << std::endl; } @@ -82,101 +89,114 @@ void BitstreamGenerator::encodeBatch(const std::vector &inputPaths, } } -// Generate a bitstream from a single audio file -std::vector BitstreamGenerator::generateFrames(const std::string &inputPath) const { +std::vector BitstreamGenerator::generateFrames( + const std::string &path) const { // Mix audio to 8kHz mono and store in a segmented buffer - auto lpcBuffer = *AudioBuffer::Create(inputPath, 8000, windowMs); + // TODO(Joseph Bellahcen): Handle nullptr + auto lpc_buffer = *AudioBuffer::Create(path, 8000, window_width_ms_); - // Copy the buffer so that upper and lower vocal tract analysis may occur separately - auto pitchBuffer = AudioBuffer(lpcBuffer); + // Copy the buffer so that upper and lower vocal tract analysis may occur + // separately + auto pitch_buffer = lpc_buffer.copy(); - // Preprocess the buffers + // Apply preprocessing // - // The pitch buffer will ONLY be lowpass-filtered, as pitch is a low-frequency component of the signal. Neither - // highpass filtering nor preemphasis, which exaggerate high-frequency components, will improve pitch estimation + // The pitch buffer will ONLY be lowpass-filtered, as pitch is a + // low-frequency component of the signal. Neither highpass filtering nor + // pre-emphasis, which exaggerate high-frequency components, will improve + // pitch estimation auto preprocessor = AudioFilter(); - preprocessor.applyPreEmphasis(lpcBuffer, preemphasisAlpha); - preprocessor.applyHighpass(lpcBuffer, highpassHz); - preprocessor.applyLowpass(pitchBuffer, lowpassHz); + preprocessor.applyPreEmphasis(lpc_buffer, pre_emphasis_alpha_); + preprocessor.applyHighpass(lpc_buffer, highpass_cutoff_hz_); + preprocessor.applyLowpass(pitch_buffer, lowpass_cutoff_hz_); // Extract buffer metadata // - // Only the LPC buffer is queried for metadata, since it will have the same number of samples as the pitch buffer. - // The sample rate of the buffer is extracted despite being known, as future iterations of TMS Express may support - // encoding 10kHz/variable sample rate audio for the TMS5200C - auto nSegments = lpcBuffer.getNSegments(); - auto sampleRate = lpcBuffer.getSampleRateHz(); + // Only the LPC buffer is queried for metadata, since it will have the same + // number of samples as the pitch buffer. The sample rate of the buffer is + // extracted despite being known, as future iterations of TMS Express may + // support encoding 10kHz/variable sample rate audio for the TMS5200C + auto n_segments = lpc_buffer.getNSegments(); + auto sample_rate = lpc_buffer.getSampleRateHz(); // Initialize analysis objects and data structures auto linearPredictor = LinearPredictor(); - auto pitchEstimator = PitchEstimator(int(sampleRate), minHz, maxHz); + auto pitchEstimator = PitchEstimator(sample_rate, min_pitch_hz_, + max_pitch_hz_); auto frames = std::vector(); - for (int i = 0; i < nSegments; i++) { + for (int i = 0; i < n_segments; i++) { // Get segment for frame - auto pitchSegment = pitchBuffer.getSegment(i); - auto lpcSegment = lpcBuffer.getSegment(i); + auto pitch_segment = pitch_buffer.getSegment(i); + auto lpc_segment = lpc_buffer.getSegment(i); // Apply a window function to the segment to smoothen its boundaries // - // Because information about the transition between adjacent frames is lost during segmentation, a window will - // help produce smoother results - preprocessor.applyHammingWindow(lpcSegment); + // Because information about the transition between adjacent frames is + // lost during segmentation, a window will help produce smoother results + preprocessor.applyHammingWindow(lpc_segment); - // Compute the autocorrelation of each segment, which serves as the basis of all analysis - auto lpcAcf = tms_express::Autocorrelation(lpcSegment); - auto pitchAcf = tms_express::Autocorrelation(pitchSegment); + // Compute the autocorrelation of each segment, which serves as the + // basis of all analysis + auto lpc_acf = tms_express::Autocorrelation(lpc_segment); + auto pitch_acf = tms_express::Autocorrelation(pitch_segment); // Extract LPC reflector coefficients and compute the predictor gain - auto coeffs = linearPredictor.reflectorCoefficients(lpcAcf); + auto coeffs = linearPredictor.reflectorCoefficients(lpc_acf); auto gain = linearPredictor.gain(); // Estimate pitch - auto pitchPeriod = pitchEstimator.estimatePeriod(pitchAcf); + auto pitch_period = pitchEstimator.estimatePeriod(pitch_acf); // Decide whether the segment is voiced or unvoiced - auto segmentIsVoiced = coeffs[0] < 0; + auto segment_is_voiced = coeffs[0] < 0; // Store parameters in a Frame object - auto frame = Frame(pitchPeriod, segmentIsVoiced, gain, coeffs); + auto frame = Frame(pitch_period, segment_is_voiced, gain, coeffs); frames.push_back(frame); } - // Apply preprocessing - auto postProcessor = FramePostprocessor(&frames, maxVoicedDB, maxUnvoicedDB); - postProcessor.normalizeGain(); - postProcessor.shiftGain(gainShift); + // Apply post-processing + auto post_processor = FramePostprocessor(&frames, main_voiced_gain_db_, + max_unvoiced_gain_db_); + post_processor.normalizeGain(); + post_processor.shiftGain(gain_shift_); - if (detectRepeats) { - postProcessor.detectRepeatFrames(); + if (detect_repeat_frames_) { + post_processor.detectRepeatFrames(); } return frames; } -// Format raw bitstream for use in various contexts such as C headers -std::string BitstreamGenerator::formatBitstream(const std::vector& frames, const std::string &filename) { +std::string BitstreamGenerator::serializeFrames( + const std::vector& frames, const std::string &filename) const { // Encode frames to hex bitstreams - auto encoder = FrameEncoder(frames, style != ENCODERSTYLE_ASCII, ','); + auto encoder = FrameEncoder(frames, style_ != ENCODERSTYLE_ASCII, ','); std::string bitstream; - // Either export the bitstream as a string for testing or as a C array for embedded development - switch(style) { + switch (style_) { case ENCODERSTYLE_ASCII: - bitstream = encoder.toHex(includeStopFrame); + bitstream = encoder.toHex(include_stop_frame_); break; + case ENCODERSTYLE_C: - // C-style bitstreams are headers which contain an integer array of bitstream values + // C-style bitstreams are headers which contain a byte array // Format: const int bitstream_name [] = {}; - bitstream = encoder.toHex(includeStopFrame); + bitstream = encoder.toHex(include_stop_frame_); bitstream = "const int " + filename + "[] = {" + bitstream + "};\n"; break; + case ENCODERSTYLE_ARDUINO: - // Arduino-style bitstreams are C-style bitstreams which include the Arduino header and PROGMEM keyword - // Format: extern const uint8_t bitstream_name [] PROGMEM = {}; - bitstream = encoder.toHex(includeStopFrame); - bitstream = "extern const uint8_t " + filename + "[] PROGMEM = {" + bitstream + "};\n"; + // Arduino-style bitstreams are C-style bitstreams which include the + // Arduino header and PROGMEM keyword. This is for compatibility + // with the Arduino Talkie library + // Format: extern const uint8_t name [] PROGMEM = {}; + bitstream = encoder.toHex(include_stop_frame_); + bitstream = "extern const uint8_t " + filename + "[] PROGMEM = {" + + bitstream + "};\n"; break; + case ENCODERSTYLE_JSON: bitstream = encoder.toJSON(); break; diff --git a/tms_express/Bitstream_Generation/BitstreamGenerator.hpp b/tms_express/Bitstream_Generation/BitstreamGenerator.hpp index 3bc2867..1f12746 100644 --- a/tms_express/Bitstream_Generation/BitstreamGenerator.hpp +++ b/tms_express/Bitstream_Generation/BitstreamGenerator.hpp @@ -1,42 +1,106 @@ -// Author: Joseph Bellahcen +// Copyright (C) 2023 Joseph Bellahcen -#ifndef TMS_EXPRESS_BITSTREAMGENERATOR_H -#define TMS_EXPRESS_BITSTREAMGENERATOR_H +#ifndef TMS_EXPRESS_BITSTREAM_GENERATION_BITSTREAMGENERATOR_HPP_ +#define TMS_EXPRESS_BITSTREAM_GENERATION_BITSTREAMGENERATOR_HPP_ -#include "Frame_Encoding/Frame.h" #include +#include + +#include "Frame_Encoding/Frame.h" namespace tms_express { +/// @brief Facilitates IO, preprocessing, analysis, and encoding class BitstreamGenerator { -public: - typedef enum {ENCODERSTYLE_ASCII, ENCODERSTYLE_C, ENCODERSTYLE_ARDUINO, ENCODERSTYLE_JSON} EncoderStyle; - BitstreamGenerator(float windowMs, int highpassHz, int lowpassHz, float preemphasis, EncoderStyle style, - bool includeStopFrame, int gainShift, float maxVoicedDb, float maxUnvoicedDb, bool detectRepeats, - int maxHz, int minHz); - - void encode(const std::string &inputPath, const std::string &inputFilename, const std::string &outputPath); - void encodeBatch(const std::vector &inputPaths, const std::vector &inputFilenames, - const std::string &outputPath); - -private: - float windowMs; - int highpassHz; - int lowpassHz; - float preemphasisAlpha; - EncoderStyle style; - bool includeStopFrame; - int gainShift; - float maxVoicedDB; - float maxUnvoicedDB; - bool detectRepeats; - int maxHz; - int minHz; - - std::vector generateFrames(const std::string &inputPath) const; - std::string formatBitstream(const std::vector& frames, const std::string &filename); + public: + /// @brief Defines the format of the bitstream + enum EncoderStyle { + /// @brief Bitstream with comma-delimited ASCII hex bytes + ENCODERSTYLE_ASCII, + + /// @brief Bitstream as C header which defines array of bytes + ENCODERSTYLE_C, + + /// @brief Bitstream as C header which defines array of bytes in PROGMEM + ENCODERSTYLE_ARDUINO, + + /// @brief Bitstream as JSON file + ENCODERSTYLE_JSON + }; + + /// @brief Creates a new Bitstream Generator with the given configuration + /// @param window_width_ms Segmentation widnow width, in milliseconds + /// @param highpass_cutoff_hz Highpass filter cutoff frequency, in Hertz + /// @param lowpass_cutoff_hz Lowpass filter cutoff frequency, in Hertz + /// @param pre_emphasis_alpha Pre-emphasis filter coefficient + /// @param style Encoder Style dictating bitstream format + /// @param include_stop_frame true to end the bitstream with a stop frame, + /// false otherwise + /// @param gain_shift Integer gain shift to be applied in post-processing + /// @param max_voiced_gain_db Max voiced frame gain, in decibels + /// @param max_unvoiced_gain_db Max unvoiced frame gain, in decibels + /// @param detect_repeat_frames true to detect similar frames, essentially + /// compressing the bitstream, false otherwise + /// @param max_pitch_hz Pitch frequency ceiling, in Hertz + /// @param min_pitch_hz Pitch frequency floor, in Hertz + BitstreamGenerator(float window_width_ms, int highpass_cutoff_hz, + int lowpass_cutoff_hz, float pre_emphasis_alpha, EncoderStyle style, + bool include_stop_frame, int gain_shift, float max_voiced_gain_db, + float max_unvoiced_gain_db, bool detect_repeat_frames, + int max_pitch_hz, int min_pitch_hz); + + /// @brief Produces bitstream from provided audio file + /// @param audio_input_path Path to audio file + /// @param bitstream_name Name of bitstream, for C headers, which will + /// become the name of the byte array + /// @param output_path Output path of bitstream file + void encode(const std::string &audio_input_path, + const std::string &bitstream_name, const std::string &output_path) + const; + + /// @brief Produces composite bitstream from multiple audio files + /// @param audio_input_paths Vector of audio file paths as inputs + /// @param bitstream_names Names of each bitstream for filenames and + /// C headers + /// @param output_path Output path to bitstream directory in the case + /// of ASCII-style bitstream, path to single bitstream + /// file otherwise + /// @note If instructed to produce ASCII bitstreams, this function will + /// produce on bitstream per audio file in a directory specified + /// by the output path. For all other formats, the bitstream + /// will be a single file + void encodeBatch(const std::vector &audio_input_paths, + const std::vector &bitstream_names, + const std::string &output_path) const; + + private: + /// @brief Converts audio file to sequence of LPC frames which characterize + /// the sample within each segmentation window + /// @param path Path to audio file + /// @return Vector of encoded frames + std::vector generateFrames(const std::string &path) const; + + /// @brief Converts Frame vector to bitstream file(s) + /// @param frames Vector of Frames + /// @param filename Name of bitstream, for C headers + /// @return Bitstream, as a string + std::string serializeFrames(const std::vector& frames, + const std::string &filename) const; + + float window_width_ms_; + int highpass_cutoff_hz_; + int lowpass_cutoff_hz_; + float pre_emphasis_alpha_; + EncoderStyle style_; + bool include_stop_frame_; + int gain_shift_; + float main_voiced_gain_db_; + float max_unvoiced_gain_db_; + bool detect_repeat_frames_; + int max_pitch_hz_; + int min_pitch_hz_; }; }; // namespace tms_express -#endif //TMS_EXPRESS_BITSTREAMGENERATOR_H +#endif // TMS_EXPRESS_BITSTREAM_GENERATION_BITSTREAMGENERATOR_HPP_ diff --git a/tms_express/User_Interfaces/CommandLineApp.cpp b/tms_express/User_Interfaces/CommandLineApp.cpp index 391014f..612aa10 100644 --- a/tms_express/User_Interfaces/CommandLineApp.cpp +++ b/tms_express/User_Interfaces/CommandLineApp.cpp @@ -6,7 +6,7 @@ // Author: Joseph Bellahcen /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#include "Bitstream_Generation/BitstreamGenerator.h" +#include "Bitstream_Generation/BitstreamGenerator.hpp" #include "Bitstream_Generation/PathUtils.h" #include "User_Interfaces/CommandLineApp.h" diff --git a/tms_express/User_Interfaces/CommandLineApp.h b/tms_express/User_Interfaces/CommandLineApp.h index 6c1233f..870e877 100644 --- a/tms_express/User_Interfaces/CommandLineApp.h +++ b/tms_express/User_Interfaces/CommandLineApp.h @@ -3,7 +3,7 @@ #ifndef TMS_EXPRESS_COMMANDLINEAPP_H #define TMS_EXPRESS_COMMANDLINEAPP_H -#include "Bitstream_Generation/BitstreamGenerator.h" +#include "Bitstream_Generation/BitstreamGenerator.hpp" #include "CLI11.hpp" namespace tms_express { diff --git a/tms_express/User_Interfaces/MainWindow.h b/tms_express/User_Interfaces/MainWindow.h index 29c2f72..ce10dcb 100644 --- a/tms_express/User_Interfaces/MainWindow.h +++ b/tms_express/User_Interfaces/MainWindow.h @@ -5,7 +5,7 @@ #include "Audio/AudioBuffer.hpp" #include "Audio/AudioFilter.hpp" -#include "Bitstream_Generation/BitstreamGenerator.h" +#include "Bitstream_Generation/BitstreamGenerator.hpp" #include "Frame_Encoding/Frame.h" #include "Frame_Encoding/FrameEncoder.h" #include "Frame_Encoding/FramePostprocessor.h" From 460d7f0d5cd9d2fa8a514beab5657355c2e667d1 Mon Sep 17 00:00:00 2001 From: Joseph Bellahcen Date: Fri, 21 Jul 2023 19:24:45 -0700 Subject: [PATCH 08/30] Refactored PathUtils --- .../Bitstream_Generation/PathUtils.cpp | 75 +++++++------------ tms_express/Bitstream_Generation/PathUtils.h | 34 --------- .../Bitstream_Generation/PathUtils.hpp | 71 ++++++++++++++++++ .../User_Interfaces/CommandLineApp.cpp | 6 +- 4 files changed, 103 insertions(+), 83 deletions(-) delete mode 100644 tms_express/Bitstream_Generation/PathUtils.h create mode 100644 tms_express/Bitstream_Generation/PathUtils.hpp diff --git a/tms_express/Bitstream_Generation/PathUtils.cpp b/tms_express/Bitstream_Generation/PathUtils.cpp index 611891e..eb5cf68 100644 --- a/tms_express/Bitstream_Generation/PathUtils.cpp +++ b/tms_express/Bitstream_Generation/PathUtils.cpp @@ -1,13 +1,7 @@ -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// Class: PathUtils -// -// Description: The PathUtils class provides basic file metadata. It can crawl directories for files as well as isolate -// filenames from other path components -// -// Author: Joseph Bellahcen -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -#include "Bitstream_Generation/PathUtils.h" +// Copyright (C) 2023 Joseph Bellahcen + +#include "Bitstream_Generation/PathUtils.hpp" + #include #include #include @@ -15,21 +9,21 @@ namespace tms_express { -namespace fs = std::filesystem; - -PathUtils::PathUtils(std::string filepath) { +PathUtils::PathUtils(const std::string &filepath) { // Gather file metadata - srcPath = std::move(filepath); - exists = fs::exists(srcPath); - fileIsDirectory = fs::is_directory(srcPath); + exists_ = std::filesystem::exists(filepath); + is_directory_ = std::filesystem::is_directory(filepath); // Traverse directory (if applicable) paths = std::vector(); - if (!fileIsDirectory) { - paths.push_back(srcPath); + + if (!is_directory_) { + paths.push_back(filepath); + } else { - for (const auto& dirEntry : fs::directory_iterator(srcPath)) { - auto path = dirEntry.path().string(); + auto iterator = std::filesystem::directory_iterator(filepath); + for (const auto& entry : iterator) { + auto path = entry.path().string(); paths.push_back(path); } } @@ -42,58 +36,47 @@ PathUtils::PathUtils(std::string filepath) { } } -// Check if file exists -bool PathUtils::fileExists() const { - return exists; +bool PathUtils::exists() const { + return exists_; } -// Check if file is directory bool PathUtils::isDirectory() const { - return fileIsDirectory; + return is_directory_; } -// Get vector of paths pointed to by file -// -// Returns vector with a single element if the file is not a directory -std::vector PathUtils::getPaths() { + +std::vector PathUtils::getPaths() const { return paths; } -// Return vector of filenames residing at path -// -//Returns vector with a single element if the file is not a directory -std::vector PathUtils::getFilenames() { +std::vector PathUtils::getFilenames() const { return filenames; } -// Isolate filename from other path components, including extensions std::string PathUtils::extractFilenameFromPath(const std::string &path) { // Get the lowest element in the path hierarchy - auto fullFilename = splitString(path, "/").back(); + auto filename_with_extension = splitString(path, "/").back(); // Remove the extension - auto filename = splitString(fullFilename, ".").at(0); + auto filename = splitString(filename_with_extension, ".").at(0); return filename; } -// Split string at delimiter into vector of substrings -// -// Does not include delimiter in substrings -std::vector PathUtils::splitString(const std::string& str, const std::string& delim) { +std::vector PathUtils::splitString(const std::string& str, + const std::string& delim) { auto result = std::vector(); + auto delim_size = delim.length(); - int delimSize = int(delim.length()); - - int start = 0; - int end = int(str.find(delim)); + auto start = 0; + auto end = str.find(delim); while (end != std::string::npos) { auto substr = str.substr(start, end - start); result.push_back(substr); - start = end + delimSize; - end = int(str.find(delim, start)); + start = end + delim_size; + end = str.find(delim, start); } result.push_back(str.substr(start, end - start)); diff --git a/tms_express/Bitstream_Generation/PathUtils.h b/tms_express/Bitstream_Generation/PathUtils.h deleted file mode 100644 index 6d9d10a..0000000 --- a/tms_express/Bitstream_Generation/PathUtils.h +++ /dev/null @@ -1,34 +0,0 @@ -// Author: Joseph Bellahcen - -#ifndef TMS_EXPRESS_PATHUTILS_H -#define TMS_EXPRESS_PATHUTILS_H - -#include -#include - -namespace tms_express { - -class PathUtils { -public: - explicit PathUtils(std::string filepath); - - bool fileExists() const; - bool isDirectory() const; - std::vector getPaths(); - std::vector getFilenames(); - -private: - std::string srcPath; - std::string fileExtension; - bool exists; - bool fileIsDirectory; - std::vector paths; - std::vector filenames; - - static std::string extractFilenameFromPath(const std::string &path); - static std::vector splitString(const std::string& str, const std::string& delim); -}; - -}; // namespace tms_express - -#endif //TMS_EXPRESS_PATHUTILS_H diff --git a/tms_express/Bitstream_Generation/PathUtils.hpp b/tms_express/Bitstream_Generation/PathUtils.hpp new file mode 100644 index 0000000..23d6b14 --- /dev/null +++ b/tms_express/Bitstream_Generation/PathUtils.hpp @@ -0,0 +1,71 @@ +// Copyright (C) 2023 Joseph Bellahcen + +#ifndef TMS_EXPRESS_BITSTREAM_GENERATION_PATHUTILS_HPP_ +#define TMS_EXPRESS_BITSTREAM_GENERATION_PATHUTILS_HPP_ + +#include +#include + +namespace tms_express { + +/// @brief Provides basic filesystem functionality, including directory +// crawling, file metadata, and path component separation +class PathUtils { + public: + /// @brief Creates new Path Utils instance for inspecting given path + /// @param filepath Path to analyze + explicit PathUtils(const std::string &filepath); + + /// @brief Checks if path corresponds to existing file or directory + /// @return true if file or directory exists at path, false otherwise + bool exists() const; + + /// @brief Checks if path corresponds to directory + /// @return true if path points to directory, false otherwise + bool isDirectory() const; + + /// @brief Crawls directory at path for files + /// @return Vector of files in directory + /// @note If path does not point to directory, returns single-element + /// vector containing path + std::vector getPaths() const; + + /// @brief Crawls directory at path for files and gathers their names + /// @return Vector of filenames in directory, without path components or + /// file extensions + /// @note If path does not point to directory, returns single-element + /// vector containing filename + std::vector getFilenames() const; + + private: + /// @brief Strips path components and file extension from path string + /// @param path Path string + /// @return Filename component of path + static std::string extractFilenameFromPath(const std::string &path); + + /// @brief Splits string at delimiter + /// @param str Source string + /// @param delim Delimeter + /// @return Vector of substrings, not including delimeter + static std::vector splitString(const std::string& str, + const std::string& delim); + + /// @brief true if file or directory exists at path, false otherwise + bool exists_; + + /// @brief true if path points to directory, false otherwise + bool is_directory_; + + /// @brief Collection of paths at directory if path is directory, + /// single-element vector with original path otherwise + std::vector paths; + + /// @brief Collection of filenames from path directory, excluding + /// path components or filename extensions. If path is not + /// a directory, hold single filename of original path + std::vector filenames; +}; + +}; // namespace tms_express + +#endif // TMS_EXPRESS_BITSTREAM_GENERATION_PATHUTILS_HPP_ diff --git a/tms_express/User_Interfaces/CommandLineApp.cpp b/tms_express/User_Interfaces/CommandLineApp.cpp index 612aa10..1322c75 100644 --- a/tms_express/User_Interfaces/CommandLineApp.cpp +++ b/tms_express/User_Interfaces/CommandLineApp.cpp @@ -7,7 +7,7 @@ /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #include "Bitstream_Generation/BitstreamGenerator.hpp" -#include "Bitstream_Generation/PathUtils.h" +#include "Bitstream_Generation/PathUtils.hpp" #include "User_Interfaces/CommandLineApp.h" #include "CLI11.hpp" @@ -35,12 +35,12 @@ int CommandLineApp::run(int argc, char** argv) { auto input = PathUtils(inputPath); auto output = PathUtils(outputPath); - if (!input.fileExists()) { + if (!input.exists()) { std::cerr << "Input file does not exist or is empty" << std::endl; return 1; } - if (input.isDirectory() && !bitstreamFormat && (!output.isDirectory() && output.fileExists())) { + if (input.isDirectory() && !bitstreamFormat && (!output.isDirectory() && output.exists())) { std::cerr << "Batch mode requires a directory for ASCII bitstreams" << std::endl; return 0; } From e37309e98ff4d1c85a378a9b84d935101811ce7e Mon Sep 17 00:00:00 2001 From: Joseph Bellahcen Date: Sat, 22 Jul 2023 18:55:38 -0700 Subject: [PATCH 09/30] Refactored Frame --- CMakeLists.txt | 2 +- test/FrameEncoderTests.cpp | 2 +- test/FrameTests.cpp | 2 +- tms_express/Audio/AudioBuffer.cpp | 2 +- tms_express/Audio/AudioBuffer.hpp | 18 +- tms_express/Audio/AudioFilter.cpp | 16 ++ tms_express/Audio/AudioFilter.hpp | 28 +- .../BitstreamGenerator.cpp | 2 +- .../BitstreamGenerator.hpp | 49 +++- .../Bitstream_Generation/PathUtils.cpp | 43 ++- .../Bitstream_Generation/PathUtils.hpp | 38 ++- tms_express/Frame_Encoding/Frame.cpp | 252 +++++++----------- tms_express/Frame_Encoding/Frame.h | 61 ----- tms_express/Frame_Encoding/Frame.hpp | 178 +++++++++++++ tms_express/Frame_Encoding/FrameEncoder.cpp | 2 +- tms_express/Frame_Encoding/FrameEncoder.h | 2 +- .../Frame_Encoding/FramePostprocessor.cpp | 2 +- .../Frame_Encoding/FramePostprocessor.h | 2 +- tms_express/Frame_Encoding/Synthesizer.cpp | 2 +- tms_express/Frame_Encoding/Synthesizer.h | 2 +- tms_express/User_Interfaces/MainWindow.h | 2 +- 21 files changed, 454 insertions(+), 253 deletions(-) delete mode 100644 tms_express/Frame_Encoding/Frame.h create mode 100644 tms_express/Frame_Encoding/Frame.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index f48b908..575f618 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,8 +16,8 @@ option(TMSEXPRESS_BUILD_TESTS "Build test programs" ON) # PROGRAM FILES # ==================================== include_directories( + ${CMAKE_CURRENT_LIST_DIR} tms_express - lib ) add_executable(${PROJECT_NAME} diff --git a/test/FrameEncoderTests.cpp b/test/FrameEncoderTests.cpp index af51b94..491b8e7 100644 --- a/test/FrameEncoderTests.cpp +++ b/test/FrameEncoderTests.cpp @@ -1,4 +1,4 @@ -#include "Frame_Encoding/Frame.h" +#include "Frame_Encoding/Frame.hpp" #include "Frame_Encoding/FrameEncoder.h" #include diff --git a/test/FrameTests.cpp b/test/FrameTests.cpp index d286d27..6b7284f 100644 --- a/test/FrameTests.cpp +++ b/test/FrameTests.cpp @@ -1,4 +1,4 @@ -#include "Frame_Encoding/Frame.h" +#include "Frame_Encoding/Frame.hpp" #include #include diff --git a/tms_express/Audio/AudioBuffer.cpp b/tms_express/Audio/AudioBuffer.cpp index a9572b0..9b0475b 100644 --- a/tms_express/Audio/AudioBuffer.cpp +++ b/tms_express/Audio/AudioBuffer.cpp @@ -181,7 +181,7 @@ AudioBuffer AudioBuffer::copy() const { } bool AudioBuffer::render(const std::string &path) const { - // Throw error if buffer is empty + // Indicate error if buffer is empty if (empty()) { return false; } diff --git a/tms_express/Audio/AudioBuffer.hpp b/tms_express/Audio/AudioBuffer.hpp index 5fee425..ac86a42 100644 --- a/tms_express/Audio/AudioBuffer.hpp +++ b/tms_express/Audio/AudioBuffer.hpp @@ -98,7 +98,7 @@ class AudioBuffer { // Utility //////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////// - /// @brief Creates an independent copy of the Audio Buffer + /// @brief Creates an independent copy of the Audio Buffer /// @return New Audio Buffer which is separate from but identical to its /// parent at the time of creation AudioBuffer copy() const; @@ -132,13 +132,25 @@ class AudioBuffer { int src_sample_rate_hz, int target_sample_rate_hz); /////////////////////////////////////////////////////////////////////////// - // Members //////////////////////////////////////// + // Members //////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////// + /// @brief Sampling rate used to generate or import samples, in Hertz + int sample_rate_hz_; + + /// @brief Number of segments in Audio Buffer, determined by segmentation + /// window width int n_segments_; + + /// @brief Number of samples in each segment, determined by segmentation + /// window width int n_samples_per_segment_; - int sample_rate_hz_; + + /// @brief Flat (unsegmented) buffer of samples std::vector samples_; + + /// @brief Original samples set during initialization + // TODO(Joseph Bellahcen): Remove std::vector original_samples_; }; diff --git a/tms_express/Audio/AudioFilter.cpp b/tms_express/Audio/AudioFilter.cpp index e58e07c..f735d69 100644 --- a/tms_express/Audio/AudioFilter.cpp +++ b/tms_express/Audio/AudioFilter.cpp @@ -7,6 +7,10 @@ namespace tms_express { +/////////////////////////////////////////////////////////////////////////////// +// Windowing ////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + void AudioFilter::applyHammingWindow(AudioBuffer &buffer) const { for (auto &segment : buffer.getAllSegments()) { applyHammingWindow(segment); @@ -24,6 +28,10 @@ void AudioFilter::applyHammingWindow(std::vector &segment) const { } } +/////////////////////////////////////////////////////////////////////////////// +// Bi-Quadratic Filters /////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + void AudioFilter::applyHighpass(AudioBuffer &buffer, int cutoff_hz) { computeCoeffs(HPF, cutoff_hz); applyBiquad(buffer); @@ -34,6 +42,10 @@ void AudioFilter::applyLowpass(AudioBuffer &buffer, int cutoff_hz) { applyBiquad(buffer); } +/////////////////////////////////////////////////////////////////////////////// +// Simple Filters ///////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + void AudioFilter::applyPreEmphasis(AudioBuffer &buffer, float alpha) const { // Initialize filtered buffer auto samples = buffer.getSamples(); @@ -53,6 +65,10 @@ void AudioFilter::applyPreEmphasis(AudioBuffer &buffer, float alpha) const { buffer.setSamples(filteredSamples); } +/////////////////////////////////////////////////////////////////////////////// +// Helpers //////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + void AudioFilter::applyBiquad(AudioBuffer &buffer) { // Rename coefficients for readability float k0 = coeffs_[0]; diff --git a/tms_express/Audio/AudioFilter.hpp b/tms_express/Audio/AudioFilter.hpp index fc5f6bb..38c9785 100644 --- a/tms_express/Audio/AudioFilter.hpp +++ b/tms_express/Audio/AudioFilter.hpp @@ -14,6 +14,10 @@ namespace tms_express { /// @brief Implements various digital filters for processing audio samples class AudioFilter { public: + /////////////////////////////////////////////////////////////////////////// + // Windowing ////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + /// @brief Applies Hamming window to entire buffer /// @param buffer Audio Buffer to apply window to void applyHammingWindow(AudioBuffer &buffer) const; @@ -22,6 +26,10 @@ class AudioFilter { /// @param buffer Segment vector to apply window to void applyHammingWindow(std::vector &segment) const; + /////////////////////////////////////////////////////////////////////////// + // Bi-Quadratic Filters /////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + /// @brief Applies highpass filter to entire buffer /// @param buffer Audio Buffer to apply filter to /// @param cutoffHz Highpass cutoff frequency, in Hertz @@ -32,19 +40,28 @@ class AudioFilter { /// @param cutoffHz Lowpass cutoff frequency, in Hertz void applyLowpass(AudioBuffer &buffer, int cutoff_hz); + /////////////////////////////////////////////////////////////////////////// + // Simple Filters ///////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + /// @brief Applies pre-emphasis filter to entire buffer /// @param buffer Audio Buffer to apply filter to /// @param alpha Pre-emphasis coefficient (usually 0.9375) void applyPreEmphasis(AudioBuffer &buffer, float alpha = 0.9375) const; private: + /////////////////////////////////////////////////////////////////////////// + // Enums ////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + /// @brief Last-used filter mode /// @note The Filter Mode is stored to prevent unnecessary re-calculation /// of filter coefficients enum FilterMode {HPF, LPF}; - /// @brief Bi-quadratic filter coefficients - std::array coeffs_{0, 0, 0, 0, 0, 0}; + /////////////////////////////////////////////////////////////////////////// + // Helpers //////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// /// @brief Applies bi-quadratic filter coefficients to entire Buffer /// @param buffer Audio Buffer to apply filter to @@ -53,6 +70,13 @@ class AudioFilter { /// @brief Computes bi-quadratic filter coefficients for a highpass or /// lowpass filter void computeCoeffs(FilterMode mode, int cutoff_hz); + + /////////////////////////////////////////////////////////////////////////// + // Members //////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @brief Bi-quadratic filter coefficients + std::array coeffs_{0, 0, 0, 0, 0, 0}; }; }; // namespace tms_express diff --git a/tms_express/Bitstream_Generation/BitstreamGenerator.cpp b/tms_express/Bitstream_Generation/BitstreamGenerator.cpp index 2bc2698..2de43c2 100644 --- a/tms_express/Bitstream_Generation/BitstreamGenerator.cpp +++ b/tms_express/Bitstream_Generation/BitstreamGenerator.cpp @@ -10,7 +10,7 @@ #include "Audio/AudioBuffer.hpp" #include "Audio/AudioFilter.hpp" -#include "Frame_Encoding/Frame.h" +#include "Frame_Encoding/Frame.hpp" #include "Frame_Encoding/FrameEncoder.h" #include "Frame_Encoding/FramePostprocessor.h" #include "LPC_Analysis/Autocorrelation.h" diff --git a/tms_express/Bitstream_Generation/BitstreamGenerator.hpp b/tms_express/Bitstream_Generation/BitstreamGenerator.hpp index 1f12746..5e0cf0a 100644 --- a/tms_express/Bitstream_Generation/BitstreamGenerator.hpp +++ b/tms_express/Bitstream_Generation/BitstreamGenerator.hpp @@ -6,13 +6,17 @@ #include #include -#include "Frame_Encoding/Frame.h" +#include "Frame_Encoding/Frame.hpp" namespace tms_express { /// @brief Facilitates IO, preprocessing, analysis, and encoding class BitstreamGenerator { public: + /////////////////////////////////////////////////////////////////////////// + // Enums ////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + /// @brief Defines the format of the bitstream enum EncoderStyle { /// @brief Bitstream with comma-delimited ASCII hex bytes @@ -28,6 +32,10 @@ class BitstreamGenerator { ENCODERSTYLE_JSON }; + /////////////////////////////////////////////////////////////////////////// + // Initializers /////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + /// @brief Creates a new Bitstream Generator with the given configuration /// @param window_width_ms Segmentation widnow width, in milliseconds /// @param highpass_cutoff_hz Highpass filter cutoff frequency, in Hertz @@ -49,6 +57,10 @@ class BitstreamGenerator { float max_unvoiced_gain_db, bool detect_repeat_frames, int max_pitch_hz, int min_pitch_hz); + /////////////////////////////////////////////////////////////////////////// + // Encoding /////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + /// @brief Produces bitstream from provided audio file /// @param audio_input_path Path to audio file /// @param bitstream_name Name of bitstream, for C headers, which will @@ -74,6 +86,10 @@ class BitstreamGenerator { const std::string &output_path) const; private: + /////////////////////////////////////////////////////////////////////////// + // Helpers //////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + /// @brief Converts audio file to sequence of LPC frames which characterize /// the sample within each segmentation window /// @param path Path to audio file @@ -87,17 +103,46 @@ class BitstreamGenerator { std::string serializeFrames(const std::vector& frames, const std::string &filename) const; + /////////////////////////////////////////////////////////////////////////// + // Members //////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @brief Target bitstream format + EncoderStyle style_; + + /// @brief Segmentation window width, in milliseconds float window_width_ms_; + + /// @brief Highpass filter cutoff, in Hertz int highpass_cutoff_hz_; + + /// @brief Lowpass filter cutoff, in Hertz int lowpass_cutoff_hz_; + + /// @brief Pre-emphasis filter coefficient float pre_emphasis_alpha_; - EncoderStyle style_; + + /// @brief true if bitstream should end with explicit stop frame, + /// false otherwise bool include_stop_frame_; + + /// @brief Post-processing gain shift, as TMS5220 Coding Table index offset int gain_shift_; + + /// @brief Max gain for voiced (vowel) frames, in decibels float main_voiced_gain_db_; + + /// @brief Max gain for unvoiced (consonant) frames, in decibels float max_unvoiced_gain_db_; + + /// @brief true if bitstream should be "compressed" via detection of similar + /// frames, which are marked a repeats and fully encoded just once bool detect_repeat_frames_; + + /// @brief Max pitch frequency, in Hertz int max_pitch_hz_; + + /// @brief Min pitch frequency, in Hertz int min_pitch_hz_; }; diff --git a/tms_express/Bitstream_Generation/PathUtils.cpp b/tms_express/Bitstream_Generation/PathUtils.cpp index eb5cf68..7755c9b 100644 --- a/tms_express/Bitstream_Generation/PathUtils.cpp +++ b/tms_express/Bitstream_Generation/PathUtils.cpp @@ -9,50 +9,65 @@ namespace tms_express { +/////////////////////////////////////////////////////////////////////////////// +// Initializers /////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + PathUtils::PathUtils(const std::string &filepath) { // Gather file metadata exists_ = std::filesystem::exists(filepath); is_directory_ = std::filesystem::is_directory(filepath); // Traverse directory (if applicable) - paths = std::vector(); + paths_ = std::vector(); if (!is_directory_) { - paths.push_back(filepath); + paths_.push_back(filepath); } else { auto iterator = std::filesystem::directory_iterator(filepath); for (const auto& entry : iterator) { auto path = entry.path().string(); - paths.push_back(path); + paths_.push_back(path); } } // Extract filenames - filenames = std::vector(); - for (const auto& path : paths) { + filenames_ = std::vector(); + for (const auto& path : paths_) { auto filename = extractFilenameFromPath(path); - filenames.push_back(filename); + filenames_.push_back(filename); } } -bool PathUtils::exists() const { - return exists_; +/////////////////////////////////////////////////////////////////////////////// +// Accessors ////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +std::vector PathUtils::getPaths() const { + return paths_; } -bool PathUtils::isDirectory() const { - return is_directory_; +std::vector PathUtils::getFilenames() const { + return filenames_; } +/////////////////////////////////////////////////////////////////////////////// +// Metadata /////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// -std::vector PathUtils::getPaths() const { - return paths; +bool PathUtils::exists() const { + return exists_; } -std::vector PathUtils::getFilenames() const { - return filenames; +bool PathUtils::isDirectory() const { + return is_directory_; } +/////////////////////////////////////////////////////////////////////////////// +// Static Helpers ///////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + std::string PathUtils::extractFilenameFromPath(const std::string &path) { // Get the lowest element in the path hierarchy auto filename_with_extension = splitString(path, "/").back(); diff --git a/tms_express/Bitstream_Generation/PathUtils.hpp b/tms_express/Bitstream_Generation/PathUtils.hpp index 23d6b14..8d790b1 100644 --- a/tms_express/Bitstream_Generation/PathUtils.hpp +++ b/tms_express/Bitstream_Generation/PathUtils.hpp @@ -12,17 +12,17 @@ namespace tms_express { // crawling, file metadata, and path component separation class PathUtils { public: + /////////////////////////////////////////////////////////////////////////// + // Initializers /////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + /// @brief Creates new Path Utils instance for inspecting given path /// @param filepath Path to analyze explicit PathUtils(const std::string &filepath); - /// @brief Checks if path corresponds to existing file or directory - /// @return true if file or directory exists at path, false otherwise - bool exists() const; - - /// @brief Checks if path corresponds to directory - /// @return true if path points to directory, false otherwise - bool isDirectory() const; + /////////////////////////////////////////////////////////////////////////// + // Accessors ////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// /// @brief Crawls directory at path for files /// @return Vector of files in directory @@ -37,7 +37,23 @@ class PathUtils { /// vector containing filename std::vector getFilenames() const; + /////////////////////////////////////////////////////////////////////////// + // Metadata /////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @brief Checks if path corresponds to existing file or directory + /// @return true if file or directory exists at path, false otherwise + bool exists() const; + + /// @brief Checks if path corresponds to directory + /// @return true if path points to directory, false otherwise + bool isDirectory() const; + private: + /////////////////////////////////////////////////////////////////////////// + // Static Helpers ///////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + /// @brief Strips path components and file extension from path string /// @param path Path string /// @return Filename component of path @@ -50,6 +66,10 @@ class PathUtils { static std::vector splitString(const std::string& str, const std::string& delim); + /////////////////////////////////////////////////////////////////////////// + // Members //////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + /// @brief true if file or directory exists at path, false otherwise bool exists_; @@ -58,12 +78,12 @@ class PathUtils { /// @brief Collection of paths at directory if path is directory, /// single-element vector with original path otherwise - std::vector paths; + std::vector paths_; /// @brief Collection of filenames from path directory, excluding /// path components or filename extensions. If path is not /// a directory, hold single filename of original path - std::vector filenames; + std::vector filenames_; }; }; // namespace tms_express diff --git a/tms_express/Frame_Encoding/Frame.cpp b/tms_express/Frame_Encoding/Frame.cpp index dcc1c59..57ffe25 100644 --- a/tms_express/Frame_Encoding/Frame.cpp +++ b/tms_express/Frame_Encoding/Frame.cpp @@ -1,202 +1,165 @@ -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// Class: Frame -// -// Description: A Frame is the smallest unit of speech that can be represented with linear predictive coding. It -// typically corresponds to a 22.5-30 ms window of audio, and consists of different parameters. Different -// kinds of frames contain different parameters. The parameters in a Frame do not actually represent -// to numerical values, but are instead indices to a coding table that the synthesizer possesses -// -// Author: Joseph Bellahcen -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -#include "Frame_Encoding/Frame.h" -#include "Frame_Encoding/Tms5220CodingTable.h" +// Copyright (C) 2023 Joseph Bellahcen -#include "json.hpp" +#include "Frame_Encoding/Frame.hpp" #include #include #include +#include "lib/json.hpp" + +#include "Frame_Encoding/Tms5220CodingTable.h" + namespace tms_express { -using namespace Tms5220CodingTable; - -/// Create a new frame -/// -/// \param pitchPeriod Pitch period (in samples) -/// \param isVoiced Whether frame corresponds to a consonant (unvoiced) or a vowel (voiced) -/// \param gainDB Frame gain (in decibels) -/// \param coeffs LPC reflector coefficients -Frame::Frame(int pitchPeriod, bool isVoiced, float gainDB, std::vector coeffs) { - gain = gainDB; - pitch = pitchPeriod; - reflectorCoeffs = std::move(coeffs); - isRepeatFrame = false; - isVoicedFrame = isVoiced; - - // The gain may be NaN if the autocorrelation is zero, which has been observed in the following scenarios: - // 1. The Frame is completely silent (source audio is noise-isolated) - // 2. The highpass filter cutoff is too low - if (std::isnan(gainDB)) { - gain = 0.0f; - reflectorCoeffs.assign(reflectorCoeffs.size(), 0.0f); +/////////////////////////////////////////////////////////////////////////////// +// Initializers /////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +Frame::Frame(int pitch_period, bool is_voiced, float gain_db, + std::vector coeffs) { + gain_db_ = gain_db; + pitch_period_ = pitch_period; + coeffs_ = coeffs; + is_repeat_ = false; + is_voiced_ = is_voiced; + + // The gain may be NaN if the autocorrelation is zero, meaning: + // 1. The Frame is completely silent (source audio is noise-isolated) + // 2. The highpass filter cutoff is too low + if (std::isnan(gain_db)) { + gain_db_ = 0.0f; + coeffs_.assign(coeffs_.size(), 0.0f); } } /////////////////////////////////////////////////////////////////////////////// -// Getters & Setters +// Accessors ////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// -/// Return LPC reflector coefficients -std::vector Frame::getCoeffs() { - return reflectorCoeffs; +std::vector Frame::getCoeffs() const { + return coeffs_; } -/// Replace LPC reflector coefficients -/// -/// \param coeffs New reflector coefficients void Frame::setCoeffs(std::vector coeffs) { - reflectorCoeffs = std::move(coeffs); + coeffs_ = coeffs; } -/// Return gain (in decibels) float Frame::getGain() const { - return gain; + return gain_db_; } -/// Set gain to exact value (in decibels) -/// -/// \param gainDb New gain (in decibels) -void Frame::setGain(float gainDb) { - gain = gainDb; +void Frame::setGain(float gain_db) { + gain_db_ = gain_db; } -/// Set decibel gain based on coding table index -/// -/// \note setGain(i) adjusts the gain to codingTable[i] dB -/// -/// \param codingTableIdx -void Frame::setGain(int codingTableIdx) { - gain = Tms5220CodingTable::rms.at(codingTableIdx); +void Frame::setGain(int idx) { + if (idx < 0) { + gain_db_ = *Tms5220CodingTable::rms.begin(); + + } else if (idx > Tms5220CodingTable::rms.size()) { + gain_db_ = *Tms5220CodingTable::rms.end(); + + } else { + gain_db_ = Tms5220CodingTable::rms.at(idx); + } } -/// Return pitch period (in samples) int Frame::getPitch() const { - return pitch; + return pitch_period_; } -/// Set pitch period (in samples) -/// -/// \param pitchPeriod Pitch period (in samples) -void Frame::setPitch(int pitchPeriod) { - pitch = pitchPeriod; +void Frame::setPitch(int pitch) { + pitch_period_ = pitch; } -/// Return whether or not frame is identical to its neighbor bool Frame::getRepeat() const { - return isRepeatFrame; + return is_repeat_; } -/// Mark frame as identical to its neighbor -/// -/// \note Marking a frame as repeat reduces the storage cost of the frame -/// -/// \param isRepeat Whether or not frame is identical to its neighbor -void Frame::setRepeat(bool isRepeat) { - isRepeatFrame = isRepeat; +void Frame::setRepeat(bool is_repeat) { + is_repeat_ = is_repeat; } -/// Return whether frame corresponds to a consonant (unvoiced) or a vowel (voiced) bool Frame::getVoicing() const { - return isVoicedFrame; + return is_voiced_; } -/// Mark frame as voiced or unvoiced -/// -/// \param isVoiced Whether frame corresponds to a consonant (unvoiced) or a vowel (voiced) void Frame::setVoicing(bool isVoiced) { - isVoicedFrame = isVoiced; + is_voiced_ = isVoiced; } /////////////////////////////////////////////////////////////////////////////// -// Const Getters +// Quantized Getters ////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// -/// Return LPC reflector coefficient indices, corresponding to coding table entries -std::vector Frame::quantizedCoeffs() { - auto size = nKCoeffs; - auto coeffsIndices = std::vector(size); +std::vector Frame::quantizedCoeffs() const { + auto size = Tms5220CodingTable::nKCoeffs; + auto indices = std::vector(size); // Only parse as many coefficients as the coding table supports for (int i = 0; i < size; i++) { - auto kCoeffTable = kCoeffsSlice(i); - float coeff = reflectorCoeffs[i]; + auto table = Tms5220CodingTable::kCoeffsSlice(i); + float coeff = coeffs_[i]; - int idx = closestCodingTableIndexForValue(coeff, kCoeffTable); - coeffsIndices[i] = idx; + int idx = closestIndex(coeff, table); + indices[i] = idx; } - return coeffsIndices; + return indices; } -/// Return frame gain index, corresponding to coding table entry int Frame::quantizedGain() const { - auto gainTable = Tms5220CodingTable::rms; - auto gainVec = std::vector(gainTable.begin(), gainTable.end()); + auto table_array = Tms5220CodingTable::rms; + auto table_vector = std::vector(table_array.begin(), + table_array.end()); - int idx = closestCodingTableIndexForValue(gain, gainVec); + int idx = closestIndex(gain_db_, table_vector); return idx; } /// Return pitch period indices, corresponding to coding table entries int Frame::quantizedPitch() const { - auto pitchTable = Tms5220CodingTable::pitch; - auto pitchVec = std::vector(pitchTable.begin(), pitchTable.end()); + auto table = Tms5220CodingTable::pitch; + auto table_vector = std::vector(table.begin(), table.end()); - int idx = closestCodingTableIndexForValue(float(pitch), pitchVec); + int idx = closestIndex(pitch_period_, table_vector); return idx; } -/// Return whether frame corresponds to a consonant (unvoiced) or a vowel (voiced) -/// -/// \note Alias of \code Frame::getVoicing() \endcode int Frame::quantizedVoicing() const { - return int(isVoicedFrame); + return is_voiced_; } /////////////////////////////////////////////////////////////////////////////// -// Boolean Properties +// Metadata /////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// -/// Return whether or not frame is identical to its neighbor bool Frame::isRepeat() const { - return isRepeatFrame; + return is_repeat_; } -/// Return whether or not frame is silent -bool Frame::isSilent() { +bool Frame::isSilent() const { return !quantizedGain(); } -/// whether frame corresponds to a consonant (unvoiced) or a vowel (voiced) -/// -/// \note Alias of \code Frame::getQuantizedVoicing() \endcode bool Frame::isVoiced() const { - return isVoicedFrame; + return is_voiced_; } /////////////////////////////////////////////////////////////////////////////// -// Frame Serialization +// Serializers //////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// -/// Return binary string representation of frame std::string Frame::toBinary() { + // Reference: TMS 5220 VOICE SYNTHESIS PROCESSOR DATA MANUAL + // http://sprow.co.uk/bbc/hardware/speech/tms5220.pdf + std::string bin; // At minimum, a frame will contain an energy parameter - int energyIdx = quantizedGain(); - bin += valueToBinary(energyIdx, Tms5220CodingTable::gainWidth); + int gain_idx = quantizedGain(); + bin += valueToBinary(gain_idx, Tms5220CodingTable::gainWidth); // A silent frame will contain no further parameters if (isSilent()) { @@ -207,37 +170,37 @@ std::string Frame::toBinary() { bin += isRepeat() ? "1" : "0"; // A voiced frame will have a non-zero pitch - int pitchIdx = isVoiced() ? quantizedPitch() : 0; - bin += valueToBinary(pitchIdx, Tms5220CodingTable::pitchWidth); + int pitch_idx = isVoiced() ? quantizedPitch() : 0; + bin += valueToBinary(pitch_idx, Tms5220CodingTable::pitchWidth); if (isRepeat()) { return bin; } - // Both voiced and unvoiced frames contain reflector coefficients, but vary in quantity + // Both voiced and unvoiced frames contain reflector coefficients, but vary + // in quantity auto coeffs = quantizedCoeffs(); - int nCoeffs = isVoiced() ? 10 : 4; + int n_coeffs = isVoiced() ? 10 : 4; - for (int i = 0; i < nCoeffs; i++) { + for (int i = 0; i < n_coeffs; i++) { int coeff = coeffs.at(i); - int coeffWidth = Tms5220CodingTable::coeffWidths.at(i); + int coeff_width = Tms5220CodingTable::coeffWidths.at(i); - bin += valueToBinary(coeff, coeffWidth); + bin += valueToBinary(coeff, coeff_width); } return bin; } -/// Return JSON representation of frame nlohmann::json Frame::toJSON() { nlohmann::json jFrame; // Raw values - jFrame["pitch"] = pitch; - jFrame["isVoiced"] = isVoicedFrame; - jFrame["isRepeat"] = isRepeatFrame; - jFrame["gain"] = gain; - jFrame["coeffs"] = nlohmann::json(reflectorCoeffs); + jFrame["pitch"] = pitch_period_; + jFrame["isVoiced"] = is_voiced_; + jFrame["isRepeat"] = is_repeat_; + jFrame["gain"] = gain_db_; + jFrame["coeffs"] = nlohmann::json(coeffs_); // Quantized values jFrame["tms_pitch"] = quantizedPitch(); @@ -248,53 +211,42 @@ nlohmann::json Frame::toJSON() { } /////////////////////////////////////////////////////////////////////////////// -// Utility Functions +// Static Helpers ///////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// -/// Find index of coding table entry which most closely matches given value -/// -/// \param value Value to quantize via coding table -/// \param codingTableRow Row (parameter vector) of coding table -/// \return Index of coding table row which is closest to given value -int Frame::closestCodingTableIndexForValue(float value, std::vector codingTableRow) { - auto size = int(codingTableRow.size()); +int Frame::closestIndex(float value, std::vector table) { + auto size = table.size(); // First, check if the value is within the lower bound of the array values - if (value <= codingTableRow.at(0)) { + if (value <= table.at(0)) { return 0; } - // Check the elements to the left and right to find where the value best fits + // Check elements to left and right to find where value best fits for (int i = 1; i < size; i++) { - float rightEntry = codingTableRow.at(i); - float leftEntry = codingTableRow.at(i - 1); + float right = table.at(i); + float left = table.at(i - 1); - if (value < rightEntry) { - float rightDistance = rightEntry - value; - float leftDistance = value - leftEntry; + if (value < right) { + float right_distance = right - value; + float left_distance = value - left; - return (rightDistance < leftDistance) ? i : i - 1; + return (right_distance < left_distance) ? i : i - 1; } } return size - 1; } -/// Convert integer to binary string of given width -/// -/// \param value Value to convert to binary -/// \param bitWidth Width (number of bits) of binary string -/// \return Binary string with width-number of bits -std::string Frame::valueToBinary(int value, int bitWidth) { +std::string Frame::valueToBinary(int value, int width) { std::string bin; - for (int i = 0; i < bitWidth; i++) { + for (int i = 0; i < width; i++) { bin += (value % 2 == 0) ? "0" : "1"; value /= 2; } std::reverse(bin.begin(), bin.end()); - return bin; } diff --git a/tms_express/Frame_Encoding/Frame.h b/tms_express/Frame_Encoding/Frame.h deleted file mode 100644 index 7351479..0000000 --- a/tms_express/Frame_Encoding/Frame.h +++ /dev/null @@ -1,61 +0,0 @@ -// Author: Joseph Bellahcen - -#ifndef TMS_EXPRESS_FRAME_H -#define TMS_EXPRESS_FRAME_H - -#include "json.hpp" -#include -#include - -namespace tms_express { - -class Frame { -public: - Frame(int pitchPeriod, bool isVoiced, float gainDB, std::vector coeffs); - - // Getters & setters - std::vector getCoeffs(); - void setCoeffs(std::vector coeffs); - - [[nodiscard]] float getGain() const; - void setGain(float gainDb); - void setGain(int codingTableIdx); - - [[nodiscard]] int getPitch() const; - void setPitch(int pitchPeriod); - - bool getRepeat() const; - void setRepeat(bool isRepeat); - - [[nodiscard]] bool getVoicing() const; - void setVoicing(bool isVoiced); - - // Const getters - std::vector quantizedCoeffs(); - [[nodiscard]] int quantizedGain() const; - [[nodiscard]] int quantizedPitch() const; - [[nodiscard]] [[maybe_unused]] int quantizedVoicing() const; - - // Boolean properties - [[nodiscard]] bool isRepeat() const; - [[nodiscard]] bool isSilent(); - [[nodiscard]] bool isVoiced() const; - - // Serialization - std::string toBinary(); - nlohmann::json toJSON(); - -private: - float gain; - int pitch; - std::vector reflectorCoeffs; - bool isRepeatFrame; - bool isVoicedFrame; - - static int closestCodingTableIndexForValue(float value, std::vector codingTableRow); - static std::string valueToBinary(int value, int bitWidth); -}; - -}; // namespace tms_express - -#endif //TMS_EXPRESS_FRAME_H diff --git a/tms_express/Frame_Encoding/Frame.hpp b/tms_express/Frame_Encoding/Frame.hpp new file mode 100644 index 0000000..2486274 --- /dev/null +++ b/tms_express/Frame_Encoding/Frame.hpp @@ -0,0 +1,178 @@ +// Copyright (C) 2023 Joseph Bellahcen + +#ifndef TMS_EXPRESS_FRAME_ENCODING_FRAME_HPP_ +#define TMS_EXPRESS_FRAME_ENCODING_FRAME_HPP_ + +#include +#include + +#include "lib/json.hpp" + +namespace tms_express { + +/// @brief Characterizes speech data +/// @details One Frame holds information about a window of speech data, +/// typically corresponding to 22.5-30 ms of audio +class Frame { + public: + /////////////////////////////////////////////////////////////////////////// + // Initializers /////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @brief Stores a new Frame + /// @param pitch_period Pitch period, in samples + /// @param is_voiced true if Frame represents voiced (vowel) sample, + /// false for unvoiced (consonant) + /// @param gain_db Gain, in decibels + /// @param coeffs LPC reflector coefficients + Frame(int pitch_period, bool is_voiced, float gain_db, + std::vector coeffs); + + /////////////////////////////////////////////////////////////////////////// + // Accessors ////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @brief Accesses LPC reflector coefficients + /// @return Vector of coefficients, with length proportional to LPC + /// model order. Although voiced and unvoiced Frames differ in the + /// number of coefficients encoded into a bitstream, the coeffs + /// vector will always contain the full set + std::vector getCoeffs() const; + + /// @brief Replaces LPC reflector coefficients + /// @param coeffs Vector of coefficients + // TODO(Joseph Bellahcen): Enforce model order + void setCoeffs(std::vector coeffs); + + /// @brief Accesses the gain + /// @return Gain, in decibels + float getGain() const; + + /// @brief Sets the gain precisely + /// @param gain_db New gain, in decibels + void setGain(float gain_db); + + /// @brief Sets the gain coarsely via TMS5220 Coding Table + /// @param idx Index into TMS5220 Coding Table's RMS vector + /// @note If index out of bounds, it will be floored or ceiling to the + /// appropriate bound, resulting in a Frame with either minimum + /// or maximum gain + void setGain(int idx); + + /// @brief Accesses the pitch period + /// @return Pitch period, in samples + int getPitch() const; + + /// @brief Sets the pitch period + /// @param pitch New pitch period, in samples + void setPitch(int pitch); + + /// @brief Checks if Frame is repeat of adjacent Frame + /// @return true if Frame marked as repeat, false otherwise + /// @note This property is determined by the Frame Post-Processor + [[deprecated]] bool getRepeat() const; + + /// @brief Mark frame as repeat + /// @param is_repeat true if Frame is repeat, false otherwise + void setRepeat(bool is_repeat); + + /// @brief Checks voicing of Frame + /// @return true if Frame is voiced (vowel) or unvoiced (consonant) + [[deprecated]] bool getVoicing() const; + + /// @brief Sets voicing of Frame + /// @param isVoiced true if Frame is voiced (vowel) or unvoiced (consonant) + void setVoicing(bool isVoiced); + + /////////////////////////////////////////////////////////////////////////// + // Quantized Getters ////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @brief Quantizes LPC reflector coefficients via TMS5220 Coding Table + /// @return Vector of indicies of closest LPC reflector coefficient value + /// in TMS5220 Coding Table + std::vector quantizedCoeffs() const; + + /// @brief Quantizes gain via TMS5220 Coding Table + /// @return Index of closest gain value in TMS5220 Coding Table + int quantizedGain() const; + + /// @brief Quantizes pitch via TMS5220 Coding Table + /// @return Index of closest pitch period value in TMS5220 Coding Table + int quantizedPitch() const; + + /// @brief Quantizes LPC voicing via TMS5220 Coding Table + /// @return Index of closest gain value in TMS5220 Coding Table + [[deprecated]] int quantizedVoicing() const; + + /////////////////////////////////////////////////////////////////////////// + // Metadata /////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @brief Reports whether Frame is repeat (identical to neighbor) + /// @return true if Frame is repeat, false otherwise + bool isRepeat() const; + + /// @brief Reports whether Frame contains audio + /// @return true if Frame is silent, false otherwise + bool isSilent() const; + + /// @brief Reports whether frame is voiced (vowel) or unvoiced (consonant) + /// @return true if Frame is voiced, false if unvoiced + bool isVoiced() const; + + /////////////////////////////////////////////////////////////////////////// + // Serializers //////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @brief Converts Frame to binary representation, per the TMS5220's LPC + /// encoding specification + /// @return Encoded Frame, as binary string + std::string toBinary(); + + /// @brief Converts Frame to JSON object which closely mirrors internal + /// implementation + /// @return Encoded Frame, as JSON object + nlohmann::json toJSON(); + + private: + /////////////////////////////////////////////////////////////////////////// + // Static Helpers ///////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @brief Finds value in TMS5220 Coding Table whose value is closest to + /// argument, and provides its index + /// @param value Value to look for + /// @param table TMS5220 Coding Table entry + /// @return Index of closest value to argument in TMS5220 Coding Table + static int closestIndex(float value, std::vector table); + + /// @brief Converts integer value to binary string + /// @param value Value to convert + /// @param width Number of bits to use for encoding + /// @return Binary string corresponding to value, MSB first + static std::string valueToBinary(int value, int width); + + /////////////////////////////////////////////////////////////////////////// + // Members //////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @brief Gain, in decibels + float gain_db_; + + /// @brief Pitch period, in samples + int pitch_period_; + + /// @brief LPC reflector coefficients + std::vector coeffs_; + + /// @brief true if Frame is repeat (identical to neighbor), false otherwise + bool is_repeat_; + + /// @brief true if Frame is voiced (vowel), false if unvoiced (consonant) + bool is_voiced_; +}; + +}; // namespace tms_express + +#endif // TMS_EXPRESS_FRAME_ENCODING_FRAME_HPP_ diff --git a/tms_express/Frame_Encoding/FrameEncoder.cpp b/tms_express/Frame_Encoding/FrameEncoder.cpp index 64dfbc7..c046522 100644 --- a/tms_express/Frame_Encoding/FrameEncoder.cpp +++ b/tms_express/Frame_Encoding/FrameEncoder.cpp @@ -9,7 +9,7 @@ /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #include "Frame_Encoding/FrameEncoder.h" -#include "Frame_Encoding/Frame.h" +#include "Frame_Encoding/Frame.hpp" #include "Frame_Encoding/Tms5220CodingTable.h" #include "json.hpp" diff --git a/tms_express/Frame_Encoding/FrameEncoder.h b/tms_express/Frame_Encoding/FrameEncoder.h index 8f6146a..7461f27 100644 --- a/tms_express/Frame_Encoding/FrameEncoder.h +++ b/tms_express/Frame_Encoding/FrameEncoder.h @@ -3,7 +3,7 @@ #ifndef TMS_EXPRESS_FRAMEENCODER_H #define TMS_EXPRESS_FRAMEENCODER_H -#include "Frame.h" +#include "Frame.hpp" #include #include #include diff --git a/tms_express/Frame_Encoding/FramePostprocessor.cpp b/tms_express/Frame_Encoding/FramePostprocessor.cpp index 25325d3..dc1c588 100644 --- a/tms_express/Frame_Encoding/FramePostprocessor.cpp +++ b/tms_express/Frame_Encoding/FramePostprocessor.cpp @@ -7,7 +7,7 @@ // Author: Joseph Bellahcen /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#include "Frame_Encoding/Frame.h" +#include "Frame_Encoding/Frame.hpp" #include "Frame_Encoding/FramePostprocessor.h" #include "Frame_Encoding/Tms5220CodingTable.h" diff --git a/tms_express/Frame_Encoding/FramePostprocessor.h b/tms_express/Frame_Encoding/FramePostprocessor.h index 3fc95bc..0a6630f 100644 --- a/tms_express/Frame_Encoding/FramePostprocessor.h +++ b/tms_express/Frame_Encoding/FramePostprocessor.h @@ -3,7 +3,7 @@ #ifndef TMS_EXPRESS_FRAMEPOSTPROCESSOR_H #define TMS_EXPRESS_FRAMEPOSTPROCESSOR_H -#include "Frame.h" +#include "Frame.hpp" #include namespace tms_express { diff --git a/tms_express/Frame_Encoding/Synthesizer.cpp b/tms_express/Frame_Encoding/Synthesizer.cpp index e076420..f113766 100644 --- a/tms_express/Frame_Encoding/Synthesizer.cpp +++ b/tms_express/Frame_Encoding/Synthesizer.cpp @@ -15,7 +15,7 @@ #include "Audio/AudioBuffer.hpp" #include "Frame_Encoding/Synthesizer.h" -#include "Frame_Encoding/Frame.h" +#include "Frame_Encoding/Frame.hpp" #include "Frame_Encoding/Tms5220CodingTable.h" #include diff --git a/tms_express/Frame_Encoding/Synthesizer.h b/tms_express/Frame_Encoding/Synthesizer.h index b1d7473..d1da625 100644 --- a/tms_express/Frame_Encoding/Synthesizer.h +++ b/tms_express/Frame_Encoding/Synthesizer.h @@ -3,7 +3,7 @@ #ifndef TMS_EXPRESS_SYNTHESIZER_H #define TMS_EXPRESS_SYNTHESIZER_H -#include "Frame_Encoding/Frame.h" +#include "Frame_Encoding/Frame.hpp" #include #include #include diff --git a/tms_express/User_Interfaces/MainWindow.h b/tms_express/User_Interfaces/MainWindow.h index ce10dcb..71b7fe8 100644 --- a/tms_express/User_Interfaces/MainWindow.h +++ b/tms_express/User_Interfaces/MainWindow.h @@ -6,7 +6,7 @@ #include "Audio/AudioBuffer.hpp" #include "Audio/AudioFilter.hpp" #include "Bitstream_Generation/BitstreamGenerator.hpp" -#include "Frame_Encoding/Frame.h" +#include "Frame_Encoding/Frame.hpp" #include "Frame_Encoding/FrameEncoder.h" #include "Frame_Encoding/FramePostprocessor.h" #include "Frame_Encoding/Synthesizer.h" From c310fa9120fb402a6781ea22adf78d06224be1ea Mon Sep 17 00:00:00 2001 From: Joseph Bellahcen Date: Sun, 23 Jul 2023 13:20:12 -0700 Subject: [PATCH 10/30] Refactored FrameEncoder --- CMakeLists.txt | 3 + test/FrameEncoderTests.cpp | 12 +- .../BitstreamGenerator.cpp | 4 +- tms_express/Frame_Encoding/FrameEncoder.cpp | 420 +++++++++--------- tms_express/Frame_Encoding/FrameEncoder.h | 50 --- tms_express/Frame_Encoding/FrameEncoder.hpp | 142 ++++++ tms_express/User_Interfaces/MainWindow.cpp | 6 +- tms_express/User_Interfaces/MainWindow.h | 2 +- 8 files changed, 364 insertions(+), 275 deletions(-) delete mode 100644 tms_express/Frame_Encoding/FrameEncoder.h create mode 100644 tms_express/Frame_Encoding/FrameEncoder.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 575f618..8999979 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,7 +16,10 @@ option(TMSEXPRESS_BUILD_TESTS "Build test programs" ON) # PROGRAM FILES # ==================================== include_directories( + # For lib directory ${CMAKE_CURRENT_LIST_DIR} + + # For project sources tms_express ) diff --git a/test/FrameEncoderTests.cpp b/test/FrameEncoderTests.cpp index 491b8e7..aadb5c5 100644 --- a/test/FrameEncoderTests.cpp +++ b/test/FrameEncoderTests.cpp @@ -1,5 +1,5 @@ #include "Frame_Encoding/Frame.hpp" -#include "Frame_Encoding/FrameEncoder.h" +#include "Frame_Encoding/FrameEncoder.hpp" #include @@ -19,7 +19,7 @@ TEST(FrameEncoderTests, StopFrame) { TEST(FrameEncoderTests, AsciiStopFrame) { auto frameEncoder = FrameEncoder(); - frameEncoder.parseAsciiBitstream("0x0f"); + frameEncoder.importASCIIFromString("0x0f"); auto hex = frameEncoder.toHex(); EXPECT_EQ(hex, "0f"); @@ -43,7 +43,7 @@ TEST(FrameEncoderTests, SilentFrame) { TEST(FrameEncoderTests, AsciiSilentFrame) { auto frameEncoder = FrameEncoder(); - frameEncoder.parseAsciiBitstream("0xf0"); + frameEncoder.importASCIIFromString("0xf0"); auto hex = frameEncoder.toHex(); EXPECT_EQ(hex, "f0"); @@ -66,7 +66,7 @@ TEST(FrameEncoderTests, VoicedFrame) { TEST(FrameEncoderTests, AsciiVoicedFrame) { auto frameEncoder = FrameEncoder(); - frameEncoder.parseAsciiBitstream("0xc8,0x88,0x4f,0x25,0xce,0xab,0x3c"); + frameEncoder.importASCIIFromString("0xc8,0x88,0x4f,0x25,0xce,0xab,0x3c"); auto bin = frameEncoder.toHex(); EXPECT_EQ(bin, "c8,88,4f,25,ce,ab,3c"); @@ -87,7 +87,7 @@ TEST(FrameEncoderTests, UnvoicedFrame) { TEST(FrameEncoderTests, AsciiUnvoicedFrame) { auto frameEncoder = FrameEncoder(); - frameEncoder.parseAsciiBitstream("0x08,0x88,0x4f,0xe5,0x01"); + frameEncoder.importASCIIFromString("0x08,0x88,0x4f,0xe5,0x01"); auto bin = frameEncoder.toHex(); EXPECT_EQ(bin, "08,88,4f,e5,01"); @@ -120,7 +120,7 @@ TEST(FrameEncoderTests, MixtureOfFrames) { TEST(FrameEncoderTests, AsciiMixtureOfFrames) { auto frameEncoder = FrameEncoder(); - frameEncoder.parseAsciiBitstream("0xc0,0x8c,0xa4,0x5b,0xe2,0xbc,0x0a,0x33,0x92,0x6e,0x89,0xf3,0x2a,0x08,0x88,0x4f,0xe5,0x01"); + frameEncoder.importASCIIFromString("0xc0,0x8c,0xa4,0x5b,0xe2,0xbc,0x0a,0x33,0x92,0x6e,0x89,0xf3,0x2a,0x08,0x88,0x4f,0xe5,0x01"); auto bin = frameEncoder.toHex(); EXPECT_EQ(bin, "c0,8c,a4,5b,e2,bc,0a,33,92,6e,89,f3,2a,08,88,4f,e5,01"); diff --git a/tms_express/Bitstream_Generation/BitstreamGenerator.cpp b/tms_express/Bitstream_Generation/BitstreamGenerator.cpp index 2de43c2..7fa9095 100644 --- a/tms_express/Bitstream_Generation/BitstreamGenerator.cpp +++ b/tms_express/Bitstream_Generation/BitstreamGenerator.cpp @@ -11,7 +11,7 @@ #include "Audio/AudioBuffer.hpp" #include "Audio/AudioFilter.hpp" #include "Frame_Encoding/Frame.hpp" -#include "Frame_Encoding/FrameEncoder.h" +#include "Frame_Encoding/FrameEncoder.hpp" #include "Frame_Encoding/FramePostprocessor.h" #include "LPC_Analysis/Autocorrelation.h" #include "LPC_Analysis/LinearPredictor.h" @@ -172,7 +172,7 @@ std::vector BitstreamGenerator::generateFrames( std::string BitstreamGenerator::serializeFrames( const std::vector& frames, const std::string &filename) const { // Encode frames to hex bitstreams - auto encoder = FrameEncoder(frames, style_ != ENCODERSTYLE_ASCII, ','); + auto encoder = FrameEncoder(frames, style_ != ENCODERSTYLE_ASCII); std::string bitstream; switch (style_) { diff --git a/tms_express/Frame_Encoding/FrameEncoder.cpp b/tms_express/Frame_Encoding/FrameEncoder.cpp index c046522..ce91bfc 100644 --- a/tms_express/Frame_Encoding/FrameEncoder.cpp +++ b/tms_express/Frame_Encoding/FrameEncoder.cpp @@ -1,321 +1,315 @@ -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// Class: FrameEncoder -// -// Description: The FrameEncoder generates a bitstream representing a Frame vector. This bitstream adheres to the LPC-10 -// specification, and data is segmented into reversed hex bytes to mimic the behavior of the TMS6100 Voice -// Synthesis Memory device. -// -// Author: Joseph Bellahcen -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -#include "Frame_Encoding/FrameEncoder.h" -#include "Frame_Encoding/Frame.hpp" -#include "Frame_Encoding/Tms5220CodingTable.h" +// Copyright (C) 2023 Joseph Bellahcen -#include "json.hpp" +#include "Frame_Encoding/FrameEncoder.hpp" -#include -#include -#include -#include #include #include +#include "lib/json.hpp" + +#include "Frame_Encoding/Frame.hpp" +#include "Frame_Encoding/Tms5220CodingTable.h" + namespace tms_express { -/// Create a new Frame Encoder with an empty frame buffer -/// -/// \param includeHexPrefix Whether or not to include '0x' before hex bytes -/// \param separator Character with which to separate hex bytes -FrameEncoder::FrameEncoder(bool includeHexPrefix, char separator) { - shouldIncludeHexPrefix = includeHexPrefix; - byteSeparator = separator; - frames = std::vector(); - binary = std::vector(1, ""); +/////////////////////////////////////////////////////////////////////////////// +// Initializers /////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +FrameEncoder::FrameEncoder(bool include_hex_prefix) { + include_hex_prefix_ = include_hex_prefix; + frames_ = std::vector(); + binary_bitstream_ = std::vector(1, ""); } -/// Create a new Frame Encoder and populate it with frames -/// -/// \param initialFrames Frames with which to populate encoder -/// \param includeHexPrefix Whether or not to include '0x' before hex bytes -/// \param separator Character with which to separate hex bytes -FrameEncoder::FrameEncoder(const std::vector &initialFrames, bool includeHexPrefix, char separator) { - binary = std::vector(1, ""); - byteSeparator = separator; - frames = std::vector(); - shouldIncludeHexPrefix = includeHexPrefix; - - append(initialFrames); +FrameEncoder::FrameEncoder(const std::vector &frames, + bool include_hex_prefix) { + binary_bitstream_ = std::vector(1, ""); + frames_ = std::vector(); + include_hex_prefix_ = include_hex_prefix; + + append(frames); } /////////////////////////////////////////////////////////////////////////////// -// Append Functions +// Frame Appenders //////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// -/// Append binary representation of a frame to the end of the encoder buffer -/// -/// \note The binary representation of a Frame is seldom cleanly divisible into bytes. As such, the first few bits of -/// a Frame may be packed into the empty space of an existing vector element, or the last few bits may partially -/// occupy a new vector element void FrameEncoder::append(Frame frame) { - frames.push_back(frame); + frames_.push_back(frame); auto bin = frame.toBinary(); - // Check to see if the previous byte is incomplete (contains less than 8 characters), and fill it if so - auto emptyBitsInLastByte = 8 - binary.back().size(); - if (emptyBitsInLastByte != 0) { - binary.back() += bin.substr(0, emptyBitsInLastByte); - bin.erase(0, emptyBitsInLastByte); + // The binary representation of a Frame is seldom cleanly divisible into + // bytes. As such, the first few bits of a Frame may be packed into the + // empty space of an existing vector element, or the last few bits may + // partially ccupy a new vector element + // + // Check to see if the previous byte is incomplete (contains less than 8 + // characters), and fill it if so + auto empty_bits_in_last_byte = 8 - binary_bitstream_.back().size(); + if (empty_bits_in_last_byte != 0) { + binary_bitstream_.back() += bin.substr(0, empty_bits_in_last_byte); + bin.erase(0, empty_bits_in_last_byte); } - // Segment the rest of the binary frame into binary. The final byte will likely be incomplete, but that will be - // addressed either in a subsequent call to append() or during hex stream generation + // Segment the rest of the binary frame into binary. The final byte will + // likely be incomplete, but that will be addressed either in a subsequent + // call to append() or during hex stream generation while (!bin.empty()) { auto byte = bin.substr(0, 8); - binary.push_back(byte); + binary_bitstream_.push_back(byte); bin.erase(0, 8); } } -/// Append binary representation of new frames to the end of the encoder buffer -/// -/// \param newFrames Frames to be appended -void FrameEncoder::append(const std::vector &newFrames) { - for (const auto &frame: newFrames) { +void FrameEncoder::append(const std::vector &frames) { + for (const auto &frame : frames) { append(frame); } } -/////////////////////////////////////////////////////////////////////////////// -// Import Functions -/////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +// (De-)Serialization //////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// -/// Import ASCII bitstream (CSV) from disk -/// -/// \param path Path to comma-delimited ASCII bytes -/// \return Number of frames imported -size_t FrameEncoder::importFromAscii(const std::string &path) { +size_t FrameEncoder::importASCIIFromFile(const std::string &path) { // Flatten bitstream and remove delimiter std::ifstream file(path); - std::string flatBitstream = std::string((std::istreambuf_iterator(file)), std::istreambuf_iterator()); + std::string flat = std::string((std::istreambuf_iterator(file)), + std::istreambuf_iterator()); - return parseAsciiBitstream(flatBitstream); + return importASCIIFromString(flat); } -/////////////////////////////////////////////////////////////////////////////// -// Serialization -/////////////////////////////////////////////////////////////////////////////// +size_t FrameEncoder::importASCIIFromString(std::string flat_bitstream) { + // Copy reversed-hex bytes into binary buffer + std::string buffer = reverseHexBytes(flat_bitstream); + + // Parse frames + frames_.clear(); + const auto blank_frame = Frame(0, false, 0.0f, std::vector(10, 0.f)); + + while (!buffer.empty()) { + // TODO(Joseph Bellahcen): Handle exception + auto energy_idx = std::stoul(buffer.substr(0, 4), nullptr, 2); + + // Stop frame + if (energy_idx == 0xf) { + break; + } + + // Silent frame + if (energy_idx == 0x0) { + append(blank_frame); + buffer.erase(0, 4); + continue; + } + + auto is_repeat = (buffer[4] == '1'); + // TODO(Joseph Bellahcen): Handle exception + auto pitch_idx = std::stoul(buffer.substr(5, 6), nullptr, 2); + + auto gain = Tms5220CodingTable::rms.at(energy_idx); + auto pitch = Tms5220CodingTable::pitch.at(pitch_idx); + + if (is_repeat) { + append(Frame(pitch, false, gain, std::vector(10, 0.0f))); + frames_.end()->setRepeat(true); + buffer.erase(0, 11); + continue; + } + + float k1, k2, k3, k4, k5, k6, k7, k8, k9, k10 = 0; + extractUnvoicedCoeffs(buffer, &k1, &k2, &k3, &k4); + + if (pitch == 0x0) { + buffer.erase(0, 29); + + } else { + extractVoicedCoeffs(buffer, &k5, &k6, &k7, &k8, &k9, &k10); + + buffer.erase(0, 50); + } + + append( + Frame(pitch, + pitch != 0x0, + gain, + std::vector{k1, k2, k3, k4, k5, k6, k7, k8, k9, k10})); + } + + return frames_.size(); +} -/// Serialize the Frame data to a stream of hex bytes -/// -/// \note Appending a stop frame tells the TMS5220 to exit Speak External mode. It is not necessary for -/// software emulations of the TMS5220 or for bitstreams intended to be stored in the TMS6100 Voice Synthesis -/// Memory IC -// -/// \param shouldAppendStopFrame Whether or not to include an explicit stop frame at the end of the bitstream std::string FrameEncoder::toHex(bool shouldAppendStopFrame) { - std::string hexStream; + std::string hex_stream; if (shouldAppendStopFrame) { appendStopFrame(); } // Pad final byte with zeros - auto emptyBitsInLastByte = 8 - binary.back().size(); - if (emptyBitsInLastByte != 0) { - binary.back() += std::string(emptyBitsInLastByte, '0'); + auto n_empty_bits_in_last_byte = 8 - binary_bitstream_.back().size(); + if (n_empty_bits_in_last_byte != 0) { + binary_bitstream_.back() += std::string(n_empty_bits_in_last_byte, '0'); } // Reverse each byte and convert to hex - for (auto byte: binary) { + for (auto byte : binary_bitstream_) { std::reverse(byte.begin(), byte.end()); - hexStream += byteToHex(byte) + byteSeparator; + hex_stream += binToHex(byte, include_hex_prefix_) + byte_delimiter; } // Remove final trailing comma - hexStream.erase(hexStream.end() - 1); - - return hexStream; + hex_stream.erase(hex_stream.end() - 1); + return hex_stream; } -/// Serialize frame data to a vector of raw bytes -/// -/// \return Frame table data represented as bytes -std::vector FrameEncoder::toBin(bool shouldAppendStopFrame) { +std::vector FrameEncoder::toBytes(bool append_stop_frame) { auto bytes = std::vector(); - if (shouldAppendStopFrame) { + if (append_stop_frame) { appendStopFrame(); } // Pad final byte with zeros - auto emptyBitsInLastByte = 8 - binary.back().size(); - if (emptyBitsInLastByte != 0) { - binary.back() += std::string(emptyBitsInLastByte, '0'); + auto n_empty_bits_in_last_byte = 8 - binary_bitstream_.back().size(); + if (n_empty_bits_in_last_byte != 0) { + binary_bitstream_.back() += std::string(n_empty_bits_in_last_byte, '0'); } // Reverse each byte and convert to hex - for (auto byte: binary) { + for (auto byte : binary_bitstream_) { std::reverse(byte.begin(), byte.end()); - auto data = std::byte(std::stoul(byteToHex(byte), nullptr, 16)); + auto data = std::byte( + std::stoul(binToHex(byte, include_hex_prefix_), nullptr, 16)); bytes.push_back(data); } return bytes; } -/// Serialize the Frame data to a JSON object -std::string FrameEncoder::toJSON() { +std::string FrameEncoder::toJSON() const { nlohmann::json json; - for (auto frame: frames) { + for (auto frame : frames_) { json.push_back(frame.toJSON()); } return json.dump(4); } -/// Pass the frame table vector -std::vector FrameEncoder::frameTable() { - return frames; -} - /////////////////////////////////////////////////////////////////////////////// -// Helpers +// Accessors ////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// -/// Append a stop frame to the end of the bitstream -/// -/// \note See \code FrameEncoder::toHex() \endcode for more information on stop frames -void FrameEncoder::appendStopFrame() { - auto bin = std::string("1111"); - - // Check to see if the previous byte is incomplete (contains less than 8 characters), and fill it if so - auto emptyBitsInLastByte = 8 - binary.back().size(); - if (emptyBitsInLastByte != 0) { - binary.back() += bin.substr(0, emptyBitsInLastByte); - bin.erase(0, emptyBitsInLastByte); - } - - // Segment the rest of the binary frame into bytes. The final byte will likely be incomplete, but that will be - // addressed either in a subsequent call to append() or during hex stream generation - while (!bin.empty()) { - auto byte = bin.substr(0, 8); - binary.push_back(byte); - - bin.erase(0, 8); - } +std::vector FrameEncoder::getFrameTable() const { + return frames_; } -/// Convert binary string to its ASCII hex bytes -std::string FrameEncoder::byteToHex(const std::string &byte) const { - int value = std::stoi(byte, nullptr, 2); +/////////////////////////////////////////////////////////////////////////////// +// Static Helpers ///////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +std::string FrameEncoder::binToHex(const std::string &bin_str, + bool include_hex_prefix) { + // TODO(Joseph Bellahcen): Handle exception + int value = std::stoi(bin_str, nullptr, 2); - char hexByte[6]; + char hex_byte[6]; - if (shouldIncludeHexPrefix) { - snprintf(hexByte, 5, "0x%02x", value); + if (include_hex_prefix) { + snprintf(hex_byte, sizeof(hex_byte), "0x%02x", value); } else { - snprintf(hexByte, 5, "%02x", value); + snprintf(hex_byte, sizeof(hex_byte), "%02x", value); } - return {hexByte}; + return {hex_byte}; } -size_t FrameEncoder::parseAsciiBitstream(std::string flatBitstream) { - // Copy reversed-hex bytes into binary buffer +std::string FrameEncoder::reverseHexBytes(std::string bitstream) { std::string buffer; - flatBitstream.erase(std::remove(flatBitstream.begin(), flatBitstream.end(), ','), flatBitstream.end()); - for (int i = 0; i < flatBitstream.size() - 1; i += 4) { - auto substr = flatBitstream.substr(i, 4); + bitstream.erase(std::remove(bitstream.begin(), bitstream.end(), ','), + bitstream.end()); + + // TODO(Joseph Bellahcen): Handle prefix/no prefix + for (int i = 0; i < bitstream.size() - 1; i += 4) { + auto substr = bitstream.substr(i, 4); std::reverse(substr.begin(), substr.end()); + + // TODO(Joseph Bellahcen): Handle exception uint8_t byte = std::stoul(substr, nullptr, 16); auto bin = std::bitset<8>(byte); - auto firstHalf = bin.to_string().substr(0, 4); - auto secondhalf = bin.to_string().substr(4, 4); + auto first_half = bin.to_string().substr(0, 4); + auto second_half = bin.to_string().substr(4, 4); - std::reverse(firstHalf.begin(), firstHalf.end()); - std::reverse(secondhalf.begin(), secondhalf.end()); - buffer += (firstHalf + secondhalf); + std::reverse(first_half.begin(), first_half.end()); + std::reverse(second_half.begin(), second_half.end()); + buffer += (first_half + second_half); } - // Parse frames - frames.clear(); - const auto blankFrame = Frame(0, false, 0.0f, std::vector(10, 0.0f)); - - while (!buffer.empty()) { - auto energyIdx = std::stoul(buffer.substr(0, 4), nullptr, 2); - - // Stop frame - if (energyIdx == 0xf) { - break; - } - - // Silent frame - if (energyIdx == 0x0) { - append(blankFrame); - buffer.erase(0, 4); - continue; - } - - auto isRepeat = (buffer[4] == '1'); - auto pitchIdx = std::stoul(buffer.substr(5, 6), nullptr, 2); - - auto gain = Tms5220CodingTable::rms.at(energyIdx); - auto pitch = int(Tms5220CodingTable::pitch.at(pitchIdx)); - - if (isRepeat) { - append(Frame(pitch, false, gain, std::vector(10, 0.0f))); - frames.end()->setRepeat(true); - buffer.erase(0, 11); - continue; - } - - auto kIdx1 = std::stoul(buffer.substr(11, 5), nullptr, 2); - auto kIdx2 = std::stoul(buffer.substr(16, 5), nullptr, 2); - auto kIdx3 = std::stoul(buffer.substr(21, 4), nullptr, 2); - auto kIdx4 = std::stoul(buffer.substr(25, 4), nullptr, 2); + return buffer; +} - auto k1 = Tms5220CodingTable::k1.at(kIdx1); - auto k2 = Tms5220CodingTable::k2.at(kIdx2); - auto k3 = Tms5220CodingTable::k3.at(kIdx3); - auto k4 = Tms5220CodingTable::k4.at(kIdx4); +void FrameEncoder::extractUnvoicedCoeffs(const std::string &chunk, float *k1, + float *k2, float *k3, float *k4) { + auto k1_idx = std::stoul(chunk.substr(11, 5), nullptr, 2); + auto k2_idx = std::stoul(chunk.substr(16, 5), nullptr, 2); + auto k3_idx = std::stoul(chunk.substr(21, 4), nullptr, 2); + auto k4_idx = std::stoul(chunk.substr(25, 4), nullptr, 2); + + // TODO(Joseph Bellahcen): Guard against nullptr dereference + *k1 = Tms5220CodingTable::k1.at(k1_idx); + *k2 = Tms5220CodingTable::k2.at(k2_idx); + *k3 = Tms5220CodingTable::k3.at(k3_idx); + *k4 = Tms5220CodingTable::k4.at(k4_idx); +} - auto k5 = 0.0f; - auto k6 = 0.0f; - auto k7 = 0.0f; - auto k8 = 0.0f; - auto k9 = 0.0f; - auto k10 = 0.0f; +void FrameEncoder::extractVoicedCoeffs(const std::string &chunk, float *k5, + float *k6, float *k7, float *k8, float *k9, float *k10) { + auto k5_idx = std::stoul(chunk.substr(29, 4), nullptr, 2); + auto k6_idx = std::stoul(chunk.substr(33, 4), nullptr, 2); + auto k7_idx = std::stoul(chunk.substr(37, 4), nullptr, 2); + auto k8_idx = std::stoul(chunk.substr(41, 3), nullptr, 2); + auto k9_idx = std::stoul(chunk.substr(44, 3), nullptr, 2); + auto k10_idx = std::stoul(chunk.substr(47, 3), nullptr, 2); + + // TODO(Joseph Bellahcen): Guard against nullptr dereference + *k5 = Tms5220CodingTable::k5.at(k5_idx); + *k6 = Tms5220CodingTable::k6.at(k6_idx); + *k7 = Tms5220CodingTable::k7.at(k7_idx); + *k8 = Tms5220CodingTable::k8.at(k8_idx); + *k9 = Tms5220CodingTable::k9.at(k9_idx); + *k10 = Tms5220CodingTable::k10.at(k10_idx); +} - if (pitch == 0x0) { - buffer.erase(0, 29); +/////////////////////////////////////////////////////////////////////////////// +// Helpers //////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// - } else { - auto kIdx5 = std::stoul(buffer.substr(29, 4), nullptr, 2); - auto kIdx6 = std::stoul(buffer.substr(33, 4), nullptr, 2); - auto kIdx7 = std::stoul(buffer.substr(37, 4), nullptr, 2); - auto kIdx8 = std::stoul(buffer.substr(41, 3), nullptr, 2); - auto kIdx9 = std::stoul(buffer.substr(44, 3), nullptr, 2); - auto kIdx10 = std::stoul(buffer.substr(47, 3), nullptr, 2); - - k5 = Tms5220CodingTable::k5.at(kIdx5); - k6 = Tms5220CodingTable::k6.at(kIdx6); - k7 = Tms5220CodingTable::k7.at(kIdx7); - k8 = Tms5220CodingTable::k8.at(kIdx8); - k9 = Tms5220CodingTable::k9.at(kIdx9); - k10 = Tms5220CodingTable::k10.at(kIdx10); +void FrameEncoder::appendStopFrame() { + std::string bin = "1111"; + + // Check to see if the previous byte is incomplete (contains less than 8 + // characters), and fill it if so + auto empty_bits_in_last_byte = 8 - binary_bitstream_.back().size(); + if (empty_bits_in_last_byte != 0) { + binary_bitstream_.back() += bin.substr(0, empty_bits_in_last_byte); + bin.erase(0, empty_bits_in_last_byte); + } - buffer.erase(0, 50); - } + // Segment the rest of the binary frame into bytes. The final byte will + // likely be incomplete, but that will be addressed either in a subsequent + // call to append() or during hex stream generation + while (!bin.empty()) { + auto byte = bin.substr(0, 8); + binary_bitstream_.push_back(byte); - append(Frame(pitch, pitch != 0x0, gain, std::vector{k1, k2, k3, k4, k5, k6, k7, k8, k9, k10})); + bin.erase(0, 8); } - - return frames.size(); } }; // namespace tms_express diff --git a/tms_express/Frame_Encoding/FrameEncoder.h b/tms_express/Frame_Encoding/FrameEncoder.h deleted file mode 100644 index 7461f27..0000000 --- a/tms_express/Frame_Encoding/FrameEncoder.h +++ /dev/null @@ -1,50 +0,0 @@ -// Author: Joseph Bellahcen - -#ifndef TMS_EXPRESS_FRAMEENCODER_H -#define TMS_EXPRESS_FRAMEENCODER_H - -#include "Frame.hpp" -#include -#include -#include - -namespace tms_express { - -class FrameEncoder { -public: - explicit FrameEncoder(bool includeHexPrefix = false, char separator = ','); - explicit FrameEncoder(const std::vector &initialFrames, bool includeHexPrefix = false, char separator = ','); - - // Append functions - void append(Frame frame); - void append(const std::vector &newFrames); - - // Import functions - size_t importFromAscii(const std::string &path); - //size_t importFromBin(const std::string &path); - //size_t importFromEmbedded(std::string path); - //int importFromJson(std::string path) {}; - - // Serialization - std::string toHex(bool shouldAppendStopFrame = true); - std::vector toBin(bool shouldAppendStopFrame = true); - - std::string toJSON(); - std::vector frameTable(); - - // De-serialization - size_t parseAsciiBitstream(std::string flatBitstream); - -private: - std::vector binary; - char byteSeparator; - std::vector frames; - bool shouldIncludeHexPrefix; - - void appendStopFrame(); - [[nodiscard]] std::string byteToHex(const std::string &byte) const; -}; - -}; // namespace tms_express - -#endif //TMS_EXPRESS_FRAMEENCODER_H diff --git a/tms_express/Frame_Encoding/FrameEncoder.hpp b/tms_express/Frame_Encoding/FrameEncoder.hpp new file mode 100644 index 0000000..2a4f55c --- /dev/null +++ b/tms_express/Frame_Encoding/FrameEncoder.hpp @@ -0,0 +1,142 @@ +// Copyright (C) 2023 Joseph Bellahcen + +#ifndef TMS_EXPRESS_FRAME_ENCODING_FRAMEENCODER_HPP_ +#define TMS_EXPRESS_FRAME_ENCODING_FRAMEENCODER_HPP_ + +#include +#include + +#include "Frame_Encoding/Frame.hpp" + +namespace tms_express { + +/// @brief Generates bitstreams which adhere to TMS5220 LPC-10 specification +/// @details The Frame Encoder represents Frames as reversed hex bytes, +/// mimicking the behavior of the TMS6100 Voice Synthesis Memory +class FrameEncoder { + public: + /////////////////////////////////////////////////////////////////////////// + // Initializers /////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @brief Creates a new Frame Encoder with empty Frame buffer + /// @param include_hex_prefix true to include '0x' in front of hex bytes, + /// false otherwise + explicit FrameEncoder(bool include_hex_prefix = false); + + /// @brief Creates a new Frame Encoder, starting with given Frame buffer + /// @param frames Vector of Frames to encode + /// @param include_hex_prefix true to include '0x' in front of hex bytes, + /// false otherwise + explicit FrameEncoder(const std::vector &frames, + bool include_hex_prefix = false); + + /////////////////////////////////////////////////////////////////////////// + // Frame Appenders //////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @brief Appends single Frame to end of Frame buffer + /// @param frame Frame to append + void append(Frame frame); + + /// @brief Appends multiple Frames to end of Frame buffer + /// @param frames Vector of frames to append + void append(const std::vector &frames); + + /////////////////////////////////////////////////////////////////////////// + // (De-)Serialization ///////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @brief Imports ASCII bitstream from disk + /// @param path Path to ASCII bitstream file + /// @return Number of Frames imported from file + size_t importASCIIFromFile(const std::string &path); + + /// @brief Imports ASCII bitstream from string + /// @param flat_bitstream String of comma-delimited ASCII hex bytes + /// @return Number of Frames imported from string + size_t importASCIIFromString(std::string flat_bitstream); + + /// @brief Serializes Frames to ASCII bitstream + /// @param append_stop_frame true to append explicit stop frame to end of + /// bitstream, false otherwise + /// @return ASCII bitstream, as string + /// @note Appending a stop frame tells the TMS5220 to exit Speak External + /// mode. It is not strictly required for emulations of the device, + /// nor for bitstreams intended to be stored on a TMS6100 Voice + /// Synthesis Memory chip + std::string toHex(bool append_stop_frame = true); + + /// @brief Serializes Frames to vector of raw bytes + /// @param append_stop_frame true to append explicit stop frame to end of + /// bitstream, false otherwise + /// @return Vector of bytes corresponding to bitstream + std::vector toBytes(bool append_stop_frame = true); + + /// @brief Converts Frames buffer to JSON array of Frame JSON objects + /// @return JSON object array, as a string + std::string toJSON() const; + + /////////////////////////////////////////////////////////////////////////// + // Accessors ////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + std::vector getFrameTable() const; + + private: + /////////////////////////////////////////////////////////////////////////// + // Static Helpers ///////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @brief Converts binary string to ASCII hex byte + /// @param bin_str Binary string corresponding to byte + /// @param include_hex_prefix true to include '0x' prefix, false otherwise + /// @return ASCII hex representation of binary string + static std::string binToHex(const std::string &bin_str, + bool include_hex_prefix); + + /// @brief Reverses hex bytes in bitstream, effectively converting between + /// host representation and TMS6100 Voice Synthesis Memory format + /// @param bitstream Bitstream string + /// @return Bitstream with reversed hex bytes + static std::string reverseHexBytes(std::string bitstream); + + /// @brief Extracts unvoiced LPC reflector coefficients (K1-K4) from binary + /// representation of Frame + /// @param chunk Chunk holding binary representation of single Frame + /// @param k1 Pointer to store K1 LPC reflector coefficient + /// @param k2 Pointer to store K2 LPC reflector coefficient + /// @param k3 Pointer to store K3 LPC reflector coefficient + /// @param k4 Pointer to store K4 LPC reflector coefficient + static void extractUnvoicedCoeffs(const std::string &chunk, float *k1, + float *k2, float *k3, float *k4); + + /// @brief Extracts voiced LPC reflector coefficients (K5-K10) from binary + /// representation of Frame + /// @param chunk Chunk holding binary representation of single Frame + /// @param k5 Pointer to store K5 LPC reflector coefficient + /// @param k6 Pointer to store K6 LPC reflector coefficient + /// @param k7 Pointer to store K7 LPC reflector coefficient + /// @param k8 Pointer to store K8 LPC reflector coefficient + static void extractVoicedCoeffs(const std::string &chunk, float *k5, + float *k6, float *k7, float *k8, float *k9, float *k10); + + /////////////////////////////////////////////////////////////////////////// + // Helpers //////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + void appendStopFrame(); + + /////////////////////////////////////////////////////////////////////////// + // Members //////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + std::vector binary_bitstream_; + static const char byte_delimiter = ','; + std::vector frames_; + bool include_hex_prefix_; +}; + +}; // namespace tms_express + +#endif // TMS_EXPRESS_FRAME_ENCODING_FRAMEENCODER_HPP_ diff --git a/tms_express/User_Interfaces/MainWindow.cpp b/tms_express/User_Interfaces/MainWindow.cpp index f8a1b5a..a5b612c 100644 --- a/tms_express/User_Interfaces/MainWindow.cpp +++ b/tms_express/User_Interfaces/MainWindow.cpp @@ -520,14 +520,14 @@ void MainWindow::performBitstreamParsing(const std::string &path) { auto frameEncoder = FrameEncoder(); if (filePath.endsWith(".lpc")) { - auto frame_count = frameEncoder.importFromAscii(path); + auto frame_count = frameEncoder.importASCIIFromFile(path); } else { // TODO: Binary parsing return; } - frameTable = frameEncoder.frameTable(); + frameTable = frameEncoder.getFrameTable(); } void MainWindow::exportBitstream(const std::string& path) { @@ -543,7 +543,7 @@ void MainWindow::exportBitstream(const std::string& path) { lpcOut.close(); } else if (filePath.endsWith(".bin")) { - auto bin = frameEncoder.toBin(); + auto bin = frameEncoder.toBytes(); std::ofstream binOut(path, std::ios::out | std::ios::binary); binOut.write((char *)(bin.data()), long(bin.size())); diff --git a/tms_express/User_Interfaces/MainWindow.h b/tms_express/User_Interfaces/MainWindow.h index 71b7fe8..a8dd6e9 100644 --- a/tms_express/User_Interfaces/MainWindow.h +++ b/tms_express/User_Interfaces/MainWindow.h @@ -7,7 +7,7 @@ #include "Audio/AudioFilter.hpp" #include "Bitstream_Generation/BitstreamGenerator.hpp" #include "Frame_Encoding/Frame.hpp" -#include "Frame_Encoding/FrameEncoder.h" +#include "Frame_Encoding/FrameEncoder.hpp" #include "Frame_Encoding/FramePostprocessor.h" #include "Frame_Encoding/Synthesizer.h" #include "LPC_Analysis/PitchEstimator.h" From fe21a2d67320610681156d1b3d4650e629361400 Mon Sep 17 00:00:00 2001 From: Joseph Bellahcen Date: Sun, 23 Jul 2023 18:05:49 -0700 Subject: [PATCH 11/30] Refactored FramePostProcessor --- .../BitstreamGenerator.cpp | 2 +- .../BitstreamGenerator.hpp | 2 +- .../Bitstream_Generation/PathUtils.hpp | 2 +- tms_express/Frame_Encoding/Frame.hpp | 2 +- tms_express/Frame_Encoding/FrameEncoder.hpp | 2 +- .../Frame_Encoding/FramePostprocessor.cpp | 196 ++++++++---------- .../Frame_Encoding/FramePostprocessor.h | 44 ---- .../Frame_Encoding/FramePostprocessor.hpp | 108 ++++++++++ tms_express/User_Interfaces/MainWindow.cpp | 2 +- tms_express/User_Interfaces/MainWindow.h | 2 +- 10 files changed, 201 insertions(+), 161 deletions(-) delete mode 100644 tms_express/Frame_Encoding/FramePostprocessor.h create mode 100644 tms_express/Frame_Encoding/FramePostprocessor.hpp diff --git a/tms_express/Bitstream_Generation/BitstreamGenerator.cpp b/tms_express/Bitstream_Generation/BitstreamGenerator.cpp index 7fa9095..46abb5b 100644 --- a/tms_express/Bitstream_Generation/BitstreamGenerator.cpp +++ b/tms_express/Bitstream_Generation/BitstreamGenerator.cpp @@ -12,7 +12,7 @@ #include "Audio/AudioFilter.hpp" #include "Frame_Encoding/Frame.hpp" #include "Frame_Encoding/FrameEncoder.hpp" -#include "Frame_Encoding/FramePostprocessor.h" +#include "Frame_Encoding/FramePostprocessor.hpp" #include "LPC_Analysis/Autocorrelation.h" #include "LPC_Analysis/LinearPredictor.h" #include "LPC_Analysis/PitchEstimator.h" diff --git a/tms_express/Bitstream_Generation/BitstreamGenerator.hpp b/tms_express/Bitstream_Generation/BitstreamGenerator.hpp index 5e0cf0a..1c23960 100644 --- a/tms_express/Bitstream_Generation/BitstreamGenerator.hpp +++ b/tms_express/Bitstream_Generation/BitstreamGenerator.hpp @@ -148,4 +148,4 @@ class BitstreamGenerator { }; // namespace tms_express -#endif // TMS_EXPRESS_BITSTREAM_GENERATION_BITSTREAMGENERATOR_HPP_ +#endif // TMS_EXPRESS_BITSTREAM_GENERATION_BITSTREAMGENERATOR_HPP_ diff --git a/tms_express/Bitstream_Generation/PathUtils.hpp b/tms_express/Bitstream_Generation/PathUtils.hpp index 8d790b1..514a244 100644 --- a/tms_express/Bitstream_Generation/PathUtils.hpp +++ b/tms_express/Bitstream_Generation/PathUtils.hpp @@ -88,4 +88,4 @@ class PathUtils { }; // namespace tms_express -#endif // TMS_EXPRESS_BITSTREAM_GENERATION_PATHUTILS_HPP_ +#endif // TMS_EXPRESS_BITSTREAM_GENERATION_PATHUTILS_HPP_ diff --git a/tms_express/Frame_Encoding/Frame.hpp b/tms_express/Frame_Encoding/Frame.hpp index 2486274..8862f63 100644 --- a/tms_express/Frame_Encoding/Frame.hpp +++ b/tms_express/Frame_Encoding/Frame.hpp @@ -175,4 +175,4 @@ class Frame { }; // namespace tms_express -#endif // TMS_EXPRESS_FRAME_ENCODING_FRAME_HPP_ +#endif // TMS_EXPRESS_FRAME_ENCODING_FRAME_HPP_ diff --git a/tms_express/Frame_Encoding/FrameEncoder.hpp b/tms_express/Frame_Encoding/FrameEncoder.hpp index 2a4f55c..e47a17d 100644 --- a/tms_express/Frame_Encoding/FrameEncoder.hpp +++ b/tms_express/Frame_Encoding/FrameEncoder.hpp @@ -139,4 +139,4 @@ class FrameEncoder { }; // namespace tms_express -#endif // TMS_EXPRESS_FRAME_ENCODING_FRAMEENCODER_HPP_ +#endif // TMS_EXPRESS_FRAME_ENCODING_FRAMEENCODER_HPP_ diff --git a/tms_express/Frame_Encoding/FramePostprocessor.cpp b/tms_express/Frame_Encoding/FramePostprocessor.cpp index dc1c588..256cb2c 100644 --- a/tms_express/Frame_Encoding/FramePostprocessor.cpp +++ b/tms_express/Frame_Encoding/FramePostprocessor.cpp @@ -1,140 +1,93 @@ -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// Class: FramePostprocessor -// -// Description: After LPC analysis and Frame packing, postprocessing may improve the quality and realism of synthesized -// speech. The FramePostprocessor facilitates such modifications. -// -// Author: Joseph Bellahcen -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2023 Joseph Bellahcen -#include "Frame_Encoding/Frame.hpp" -#include "Frame_Encoding/FramePostprocessor.h" -#include "Frame_Encoding/Tms5220CodingTable.h" +#include "Frame_Encoding/FramePostprocessor.hpp" #include +#include "Frame_Encoding/Frame.hpp" +#include "Frame_Encoding/Tms5220CodingTable.h" + namespace tms_express { -/// Create a new Frame Postprocessor -/// -/// \param frames Frames to modify -/// \param maxVoicedGainDB Max audio gain for voiced (vowel) segments (in decibels) -/// \param maxUnvoicedGainDB Max audio gain for unvoiced (consonant) segments (in decibels) -FramePostprocessor::FramePostprocessor(std::vector *frames, float maxVoicedGainDB, float maxUnvoicedGainDB) { - originalFrameTable = std::vector(frames->begin(), frames->end()); - frameTable = frames; - maxUnvoicedGain = maxUnvoicedGainDB; - maxVoicedGain = maxVoicedGainDB; +/////////////////////////////////////////////////////////////////////////////// +// Initializers /////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +FramePostprocessor::FramePostprocessor(std::vector *frames, + float max_voiced_gain_db, float max_unvoiced_gain_db) { + original_frame_table_ = std::vector(frames->begin(), frames->end()); + frame_table_ = frames; + max_unvoiced_gain_db_ = max_unvoiced_gain_db; + max_voiced_gain_db_ = max_voiced_gain_db; } + /////////////////////////////////////////////////////////////////////////////// -// Getters & Setters +// Accessors ////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// float FramePostprocessor::getMaxUnvoicedGainDB() const { - return maxUnvoicedGain; + return max_unvoiced_gain_db_; } -void FramePostprocessor::setMaxUnvoicedGainDB(float gainDB) { - maxUnvoicedGain = gainDB; +void FramePostprocessor::setMaxUnvoicedGainDB(float gain_db) { + max_unvoiced_gain_db_ = gain_db; } float FramePostprocessor::getMaxVoicedGainDB() const { - return maxVoicedGain; + return max_voiced_gain_db_; } -void FramePostprocessor::setMaxVoicedGainDB(float gainDB) { - maxVoicedGain = gainDB; +void FramePostprocessor::setMaxVoicedGainDB(float gain_db) { + max_voiced_gain_db_ = gain_db; } /////////////////////////////////////////////////////////////////////////////// -// Frame Table Manipulations +// Frame Table Manipulators /////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// -/// Identify frames which are similar to their neighbor and mark them as repeated -/// -/// \note Because the human vocal tract changes rather slowly, consecutive encoded Frames may not always vary -/// significantly. In this case, the size of the bitstream can be reduced by marking certain Frames a repeats of -/// preceding ones and allowing the LPC synthesizer to reuse parameters -/// -/// \return Number of frames converted to a repeat frame int FramePostprocessor::detectRepeatFrames() { - int nRepeatFrames = 0; + int n_repeat_frames = 0; - for (int i = 1; i < frameTable->size(); i++) { - Frame previousFrame = frameTable->at(i - 1); - Frame &frame = frameTable->at(i); + for (int i = 1; i < frame_table_->size(); i++) { + Frame previous_frame = frame_table_->at(i - 1); + Frame ¤t_frame = frame_table_->at(i); - if (frame.isSilent() || previousFrame.isSilent()) { + if (current_frame.isSilent() || previous_frame.isSilent()) { continue; } - // TODO: Implement variety of repeat detection algorithms - // The first reflector coefficient is useful in characterizing a Frame, and experimentally is a good indicator - // of similarity between consecutive Frames - int prevCoeff = previousFrame.quantizedCoeffs()[0]; - auto coeff = frame.quantizedCoeffs()[0]; + // The first reflector coefficient is typically effective at + // characterizing a Frame, and a useful indicator of similarity + int previous_coeff = previous_frame.quantizedCoeffs()[0]; + auto current_coeff = current_frame.quantizedCoeffs()[0]; - if (abs(coeff - prevCoeff) == 1) { - frame.setRepeat(true); - nRepeatFrames++; + if (abs(current_coeff - previous_coeff) == 1) { + current_frame.setRepeat(true); + n_repeat_frames++; } } - return nRepeatFrames; + return n_repeat_frames; } -/// Normalize Frame gain -/// -/// \note Gain normalization gain help reduce DC offsets and improve perceived volume void FramePostprocessor::normalizeGain() { normalizeGain(true); normalizeGain(false); } -/// Normalize gain of either all voiced or all unvoiced frames -/// -/// \param normalizeVoicedFrames Whether to operate on voiced or unvoiced frames -void FramePostprocessor::normalizeGain(bool normalizeVoicedFrames) { - // Compute the max gain value for a Frame category - float maxGain = 0.0f; - for (const Frame &frame : *frameTable) { - bool isVoiced = frame.isVoiced(); - float gain = frame.getGain(); - - if (isVoiced == normalizeVoicedFrames && gain > maxGain) { - maxGain = gain; - } - } - - // Apply scaling factor to improve naturalness of perceived volume - float scale = (normalizeVoicedFrames ? maxVoicedGain : maxUnvoicedGain) / maxGain; - for (Frame &frame : *frameTable) { - bool isVoiced = frame.isVoiced(); - float gain = frame.getGain(); - - if (isVoiced == normalizeVoicedFrames) { - float scaledGain = gain * scale; - frame.setGain(scaledGain); - } - } -} - -/// Shift gain by an integer offset in the coding table -/// -/// \note Following LPC analysis, changing the gain of audio is as simple as selecting a new index of the energy -/// table. A ceiling is applied to the offset to prevent unstable bitstreams void FramePostprocessor::shiftGain(int offset) { // If zero offset, do nothing if (!offset) { return; } - for (Frame &frame : *frameTable) { - int quantizedGain = frame.quantizedGain(); - int change = quantizedGain + offset; + for (Frame &frame : *frame_table_) { + int quantized_gain = frame.quantizedGain(); + int change = quantized_gain + offset; - // If the shifted gain would exceed the maximum representable gain of the coding table, let it "hit the - // ceiling." Overuse of the largest gain parameter may destabilize the synthesized signal + // If the shifted gain would exceed the maximum representable gain of + // the coding table, let it "hit the ceiling." Overuse of the largest + // gain parameter may destabilize the synthesized signal if (change >= Tms5220CodingTable::rms.size()) { frame.setGain(*Tms5220CodingTable::rms.end()); @@ -147,64 +100,87 @@ void FramePostprocessor::shiftGain(int offset) { } } -/// Shift pitch by an integer offset in the coding table void FramePostprocessor::shiftPitch(int offset) { if (!offset) { return; } - for (Frame &frame : *frameTable) { - int quantizedPitch = frame.quantizedPitch(); - int change = quantizedPitch + offset; + for (Frame &frame : *frame_table_) { + int quantized_pitch = frame.quantizedPitch(); + int change = quantized_pitch + offset; // If the Frame is silent, do nothing if (frame.isSilent()) { continue; } - // If the shifted gain would exceed the maximum representable gain of the coding table, let it "hit the - // ceiling." Overuse of the largest gain parameter may destabilize the synthesized signal if (change >= Tms5220CodingTable::pitch.size()) { - frame.setPitch(int(*Tms5220CodingTable::pitch.end())); + frame.setPitch(*Tms5220CodingTable::pitch.end()); } else if (change < 0) { frame.setPitch(0); } else { - frame.setPitch(int(Tms5220CodingTable::pitch.at(change))); + frame.setPitch(Tms5220CodingTable::pitch.at(change)); } } - } -/// Set the pitch of all non-silent frames to an index of the coding table void FramePostprocessor::overridePitch(int index) { - for (Frame &frame : *frameTable) { + for (Frame &frame : *frame_table_) { if (!frame.isSilent()) { if (index >= Tms5220CodingTable::pitch.size()) { - frame.setPitch(int(*Tms5220CodingTable::pitch.end())); + frame.setPitch(*Tms5220CodingTable::pitch.end()); } else { - frame.setPitch(int(Tms5220CodingTable::pitch.at(index))); + frame.setPitch(Tms5220CodingTable::pitch.at(index)); } } } } /////////////////////////////////////////////////////////////////////////////// -// Utility +// Utility //////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// -/// Restore frame table to its initialization state -/// -/// \note This function will NOT reset voiced or unvoiced limits void FramePostprocessor::reset() { - for (int i = 0; i < frameTable->size(); i++) { - auto &frame = frameTable->at(i); - auto originalFrame = originalFrameTable.at(i); + for (int i = 0; i < frame_table_->size(); i++) { + auto &frame = frame_table_->at(i); + auto originalFrame = original_frame_table_.at(i); frame = originalFrame; } } +/////////////////////////////////////////////////////////////////////////////// +// Helpers //////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +void FramePostprocessor::normalizeGain(bool target_voiced) { + // Compute the max gain value for a Frame category + float max_gain = 0.0f; + for (const Frame &frame : *frame_table_) { + bool is_voiced = frame.isVoiced(); + float gain = frame.getGain(); + + if (is_voiced == target_voiced && gain > max_gain) { + max_gain = gain; + } + } + + // Apply scaling factor to improve naturalness of perceived volume + float scale = (target_voiced ? max_voiced_gain_db_ : max_unvoiced_gain_db_); + scale /= max_gain; + + for (Frame &frame : *frame_table_) { + bool is_voiced = frame.isVoiced(); + float gain = frame.getGain(); + + if (is_voiced == target_voiced) { + float scaled_gain = gain * scale; + frame.setGain(scaled_gain); + } + } +} + }; // namespace tms_express diff --git a/tms_express/Frame_Encoding/FramePostprocessor.h b/tms_express/Frame_Encoding/FramePostprocessor.h deleted file mode 100644 index 0a6630f..0000000 --- a/tms_express/Frame_Encoding/FramePostprocessor.h +++ /dev/null @@ -1,44 +0,0 @@ -// Author: Joseph Bellahcen - -#ifndef TMS_EXPRESS_FRAMEPOSTPROCESSOR_H -#define TMS_EXPRESS_FRAMEPOSTPROCESSOR_H - -#include "Frame.hpp" -#include - -namespace tms_express { - -class FramePostprocessor { -public: - explicit FramePostprocessor(std::vector *frames, float maxVoicedGainDB = 37.5, float maxUnvoicedGainDB = 37.5); - - // Getters & setters - float getMaxUnvoicedGainDB() const; - void setMaxUnvoicedGainDB(float gainDB); - - float getMaxVoicedGainDB() const; - void setMaxVoicedGainDB(float gainDB); - - // Frame table manipulation - int detectRepeatFrames(); - void normalizeGain(); - void shiftGain(int offset); - void shiftPitch(int offset); - void overridePitch(int index); - // TODO: void interpolatePitch(); - - // Utility - void reset(); - -private: - std::vector originalFrameTable; - std::vector *frameTable; - float maxUnvoicedGain; - float maxVoicedGain; - - void normalizeGain(bool normalizeVoicedFrames); -}; - -}; // namespace tms_express - -#endif //TMS_EXPRESS_FRAMEPOSTPROCESSOR_H diff --git a/tms_express/Frame_Encoding/FramePostprocessor.hpp b/tms_express/Frame_Encoding/FramePostprocessor.hpp new file mode 100644 index 0000000..da56129 --- /dev/null +++ b/tms_express/Frame_Encoding/FramePostprocessor.hpp @@ -0,0 +1,108 @@ +// Copyright (C) 2023 Joseph Bellahcen + +#ifndef TMS_EXPRESS_FRAME_ENCODING_FRAMEPOSTPROCESSOR_HPP_ +#define TMS_EXPRESS_FRAME_ENCODING_FRAMEPOSTPROCESSOR_HPP_ + +#include + +#include "Frame_Encoding/Frame.hpp" + +namespace tms_express { + +/// @brief Applies post-processing to Frame table to improve synthesis quality +class FramePostprocessor { + public: + /////////////////////////////////////////////////////////////////////////// + // Initializers /////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @brief Creates new Frame Postprocessor to operate on given Frame table + /// @param frames Pointer to Frame table + /// @param max_voiced_gain_db Max voiced (vowel) gain, in decibels + /// @param max_unvoiced_gain_db Max unvoiced (consonant) gain, in decibels + explicit FramePostprocessor(std::vector *frames, + float max_voiced_gain_db = 37.5, float max_unvoiced_gain_db = 37.5); + + /////////////////////////////////////////////////////////////////////////// + // Accessors ////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @brief Accesses the max unvoiced (consonant) gain + /// @return Max unvoiced gain, in decibels + float getMaxUnvoicedGainDB() const; + + /// @brief Sets the max unvoiced (consonant) gain + /// @param gain_db Max unvoiced gain, in decibels + void setMaxUnvoicedGainDB(float gain_db); + + /// @brief Accesses the max voiced (vowel) gain + /// @return Max voiced gain, in decibels + float getMaxVoicedGainDB() const; + + /// @brief Sets the max voiced (vowel) gain + /// @param gain_db Max voiced gain, in decibels + void setMaxVoicedGainDB(float gain_db); + + /////////////////////////////////////////////////////////////////////////// + // Frame Table Manipulators /////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @brief Marks Frames which are similar to their neighbors as repeat + /// @return Number of repeat Frames detected + /// @details Because the LPC model of the human vocal tract asserts that + /// speech signals vary relatively slowly, it may be observed + /// that consecutive Frames are often similar. Such Frames may + /// be marked as repeats, such that only the original is fully + /// encoded. This effectively compresses the bitstream + int detectRepeatFrames(); + + /// @brief Applies gain normalization to all Frames + /// @note Gain normalization reduces DC offset and creates natural volume + void normalizeGain(); + + /// @brief Shifts gain by integer offset into TMS5220 Coding Table + /// @param offset Offset into TMS5200 Coding Table entry + /// @note Offset is subject to floor/ceiling to prevent unstable bitstreams + void shiftGain(int offset); + + /// @brief Shifts pitch by integer offset into TMS5220 Coding Table + /// @param offset Offset into TMS5200 Coding Table entry + /// @note Offset is subject to floor/ceiling to prevent unstable bitstreams + void shiftPitch(int offset); + + /// @brief Sets the pitch of all Frames to value from TMS5220 Coding Table + /// @param index Index into TMS5220 Coding Table entry + void overridePitch(int index); + + /////////////////////////////////////////////////////////////////////////// + // Utility //////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @brief Restores Frame table to initialization state + /// @note Does not reset voiced or unvoiced gain limits + [[deprecated]] void reset(); + + private: + /////////////////////////////////////////////////////////////////////////// + // Helpers //////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @brief Applies normalization to either voiced or unvoiced Frames + /// @param target_voiced true to normalized voiced Frames, false + /// for unvoiced Frames + void normalizeGain(bool target_voiced); + + /////////////////////////////////////////////////////////////////////////// + // Members //////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + std::vector original_frame_table_; + std::vector *frame_table_; + float max_unvoiced_gain_db_; + float max_voiced_gain_db_; + +}; + +}; // namespace tms_express + +#endif // TMS_EXPRESS_FRAME_ENCODING_FRAMEPOSTPROCESSOR_HPP_ diff --git a/tms_express/User_Interfaces/MainWindow.cpp b/tms_express/User_Interfaces/MainWindow.cpp index a5b612c..0271c69 100644 --- a/tms_express/User_Interfaces/MainWindow.cpp +++ b/tms_express/User_Interfaces/MainWindow.cpp @@ -7,7 +7,7 @@ /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #include "Audio/AudioBuffer.hpp" -#include "Frame_Encoding/FramePostprocessor.h" +#include "Frame_Encoding/FramePostprocessor.hpp" #include "LPC_Analysis/Autocorrelation.h" #include "User_Interfaces/Audio_Waveform/AudioWaveformView.h" #include "User_Interfaces/MainWindow.h" diff --git a/tms_express/User_Interfaces/MainWindow.h b/tms_express/User_Interfaces/MainWindow.h index a8dd6e9..1eb2d2b 100644 --- a/tms_express/User_Interfaces/MainWindow.h +++ b/tms_express/User_Interfaces/MainWindow.h @@ -8,7 +8,7 @@ #include "Bitstream_Generation/BitstreamGenerator.hpp" #include "Frame_Encoding/Frame.hpp" #include "Frame_Encoding/FrameEncoder.hpp" -#include "Frame_Encoding/FramePostprocessor.h" +#include "Frame_Encoding/FramePostprocessor.hpp" #include "Frame_Encoding/Synthesizer.h" #include "LPC_Analysis/PitchEstimator.h" #include "LPC_Analysis/LinearPredictor.h" From 72669a36a80e3ef554e4b9276f9fa342d0a32f04 Mon Sep 17 00:00:00 2001 From: Joseph Bellahcen Date: Sun, 23 Jul 2023 19:02:19 -0700 Subject: [PATCH 12/30] Re-added casts --- tms_express/Audio/AudioBuffer.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/tms_express/Audio/AudioBuffer.cpp b/tms_express/Audio/AudioBuffer.cpp index 9b0475b..da26046 100644 --- a/tms_express/Audio/AudioBuffer.cpp +++ b/tms_express/Audio/AudioBuffer.cpp @@ -98,8 +98,8 @@ void AudioBuffer::setSamples(const std::vector &samples) { } float AudioBuffer::getWindowWidthMs() const { - float numerator = n_samples_per_segment_; - float denominator = sample_rate_hz_ * 1.0e-3; + float numerator = static_cast(n_samples_per_segment_); + float denominator = static_cast(sample_rate_hz_) * 1.0e-3; return numerator / denominator; } @@ -112,7 +112,8 @@ void AudioBuffer::setWindowWidthMs(float window_width_ms) { } // Re-compute segment bounds - n_samples_per_segment_ = sample_rate_hz_ * window_width_ms * 1e-3; + n_samples_per_segment_ = static_cast( + static_cast(sample_rate_hz_) * window_width_ms * 1e-3); n_segments_ = samples_.size() / n_samples_per_segment_; // Pad final segment with zeros @@ -235,8 +236,12 @@ std::vector AudioBuffer::resample(std::vector samples, // compatibility with stereo audio, compute the // number of frames as: size / (channels * ratio) // and the number of samples as: (frames * channels) - double ratio = target_sample_rate_hz / src_sample_rate_hz; - auto n_frames = samples.size() * ratio; + double ratio = static_cast(target_sample_rate_hz) / + static_cast(src_sample_rate_hz); + + auto n_frames = static_cast( + static_cast(samples.size()) * ratio); + auto resampled_buffer = std::vector(n_frames); // Initialize resampler From a40200e39de9aba61fb14f28f76a896b1f5b7d80 Mon Sep 17 00:00:00 2001 From: Joseph Bellahcen Date: Sun, 23 Jul 2023 19:11:26 -0700 Subject: [PATCH 13/30] Refactored Synthesizer --- tms_express/Frame_Encoding/FrameEncoder.hpp | 7 + .../Frame_Encoding/FramePostprocessor.hpp | 7 + tms_express/Frame_Encoding/Synthesizer.cpp | 235 +++++++++--------- tms_express/Frame_Encoding/Synthesizer.h | 42 ---- tms_express/Frame_Encoding/Synthesizer.hpp | 110 ++++++++ tms_express/User_Interfaces/MainWindow.cpp | 6 +- tms_express/User_Interfaces/MainWindow.h | 2 +- 7 files changed, 242 insertions(+), 167 deletions(-) delete mode 100644 tms_express/Frame_Encoding/Synthesizer.h create mode 100644 tms_express/Frame_Encoding/Synthesizer.hpp diff --git a/tms_express/Frame_Encoding/FrameEncoder.hpp b/tms_express/Frame_Encoding/FrameEncoder.hpp index e47a17d..90ce598 100644 --- a/tms_express/Frame_Encoding/FrameEncoder.hpp +++ b/tms_express/Frame_Encoding/FrameEncoder.hpp @@ -131,9 +131,16 @@ class FrameEncoder { // Members //////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////// + /// @brief Binary representation of Frame table std::vector binary_bitstream_; + + /// @brief Hex byte separator for ASCII bitstreams static const char byte_delimiter = ','; + + /// @brief Frame table std::vector frames_; + + /// @brief true to prefix hex bytes with '0x', false otherwise bool include_hex_prefix_; }; diff --git a/tms_express/Frame_Encoding/FramePostprocessor.hpp b/tms_express/Frame_Encoding/FramePostprocessor.hpp index da56129..b901724 100644 --- a/tms_express/Frame_Encoding/FramePostprocessor.hpp +++ b/tms_express/Frame_Encoding/FramePostprocessor.hpp @@ -96,9 +96,16 @@ class FramePostprocessor { // Members //////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////// + /// @brief Frame table at initialization std::vector original_frame_table_; + + /// @brief Frame table std::vector *frame_table_; + + /// @brief Max unvoiced (consonant) gain, in decibels float max_unvoiced_gain_db_; + + /// @brief Max voiced (vowel) gain, in decibels float max_voiced_gain_db_; }; diff --git a/tms_express/Frame_Encoding/Synthesizer.cpp b/tms_express/Frame_Encoding/Synthesizer.cpp index f113766..3821b05 100644 --- a/tms_express/Frame_Encoding/Synthesizer.cpp +++ b/tms_express/Frame_Encoding/Synthesizer.cpp @@ -1,66 +1,44 @@ -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// Class: Synthesizer -// -// Description: The Synthesizer converts a Frame vector into PCM samples representing synthesized speech. The lattice -// filter through which Frame parameters pass is modeled after the TMS5220 Voice Synthesis Processor -// -// Author: Joseph Bellahcen -// -// Acknowledgement: This class implements a pure-software version of the popular Arduino Talkie library, written by -// Peter Knight and Jonathan Gevaryahu, which emulates the behavior of the TMS5220. It also draws from -// a Lua implementation of the original C++ codebase, which utilized a floating-point coding table. -// The original source codes may be found at https://github.com/going-digital/Talkie and -// https://github.com/tocisz/talkie.love -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2023 Joseph Bellahcen +// References: https://github.com/going-digital/Talkie +// https://github.com/tocisz/talkie.love -#include "Audio/AudioBuffer.hpp" -#include "Frame_Encoding/Synthesizer.h" -#include "Frame_Encoding/Frame.hpp" -#include "Frame_Encoding/Tms5220CodingTable.h" +#include "Frame_Encoding/Synthesizer.hpp" +#include #include #include #include +#include "Audio/AudioBuffer.hpp" +#include "Frame_Encoding/Frame.hpp" +#include "Frame_Encoding/Tms5220CodingTable.h" + namespace tms_express { -using namespace Tms5220CodingTable; - -/// Create a new synthesizer -/// -/// \note The synthesizer consists of three parts: the synthesis table, the random noise generator, and the lattice -/// filter. The synthesis table holds parameters of the current frame. These parameters pass through the lattice -/// filter repeatedly to produce audio. The random noise generator provides the basis for unvoiced sounds -/// -/// \param sampleRateHz Sample rate of synthesized audio (in Hertz) -/// \param frameRateMs Frame rate of synthesized audio, or duration of each frame (in milliseconds) -Synthesizer::Synthesizer(int sampleRateHz, float frameRateMs) { - sampleRate = sampleRateHz; - windowWidth = frameRateMs; - samplesPerFrame = int(float(sampleRateHz) * frameRateMs * 1e-3f); - - synthEnergy = synthPeriod = 0; - synthK1 = synthK2 = synthK3 = synthK4 = synthK5 = synthK6 = synthK7 = synthK8 = synthK9 = synthK10 = 0; - x0 = x1 = x2 = x3 = x4 = x5 = x6 = x7 = x8 = x9 = u0 = 0; - synthRand = periodCounter = 0; - synthesizedSamples = {}; -} +/////////////////////////////////////////////////////////////////////////////// +// Initializers /////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// -/// Reset the synthesizer -/// -/// \note Synthesizer::synthesize calls this automatically -void Synthesizer::reset() { - synthEnergy = synthPeriod = 0; - synthK1 = synthK2 = synthK3 = synthK4 = synthK5 = synthK6 = synthK7 = synthK8 = synthK9 = synthK10 = 0; - x0 = x1 = x2 = x3 = x4 = x5 = x6 = x7 = x8 = x9 = u0 = 0; - synthRand = periodCounter = 0; - synthesizedSamples = {}; +Synthesizer::Synthesizer(int sample_rate_hz, float frame_rate_ms) { + sample_rate_hz_ = sample_rate_hz; + window_width_ms_ = frame_rate_ms; + n_samples_per_frame_ = sample_rate_hz * frame_rate_ms * 1e-3f; + + energy_ = period_ = 0; + + k1_ = k2_ = k3_ = k4_ = + k5_ = k6_ = k7_ = k8_ = + k9_ = k10_ = 0; + + x0_ = x1_ = x2_ = x3_ = x4_ = x5_ = x6_ = x7_ = x8_ = x9_ = u0_ = 0; + rand_noise_ = period_count_ = 0; + samples_ = {}; } -/// Reconstruct audio from frame data -/// -/// \param frames Frames to synthesize -/// \return Synthesized PCM samples +/////////////////////////////////////////////////////////////////////////////// +// Synthesis Interfaces /////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + std::vector Synthesizer::synthesize(const std::vector& frames) { reset(); @@ -69,55 +47,73 @@ std::vector Synthesizer::synthesize(const std::vector& frames) { break; } - for (int i = 0; i < samplesPerFrame; i++) - synthesizedSamples.push_back(updateLatticeFilter()); + for (int i = 0; i < n_samples_per_frame_; i++) + samples_.push_back(updateLatticeFilter()); } - return synthesizedSamples; + return samples_; } +/////////////////////////////////////////////////////////////////////////////// +// Accessors ////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +std::vector Synthesizer::getSamples() const { + return samples_; +} + +/////////////////////////////////////////////////////////////////////////////// +// Static Utilities /////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + /// Export synthesized samples as an audio file -void Synthesizer::render(const std::string &path) { - AudioBuffer(synthesizedSamples, sampleRate, windowWidth).render(path); +void Synthesizer::render(const std::vector &samples, + const std::string& path, int sample_rate_hz, float frame_rate_ms) { + AudioBuffer(samples, sample_rate_hz, frame_rate_ms).render(path); +} + +/////////////////////////////////////////////////////////////////////////////// +// Synthesis Functions //////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +bool Synthesizer::updateNoiseGenerator() { + rand_noise_ = (rand_noise_ >> 1) ^ ((rand_noise_ & 1) ? 0xB800 : 0); + return (rand_noise_ & 1); } -/// Update the synthesizer state based on incoming frame -/// -/// \param frame Frame to load into synthesis table -/// \return Whether stop frame encountered, at which point synthesis should halt bool Synthesizer::updateSynthTable(Frame frame) { - auto quantizedGain = frame.quantizedGain(); + auto quantized_gain = frame.quantizedGain(); // Silent frame - if (quantizedGain == 0) { - synthEnergy = 0; + if (quantized_gain == 0) { + energy_ = 0; - // Stop frame - } else if (quantizedGain == 0xf) { + // Stop frame + } else if (quantized_gain == 0xf) { reset(); return true; } else { - synthEnergy = energy[quantizedGain]; - synthPeriod = pitch[frame.quantizedPitch()]; + energy_ = Tms5220CodingTable::energy[quantized_gain]; + period_ = Tms5220CodingTable::pitch[frame.quantizedPitch()]; if (!frame.isRepeat()) { auto coeffs = frame.quantizedCoeffs(); // Voiced/unvoiced parameters - synthK1 = k1[coeffs[0]]; - synthK2 = k2[coeffs[1]]; - synthK3 = k3[coeffs[2]]; - synthK4 = k4[coeffs[3]]; + k1_ = Tms5220CodingTable::k1[coeffs[0]]; + k2_ = Tms5220CodingTable::k2[coeffs[1]]; + k3_ = Tms5220CodingTable::k3[coeffs[2]]; + k4_ = Tms5220CodingTable::k4[coeffs[3]]; // Voiced-only parameters - if (std::fpclassify(synthPeriod) != FP_ZERO) { - synthK5 = k5[coeffs[4]]; - synthK6 = k6[coeffs[5]]; - synthK7 = k7[coeffs[6]]; - synthK8 = k8[coeffs[7]]; - synthK9 = k9[coeffs[8]]; - synthK10 = k9[coeffs[9]]; + if (std::fpclassify(period_) != FP_ZERO) { + k5_ = Tms5220CodingTable::k5[coeffs[4]]; + k6_ = Tms5220CodingTable::k6[coeffs[5]]; + k7_ = Tms5220CodingTable::k7[coeffs[6]]; + k8_ = Tms5220CodingTable::k8[coeffs[7]]; + k9_ = Tms5220CodingTable::k9[coeffs[8]]; + k10_ = Tms5220CodingTable::k10[coeffs[9]]; } } } @@ -125,74 +121,71 @@ bool Synthesizer::updateSynthTable(Frame frame) { return false; } -/// Advance the random noise generator -/// -/// \return The polarity (+true, -false) of the unvoiced sample to generate -bool Synthesizer::updateNoiseGenerator() { - synthRand = (synthRand >> 1) ^ ((synthRand & 1) ? 0xB800 : 0); - return (synthRand & 1); -} - -/// Synthesize new sample and advance synthesizer state -/// -/// \return Newly synthesized sample float Synthesizer::updateLatticeFilter() { // Generate voiced sample - if (std::fpclassify(synthPeriod) != FP_ZERO) { - if (float(periodCounter) < synthPeriod) { - periodCounter++; + if (std::fpclassify(period_) != FP_ZERO) { + if (static_cast(period_count_) < period_) { + period_count_++; } else { - periodCounter = 0; + period_count_ = 0; } - if (periodCounter < chirpWidth) { - u0 = ((chirp[periodCounter]) * synthEnergy); + if (period_count_ < Tms5220CodingTable::chirpWidth) { + u0_ = ((Tms5220CodingTable::chirp[period_count_]) * energy_); } else { - u0 = 0; + u0_ = 0; } // Generate unvoiced sample } else { - u0 = (updateNoiseGenerator()) ? synthEnergy : -synthEnergy; + u0_ = (updateNoiseGenerator()) ? energy_ : -energy_; } // Push new data through lattice filter - if (std::fpclassify(synthPeriod) != FP_ZERO) { - u0 -= (synthK10 * x9) + (synthK9 * x8); - x9 = x8 + (synthK9 * u0); + if (std::fpclassify(period_) != FP_ZERO) { + u0_ -= (k10_ * x9_) + (k9_ * x8_); + x9_ = x8_ + (k9_ * u0_); - u0 -= synthK8 * x7; - x8 = x7 + (synthK8 * u0); + u0_ -= k8_ * x7_; + x8_ = x7_ + (k8_ * u0_); - u0 -= synthK7 * x6; - x7 = x6 + (synthK7 * u0); + u0_ -= k7_ * x6_; + x7_ = x6_ + (k7_ * u0_); - u0 -= synthK6 * x5; - x6 = x5 + (synthK6 * u0); + u0_ -= k6_ * x5_; + x6_ = x5_ + (k6_ * u0_); - u0 -= synthK5 * x4; - x5 = x4 + (synthK5 * u0); + u0_ -= k5_ * x4_; + x5_ = x4_ + (k5_ * u0_); } - u0 -= synthK4 * x3; - x4 = x3 + (synthK4 * u0); + u0_ -= k4_ * x3_; + x4_ = x3_ + (k4_ * u0_); - u0 -= synthK3 * x2; - x3 = x2 + (synthK3 * u0); + u0_ -= k3_ * x2_; + x3_ = x2_ + (k3_ * u0_); - u0 -= synthK2 * x1; - x2 = x1 + (synthK2 * u0); + u0_ -= k2_ * x1_; + x2_ = x1_ + (k2_ * u0_); - u0 -= synthK1 * x0; - x1 = x0 + (synthK1 * u0); + u0_ -= k1_ * x0_; + x1_ = x0_ + (k1_ * u0_); // Normalize result - x0 = std::max(std::min(u0, 1.0f), -1.0f); - return x0; + x0_ = std::max(std::min(u0_, 1.0f), -1.0f); + return x0_; } -std::vector Synthesizer::samples() { - return synthesizedSamples; +/////////////////////////////////////////////////////////////////////////// +// Utility Functions ////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////// + +void Synthesizer::reset() { + energy_ = period_ = 0; + k1_ = k2_ = k3_ = k4_ = k5_ = k6_ = k7_ = k8_ = k9_ = k10_ = 0; + x0_ = x1_ = x2_ = x3_ = x4_ = x5_ = x6_ = x7_ = x8_ = x9_ = u0_ = 0; + rand_noise_ = period_count_ = 0; + samples_ = {}; } }; // namespace tms_express diff --git a/tms_express/Frame_Encoding/Synthesizer.h b/tms_express/Frame_Encoding/Synthesizer.h deleted file mode 100644 index d1da625..0000000 --- a/tms_express/Frame_Encoding/Synthesizer.h +++ /dev/null @@ -1,42 +0,0 @@ -// Author: Joseph Bellahcen - -#ifndef TMS_EXPRESS_SYNTHESIZER_H -#define TMS_EXPRESS_SYNTHESIZER_H - -#include "Frame_Encoding/Frame.hpp" -#include -#include -#include - -namespace tms_express { - -class Synthesizer { -public: - explicit Synthesizer(int sampleRateHz = 8000, float frameRateMs = 25.0f); - - std::vector synthesize(const std::vector& frames); - void render(const std::string& path); - std::vector samples(); - -private: - int sampleRate; - float windowWidth; - int samplesPerFrame; - - float synthEnergy, synthPeriod; - float synthK1, synthK2, synthK3, synthK4, synthK5, synthK6, synthK7, synthK8, synthK9, synthK10; - float x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, u0; - int synthRand, periodCounter; - - std::vector synthesizedSamples; - - void reset(); - - bool updateSynthTable(Frame frame); - bool updateNoiseGenerator(); - float updateLatticeFilter(); -}; - -}; // namespace tms_express - -#endif //TMS_EXPRESS_SYNTHESIZER_H diff --git a/tms_express/Frame_Encoding/Synthesizer.hpp b/tms_express/Frame_Encoding/Synthesizer.hpp new file mode 100644 index 0000000..7deb6af --- /dev/null +++ b/tms_express/Frame_Encoding/Synthesizer.hpp @@ -0,0 +1,110 @@ +// Copyright (C) 2023 Joseph Bellahcen + +#ifndef TMS_EXPRESS_FRAME_ENCODING_SYNTHESIZER_HPP_ +#define TMS_EXPRESS_FRAME_ENCODING_SYNTHESIZER_HPP_ + +#include +#include + +#include "Frame_Encoding/Frame.hpp" + +namespace tms_express { + +/// @brief Synthesizes Frame table as PCM audio samples +class Synthesizer { + public: + /////////////////////////////////////////////////////////////////////////// + // Initializers /////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @brief Creates a new Synthesizer + /// @param sample_rate_hz Sample rate of synthesized audio, in Hertz + /// @param frame_rate_ms Duration of each Frame, in milliseconds + explicit Synthesizer(int sample_rate_hz = 8000, + float frame_rate_ms = 25.0f); + + /////////////////////////////////////////////////////////////////////////// + // Synthesis Interfaces /////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @brief Push Frame table through synthesis filter + /// @param frames Frame table to synthesize + /// @return Synthesized PCM samples + std::vector synthesize(const std::vector& frames); + + /////////////////////////////////////////////////////////////////////////// + // Accessors ////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @brief Accesses synthesized samples + /// @return Synthesized PCM samples + std::vector getSamples() const; + + /////////////////////////////////////////////////////////////////////////// + // Static Utilities /////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @brief Renders samples to audio file + /// @param samples Synthesized PCM audio samples + /// @param path Output path to audio file + /// @param sample_rate_hz Sample rate used to generate samples, in Hertz + /// @param frame_rate_ms Duration of each Frame, in milliseconds + static void render(const std::vector &samples, + const std::string& path, int sample_rate_hz, float frame_rate_ms); + + private: + /////////////////////////////////////////////////////////////////////////// + // Synthesis Functions //////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @brief Advances random noise generator state machine + /// @return Polarity of unvoiced sample to generate, true for positive, + /// false for negative + bool updateNoiseGenerator(); + + /// @brief Loads new Frame into synthesizer + /// @param frame Frame to synthesize + /// @return true if stop Frame encountered, at which point synthesis should + /// halt, false otherwise + bool updateSynthTable(Frame frame); + + /// @brief Pushes Frame parameters through synthesis lattice filter + /// @return Newly synthesized sample + float updateLatticeFilter(); + + /////////////////////////////////////////////////////////////////////////// + // Utility Functions ////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @brief Resets synthesizer to initialization state + void reset(); + + /////////////////////////////////////////////////////////////////////////// + // Members: Audio IO ////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @brief Sampling rate of synthesized audio, in Hertz + int sample_rate_hz_; + + /// @brief Window width (duration of each Frame), in milliseconds + float window_width_ms_; + + /// @brief Number of audio samples which comprise a single Frame + int n_samples_per_frame_; + + /// @brief Synthesized PCM samples + std::vector samples_; + + /////////////////////////////////////////////////////////////////////////// + // Members: Lattice Filter //////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + float energy_, period_; + float k1_, k2_, k3_, k4_, k5_, k6_, k7_, k8_, k9_, k10_; + float x0_, x1_, x2_, x3_, x4_, x5_, x6_, x7_, x8_, x9_, u0_; + int rand_noise_, period_count_; +}; + +}; // namespace tms_express + +#endif // TMS_EXPRESS_FRAME_ENCODING_SYNTHESIZER_HPP_ diff --git a/tms_express/User_Interfaces/MainWindow.cpp b/tms_express/User_Interfaces/MainWindow.cpp index 0271c69..5417920 100644 --- a/tms_express/User_Interfaces/MainWindow.cpp +++ b/tms_express/User_Interfaces/MainWindow.cpp @@ -270,7 +270,7 @@ void MainWindow::onExportAudio() { return; } - synthesizer.render(filePath.toStdString()); + synthesizer.render(synthesizer.getSamples(), filePath.toStdString(), lpcBuffer.getSampleRateHz(), lpcBuffer.getWindowWidthMs()); } /// Play contents of input buffer @@ -315,14 +315,14 @@ void MainWindow::onLpcAudioPlay() { // be significant enough to modify the buffer checksum alone char filename[35]; - uint checksum = (!lpcBuffer.empty()) ? samplesChecksum(lpcBuffer.getSamples()) : samplesChecksum(synthesizer.samples()); + uint checksum = (!lpcBuffer.empty()) ? samplesChecksum(lpcBuffer.getSamples()) : samplesChecksum(synthesizer.getSamples()); snprintf(filename, 35, "tmsexpress_lpc_render_%x.wav", checksum); // Only render audio if this particular buffer does not exist auto tempDir = std::filesystem::temp_directory_path(); tempDir.append(filename); qDebug() << "Playing " << tempDir.c_str(); - synthesizer.render(tempDir); + synthesizer.render(synthesizer.getSamples(), tempDir, lpcBuffer.getSampleRateHz(), lpcBuffer.getWindowWidthMs()); // Setup player and play player->setAudioOutput(audioOutput); diff --git a/tms_express/User_Interfaces/MainWindow.h b/tms_express/User_Interfaces/MainWindow.h index 1eb2d2b..20b0b39 100644 --- a/tms_express/User_Interfaces/MainWindow.h +++ b/tms_express/User_Interfaces/MainWindow.h @@ -9,7 +9,7 @@ #include "Frame_Encoding/Frame.hpp" #include "Frame_Encoding/FrameEncoder.hpp" #include "Frame_Encoding/FramePostprocessor.hpp" -#include "Frame_Encoding/Synthesizer.h" +#include "Frame_Encoding/Synthesizer.hpp" #include "LPC_Analysis/PitchEstimator.h" #include "LPC_Analysis/LinearPredictor.h" #include "User_Interfaces/Audio_Waveform/AudioWaveformView.h" From 936c029b803b73af401174c6da46021ce3887dd2 Mon Sep 17 00:00:00 2001 From: Joseph Bellahcen Date: Sun, 23 Jul 2023 20:48:09 -0700 Subject: [PATCH 14/30] Refactored Tms5220CodingTable -> CodingTable --- tms_express/Frame_Encoding/CodingTable.hpp | 129 ++++++++++++++++++ tms_express/Frame_Encoding/Frame.cpp | 24 ++-- tms_express/Frame_Encoding/FrameEncoder.cpp | 26 ++-- .../Frame_Encoding/FramePostprocessor.cpp | 20 +-- tms_express/Frame_Encoding/Synthesizer.cpp | 34 ++--- .../Frame_Encoding/Tms5220CodingTable.h | 122 ----------------- .../Control_Panels/ControlPanelPostView.cpp | 18 ++- 7 files changed, 189 insertions(+), 184 deletions(-) create mode 100644 tms_express/Frame_Encoding/CodingTable.hpp delete mode 100644 tms_express/Frame_Encoding/Tms5220CodingTable.h diff --git a/tms_express/Frame_Encoding/CodingTable.hpp b/tms_express/Frame_Encoding/CodingTable.hpp new file mode 100644 index 0000000..b7d40a2 --- /dev/null +++ b/tms_express/Frame_Encoding/CodingTable.hpp @@ -0,0 +1,129 @@ +// Copyright (C) 2023 Joseph Bellahcen +// Reference: TMS 5220 VOICE SYNTHESIS PROCESSOR DATA MANUAL (http://sprow.co.uk/bbc/hardware/speech/tms5220.pdf) +// Reference: Arduino Talkie (https://github.com/going-digital/Talkie) + +#ifndef TMS_EXPRESS_FRAME_ENCODING_CODINGTABLE_HPP_ +#define TMS_EXPRESS_FRAME_ENCODING_CODINGTABLE_HPP_ + +#include +#include + +namespace tms_express::coding_table::tms5220 { + +/////////////////////////////////////////////////////////////////////////////// +// Metadata ////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +static const int kNCoeffs = 10; + +static const int kGainBitWidth = 4; +static const int kPitchBitWidth = 6; +static const int kVoicingBitWidth = 1; +static const int kCoeffBitWidths[] = {5, 5, 4, 4, 4, 4, 4, 3, 3, 3}; + +/////////////////////////////////////////////////////////////////////////////// +// RMS (Gain) Table /////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +static const std::array rms = {0, 52, 87, 123, 174, 246, 348, 491, + 694, 981, 1385, 1957, 2764, 3904, 5514, 7789}; + +/////////////////////////////////////////////////////////////////////////////// +// Pitch Period Table ///////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +static const std::array pitch = {0, 15, 16, 17, 18, 19, 20, 21, 22, + 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, + 42, 44, 46, 48, 50, 52, 53, 56, 58, 60, 62, 65, 68, 70, 72, 76, 78, 80, 84, + 86, 91, 94, 98, 101, 105, 109, 114, 118, 122, 127, 132, 137, 142, 148, 153, + 159}; + +/////////////////////////////////////////////////////////////////////////////// +// LPC Reflector Coefficients ///////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +static const std::array k1 = {-0.97850f, -0.97270f, -0.97070f, + -0.96680f, -0.96290f, -0.95900f, -0.95310f, -0.94140f, -0.93360f, -0.92580f, + -0.91600f, -0.90620f, -0.89650f, -0.88280f, -0.86910f, -0.85350f, -0.80420f, + -0.74058f, -0.66019f, -0.56116f, -0.44296f, -0.30706f, -0.15735f, -0.00005f, + 0.15725f, 0.30696f, 0.44288f, 0.56109f, 0.66013f, 0.74054f, 0.80416f, + 0.85350f}; + +static const std::array k2 = {-0.64000f, -0.58999f, -0.53500f, + -0.47507f, -0.41039f, -0.34129f, -0.26830f, -0.19209f, -0.11350f, -0.03345f, + 0.04702f, 0.12690f, 0.20515f, 0.28087f, 0.35325f, 0.42163f, 0.48553f, + 0.54464f, 0.59878f, 0.64796f, 0.69227f, 0.73190f, 0.76714f, 0.79828f, + 0.82567f, 0.84965f, 0.87057f, 0.88875f, 0.90451f, 0.91813f, 0.92988f, + 0.98830f}; + +static const std::array k3 = {-0.86000f, -0.75467f, -0.64933f, + -0.54400f, -0.43867f, -0.33333f, -0.22800f, -0.12267f, -0.01733, 0.08800f, + 0.19333f, 0.29867f, 0.40400f, 0.50933f, 0.61467f, 0.72000f}; + +static const std::array k4 = {-0.64000f, -0.53145f, -0.42289f, + -0.31434f, -0.20579f, -0.09723f, 0.01132f, 0.11987f, 0.22843f, 0.33698f, + 0.44553f, 0.55409f, 0.66264f, 0.77119f, 0.87975f, 0.98830f}; + +static const std::array k5 = {-0.64000f, -0.54933f, -0.45867f, + -0.36800f, -0.27733f, -0.18667f, -0.09600f, -0.00533f, 0.08533f, 0.17600f, + 0.26667f, 0.35733f, 0.44800f, 0.53867f, 0.62933f, 0.72000f}; + +static const std::array k6 = {-0.50000f, -0.41333f, -0.32667f, + -0.24000f, -0.15333f, -0.06667f, 0.02000f, 0.10667f, 0.19333f, 0.28000f, + 0.36667f, 0.45333f, 0.54000f, 0.62667f, 0.71333f, 0.80000f}; + +static const std::array k7 = {-0.60000f, -0.50667f, -0.41333f, + -0.32000f, -0.22667f, -0.13333f, -0.04000f, 0.05333f, 0.14667f, 0.24000f, + 0.33333f, 0.42667f, 0.52000f, 0.61333f, 0.70667f, 0.80000f}; + +static const std::array k8 = {-0.50000f, -0.31429f, -0.12857f, + 0.05714f, 0.24286f, 0.42857f, 0.61429f, 0.80000f}; + +static const std::array k9 = {-0.50000f, -0.34286f, -0.18571f, + 0.02857f, 0.12857f, 0.28571f, 0.44286f, 0.60000f}; + +static const std::array k10 = {-0.40000f, -0.25714f, -0.11429f, + 0.02857f, 0.17143f, 0.31429f, 0.45714f, 0.60000f}; + +/////////////////////////////////////////////////////////////////////////////// +// Synthesis Tables /////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +static const std::array chirp = {0, 0.328125, -0.34375, 0.390625, + -0.609375, 0.140625, 0.2890625, 0.15625, 0.015625, -0.2421875, -0.4609375, + 0.015625, 0.7421875, 0.703125, 0.0390625, 0.1171875, 0.296875, -0.03125, + -0.7109375, -0.7109375, -0.328125, -0.2734375, -0.28125, -0.03125, + 0.2890625, 0.3359375, 0.265625, 0.2578125, 0.1171875, -0.0078125, -0.0625, + -0.140625, -0.1484375, -0.1328125, -0.0703125, -0.078125, -0.046875, 0, + 0.0234375, 0.015625, 0.0078125}; + +static const std::array energy = {0, 0.00390625, 0.005859375, + 0.0078125, 0.009765625, 0.013671875, 0.01953125, 0.029296875, 0.0390625, + 0.0625, 0.080078125, 0.111328125, 0.158203125, 0.22265625, 0.314453125, 0}; + +/////////////////////////////////////////////////////////////////////////////// +// LPC Coefficient Table Getter /////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +/// @brief Gets the ith LPC reflector coefficient table +/// @param i Index of coefficient table +/// @return ith coefficient table if i in range, empty vector otherwise +static const std::vector getCoeffTable(int i) { + switch (i) { + case 0: return {k1.begin(), k1.end()}; + case 1: return {k2.begin(), k2.end()}; + case 2: return {k3.begin(), k3.end()}; + case 3: return {k4.begin(), k4.end()}; + case 4: return {k5.begin(), k5.end()}; + case 5: return {k6.begin(), k6.end()}; + case 6: return {k7.begin(), k7.end()}; + case 7: return {k8.begin(), k8.end()}; + case 8: return {k9.begin(), k9.end()}; + case 9: return {k10.begin(), k10.end()}; + default: return {}; + } +} + +}; // namespace tms_express::coding_table::tms5220 + +#endif // TMS_EXPRESS_FRAME_ENCODING_CODINGTABLE_HPP_ diff --git a/tms_express/Frame_Encoding/Frame.cpp b/tms_express/Frame_Encoding/Frame.cpp index 57ffe25..b5540fb 100644 --- a/tms_express/Frame_Encoding/Frame.cpp +++ b/tms_express/Frame_Encoding/Frame.cpp @@ -8,7 +8,7 @@ #include "lib/json.hpp" -#include "Frame_Encoding/Tms5220CodingTable.h" +#include "Frame_Encoding/CodingTable.hpp" namespace tms_express { @@ -55,13 +55,13 @@ void Frame::setGain(float gain_db) { void Frame::setGain(int idx) { if (idx < 0) { - gain_db_ = *Tms5220CodingTable::rms.begin(); + gain_db_ = *coding_table::tms5220::rms.begin(); - } else if (idx > Tms5220CodingTable::rms.size()) { - gain_db_ = *Tms5220CodingTable::rms.end(); + } else if (idx > coding_table::tms5220::rms.size()) { + gain_db_ = *coding_table::tms5220::rms.end(); } else { - gain_db_ = Tms5220CodingTable::rms.at(idx); + gain_db_ = coding_table::tms5220::rms.at(idx); } } @@ -94,12 +94,12 @@ void Frame::setVoicing(bool isVoiced) { /////////////////////////////////////////////////////////////////////////////// std::vector Frame::quantizedCoeffs() const { - auto size = Tms5220CodingTable::nKCoeffs; + auto size = coding_table::tms5220::kNCoeffs; auto indices = std::vector(size); // Only parse as many coefficients as the coding table supports for (int i = 0; i < size; i++) { - auto table = Tms5220CodingTable::kCoeffsSlice(i); + auto table = coding_table::tms5220::getCoeffTable(i); float coeff = coeffs_[i]; int idx = closestIndex(coeff, table); @@ -110,7 +110,7 @@ std::vector Frame::quantizedCoeffs() const { } int Frame::quantizedGain() const { - auto table_array = Tms5220CodingTable::rms; + auto table_array = coding_table::tms5220::rms; auto table_vector = std::vector(table_array.begin(), table_array.end()); @@ -120,7 +120,7 @@ int Frame::quantizedGain() const { /// Return pitch period indices, corresponding to coding table entries int Frame::quantizedPitch() const { - auto table = Tms5220CodingTable::pitch; + auto table = coding_table::tms5220::pitch; auto table_vector = std::vector(table.begin(), table.end()); int idx = closestIndex(pitch_period_, table_vector); @@ -159,7 +159,7 @@ std::string Frame::toBinary() { // At minimum, a frame will contain an energy parameter int gain_idx = quantizedGain(); - bin += valueToBinary(gain_idx, Tms5220CodingTable::gainWidth); + bin += valueToBinary(gain_idx, coding_table::tms5220::kGainBitWidth); // A silent frame will contain no further parameters if (isSilent()) { @@ -171,7 +171,7 @@ std::string Frame::toBinary() { // A voiced frame will have a non-zero pitch int pitch_idx = isVoiced() ? quantizedPitch() : 0; - bin += valueToBinary(pitch_idx, Tms5220CodingTable::pitchWidth); + bin += valueToBinary(pitch_idx, coding_table::tms5220::kPitchBitWidth); if (isRepeat()) { return bin; @@ -184,7 +184,7 @@ std::string Frame::toBinary() { for (int i = 0; i < n_coeffs; i++) { int coeff = coeffs.at(i); - int coeff_width = Tms5220CodingTable::coeffWidths.at(i); + auto coeff_width = coding_table::tms5220::kCoeffBitWidths[i]; bin += valueToBinary(coeff, coeff_width); } diff --git a/tms_express/Frame_Encoding/FrameEncoder.cpp b/tms_express/Frame_Encoding/FrameEncoder.cpp index ce91bfc..23811c5 100644 --- a/tms_express/Frame_Encoding/FrameEncoder.cpp +++ b/tms_express/Frame_Encoding/FrameEncoder.cpp @@ -7,8 +7,8 @@ #include "lib/json.hpp" +#include "Frame_Encoding/CodingTable.hpp" #include "Frame_Encoding/Frame.hpp" -#include "Frame_Encoding/Tms5220CodingTable.h" namespace tms_express { @@ -110,8 +110,8 @@ size_t FrameEncoder::importASCIIFromString(std::string flat_bitstream) { // TODO(Joseph Bellahcen): Handle exception auto pitch_idx = std::stoul(buffer.substr(5, 6), nullptr, 2); - auto gain = Tms5220CodingTable::rms.at(energy_idx); - auto pitch = Tms5220CodingTable::pitch.at(pitch_idx); + auto gain = coding_table::tms5220::rms.at(energy_idx); + auto pitch = coding_table::tms5220::pitch.at(pitch_idx); if (is_repeat) { append(Frame(pitch, false, gain, std::vector(10, 0.0f))); @@ -262,10 +262,10 @@ void FrameEncoder::extractUnvoicedCoeffs(const std::string &chunk, float *k1, auto k4_idx = std::stoul(chunk.substr(25, 4), nullptr, 2); // TODO(Joseph Bellahcen): Guard against nullptr dereference - *k1 = Tms5220CodingTable::k1.at(k1_idx); - *k2 = Tms5220CodingTable::k2.at(k2_idx); - *k3 = Tms5220CodingTable::k3.at(k3_idx); - *k4 = Tms5220CodingTable::k4.at(k4_idx); + *k1 = coding_table::tms5220::k1.at(k1_idx); + *k2 = coding_table::tms5220::k2.at(k2_idx); + *k3 = coding_table::tms5220::k3.at(k3_idx); + *k4 = coding_table::tms5220::k4.at(k4_idx); } void FrameEncoder::extractVoicedCoeffs(const std::string &chunk, float *k5, @@ -278,12 +278,12 @@ void FrameEncoder::extractVoicedCoeffs(const std::string &chunk, float *k5, auto k10_idx = std::stoul(chunk.substr(47, 3), nullptr, 2); // TODO(Joseph Bellahcen): Guard against nullptr dereference - *k5 = Tms5220CodingTable::k5.at(k5_idx); - *k6 = Tms5220CodingTable::k6.at(k6_idx); - *k7 = Tms5220CodingTable::k7.at(k7_idx); - *k8 = Tms5220CodingTable::k8.at(k8_idx); - *k9 = Tms5220CodingTable::k9.at(k9_idx); - *k10 = Tms5220CodingTable::k10.at(k10_idx); + *k5 = coding_table::tms5220::k5.at(k5_idx); + *k6 = coding_table::tms5220::k6.at(k6_idx); + *k7 = coding_table::tms5220::k7.at(k7_idx); + *k8 = coding_table::tms5220::k8.at(k8_idx); + *k9 = coding_table::tms5220::k9.at(k9_idx); + *k10 = coding_table::tms5220::k10.at(k10_idx); } /////////////////////////////////////////////////////////////////////////////// diff --git a/tms_express/Frame_Encoding/FramePostprocessor.cpp b/tms_express/Frame_Encoding/FramePostprocessor.cpp index 256cb2c..56c2f73 100644 --- a/tms_express/Frame_Encoding/FramePostprocessor.cpp +++ b/tms_express/Frame_Encoding/FramePostprocessor.cpp @@ -4,8 +4,8 @@ #include +#include "Frame_Encoding/CodingTable.hpp" #include "Frame_Encoding/Frame.hpp" -#include "Frame_Encoding/Tms5220CodingTable.h" namespace tms_express { @@ -88,14 +88,14 @@ void FramePostprocessor::shiftGain(int offset) { // If the shifted gain would exceed the maximum representable gain of // the coding table, let it "hit the ceiling." Overuse of the largest // gain parameter may destabilize the synthesized signal - if (change >= Tms5220CodingTable::rms.size()) { - frame.setGain(*Tms5220CodingTable::rms.end()); + if (change >= coding_table::tms5220::rms.size()) { + frame.setGain(*coding_table::tms5220::rms.end()); } else if (change < 0) { frame.setGain(0); } else { - frame.setGain(Tms5220CodingTable::rms.at(change)); + frame.setGain(coding_table::tms5220::rms.at(change)); } } } @@ -114,14 +114,14 @@ void FramePostprocessor::shiftPitch(int offset) { continue; } - if (change >= Tms5220CodingTable::pitch.size()) { - frame.setPitch(*Tms5220CodingTable::pitch.end()); + if (change >= coding_table::tms5220::pitch.size()) { + frame.setPitch(*coding_table::tms5220::pitch.end()); } else if (change < 0) { frame.setPitch(0); } else { - frame.setPitch(Tms5220CodingTable::pitch.at(change)); + frame.setPitch(coding_table::tms5220::pitch.at(change)); } } } @@ -129,11 +129,11 @@ void FramePostprocessor::shiftPitch(int offset) { void FramePostprocessor::overridePitch(int index) { for (Frame &frame : *frame_table_) { if (!frame.isSilent()) { - if (index >= Tms5220CodingTable::pitch.size()) { - frame.setPitch(*Tms5220CodingTable::pitch.end()); + if (index >= coding_table::tms5220::pitch.size()) { + frame.setPitch(*coding_table::tms5220::pitch.end()); } else { - frame.setPitch(Tms5220CodingTable::pitch.at(index)); + frame.setPitch(coding_table::tms5220::pitch.at(index)); } } } diff --git a/tms_express/Frame_Encoding/Synthesizer.cpp b/tms_express/Frame_Encoding/Synthesizer.cpp index 3821b05..8a3db2d 100644 --- a/tms_express/Frame_Encoding/Synthesizer.cpp +++ b/tms_express/Frame_Encoding/Synthesizer.cpp @@ -1,6 +1,6 @@ // Copyright (C) 2023 Joseph Bellahcen -// References: https://github.com/going-digital/Talkie -// https://github.com/tocisz/talkie.love +// Reference: Arduino Talkie (https://github.com/going-digital/Talkie) +// Reference: Talkie.Love (https://github.com/tocisz/talkie.love) #include "Frame_Encoding/Synthesizer.hpp" @@ -10,8 +10,8 @@ #include #include "Audio/AudioBuffer.hpp" +#include "Frame_Encoding/CodingTable.hpp" #include "Frame_Encoding/Frame.hpp" -#include "Frame_Encoding/Tms5220CodingTable.h" namespace tms_express { @@ -94,26 +94,26 @@ bool Synthesizer::updateSynthTable(Frame frame) { return true; } else { - energy_ = Tms5220CodingTable::energy[quantized_gain]; - period_ = Tms5220CodingTable::pitch[frame.quantizedPitch()]; + energy_ = coding_table::tms5220::energy[quantized_gain]; + period_ = coding_table::tms5220::pitch[frame.quantizedPitch()]; if (!frame.isRepeat()) { auto coeffs = frame.quantizedCoeffs(); // Voiced/unvoiced parameters - k1_ = Tms5220CodingTable::k1[coeffs[0]]; - k2_ = Tms5220CodingTable::k2[coeffs[1]]; - k3_ = Tms5220CodingTable::k3[coeffs[2]]; - k4_ = Tms5220CodingTable::k4[coeffs[3]]; + k1_ = coding_table::tms5220::k1[coeffs[0]]; + k2_ = coding_table::tms5220::k2[coeffs[1]]; + k3_ = coding_table::tms5220::k3[coeffs[2]]; + k4_ = coding_table::tms5220::k4[coeffs[3]]; // Voiced-only parameters if (std::fpclassify(period_) != FP_ZERO) { - k5_ = Tms5220CodingTable::k5[coeffs[4]]; - k6_ = Tms5220CodingTable::k6[coeffs[5]]; - k7_ = Tms5220CodingTable::k7[coeffs[6]]; - k8_ = Tms5220CodingTable::k8[coeffs[7]]; - k9_ = Tms5220CodingTable::k9[coeffs[8]]; - k10_ = Tms5220CodingTable::k10[coeffs[9]]; + k5_ = coding_table::tms5220::k5[coeffs[4]]; + k6_ = coding_table::tms5220::k6[coeffs[5]]; + k7_ = coding_table::tms5220::k7[coeffs[6]]; + k8_ = coding_table::tms5220::k8[coeffs[7]]; + k9_ = coding_table::tms5220::k9[coeffs[8]]; + k10_ = coding_table::tms5220::k10[coeffs[9]]; } } } @@ -130,8 +130,8 @@ float Synthesizer::updateLatticeFilter() { period_count_ = 0; } - if (period_count_ < Tms5220CodingTable::chirpWidth) { - u0_ = ((Tms5220CodingTable::chirp[period_count_]) * energy_); + if (period_count_ < coding_table::tms5220::chirp.size()) { + u0_ = ((coding_table::tms5220::chirp[period_count_]) * energy_); } else { u0_ = 0; } diff --git a/tms_express/Frame_Encoding/Tms5220CodingTable.h b/tms_express/Frame_Encoding/Tms5220CodingTable.h deleted file mode 100644 index 710ce8e..0000000 --- a/tms_express/Frame_Encoding/Tms5220CodingTable.h +++ /dev/null @@ -1,122 +0,0 @@ -// Author: Joseph Bellahcen - -#ifndef TMS_EXPRESS_TMS5220CODINGTABLE_H -#define TMS_EXPRESS_TMS5220CODINGTABLE_H - -#include -#include -#include - -namespace tms_express { - -using std::vector; -using std::array; - -namespace Tms5220CodingTable { - const array rms = {0, 52, 87, 123, 174, 246, 348, 491, 694, 981, - 1385, 1957, 2764, 3904, 5514, 7789}; - - const array pitch = {0, 15, 16, 17, 18, 19, 20, 21, 22, 23, - 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, - 34, 35, 36, 37, 38, 39, 40, 41, 42, 44, - 46, 48, 50, 52, 53, 56, 58, 60, 62, 65, - 68, 70, 72, 76, 78, 80, 84, 86, 91, 94, - 98, 101, 105, 109, 114, 118, 122, 127, 132, - 137, 142, 148, 153, 159}; - - namespace { - const array k1 = {-0.97850f, -0.97270f, -0.97070f, -0.96680f, -0.96290f, -0.95900f, - -0.95310f, -0.94140f, -0.93360f, -0.92580f, -0.91600f, -0.90620f, - -0.89650f, -0.88280f, -0.86910f, -0.85350f, -0.80420f, -0.74058f, - -0.66019f, -0.56116f, -0.44296f, -0.30706f, -0.15735f, -0.00005f, - 0.15725f, 0.30696f, 0.44288f, 0.56109f, 0.66013f, 0.74054f, - 0.80416f, 0.85350f}; - - const array k2 = {-0.64000f, -0.58999f, -0.53500f, -0.47507f, -0.41039f, -0.34129f, - -0.26830f, -0.19209f, -0.11350f, -0.03345f, 0.04702f, 0.12690f, - 0.20515f, 0.28087f, 0.35325f, 0.42163f, 0.48553f, 0.54464f, - 0.59878f, 0.64796f, 0.69227f, 0.73190f, 0.76714f, 0.79828f, - 0.82567f, 0.84965f, 0.87057f, 0.88875f, 0.90451f, 0.91813f, - 0.92988f, 0.98830f}; - - const array k3 = {-0.86000f, -0.75467f, -0.64933f, -0.54400f, -0.43867f, -0.33333f, - -0.22800f, -0.12267f, -0.01733, 0.08800f, 0.19333f, 0.29867f, - 0.40400f, 0.50933f, 0.61467f, 0.72000f}; - - const array k4 = {-0.64000f, -0.53145f, -0.42289f, -0.31434f, -0.20579f, -0.09723f, - 0.01132f, 0.11987f, 0.22843f, 0.33698f, 0.44553f, 0.55409f, - 0.66264f, 0.77119f, 0.87975f, 0.98830f}; - - const array k5 = {-0.64000f, -0.54933f, -0.45867f, -0.36800f, -0.27733f, -0.18667f, - -0.09600f, -0.00533f, 0.08533f, 0.17600f, 0.26667f, 0.35733f, - 0.44800f, 0.53867f, 0.62933f, 0.72000f}; - - const array k6 = {-0.50000f, -0.41333f, -0.32667f, -0.24000f, -0.15333f, -0.06667f, - 0.02000f, 0.10667f, 0.19333f, 0.28000f, 0.36667f, 0.45333f, - 0.54000f, 0.62667f, 0.71333f, 0.80000f}; - - const array k7 = {-0.60000f, -0.50667f, -0.41333f, -0.32000f, -0.22667f, -0.13333f, - -0.04000f, 0.05333f, 0.14667f, 0.24000f, 0.33333f, 0.42667f, - 0.52000f, 0.61333f, 0.70667f, 0.80000f}; - - const array k8 = {-0.50000f, -0.31429f, -0.12857f, 0.05714f, 0.24286f, 0.42857f, - 0.61429f, 0.80000f}; - - const array k9 = {-0.50000f, -0.34286f, -0.18571f, 0.02857f, 0.12857f, 0.28571f, - 0.44286f, 0.60000f}; - - const array k10 = {-0.40000f, -0.25714f, -0.11429f, 0.02857f, 0.17143f, 0.31429f, - 0.45714f, 0.60000f}; - - const array chirp = {0, 0.328125, -0.34375, 0.390625, -0.609375, 0.140625, 0.2890625, 0.15625, - 0.015625, -0.2421875, -0.4609375, 0.015625, 0.7421875, 0.703125, 0.0390625, - 0.1171875, 0.296875, -0.03125, -0.7109375, -0.7109375, -0.328125, -0.2734375, - -0.28125, -0.03125, 0.2890625, 0.3359375, 0.265625, 0.2578125, 0.1171875, - -0.0078125, -0.0625, -0.140625, -0.1484375, -0.1328125, -0.0703125, -0.078125, - -0.046875, 0, 0.0234375, 0.015625, 0.0078125}; - - const array energy = {0, 0.00390625, 0.005859375, 0.0078125, 0.009765625, 0.013671875, 0.01953125, - 0.029296875, 0.0390625, 0.0625, 0.080078125, 0.111328125, 0.158203125, - 0.22265625, 0.314453125, 0}; - } - - const int nKCoeffs = 10; - - const int gainWidth = 4; - const int rmsWidth = 16; - const int pitchWidth = 6; - const int voicingWidth = 1; - const int chirpWidth = 41; - const array coeffWidths = {5, 5, 4, 4, 4, 4, 4, 3, 3, 3}; - - static vector kCoeffsSlice(int i) { - switch (i) { - case 0: - return {k1.begin(), k1.end()}; - case 1: - return {k2.begin(), k2.end()}; - case 2: - return {k3.begin(), k3.end()}; - case 3: - return {k4.begin(), k4.end()}; - case 4: - return {k5.begin(), k5.end()}; - case 5: - return {k6.begin(), k6.end()}; - case 6: - return {k7.begin(), k7.end()}; - case 7: - return {k8.begin(), k8.end()}; - case 8: - return {k9.begin(), k9.end()}; - case 9: - return {k10.begin(), k10.end()}; - default: - throw std::range_error("K-coefficient slice index out of bounds"); - } - } -}; - -}; // namespace tms_express - -#endif //TMS_EXPRESS_TMS5220CODINGTABLE_H diff --git a/tms_express/User_Interfaces/Control_Panels/ControlPanelPostView.cpp b/tms_express/User_Interfaces/Control_Panels/ControlPanelPostView.cpp index 2999e17..1d044c1 100644 --- a/tms_express/User_Interfaces/Control_Panels/ControlPanelPostView.cpp +++ b/tms_express/User_Interfaces/Control_Panels/ControlPanelPostView.cpp @@ -6,7 +6,7 @@ // Author: Joseph Bellahcen /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#include "Frame_Encoding/Tms5220CodingTable.h" +#include "Frame_Encoding/CodingTable.hpp" #include "User_Interfaces/Control_Panels/ControlPanelPostView.h" #include @@ -14,8 +14,6 @@ namespace tms_express { -using namespace Tms5220CodingTable; - ControlPanelPostView::ControlPanelPostView(QWidget *parent): ControlPanelView("Post-Processing", parent) { auto line2 = new QFrame(this); line2->setFrameShape(QFrame::HLine); @@ -36,14 +34,14 @@ ControlPanelPostView::ControlPanelPostView(QWidget *parent): ControlPanelView("P maxVoicedGainLine = new QLineEdit("37.5", this); // Setup sliders based on TMS5220 coding table - pitchShiftSlider->setRange(-pitch.size(), pitch.size()); - pitchShiftSlider->setTickInterval(pitch.size() / 8); + pitchShiftSlider->setRange(-coding_table::tms5220::pitch.size(), coding_table::tms5220::pitch.size()); + pitchShiftSlider->setTickInterval(coding_table::tms5220::pitch.size() / 8); - pitchOverrideSlider->setRange(0, pitch.size()); - pitchOverrideSlider->setTickInterval(pitch.size() / 16); + pitchOverrideSlider->setRange(0, coding_table::tms5220::pitch.size()); + pitchOverrideSlider->setTickInterval(coding_table::tms5220::pitch.size() / 16); - gainShiftSlider->setRange(-rms.size(), rms.size()); - gainShiftSlider->setTickInterval(rms.size() / 8); + gainShiftSlider->setRange(-coding_table::tms5220::rms.size(), coding_table::tms5220::rms.size()); + gainShiftSlider->setTickInterval(coding_table::tms5220::rms.size() / 8); // Construct layout auto row = grid->rowCount(); @@ -89,7 +87,7 @@ void ControlPanelPostView::reset() { pitchShiftCheckbox->setChecked(false); pitchShiftSlider->setSliderPosition(0); pitchOverrideCheckbox->setChecked(false); - pitchOverrideSlider->setSliderPosition(pitch.size() / 2); + pitchOverrideSlider->setSliderPosition(coding_table::tms5220::pitch.size() / 2); repeatFramesCheckbox->setChecked(false); From 4423405ef038e1b4f3b6e72f7b6c9e1cc374b4ac Mon Sep 17 00:00:00 2001 From: Joseph Bellahcen Date: Tue, 25 Jul 2023 17:11:51 -0700 Subject: [PATCH 15/30] Refactored Autocorrelation --- test/AutocorrelatorTests.cpp | 2 +- tms_express/Bitstream_Generation/BitstreamGenerator.cpp | 2 +- tms_express/LPC_Analysis/Autocorrelation.cpp | 3 ++- .../{Autocorrelation.h => Autocorrelation.hpp} | 8 ++++---- tms_express/User_Interfaces/MainWindow.cpp | 2 +- 5 files changed, 9 insertions(+), 8 deletions(-) rename tms_express/LPC_Analysis/{Autocorrelation.h => Autocorrelation.hpp} (59%) diff --git a/test/AutocorrelatorTests.cpp b/test/AutocorrelatorTests.cpp index 833bbc0..dcd0888 100644 --- a/test/AutocorrelatorTests.cpp +++ b/test/AutocorrelatorTests.cpp @@ -2,7 +2,7 @@ // Created by Joseph Bellahcen on 6/1/22. // -#include "LPC_Analysis/Autocorrelation.h" +#include "LPC_Analysis/Autocorrelation.hpp" #include #include #include diff --git a/tms_express/Bitstream_Generation/BitstreamGenerator.cpp b/tms_express/Bitstream_Generation/BitstreamGenerator.cpp index 46abb5b..c6cde1d 100644 --- a/tms_express/Bitstream_Generation/BitstreamGenerator.cpp +++ b/tms_express/Bitstream_Generation/BitstreamGenerator.cpp @@ -13,7 +13,7 @@ #include "Frame_Encoding/Frame.hpp" #include "Frame_Encoding/FrameEncoder.hpp" #include "Frame_Encoding/FramePostprocessor.hpp" -#include "LPC_Analysis/Autocorrelation.h" +#include "LPC_Analysis/Autocorrelation.hpp" #include "LPC_Analysis/LinearPredictor.h" #include "LPC_Analysis/PitchEstimator.h" diff --git a/tms_express/LPC_Analysis/Autocorrelation.cpp b/tms_express/LPC_Analysis/Autocorrelation.cpp index b13c8a7..054bcde 100644 --- a/tms_express/LPC_Analysis/Autocorrelation.cpp +++ b/tms_express/LPC_Analysis/Autocorrelation.cpp @@ -1,6 +1,7 @@ // Copyright 2023 Joseph Bellahcen -#include "LPC_Analysis/Autocorrelation.h" +#include "LPC_Analysis/Autocorrelation.hpp" + #include namespace tms_express { diff --git a/tms_express/LPC_Analysis/Autocorrelation.h b/tms_express/LPC_Analysis/Autocorrelation.hpp similarity index 59% rename from tms_express/LPC_Analysis/Autocorrelation.h rename to tms_express/LPC_Analysis/Autocorrelation.hpp index 557d821..f6f82bf 100644 --- a/tms_express/LPC_Analysis/Autocorrelation.h +++ b/tms_express/LPC_Analysis/Autocorrelation.hpp @@ -1,13 +1,13 @@ // Copyright 2023 Joseph Bellahcen -#ifndef LPC_ANALYSIS_AUTOCORRELATION_H_ -#define LPC_ANALYSIS_AUTOCORRELATION_H_ +#ifndef TMS_EXPRESS_LPC_ANALYSIS_AUTOCORRELATION_HPP_ +#define TMS_EXPRESS_LPC_ANALYSIS_AUTOCORRELATION_HPP_ #include namespace tms_express { -/// @brief Compute biased autocorrelation of segment +/// @brief Computes biased autocorrelation of segment /// /// @param segment Segment from which to compute autocorrelation /// @return Biased autocorrelation of segment @@ -15,4 +15,4 @@ std::vector Autocorrelation(const std::vector &segment); }; // namespace tms_express -#endif // LPC_ANALYSIS_AUTOCORRELATION_H_ +#endif // TMS_EXPRESS_LPC_ANALYSIS_AUTOCORRELATION_HPP_ diff --git a/tms_express/User_Interfaces/MainWindow.cpp b/tms_express/User_Interfaces/MainWindow.cpp index 5417920..d6cb15e 100644 --- a/tms_express/User_Interfaces/MainWindow.cpp +++ b/tms_express/User_Interfaces/MainWindow.cpp @@ -8,7 +8,7 @@ #include "Audio/AudioBuffer.hpp" #include "Frame_Encoding/FramePostprocessor.hpp" -#include "LPC_Analysis/Autocorrelation.h" +#include "LPC_Analysis/Autocorrelation.hpp" #include "User_Interfaces/Audio_Waveform/AudioWaveformView.h" #include "User_Interfaces/MainWindow.h" #include "User_Interfaces/Control_Panels/ControlPanelPitchView.h" From 5d4a9bd3f0077ea3828a585dd8b3b65a87e0c0e6 Mon Sep 17 00:00:00 2001 From: Joseph Bellahcen Date: Tue, 25 Jul 2023 20:20:42 -0700 Subject: [PATCH 16/30] Refactored LinearPredictor --- .../BitstreamGenerator.cpp | 4 +- tms_express/Frame_Encoding/CodingTable.hpp | 3 +- tms_express/LPC_Analysis/LinearPredictor.cpp | 84 +++++++++---------- tms_express/LPC_Analysis/LinearPredictor.h | 24 ------ tms_express/LPC_Analysis/LinearPredictor.hpp | 52 ++++++++++++ tms_express/User_Interfaces/MainWindow.cpp | 2 +- tms_express/User_Interfaces/MainWindow.h | 2 +- 7 files changed, 97 insertions(+), 74 deletions(-) delete mode 100644 tms_express/LPC_Analysis/LinearPredictor.h create mode 100644 tms_express/LPC_Analysis/LinearPredictor.hpp diff --git a/tms_express/Bitstream_Generation/BitstreamGenerator.cpp b/tms_express/Bitstream_Generation/BitstreamGenerator.cpp index c6cde1d..100240a 100644 --- a/tms_express/Bitstream_Generation/BitstreamGenerator.cpp +++ b/tms_express/Bitstream_Generation/BitstreamGenerator.cpp @@ -14,7 +14,7 @@ #include "Frame_Encoding/FrameEncoder.hpp" #include "Frame_Encoding/FramePostprocessor.hpp" #include "LPC_Analysis/Autocorrelation.hpp" -#include "LPC_Analysis/LinearPredictor.h" +#include "LPC_Analysis/LinearPredictor.hpp" #include "LPC_Analysis/PitchEstimator.h" namespace tms_express { @@ -142,7 +142,7 @@ std::vector BitstreamGenerator::generateFrames( auto pitch_acf = tms_express::Autocorrelation(pitch_segment); // Extract LPC reflector coefficients and compute the predictor gain - auto coeffs = linearPredictor.reflectorCoefficients(lpc_acf); + auto coeffs = linearPredictor.computeCoeffs(lpc_acf); auto gain = linearPredictor.gain(); // Estimate pitch diff --git a/tms_express/Frame_Encoding/CodingTable.hpp b/tms_express/Frame_Encoding/CodingTable.hpp index b7d40a2..b85e16b 100644 --- a/tms_express/Frame_Encoding/CodingTable.hpp +++ b/tms_express/Frame_Encoding/CodingTable.hpp @@ -1,5 +1,6 @@ // Copyright (C) 2023 Joseph Bellahcen -// Reference: TMS 5220 VOICE SYNTHESIS PROCESSOR DATA MANUAL (http://sprow.co.uk/bbc/hardware/speech/tms5220.pdf) +// Reference: TMS 5220 VOICE SYNTHESIS PROCESSOR DATA MANUAL +// (http://sprow.co.uk/bbc/hardware/speech/tms5220.pdf) // Reference: Arduino Talkie (https://github.com/going-digital/Talkie) #ifndef TMS_EXPRESS_FRAME_ENCODING_CODINGTABLE_HPP_ diff --git a/tms_express/LPC_Analysis/LinearPredictor.cpp b/tms_express/LPC_Analysis/LinearPredictor.cpp index 35df1cf..fffac3b 100644 --- a/tms_express/LPC_Analysis/LinearPredictor.cpp +++ b/tms_express/LPC_Analysis/LinearPredictor.cpp @@ -1,52 +1,45 @@ -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// Class: LinearPredictor -// -// Description: The LinearPredictor performs upper vocal tract analysis on a speech segment, determining both the -// coefficients of an M-th order linear system which define the vocal tract and the prediction gain -// -// Author: Joseph Bellahcen -// -// Acknowledgement: The Levinson-Durbin recursion algorithm used to find the LPC coefficients is adapted from the paper -// "Levinson–Durbin Algorithm" (Castiglioni) -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -#include "LPC_Analysis/LinearPredictor.h" + + +#include "LPC_Analysis/LinearPredictor.hpp" #include #include namespace tms_express { -/// Create a new LPC solver of the given order -/// -/// \param modelOrder Order of the LPC model for which to solve (typically 10) +/////////////////////////////////////////////////////////////////////////////// +// Initializers /////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + LinearPredictor::LinearPredictor(int modelOrder) { - order = modelOrder; - error = 0.0f; + order_ = modelOrder; + error_ = 0.0f; } -/// Compute the LPC reflector coefficients of the speech signal -/// -/// \note The behavior fo the oral cavity during a segment of speech may be approximated by an M-th order all-pole -/// filter. The reflector coefficients of the filter minimize the energy of the output signal. There are many -/// equivalent algorithms for solving the linear system, which consists of an MxM Toeplitz matrix A, the -/// coefficient vector b, and the autocorrelation coefficients c. The algorithm implemented below is the -/// Levinson-Durbin recursion -std::vector LinearPredictor::reflectorCoefficients(const std::vector& acf) { - // Model parameters - // - // r: autocorrelation - // k: reflector coefficients - // e: error - // b: coefficient matrix +/////////////////////////////////////////////////////////////////////////////// +// Linear Prediction ////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +std::vector LinearPredictor::computeCoeffs( + const std::vector& acf) { + // Reference: "Levinson–Durbin Algorithm" (Castiglioni) + + // Autocorrelation alias auto r = acf; - auto k = std::vector(order + 1); - auto e = std::vector(order + 1); - auto b = std::vector>(order + 1, std::vector(order + 1)); + + // Reflector coefficients + auto k = std::vector(order_ + 1); + + // Errors + auto e = std::vector(order_ + 1); + + // Coefficient matrix + auto b = std::vector>(order_ + 1, + std::vector(order_ + 1)); e[0] = r[0]; k[0] = 0.0f; - for (int m = 1; m <= order; m++) { + for (int m = 1; m <= order_; m++) { float sum = r[m]; for (int i = 1; i < m; i++) { sum += b[m - 1][i] * r[m - i]; @@ -61,21 +54,22 @@ std::vector LinearPredictor::reflectorCoefficients(const std::vector(k.begin() + 1, k.end()); return reflectors; } -/// Compute the prediction gain of the segment -/// -/// \note The gain of the signal may be expressed as the ratio of the original signal energy and the residual error, -/// which is the final error coefficient. This error is scaled by a reference intensity and then expressed on -/// the decibel scale -/// -/// \source http://www.sengpielaudio.com/calculator-soundlevel.htm + float LinearPredictor::gain() const { + // TODO(Joseph Bellahcen): Handle case where called first + + // The gain of the signal may be expressed as the ratio of the original + // signal energy and the residual error, which is the final error + // coefficient. This error is scaled by a reference intensity and then + // expressed on the decibel scale + // Reference: http://www.sengpielaudio.com/calculator-soundlevel.htm // 10 * log10(x) == 20 * log10(sqrt(x)) - float gain = 10.0f * log10f(error / 1e-12f); + float gain = 10.0f * log10f(error_ / 1e-12f); return abs(gain); } diff --git a/tms_express/LPC_Analysis/LinearPredictor.h b/tms_express/LPC_Analysis/LinearPredictor.h deleted file mode 100644 index 3b64c77..0000000 --- a/tms_express/LPC_Analysis/LinearPredictor.h +++ /dev/null @@ -1,24 +0,0 @@ -// Author: Joseph Bellahcen - -#ifndef TMS_EXPRESS_LINEARPREDICTOR_H -#define TMS_EXPRESS_LINEARPREDICTOR_H - -#include - -namespace tms_express { - -class LinearPredictor { -public: - explicit LinearPredictor(int modelOrder = 10); - - std::vector reflectorCoefficients(const std::vector &acf); - [[nodiscard]] float gain() const; - -private: - int order; - float error; -}; - -}; // namespace tms_express - -#endif //TMS_EXPRESS_LINEARPREDICTOR_H \ No newline at end of file diff --git a/tms_express/LPC_Analysis/LinearPredictor.hpp b/tms_express/LPC_Analysis/LinearPredictor.hpp new file mode 100644 index 0000000..f8fb284 --- /dev/null +++ b/tms_express/LPC_Analysis/LinearPredictor.hpp @@ -0,0 +1,52 @@ +// Copyright 2023 Joseph Bellahcen + +#ifndef TMS_EXPRESS_LPC_ANALYSIS_LINEARPREDICTOR_HPP_ +#define TMS_EXPRESS_LPC_ANALYSIS_LINEARPREDICTOR_HPP_ + +#include + +namespace tms_express { + +/// @brief Performs upper-vocal-tract analysis, yielding LPC reflector +/// coefficients and prediction error +class LinearPredictor { + public: + /////////////////////////////////////////////////////////////////////////// + // Initializers /////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @brief Creates new Linear Predictor with given model order + /// @param modelOrder Model order, corresponding to number of filter poles + explicit LinearPredictor(int modelOrder = 10); + + /////////////////////////////////////////////////////////////////////////// + // Linear Prediction ////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @brief Computes LPC reflector coefficients of given autocorrelation + /// @param acf Autocorrelation corresponding to a segment of speech data + /// @return Vector of n_pole LPC reflector coefficients + std::vector computeCoeffs(const std::vector &acf); + + /// @brief Computes gain from prediction error + /// @return Prediction gain, in decibels + /// @warning This function must not be called before + /// LinearPredictor::computeCoeffs() + float gain() const; + + private: + /////////////////////////////////////////////////////////////////////////// + // Members //////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @brief Model order, corresponding to the number of poles in the LPC + /// lattice filter + int order_; + + /// @brief Prediction error, which is used to model the gain of the signal + float error_; +}; + +}; // namespace tms_express + +#endif // TMS_EXPRESS_LPC_ANALYSIS_LINEARPREDICTOR_HPP_ diff --git a/tms_express/User_Interfaces/MainWindow.cpp b/tms_express/User_Interfaces/MainWindow.cpp index d6cb15e..9518903 100644 --- a/tms_express/User_Interfaces/MainWindow.cpp +++ b/tms_express/User_Interfaces/MainWindow.cpp @@ -468,7 +468,7 @@ void MainWindow::performLpcAnalysis() { auto segment = lpcBuffer.getSegment(i); auto acf = tms_express::Autocorrelation(segment); - auto coeffs = linearPredictor.reflectorCoefficients(acf); + auto coeffs = linearPredictor.computeCoeffs(acf); auto gain = linearPredictor.gain(); auto pitchPeriod = pitchPeriodTable[i]; diff --git a/tms_express/User_Interfaces/MainWindow.h b/tms_express/User_Interfaces/MainWindow.h index 20b0b39..41078eb 100644 --- a/tms_express/User_Interfaces/MainWindow.h +++ b/tms_express/User_Interfaces/MainWindow.h @@ -11,7 +11,7 @@ #include "Frame_Encoding/FramePostprocessor.hpp" #include "Frame_Encoding/Synthesizer.hpp" #include "LPC_Analysis/PitchEstimator.h" -#include "LPC_Analysis/LinearPredictor.h" +#include "LPC_Analysis/LinearPredictor.hpp" #include "User_Interfaces/Audio_Waveform/AudioWaveformView.h" #include "User_Interfaces/Control_Panels/ControlPanelPitchView.h" #include "User_Interfaces/Control_Panels/ControlPanelLpcView.h" From d46c18436c8bc5e0c3aaf42600a50f91765fc9d0 Mon Sep 17 00:00:00 2001 From: Joseph Bellahcen Date: Sun, 30 Jul 2023 11:02:42 -0700 Subject: [PATCH 17/30] Refactored Pitch Estimator --- CMakeLists.txt | 3 +- .../BitstreamGenerator.cpp | 2 +- tms_express/LPC_Analysis/LinearPredictor.cpp | 4 +- tms_express/LPC_Analysis/LinearPredictor.hpp | 2 +- tms_express/LPC_Analysis/PitchEstimator.cpp | 97 +++++++------------ tms_express/LPC_Analysis/PitchEstimator.h | 36 ------- tms_express/LPC_Analysis/PitchEstimator.hpp | 82 ++++++++++++++++ tms_express/User_Interfaces/MainWindow.h | 2 +- 8 files changed, 124 insertions(+), 104 deletions(-) delete mode 100644 tms_express/LPC_Analysis/PitchEstimator.h create mode 100644 tms_express/LPC_Analysis/PitchEstimator.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 8999979..f6a0b8e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -63,7 +63,7 @@ add_executable(${PROJECT_NAME} # ==================================== # DYNAMIC LIBS # ==================================== -find_package(Qt6 COMPONENTS Core Gui Multimedia Widgets PrintSupport REQUIRED) +find_package(Qt6 COMPONENTS Core Gui Multimedia Widgets REQUIRED) find_package(PkgConfig REQUIRED) pkg_check_modules(SndFile REQUIRED IMPORTED_TARGET sndfile) @@ -76,7 +76,6 @@ target_link_libraries(${PROJECT_NAME} Qt::Core Qt::Gui Qt::Widgets - Qt::PrintSupport Qt::Multimedia ) diff --git a/tms_express/Bitstream_Generation/BitstreamGenerator.cpp b/tms_express/Bitstream_Generation/BitstreamGenerator.cpp index 100240a..ca5c84e 100644 --- a/tms_express/Bitstream_Generation/BitstreamGenerator.cpp +++ b/tms_express/Bitstream_Generation/BitstreamGenerator.cpp @@ -15,7 +15,7 @@ #include "Frame_Encoding/FramePostprocessor.hpp" #include "LPC_Analysis/Autocorrelation.hpp" #include "LPC_Analysis/LinearPredictor.hpp" -#include "LPC_Analysis/PitchEstimator.h" +#include "LPC_Analysis/PitchEstimator.hpp" namespace tms_express { diff --git a/tms_express/LPC_Analysis/LinearPredictor.cpp b/tms_express/LPC_Analysis/LinearPredictor.cpp index fffac3b..dc40528 100644 --- a/tms_express/LPC_Analysis/LinearPredictor.cpp +++ b/tms_express/LPC_Analysis/LinearPredictor.cpp @@ -10,8 +10,8 @@ namespace tms_express { // Initializers /////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// -LinearPredictor::LinearPredictor(int modelOrder) { - order_ = modelOrder; +LinearPredictor::LinearPredictor(int model_order) { + order_ = model_order; error_ = 0.0f; } diff --git a/tms_express/LPC_Analysis/LinearPredictor.hpp b/tms_express/LPC_Analysis/LinearPredictor.hpp index f8fb284..9835880 100644 --- a/tms_express/LPC_Analysis/LinearPredictor.hpp +++ b/tms_express/LPC_Analysis/LinearPredictor.hpp @@ -17,7 +17,7 @@ class LinearPredictor { /// @brief Creates new Linear Predictor with given model order /// @param modelOrder Model order, corresponding to number of filter poles - explicit LinearPredictor(int modelOrder = 10); + explicit LinearPredictor(int model_order = 10); /////////////////////////////////////////////////////////////////////////// // Linear Prediction ////////////////////////////////////////////////////// diff --git a/tms_express/LPC_Analysis/PitchEstimator.cpp b/tms_express/LPC_Analysis/PitchEstimator.cpp index 2f27fee..481255b 100644 --- a/tms_express/LPC_Analysis/PitchEstimator.cpp +++ b/tms_express/LPC_Analysis/PitchEstimator.cpp @@ -1,100 +1,75 @@ -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// Class: PitchEstimator -// -// Description: The PitchEstimator detects the pitch of a segment based on its cumulative mean normalized distribution. -// This class implements a very basic autocorrelation-based pitch detector, which exploits the fact that -// the autocorrelation of a periodic signal is also periodic -// -// Author: Joseph Bellahcen -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -#include "LPC_Analysis/PitchEstimator.h" +// Copyright 2023 Joseph Bellahcen + +#include "LPC_Analysis/PitchEstimator.hpp" + #include #include namespace tms_express { -/// Create a new pitch estimator, bounded by the min and max frequencies -/// -/// \param sampleRateHz Sample rate of the audio samples -/// \param minFrqHz Minimum frequency to search -/// \param maxFrqHz Maximum frequency to search -PitchEstimator::PitchEstimator(int sampleRateHz, int minFrqHz, int maxFrqHz) { - maxPeriod = sampleRateHz / minFrqHz; - minPeriod = sampleRateHz / maxFrqHz; - sampleRate = sampleRateHz; +/////////////////////////////////////////////////////////////////////////////// +// Initializers /////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +PitchEstimator::PitchEstimator(int sample_rate_hz, int min_frq_hz, + int max_frq_hz) { + max_period_ = sample_rate_hz / min_frq_hz; + min_period_ = sample_rate_hz / max_frq_hz; + sample_rate_hz_ = sample_rate_hz; } /////////////////////////////////////////////////////////////////////////////// -// Getters & Setters +// Accessors ////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// -/// Return the minimum pitch period (in samples) to search int PitchEstimator::getMinPeriod() const { - return minPeriod; + return min_period_; } -/// Return the minimum pitch frequency (in Hertz) to search int PitchEstimator::getMinFrq() const { - return sampleRate / maxPeriod; + return sample_rate_hz_ / max_period_; } -/// Set the minimum pitch period (in samples) to search -/// -/// \note Setting a minimum pitch period may reduce the computation time of pitch estimation -/// -/// \param maxFrqHz The minimum pitch period is determined by the maximum pitch frequency (in Hertz) void PitchEstimator::setMinPeriod(int maxFrqHz) { - minPeriod = sampleRate / maxFrqHz; + min_period_ = sample_rate_hz_ / maxFrqHz; } -/// Return the maximum pitch period (in samples) to search int PitchEstimator::getMaxPeriod() const { - return maxPeriod; + return max_period_; } -/// Return the maximum pitch frequency (in Hertz) to search int PitchEstimator::getMaxFrq() const { - return sampleRate / minPeriod; + return sample_rate_hz_ / min_period_; } -/// Set the maximum pitch period (in samples) to search -/// -/// \note Setting a maximum pitch period may reduce the computation time of pitch estimation -/// -/// \param minFrqHz The maximum pitch period is determined by the minimum pitch frequency (in Hertz) void PitchEstimator::setMaxPeriod(int minFrqHz) { - maxPeriod = sampleRate / minFrqHz; + max_period_ = sample_rate_hz_ / minFrqHz; } /////////////////////////////////////////////////////////////////////////////// -// Estimators +// Pitch Estimation /////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// -/// Estimate the pitch frequency of the segment (in Hertz) from its autocorrelation -/// -/// \param acf Autocorrelation of the segment -/// \return Pitch frequency estimate (in Hertz) float PitchEstimator::estimateFrequency(const std::vector &acf) const { auto period = estimatePeriod(acf); - return float(sampleRate) / float(period); + return static_cast(sample_rate_hz_) / static_cast(period); } -/// Estimate the pitch period of the segment (in samples) from its autocorrelation int PitchEstimator::estimatePeriod(const std::vector &acf) const { - // Restrict the search window to the min and max pitch periods set during initialization - auto acfStart = acf.begin() + minPeriod; - auto acfEnd = acf.begin() + maxPeriod; - - // Identify the first local minimum and subsequent local maximum. The distance between these values likely - // corresponds to the pitch period of the segment - auto firstLocalMin = std::min_element(acfStart, acfEnd); - auto period = int(std::distance(acf.begin(), std::max_element(firstLocalMin, acfEnd))); - - if (period > maxPeriod) { - return maxPeriod; - } else if (period < minPeriod) { - return minPeriod; + // Restrict the search window to the min and max pitch periods + auto start = acf.begin() + min_period_; + auto end = acf.begin() + max_period_; + + // Identify the first local minimum and subsequent local maximum. + // The distance between these values likely corresponds to the pitch period + // of the segment + auto local_min = std::min_element(start, end); + auto period = std::distance(acf.begin(), std::max_element(local_min, end)); + + if (period > max_period_) { + return max_period_; + } else if (period < min_period_) { + return min_period_; } else { return period; } diff --git a/tms_express/LPC_Analysis/PitchEstimator.h b/tms_express/LPC_Analysis/PitchEstimator.h deleted file mode 100644 index a5c5ebe..0000000 --- a/tms_express/LPC_Analysis/PitchEstimator.h +++ /dev/null @@ -1,36 +0,0 @@ -// Author: Joseph Bellahcen - -#ifndef TMS_EXPRESS_PITCHESTIMATOR_H -#define TMS_EXPRESS_PITCHESTIMATOR_H - -#include "Audio/AudioBuffer.hpp" -#include - -namespace tms_express { - -class PitchEstimator { -public: - explicit PitchEstimator(int sampleRateHz, int minFrqHz = 50, int maxFrqHz = 500); - - // Getters & setters - [[nodiscard]] int getMaxPeriod() const; - int getMaxFrq() const; - void setMaxPeriod(int minFrqHz); - - [[nodiscard]] int getMinPeriod() const; - int getMinFrq() const; - void setMinPeriod(int maxFrqHz); - - // Estimator functions - [[nodiscard]] float estimateFrequency(const std::vector &acf) const; - [[nodiscard]] int estimatePeriod(const std::vector &acf) const; - -private: - int maxPeriod; - int minPeriod; - int sampleRate; -}; - -}; // namespace tms_express - -#endif //TMS_EXPRESS_PITCHESTIMATOR_H \ No newline at end of file diff --git a/tms_express/LPC_Analysis/PitchEstimator.hpp b/tms_express/LPC_Analysis/PitchEstimator.hpp new file mode 100644 index 0000000..2578a50 --- /dev/null +++ b/tms_express/LPC_Analysis/PitchEstimator.hpp @@ -0,0 +1,82 @@ +// Copyright 2023 Joseph Bellahcen + +#ifndef TMS_EXPRESS_LPC_ANALYSIS_PITCHESTIMATOR_HPP_ +#define TMS_EXPRESS_LPC_ANALYSIS_PITCHESTIMATOR_HPP_ + +#include + +#include "Audio/AudioBuffer.hpp" + +namespace tms_express { + +/// @brief Estimates pitch of sample using its autocorrelation +class PitchEstimator { + public: + /////////////////////////////////////////////////////////////////////////// + // Initializers /////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @brief Creates a new autocorrelation-based Pitch Estimator + /// @param sample_rate_hz Sample rate of data to analyze + /// @param min_frq_hz Minimum frequency to detect, in Hertz + /// @param max_frq_hz Maximum frequency to detect, in Hertz + explicit PitchEstimator(int sample_rate_hz, int min_frq_hz = 50, + int max_frq_hz = 500); + + /////////////////////////////////////////////////////////////////////////// + // Accessors ////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @brief Accesses the max pitch period, in samples + /// @return Max pitch period, in samples + int getMaxPeriod() const; + + /// @brief Sets the max pitch period + /// @param min_frq_hz Min pitch frequency, in Hertz + void setMaxPeriod(int min_frq_hz); + + /// @brief Accesses the max pitch frequency, in Hertz + /// @return Max pitch frequency, in Hertz + int getMaxFrq() const; + + /// @brief Accesses the min pitch period, in samples + /// @return Min pitch period, in samples + int getMinPeriod() const; + + /// @brief Sets the min pitch period + /// @param min_frq_hz Max pitch frequency, in Hertz + void setMinPeriod(int max_frq_hz); + + /// @brief Accesses the min pitch frequency, in Hertz + /// @return Min pitch frequency, in Hertz + int getMinFrq() const; + + /////////////////////////////////////////////////////////////////////////// + // Pitch Estimation /////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @brief Estimate the frequency of sample given autocorrelation + /// @param acf Autocorrelation of sample + /// @return Estimated pitch frequency, in Hertz + float estimateFrequency(const std::vector &acf) const; + + /// @brief Estimate the period of sample given autocorrelation + /// @param acf Autocorrelation of sample + /// @return Estimated pitch period, in samples + int estimatePeriod(const std::vector &acf) const; + + private: + /////////////////////////////////////////////////////////////////////////// + // Members //////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + int max_period_; + + int min_period_; + + int sample_rate_hz_; +}; + +}; // namespace tms_express + +#endif // TMS_EXPRESS_LPC_ANALYSIS_PITCHESTIMATOR_HPP_ diff --git a/tms_express/User_Interfaces/MainWindow.h b/tms_express/User_Interfaces/MainWindow.h index 41078eb..03cb811 100644 --- a/tms_express/User_Interfaces/MainWindow.h +++ b/tms_express/User_Interfaces/MainWindow.h @@ -10,7 +10,7 @@ #include "Frame_Encoding/FrameEncoder.hpp" #include "Frame_Encoding/FramePostprocessor.hpp" #include "Frame_Encoding/Synthesizer.hpp" -#include "LPC_Analysis/PitchEstimator.h" +#include "LPC_Analysis/PitchEstimator.hpp" #include "LPC_Analysis/LinearPredictor.hpp" #include "User_Interfaces/Audio_Waveform/AudioWaveformView.h" #include "User_Interfaces/Control_Panels/ControlPanelPitchView.h" From 83f7f2b6ba1f51d1e9a749a6dcf2b648210e1a7b Mon Sep 17 00:00:00 2001 From: Joseph Bellahcen Date: Sat, 5 Aug 2023 15:54:07 -0700 Subject: [PATCH 18/30] Refactored AudioWaveform --- .../Audio_Waveform/AudioWaveform.cpp | 97 ++++++++++--------- .../Audio_Waveform/AudioWaveform.h | 32 ------ .../Audio_Waveform/AudioWaveform.hpp | 62 ++++++++++++ .../Audio_Waveform/AudioWaveformView.cpp | 6 +- .../Audio_Waveform/AudioWaveformView.h | 2 +- 5 files changed, 115 insertions(+), 84 deletions(-) delete mode 100644 tms_express/User_Interfaces/Audio_Waveform/AudioWaveform.h create mode 100644 tms_express/User_Interfaces/Audio_Waveform/AudioWaveform.hpp diff --git a/tms_express/User_Interfaces/Audio_Waveform/AudioWaveform.cpp b/tms_express/User_Interfaces/Audio_Waveform/AudioWaveform.cpp index dd3e15f..55a11bf 100644 --- a/tms_express/User_Interfaces/Audio_Waveform/AudioWaveform.cpp +++ b/tms_express/User_Interfaces/Audio_Waveform/AudioWaveform.cpp @@ -1,24 +1,19 @@ -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// Class: AudioWaveform -// -// Description: The AudioWaveform implements a Qt plot of time-domain audio samples and its corresponding pitch-estimate -// table. Both are displayed on the same graph, with the pitch table occupying the lower half of the canvas -// -// Author: Joseph Bellahcen -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Copyright 2023 Joseph Bellahcen -#include "User_Interfaces/Audio_Waveform/AudioWaveform.h" +#include "User_Interfaces/Audio_Waveform/AudioWaveform.hpp" -#include #include -#include +#include #include #include namespace tms_express { -/// Create a new Audio Waveform plot +/////////////////////////////////////////////////////////////////////////////// +// Initializers /////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + AudioWaveform::AudioWaveform(QWidget *parent) : QWidget(parent) { // Set the plot background to true black QPalette pal = QPalette(); @@ -26,66 +21,72 @@ AudioWaveform::AudioWaveform(QWidget *parent) : QWidget(parent) { setAutoFillBackground(true); setPalette(pal); - samples = {}; - pitchTable = {}; + samples_ = {}; + pitch_curve_ = {}; } -/// Plot audio samples -void AudioWaveform::plotSamples(const std::vector& _samples) { - samples = _samples; +/////////////////////////////////////////////////////////////////////////////// +// Accessors ////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// + +void AudioWaveform::setSamples(const std::vector& samples) { + samples_ = samples; repaint(); } /// Plot pitch table corresponding to audio samples -void AudioWaveform::plotPitch(const std::vector& _pitchTable) { - pitchTable = _pitchTable; +void AudioWaveform::setPitchTable(const std::vector& pitch_curve) { + pitch_curve_ = pitch_curve; repaint(); } -/// Paint the plot -/// -/// \note Called under the hood by Qt, not by the programmer -/// -/// \param event QPaintEvent +/////////////////////////////////////////////////////////////////////////////// +// Qt Widget Helpers ////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + void AudioWaveform::paintEvent(QPaintEvent * event) { - auto width = QWidget::width(); - auto height = QWidget::height(); + const auto width = QWidget::width(); + const auto height = QWidget::height(); + + // Consider the origin of the plot (y = 0) as the vertical center of the + // widget, and draw an axis line through it + const float origin = static_cast(height) / 2.0f; - // Plot axis QPainter painter(this); painter.setPen(Qt::darkGray); - - painter.drawLine(0, height / 2, width, height / 2); - - // Set plot origin to center of Y-axis - auto penOrigin = float(height) / 2.0f; + painter.drawLine(0, origin, width, origin); // Plot samples - if (!samples.empty()) { + if (!samples_.empty()) { painter.setPen(QColor(255, 128, 0)); - auto penSpacing = float(width) / float(samples.size()); + const float spacing = static_cast(width) / + static_cast(samples_.size()); - for (int i = 0; i < samples.size() - 1; i++) { - auto x1 = float(i) * penSpacing; - auto y1 = penOrigin + (samples[i]) * penOrigin; - auto x2 = float(i + 1) * penSpacing; - auto y2 = penOrigin + (samples[i + 1] * penOrigin); + for (int i = 0; i < samples_.size() - 1; i++) { + float x_1 = static_cast(i) * spacing; + float y_1 = origin + (samples_[i] * origin); - painter.drawLine(int(x1), int(y1), int(x2), int(y2)); + float x_2 = static_cast(i + 1) * spacing; + float y_2 = origin + (samples_[i + 1] * origin); + + painter.drawLine(x_1, y_1, x_2, y_2); } } - if (!pitchTable.empty()) { + // Plot pitch curve underneath the samples + if (!pitch_curve_.empty()) { painter.setPen(Qt::darkRed); - auto penSpacing = float(width) / float(pitchTable.size()); + const float spacing = static_cast(width) / + static_cast(pitch_curve_.size()); + + for (int i = 0; i < pitch_curve_.size(); i++) { + float x_1 = static_cast(i) * spacing; + float y_1 = height - (pitch_curve_[i] * origin); - for (int i = 0; i < pitchTable.size(); i++) { - auto x1 = float(i) * penSpacing; - auto y1 = float(QWidget::height()) - (pitchTable[i] * penOrigin); - auto x2 = float(i + 1) * penSpacing; - auto y2 = float(QWidget::height()) - (pitchTable[i + 1] * penOrigin); + float x_2 = static_cast(i + 1) * spacing; + float y_2 = height - (pitch_curve_[i + 1] * origin); - painter.drawLine(int(x1), int(y1), int(x2), int(y2)); + painter.drawLine(x_1, y_1, x_2, y_2); } } } diff --git a/tms_express/User_Interfaces/Audio_Waveform/AudioWaveform.h b/tms_express/User_Interfaces/Audio_Waveform/AudioWaveform.h deleted file mode 100644 index 0f49a9d..0000000 --- a/tms_express/User_Interfaces/Audio_Waveform/AudioWaveform.h +++ /dev/null @@ -1,32 +0,0 @@ -// Author: Joseph Bellahcen - -#ifndef TMS_EXPRESS_AUDIOWAVEFORM_H -#define TMS_EXPRESS_AUDIOWAVEFORM_H - -#include -#include - -#include -#include - -namespace tms_express { - -class AudioWaveform : public QWidget { - Q_OBJECT -public: - explicit AudioWaveform(QWidget *parent = nullptr); - - void plotPitch(const std::vector& _pitchTable); - void plotSamples(const std::vector& _samples); - -protected: - void paintEvent(QPaintEvent* event) override; - -private: - std::vector pitchTable; - std::vector samples; -}; - -}; // namespace tms_express - -#endif //TMS_EXPRESS_AUDIOWAVEFORM_H diff --git a/tms_express/User_Interfaces/Audio_Waveform/AudioWaveform.hpp b/tms_express/User_Interfaces/Audio_Waveform/AudioWaveform.hpp new file mode 100644 index 0000000..58975c7 --- /dev/null +++ b/tms_express/User_Interfaces/Audio_Waveform/AudioWaveform.hpp @@ -0,0 +1,62 @@ +// Copyright 2023 Joseph Bellahcen + +#ifndef TMS_EXPRESS_USER_INTERFACES_AUDIO_WAVEFORM_AUDIOWAVEFORM_HPP_ +#define TMS_EXPRESS_USER_INTERFACES_AUDIO_WAVEFORM_AUDIOWAVEFORM_HPP_ + +#include + +#include +#include + +namespace tms_express { + +/// @brief Time-domain plot of audio samples and pitch +class AudioWaveform : public QWidget { + Q_OBJECT + + public: + /////////////////////////////////////////////////////////////////////////// + // Initializers /////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @brief Creates a new Audio Waveform plot + /// @param parent Parent Qt widget + explicit AudioWaveform(QWidget *parent = nullptr); + + /////////////////////////////////////////////////////////////////////////// + // Accessors ////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @brief Replaces existing samples and repaints plot + /// @param _samples New samples + void setSamples(const std::vector& samples); + + /// @brief Replaces existing pitch curve and re-paints plot + /// @param pitch_curve New pitch table + void setPitchTable(const std::vector& pitch_curve); + + protected: + /////////////////////////////////////////////////////////////////////////// + // Qt Widget Helpers ////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @brief Re-paints widget + /// @note This function is invoked by Qt and should be called in + /// application code + void paintEvent(QPaintEvent* event) override; + + private: + /////////////////////////////////////////////////////////////////////////// + // Members //////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @brief Time-domain pitch curve, corresponding to frequency, in Hertz + std::vector pitch_curve_; + + /// @brief Time-domain audio samples + std::vector samples_; +}; + +}; // namespace tms_express + +#endif // TMS_EXPRESS_USER_INTERFACES_AUDIO_WAVEFORM_AUDIOWAVEFORM_HPP_ diff --git a/tms_express/User_Interfaces/Audio_Waveform/AudioWaveformView.cpp b/tms_express/User_Interfaces/Audio_Waveform/AudioWaveformView.cpp index c9111d8..8bb8fe9 100644 --- a/tms_express/User_Interfaces/Audio_Waveform/AudioWaveformView.cpp +++ b/tms_express/User_Interfaces/Audio_Waveform/AudioWaveformView.cpp @@ -1,6 +1,6 @@ // Author: Joseph Bellahcen -#include "User_Interfaces/Audio_Waveform/AudioWaveform.h" +#include "User_Interfaces/Audio_Waveform/AudioWaveform.hpp" #include "User_Interfaces/Audio_Waveform/AudioWaveformView.h" #include @@ -34,11 +34,11 @@ AudioWaveformView::AudioWaveformView(std::string title, uint baseWidth, uint bas } void AudioWaveformView::plotPitch(const std::vector &_pitchTable) { - waveform->plotPitch(_pitchTable); + waveform->setPitchTable(_pitchTable); } void AudioWaveformView::plotSamples(const std::vector &_samples) { - waveform->plotSamples(_samples); + waveform->setSamples(_samples); } void AudioWaveformView::onPlayButtonPressed() { diff --git a/tms_express/User_Interfaces/Audio_Waveform/AudioWaveformView.h b/tms_express/User_Interfaces/Audio_Waveform/AudioWaveformView.h index 414f2ea..d7ff0fb 100644 --- a/tms_express/User_Interfaces/Audio_Waveform/AudioWaveformView.h +++ b/tms_express/User_Interfaces/Audio_Waveform/AudioWaveformView.h @@ -3,7 +3,7 @@ #ifndef TMS_EXPRESS_AUDIOWAVEFORMVIEW_H #define TMS_EXPRESS_AUDIOWAVEFORMVIEW_H -#include "User_Interfaces/Audio_Waveform/AudioWaveform.h" +#include "User_Interfaces/Audio_Waveform/AudioWaveform.hpp" #include #include From 10a0d5d965f04d04502f304d34911a1f40ffd5f9 Mon Sep 17 00:00:00 2001 From: Joseph Bellahcen Date: Sat, 5 Aug 2023 17:46:20 -0700 Subject: [PATCH 19/30] Refactored AudioWaveformView --- .../Audio_Waveform/AudioWaveform.cpp | 6 +- .../Audio_Waveform/AudioWaveform.hpp | 7 +- .../Audio_Waveform/AudioWaveformView.cpp | 69 ++++++++++++------ .../Audio_Waveform/AudioWaveformView.h | 37 ---------- .../Audio_Waveform/AudioWaveformView.hpp | 73 +++++++++++++++++++ tms_express/User_Interfaces/MainWindow.cpp | 12 +-- tms_express/User_Interfaces/MainWindow.h | 4 +- tms_express/main.cpp | 2 +- 8 files changed, 133 insertions(+), 77 deletions(-) delete mode 100644 tms_express/User_Interfaces/Audio_Waveform/AudioWaveformView.h create mode 100644 tms_express/User_Interfaces/Audio_Waveform/AudioWaveformView.hpp diff --git a/tms_express/User_Interfaces/Audio_Waveform/AudioWaveform.cpp b/tms_express/User_Interfaces/Audio_Waveform/AudioWaveform.cpp index 55a11bf..5ef058b 100644 --- a/tms_express/User_Interfaces/Audio_Waveform/AudioWaveform.cpp +++ b/tms_express/User_Interfaces/Audio_Waveform/AudioWaveform.cpp @@ -8,7 +8,7 @@ #include #include -namespace tms_express { +namespace tms_express::ui { /////////////////////////////////////////////////////////////////////////////// // Initializers /////////////////////////////////////////////////////////////// @@ -35,7 +35,7 @@ void AudioWaveform::setSamples(const std::vector& samples) { } /// Plot pitch table corresponding to audio samples -void AudioWaveform::setPitchTable(const std::vector& pitch_curve) { +void AudioWaveform::setPitchCurve(const std::vector& pitch_curve) { pitch_curve_ = pitch_curve; repaint(); } @@ -91,4 +91,4 @@ void AudioWaveform::paintEvent(QPaintEvent * event) { } } -}; // namespace tms_express +}; // namespace tms_express::ui diff --git a/tms_express/User_Interfaces/Audio_Waveform/AudioWaveform.hpp b/tms_express/User_Interfaces/Audio_Waveform/AudioWaveform.hpp index 58975c7..e1f90ad 100644 --- a/tms_express/User_Interfaces/Audio_Waveform/AudioWaveform.hpp +++ b/tms_express/User_Interfaces/Audio_Waveform/AudioWaveform.hpp @@ -5,10 +5,9 @@ #include -#include #include -namespace tms_express { +namespace tms_express::ui { /// @brief Time-domain plot of audio samples and pitch class AudioWaveform : public QWidget { @@ -33,7 +32,7 @@ class AudioWaveform : public QWidget { /// @brief Replaces existing pitch curve and re-paints plot /// @param pitch_curve New pitch table - void setPitchTable(const std::vector& pitch_curve); + void setPitchCurve(const std::vector& pitch_curve); protected: /////////////////////////////////////////////////////////////////////////// @@ -57,6 +56,6 @@ class AudioWaveform : public QWidget { std::vector samples_; }; -}; // namespace tms_express +}; // namespace tms_express::ui #endif // TMS_EXPRESS_USER_INTERFACES_AUDIO_WAVEFORM_AUDIOWAVEFORM_HPP_ diff --git a/tms_express/User_Interfaces/Audio_Waveform/AudioWaveformView.cpp b/tms_express/User_Interfaces/Audio_Waveform/AudioWaveformView.cpp index 8bb8fe9..b5af434 100644 --- a/tms_express/User_Interfaces/Audio_Waveform/AudioWaveformView.cpp +++ b/tms_express/User_Interfaces/Audio_Waveform/AudioWaveformView.cpp @@ -1,48 +1,69 @@ -// Author: Joseph Bellahcen +// Copyright 2023 Joseph Bellahcen -#include "User_Interfaces/Audio_Waveform/AudioWaveform.hpp" -#include "User_Interfaces/Audio_Waveform/AudioWaveformView.h" +#include "User_Interfaces/Audio_Waveform/AudioWaveformView.hpp" #include -#include +#include +#include +#include + +#include "User_Interfaces/Audio_Waveform/AudioWaveform.hpp" + +namespace tms_express::ui { + +/////////////////////////////////////////////////////////////////////////////// +// Initializers /////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// -namespace tms_express { +AudioWaveformView::AudioWaveformView(std::string title, int base_width, + int base_height, QWidget *parent): QWidget(parent) { -AudioWaveformView::AudioWaveformView(std::string title, uint baseWidth, uint baseHeight, QWidget *parent): QWidget(parent) { - setMinimumSize(baseWidth, baseHeight); + // Widget properties + setMinimumSize(base_width, base_height); - rowsLayout = new QVBoxLayout(this); + // Layout properties + rows_layout = new QVBoxLayout(this); + rows_layout->setContentsMargins(0, 0, 0, 0); + rows_layout->setSpacing(10); + + // Widget title, displayed above layout auto label = new QLabel(title.c_str(), this); label->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); - rowsLayout->addWidget(label); - - rowsLayout->setContentsMargins(0, 0, 0, 0); // Set margins to 0 - rowsLayout->setSpacing(10); // Set spacing to 10 pixels + rows_layout->addWidget(label); + // Audio Waveform plot waveform = new AudioWaveform(this); waveform->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + rows_layout->addWidget(waveform); - rowsLayout->addWidget(waveform); + // Audio play button + play_button = new QPushButton("Play", this); + play_button->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum); + play_button->setFixedWidth(64); + rows_layout->addWidget(play_button); - playButton = new QPushButton("Play", this); - playButton->setFixedWidth(64); - playButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum); + connect(play_button, &QPushButton::released, this, + &AudioWaveformView::onPlayButtonPressed); +} - rowsLayout->addWidget(playButton); +/////////////////////////////////////////////////////////////////////////////// +// Accessors ////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// - connect(playButton, &QPushButton::released, this, &AudioWaveformView::onPlayButtonPressed); +void AudioWaveformView::setSamples(const std::vector &samples) { + waveform->setSamples(samples); } -void AudioWaveformView::plotPitch(const std::vector &_pitchTable) { - waveform->setPitchTable(_pitchTable); +void AudioWaveformView::setPitchCurve(const std::vector &pitch_curve) { + waveform->setPitchCurve(pitch_curve); } -void AudioWaveformView::plotSamples(const std::vector &_samples) { - waveform->setSamples(_samples); -} +/////////////////////////////////////////////////////////////////////////// +// Qt Slots /////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////// void AudioWaveformView::onPlayButtonPressed() { emit signalPlayButtonPressed(true); } -}; // namespace tms_express +}; // namespace tms_express::ui diff --git a/tms_express/User_Interfaces/Audio_Waveform/AudioWaveformView.h b/tms_express/User_Interfaces/Audio_Waveform/AudioWaveformView.h deleted file mode 100644 index d7ff0fb..0000000 --- a/tms_express/User_Interfaces/Audio_Waveform/AudioWaveformView.h +++ /dev/null @@ -1,37 +0,0 @@ -// Author: Joseph Bellahcen - -#ifndef TMS_EXPRESS_AUDIOWAVEFORMVIEW_H -#define TMS_EXPRESS_AUDIOWAVEFORMVIEW_H - -#include "User_Interfaces/Audio_Waveform/AudioWaveform.hpp" - -#include -#include - -#include - -namespace tms_express { - -class AudioWaveformView: public QWidget { - Q_OBJECT -public: - AudioWaveformView(std::string title, uint baseWidth, uint baseHeight, QWidget *parent = nullptr); - - void plotPitch(const std::vector& _pitchTable); - void plotSamples(const std::vector& _samples); - -public slots: - void onPlayButtonPressed(); - -signals: - int signalPlayButtonPressed(int val); - -private: - QVBoxLayout *rowsLayout; - AudioWaveform *waveform; - QPushButton *playButton; -}; - -}; // namespace tms_express - -#endif //TMS_EXPRESS_AUDIOWAVEFORMVIEW_H diff --git a/tms_express/User_Interfaces/Audio_Waveform/AudioWaveformView.hpp b/tms_express/User_Interfaces/Audio_Waveform/AudioWaveformView.hpp new file mode 100644 index 0000000..9bed9c6 --- /dev/null +++ b/tms_express/User_Interfaces/Audio_Waveform/AudioWaveformView.hpp @@ -0,0 +1,73 @@ +// Copyright 2023 Joseph Bellahcen + +#ifndef TMS_EXPRESS_USER_INTERFACES_AUDIO_WAVEFORM_AUDIOWAVEFORMVIEW_HPP_ +#define TMS_EXPRESS_USER_INTERFACES_AUDIO_WAVEFORM_AUDIOWAVEFORMVIEW_HPP_ + +#include +#include +#include + +#include +#include + +#include "User_Interfaces/Audio_Waveform/AudioWaveform.hpp" + +namespace tms_express::ui { + +class AudioWaveformView: public QWidget { + Q_OBJECT + + public: + /////////////////////////////////////////////////////////////////////////// + // Initializers /////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @brief Creates a new Audio Waveform view, which encapsulates an Audio + /// waveform object in a Qt layout + /// @param title Title to be displayed atop the layout + /// @param base_width Base width of widget, in px + /// @param base_height Base height of widget, in px + /// @param parent Parent Qt widget + AudioWaveformView(std::string title, int base_width, int base_height, + QWidget *parent = nullptr); + + /////////////////////////////////////////////////////////////////////////// + // Accessors ////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @copydoc AudioWaveform::setSamples(const std::vector&) + void setSamples(const std::vector &samples); + + /// @copydoc AudioWaveform::setPitchCurve(const std::vector&); + void setPitchCurve(const std::vector &pitchTable); + + public slots: + /////////////////////////////////////////////////////////////////////////// + // Qt Slots /////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @brief Emits Qt signal when user clicks play button, prompting audio + /// playback + void onPlayButtonPressed(); + + signals: + /////////////////////////////////////////////////////////////////////////// + // Qt Signals ///////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @brief Qt signal which prompts audio playback + int signalPlayButtonPressed(int val); + + private: + /////////////////////////////////////////////////////////////////////////// + // Members //////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + AudioWaveform *waveform; + QVBoxLayout *rows_layout; + QPushButton *play_button; +}; + +}; // namespace tms_express::ui + +#endif // TMS_EXPRESS_USER_INTERFACES_AUDIO_WAVEFORM_AUDIOWAVEFORMVIEW_HPP_ diff --git a/tms_express/User_Interfaces/MainWindow.cpp b/tms_express/User_Interfaces/MainWindow.cpp index 9518903..9134a8b 100644 --- a/tms_express/User_Interfaces/MainWindow.cpp +++ b/tms_express/User_Interfaces/MainWindow.cpp @@ -9,7 +9,7 @@ #include "Audio/AudioBuffer.hpp" #include "Frame_Encoding/FramePostprocessor.hpp" #include "LPC_Analysis/Autocorrelation.hpp" -#include "User_Interfaces/Audio_Waveform/AudioWaveformView.h" +#include "User_Interfaces/Audio_Waveform/AudioWaveformView.hpp" #include "User_Interfaces/MainWindow.h" #include "User_Interfaces/Control_Panels/ControlPanelPitchView.h" #include "User_Interfaces/Control_Panels/ControlPanelLpcView.h" @@ -24,7 +24,7 @@ #include #include -namespace tms_express { +namespace tms_express::ui { /// Setup the main window of the application MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { @@ -155,20 +155,20 @@ void MainWindow::configureUiState() { /// Draw the input and output signal waveforms, along with an abstract representation of their associated pitch data void MainWindow::drawPlots() { - inputWaveform->plotSamples(inputBuffer.getSamples()); + inputWaveform->setSamples(inputBuffer.getSamples()); if (!frameTable.empty()) { auto samples = synthesizer.synthesize(frameTable); - lpcWaveform->plotSamples(samples); + lpcWaveform->setSamples(samples); auto framePitchTable = std::vector(frameTable.size()); for (int i = 0; i < frameTable.size(); i++) // TODO: Parameterize framePitchTable[i] = (8000.0f / float(frameTable[i].quantizedPitch())) / float(pitchEstimator.getMaxFrq()); - lpcWaveform->plotPitch(framePitchTable); + lpcWaveform->setPitchCurve(framePitchTable); } else { - lpcWaveform->plotSamples({}); + lpcWaveform->setSamples({}); } } diff --git a/tms_express/User_Interfaces/MainWindow.h b/tms_express/User_Interfaces/MainWindow.h index 03cb811..b9d0836 100644 --- a/tms_express/User_Interfaces/MainWindow.h +++ b/tms_express/User_Interfaces/MainWindow.h @@ -12,7 +12,7 @@ #include "Frame_Encoding/Synthesizer.hpp" #include "LPC_Analysis/PitchEstimator.hpp" #include "LPC_Analysis/LinearPredictor.hpp" -#include "User_Interfaces/Audio_Waveform/AudioWaveformView.h" +#include "User_Interfaces/Audio_Waveform/AudioWaveformView.hpp" #include "User_Interfaces/Control_Panels/ControlPanelPitchView.h" #include "User_Interfaces/Control_Panels/ControlPanelLpcView.h" #include "User_Interfaces/Control_Panels/ControlPanelPostView.h" @@ -28,7 +28,7 @@ #define TMS_EXPRESS_WINDOW_MARGINS 5 #define TMS_EXPRESS_AUDIO_SAMPLE_RATE 8000 -namespace tms_express { +namespace tms_express::ui { class MainWindow : public QMainWindow { diff --git a/tms_express/main.cpp b/tms_express/main.cpp index baee6e8..163a5b5 100644 --- a/tms_express/main.cpp +++ b/tms_express/main.cpp @@ -9,7 +9,7 @@ int main(int argc, char **argv) { if (argc == 1) { QApplication app(argc, argv); - tms_express::MainWindow w; + tms_express::ui::MainWindow w; w.show(); return app.exec(); From 1df949f4f5c73e93469f2df8abb4e28ebd8c2bd5 Mon Sep 17 00:00:00 2001 From: Joseph Bellahcen Date: Sat, 5 Aug 2023 17:47:44 -0700 Subject: [PATCH 20/30] Documented members --- .../User_Interfaces/Audio_Waveform/AudioWaveformView.hpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tms_express/User_Interfaces/Audio_Waveform/AudioWaveformView.hpp b/tms_express/User_Interfaces/Audio_Waveform/AudioWaveformView.hpp index 9bed9c6..93865ed 100644 --- a/tms_express/User_Interfaces/Audio_Waveform/AudioWaveformView.hpp +++ b/tms_express/User_Interfaces/Audio_Waveform/AudioWaveformView.hpp @@ -63,8 +63,13 @@ class AudioWaveformView: public QWidget { // Members //////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////// + /// @brief Audio Waveform plot AudioWaveform *waveform; + + /// @brief Widget layout, comprised of stacked rows QVBoxLayout *rows_layout; + + /// @brief Play button, which emits signal prompting for audio playback QPushButton *play_button; }; From 8e5a492ae9cf773264a20af0ce55013cf2a62db6 Mon Sep 17 00:00:00 2001 From: Joseph Bellahcen Date: Sun, 6 Aug 2023 14:08:04 -0700 Subject: [PATCH 21/30] Re-factored Control Panel View --- .../Audio_Waveform/AudioWaveformView.hpp | 1 + .../Control_Panels/ControlPanelLpcView.cpp | 18 ++--- .../Control_Panels/ControlPanelLpcView.h | 4 +- .../Control_Panels/ControlPanelPitchView.cpp | 20 +++--- .../Control_Panels/ControlPanelPitchView.h | 4 +- .../Control_Panels/ControlPanelPostView.cpp | 22 +++---- .../Control_Panels/ControlPanelPostView.h | 4 +- .../Control_Panels/ControlPanelView.cpp | 50 +++++++------- .../Control_Panels/ControlPanelView.h | 33 ---------- .../Control_Panels/ControlPanelView.hpp | 66 +++++++++++++++++++ 10 files changed, 127 insertions(+), 95 deletions(-) delete mode 100644 tms_express/User_Interfaces/Control_Panels/ControlPanelView.h create mode 100644 tms_express/User_Interfaces/Control_Panels/ControlPanelView.hpp diff --git a/tms_express/User_Interfaces/Audio_Waveform/AudioWaveformView.hpp b/tms_express/User_Interfaces/Audio_Waveform/AudioWaveformView.hpp index 93865ed..dd94678 100644 --- a/tms_express/User_Interfaces/Audio_Waveform/AudioWaveformView.hpp +++ b/tms_express/User_Interfaces/Audio_Waveform/AudioWaveformView.hpp @@ -14,6 +14,7 @@ namespace tms_express::ui { +/// @brief QWidget container for Audio Waveform plot class AudioWaveformView: public QWidget { Q_OBJECT diff --git a/tms_express/User_Interfaces/Control_Panels/ControlPanelLpcView.cpp b/tms_express/User_Interfaces/Control_Panels/ControlPanelLpcView.cpp index 732660e..46ded94 100644 --- a/tms_express/User_Interfaces/Control_Panels/ControlPanelLpcView.cpp +++ b/tms_express/User_Interfaces/Control_Panels/ControlPanelLpcView.cpp @@ -7,12 +7,12 @@ /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #include "User_Interfaces/Control_Panels/ControlPanelLpcView.h" -#include "User_Interfaces/Control_Panels/ControlPanelView.h" +#include "User_Interfaces/Control_Panels/ControlPanelView.hpp" #include #include -namespace tms_express { +namespace tms_express::ui { ControlPanelLpcView::ControlPanelLpcView(QWidget *parent): ControlPanelView("LPC Analysis", parent) { auto analysisWindowLabel = new QLabel("Analysis window (ms)", this); @@ -40,13 +40,13 @@ ControlPanelLpcView::ControlPanelLpcView(QWidget *parent): ControlPanelView("LPC } void ControlPanelLpcView::configureSlots() { - connect(analysisWindowLine, &QLineEdit::editingFinished, this, &ControlPanelView::stateChangeSlot); - connect(hpfCheckbox, &QCheckBox::released, this, &ControlPanelView::stateChangeSlot); - connect(hpfLine, &QLineEdit::editingFinished, this, &ControlPanelView::stateChangeSlot); - connect(lpfCheckbox, &QCheckBox::released, this, &ControlPanelView::stateChangeSlot); - connect(lpfLine, &QLineEdit::editingFinished, this, &ControlPanelView::stateChangeSlot); - connect(preemphCheckbox, &QCheckBox::released, this, &ControlPanelView::stateChangeSlot); - connect(preemphLine, &QLineEdit::editingFinished, this, &ControlPanelView::stateChangeSlot); + connect(analysisWindowLine, &QLineEdit::editingFinished, this, &ControlPanelView::stateChanged); + connect(hpfCheckbox, &QCheckBox::released, this, &ControlPanelView::stateChanged); + connect(hpfLine, &QLineEdit::editingFinished, this, &ControlPanelView::stateChanged); + connect(lpfCheckbox, &QCheckBox::released, this, &ControlPanelView::stateChanged); + connect(lpfLine, &QLineEdit::editingFinished, this, &ControlPanelView::stateChanged); + connect(preemphCheckbox, &QCheckBox::released, this, &ControlPanelView::stateChanged); + connect(preemphLine, &QLineEdit::editingFinished, this, &ControlPanelView::stateChanged); } void ControlPanelLpcView::reset() { diff --git a/tms_express/User_Interfaces/Control_Panels/ControlPanelLpcView.h b/tms_express/User_Interfaces/Control_Panels/ControlPanelLpcView.h index db78864..f807aae 100644 --- a/tms_express/User_Interfaces/Control_Panels/ControlPanelLpcView.h +++ b/tms_express/User_Interfaces/Control_Panels/ControlPanelLpcView.h @@ -3,12 +3,12 @@ #ifndef TMS_EXPRESS_CONTROLPANELLPCVIEW_H #define TMS_EXPRESS_CONTROLPANELLPCVIEW_H -#include "User_Interfaces/Control_Panels/ControlPanelView.h" +#include "User_Interfaces/Control_Panels/ControlPanelView.hpp" #include #include -namespace tms_express { +namespace tms_express::ui { class ControlPanelLpcView: public ControlPanelView { Q_OBJECT diff --git a/tms_express/User_Interfaces/Control_Panels/ControlPanelPitchView.cpp b/tms_express/User_Interfaces/Control_Panels/ControlPanelPitchView.cpp index d5a782a..b1d33b3 100644 --- a/tms_express/User_Interfaces/Control_Panels/ControlPanelPitchView.cpp +++ b/tms_express/User_Interfaces/Control_Panels/ControlPanelPitchView.cpp @@ -7,11 +7,11 @@ /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #include "User_Interfaces/Control_Panels/ControlPanelPitchView.h" -#include "User_Interfaces/Control_Panels/ControlPanelView.h" +#include "User_Interfaces/Control_Panels/ControlPanelView.hpp" #include #include -namespace tms_express { +namespace tms_express::ui { ControlPanelPitchView::ControlPanelPitchView(QWidget *parent): ControlPanelView("Pitch Analysis", parent) { auto line2 = new QFrame(this); @@ -49,14 +49,14 @@ ControlPanelPitchView::ControlPanelPitchView(QWidget *parent): ControlPanelView( } void ControlPanelPitchView::configureSlots() { - connect(hpfCheckbox, &QCheckBox::released, this, &ControlPanelView::stateChangeSlot); - connect(hpfLine, &QLineEdit::editingFinished, this, &ControlPanelView::stateChangeSlot); - connect(lpfCheckbox, &QCheckBox::released, this, &ControlPanelView::stateChangeSlot); - connect(lpfLine, &QLineEdit::editingFinished, this, &ControlPanelView::stateChangeSlot); - connect(preemphCheckbox, &QCheckBox::released, this, &ControlPanelView::stateChangeSlot); - connect(preemphLine, &QLineEdit::editingFinished, this, &ControlPanelView::stateChangeSlot); - connect(maxPitchLine, &QLineEdit::editingFinished, this, &ControlPanelView::stateChangeSlot); - connect(minPitchLine, &QLineEdit::editingFinished, this, &ControlPanelView::stateChangeSlot); + connect(hpfCheckbox, &QCheckBox::released, this, &ControlPanelView::stateChanged); + connect(hpfLine, &QLineEdit::editingFinished, this, &ControlPanelView::stateChanged); + connect(lpfCheckbox, &QCheckBox::released, this, &ControlPanelView::stateChanged); + connect(lpfLine, &QLineEdit::editingFinished, this, &ControlPanelView::stateChanged); + connect(preemphCheckbox, &QCheckBox::released, this, &ControlPanelView::stateChanged); + connect(preemphLine, &QLineEdit::editingFinished, this, &ControlPanelView::stateChanged); + connect(maxPitchLine, &QLineEdit::editingFinished, this, &ControlPanelView::stateChanged); + connect(minPitchLine, &QLineEdit::editingFinished, this, &ControlPanelView::stateChanged); } void ControlPanelPitchView::reset() { diff --git a/tms_express/User_Interfaces/Control_Panels/ControlPanelPitchView.h b/tms_express/User_Interfaces/Control_Panels/ControlPanelPitchView.h index e0e7e9a..70bbe85 100644 --- a/tms_express/User_Interfaces/Control_Panels/ControlPanelPitchView.h +++ b/tms_express/User_Interfaces/Control_Panels/ControlPanelPitchView.h @@ -3,13 +3,13 @@ #ifndef TMS_EXPRESS_CONTROLPANELPITCHVIEW_H #define TMS_EXPRESS_CONTROLPANELPITCHVIEW_H -#include "User_Interfaces/Control_Panels/ControlPanelView.h" +#include "User_Interfaces/Control_Panels/ControlPanelView.hpp" #include #include #include -namespace tms_express { +namespace tms_express::ui { class ControlPanelPitchView: public ControlPanelView { Q_OBJECT diff --git a/tms_express/User_Interfaces/Control_Panels/ControlPanelPostView.cpp b/tms_express/User_Interfaces/Control_Panels/ControlPanelPostView.cpp index 1d044c1..54339d6 100644 --- a/tms_express/User_Interfaces/Control_Panels/ControlPanelPostView.cpp +++ b/tms_express/User_Interfaces/Control_Panels/ControlPanelPostView.cpp @@ -12,7 +12,7 @@ #include #include -namespace tms_express { +namespace tms_express::ui { ControlPanelPostView::ControlPanelPostView(QWidget *parent): ControlPanelView("Post-Processing", parent) { auto line2 = new QFrame(this); @@ -71,16 +71,16 @@ ControlPanelPostView::ControlPanelPostView(QWidget *parent): ControlPanelView("P } void ControlPanelPostView::configureSlots() { - connect(pitchShiftCheckbox, &QCheckBox::released, this, &ControlPanelView::stateChangeSlot); - connect(pitchShiftSlider, &QSlider::sliderReleased, this, &ControlPanelView::stateChangeSlot); - connect(pitchOverrideCheckbox, &QCheckBox::released, this, &ControlPanelView::stateChangeSlot); - connect(pitchOverrideSlider, &QSlider::sliderReleased, this, &ControlPanelView::stateChangeSlot); - connect(repeatFramesCheckbox, &QCheckBox::released, this, &ControlPanelView::stateChangeSlot); - connect(gainShiftCheckbox, &QCheckBox::released, this, &ControlPanelView::stateChangeSlot); - connect(gainShiftSlider, &QSlider::sliderReleased, this, &ControlPanelView::stateChangeSlot); - connect(gainNormalizationCheckbox, &QCheckBox::released, this, &ControlPanelView::stateChangeSlot); - connect(maxUnvoicedGainLine, &QLineEdit::editingFinished, this, &ControlPanelView::stateChangeSlot); - connect(maxVoicedGainLine, &QLineEdit::editingFinished, this, &ControlPanelView::stateChangeSlot); + connect(pitchShiftCheckbox, &QCheckBox::released, this, &ControlPanelView::stateChanged); + connect(pitchShiftSlider, &QSlider::sliderReleased, this, &ControlPanelView::stateChanged); + connect(pitchOverrideCheckbox, &QCheckBox::released, this, &ControlPanelView::stateChanged); + connect(pitchOverrideSlider, &QSlider::sliderReleased, this, &ControlPanelView::stateChanged); + connect(repeatFramesCheckbox, &QCheckBox::released, this, &ControlPanelView::stateChanged); + connect(gainShiftCheckbox, &QCheckBox::released, this, &ControlPanelView::stateChanged); + connect(gainShiftSlider, &QSlider::sliderReleased, this, &ControlPanelView::stateChanged); + connect(gainNormalizationCheckbox, &QCheckBox::released, this, &ControlPanelView::stateChanged); + connect(maxUnvoicedGainLine, &QLineEdit::editingFinished, this, &ControlPanelView::stateChanged); + connect(maxVoicedGainLine, &QLineEdit::editingFinished, this, &ControlPanelView::stateChanged); } void ControlPanelPostView::reset() { diff --git a/tms_express/User_Interfaces/Control_Panels/ControlPanelPostView.h b/tms_express/User_Interfaces/Control_Panels/ControlPanelPostView.h index ce2deff..653c56a 100644 --- a/tms_express/User_Interfaces/Control_Panels/ControlPanelPostView.h +++ b/tms_express/User_Interfaces/Control_Panels/ControlPanelPostView.h @@ -3,12 +3,12 @@ #ifndef TMS_EXPRESS_CONTROLPANELPOSTVIEW_H #define TMS_EXPRESS_CONTROLPANELPOSTVIEW_H -#include "User_Interfaces/Control_Panels/ControlPanelView.h" +#include "User_Interfaces/Control_Panels/ControlPanelView.hpp" #include #include -namespace tms_express { +namespace tms_express::ui { class ControlPanelPostView: public ControlPanelView { Q_OBJECT diff --git a/tms_express/User_Interfaces/Control_Panels/ControlPanelView.cpp b/tms_express/User_Interfaces/Control_Panels/ControlPanelView.cpp index bd483f9..3bc4a9e 100644 --- a/tms_express/User_Interfaces/Control_Panels/ControlPanelView.cpp +++ b/tms_express/User_Interfaces/Control_Panels/ControlPanelView.cpp @@ -1,39 +1,37 @@ -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// Class: ControlPanelView -// -// Description: The ControlPanelView is an abstract interface for GUI controls. It consists of a grid containing at -// least a title and a line, but it may be expanded to contain Qt widgets and items. The class implements -// a catch-all signal which may be configured to trigger when a widget's state changes -// -// Author: Joseph Bellahcen -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -#include "User_Interfaces/Control_Panels/ControlPanelView.h" - -namespace tms_express { - -/// Initialize a new Control Panel View -/// -/// At minimum, the Control Panel View will have a title with a line below it -/// -/// \param panelTitle Title to be displayed at the top of the panel -/// \param parent Parent QWidget -ControlPanelView::ControlPanelView(const std::string &panelTitle, QWidget *parent): QWidget(parent) { +// Copyright 2023 Joseph Bellahcen + +#include "User_Interfaces/Control_Panels/ControlPanelView.hpp" + +#include +#include +#include + +namespace tms_express::ui { + +/////////////////////////////////////////////////////////////////////////////// +// Initializers /////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +ControlPanelView::ControlPanelView(const std::string &title, QWidget *parent): + QWidget(parent) { grid = new QGridLayout(this); - auto title = new QLabel(panelTitle.c_str(), this); + auto panel_title = new QLabel(title.c_str(), this); auto separator = new QFrame(this); separator->setFrameShape(QFrame::HLine); - grid->addWidget(title, 0, 0); + grid->addWidget(panel_title, 0, 0); grid->addWidget(separator, 1, 0, 1, 2); setLayout(grid); } -/// Notify the application manger that a control has been modified -void ControlPanelView::stateChangeSlot() { +/////////////////////////////////////////////////////////////////////////////// +// Qt Slots /////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +void ControlPanelView::stateChanged() { emit stateChangeSignal(); } -}; // namespace tms_express +}; // namespace tms_express::ui diff --git a/tms_express/User_Interfaces/Control_Panels/ControlPanelView.h b/tms_express/User_Interfaces/Control_Panels/ControlPanelView.h deleted file mode 100644 index 4250e47..0000000 --- a/tms_express/User_Interfaces/Control_Panels/ControlPanelView.h +++ /dev/null @@ -1,33 +0,0 @@ -// Author: Joseph Bellahcen - -#ifndef TMS_EXPRESS_CONTROLPANELVIEW_H -#define TMS_EXPRESS_CONTROLPANELVIEW_H - -#include -#include - -#include - -namespace tms_express { - -class ControlPanelView: public QWidget { -Q_OBJECT -public: - ControlPanelView(const std::string &panelTitle, QWidget *parent = nullptr); - - virtual void reset() = 0; - virtual void configureSlots() = 0; - -public slots: - void stateChangeSlot(); - -signals: - void stateChangeSignal(); - -protected: - QGridLayout *grid; -}; - -}; // namespace tms_express - -#endif //TMS_EXPRESS_CONTROLPANELVIEW_H diff --git a/tms_express/User_Interfaces/Control_Panels/ControlPanelView.hpp b/tms_express/User_Interfaces/Control_Panels/ControlPanelView.hpp new file mode 100644 index 0000000..3e973bc --- /dev/null +++ b/tms_express/User_Interfaces/Control_Panels/ControlPanelView.hpp @@ -0,0 +1,66 @@ +// Copyright 2023 Joseph Bellahcen + +#ifndef TMS_EXPRESS_USER_INTERFACES_CONTROL_PANELS_CONTROLPANELVIEW_HPP_ +#define TMS_EXPRESS_USER_INTERFACES_CONTROL_PANELS_CONTROLPANELVIEW_HPP_ + +#include +#include + +#include + +namespace tms_express::ui { + +/// @brief Abstract interface for titled GUI control panel with parameter grid +class ControlPanelView: public QWidget { + Q_OBJECT + + public: + /////////////////////////////////////////////////////////////////////////// + // Initializers /////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @brief Creates a new Control Panel View for holding parameters + /// @param title Title to be displayed on top of panel + /// @param parent Parent Qt widget + explicit ControlPanelView(const std::string &title, + QWidget *parent = nullptr); + + /////////////////////////////////////////////////////////////////////////// + // Virtual Methods //////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @brief Resets parameters to default values and states + virtual void reset() = 0; + + /// @brief Assigns Qt slots and signals to UI elements + /// @note This function must only be called once + virtual void configureSlots() = 0; + + public slots: + /////////////////////////////////////////////////////////////////////////// + // Qt Slots /////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @brief Emits Qt signal when user adjusts any parameter + void stateChanged(); + + signals: + /////////////////////////////////////////////////////////////////////////// + // Qt Signals ///////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @brief Qt signal which prompts re-evaluation of parameters + void stateChangeSignal(); + + protected: + /////////////////////////////////////////////////////////////////////////// + // Members //////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @brief Grid layout for holding parameters + QGridLayout *grid; +}; + +}; // namespace tms_express::ui + +#endif // TMS_EXPRESS_USER_INTERFACES_CONTROL_PANELS_CONTROLPANELVIEW_HPP_ From 5960ea8f98316ff8c06c49da4b172285d903fb1b Mon Sep 17 00:00:00 2001 From: Joseph Bellahcen Date: Sun, 6 Aug 2023 15:37:26 -0700 Subject: [PATCH 22/30] Re-factored Control Panel Post View --- .../Control_Panels/ControlPanelPostView.cpp | 223 +++++++++++------- .../Control_Panels/ControlPanelPostView.h | 48 ---- .../Control_Panels/ControlPanelPostView.hpp | 108 +++++++++ tms_express/User_Interfaces/MainWindow.cpp | 22 +- tms_express/User_Interfaces/MainWindow.h | 2 +- 5 files changed, 256 insertions(+), 147 deletions(-) delete mode 100644 tms_express/User_Interfaces/Control_Panels/ControlPanelPostView.h create mode 100644 tms_express/User_Interfaces/Control_Panels/ControlPanelPostView.hpp diff --git a/tms_express/User_Interfaces/Control_Panels/ControlPanelPostView.cpp b/tms_express/User_Interfaces/Control_Panels/ControlPanelPostView.cpp index 54339d6..11db386 100644 --- a/tms_express/User_Interfaces/Control_Panels/ControlPanelPostView.cpp +++ b/tms_express/User_Interfaces/Control_Panels/ControlPanelPostView.cpp @@ -1,141 +1,190 @@ -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// Class: ControlPanelPostView -// -// Description: The ControlPanelPostView contains parameters which guide bitstream post-processing -// -// Author: Joseph Bellahcen -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Copyright 2023 Joseph Bellahcen -#include "Frame_Encoding/CodingTable.hpp" -#include "User_Interfaces/Control_Panels/ControlPanelPostView.h" +#include "User_Interfaces/Control_Panels/ControlPanelPostView.hpp" +#include +#include +#include +#include #include -#include + +#include "Frame_Encoding/CodingTable.hpp" namespace tms_express::ui { -ControlPanelPostView::ControlPanelPostView(QWidget *parent): ControlPanelView("Post-Processing", parent) { - auto line2 = new QFrame(this); - line2->setFrameShape(QFrame::HLine); - auto line3 = new QFrame(this); - line3->setFrameShape(QFrame::HLine); +/////////////////////////////////////////////////////////////////////////////// +// Initializers /////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +ControlPanelPostView::ControlPanelPostView(QWidget *parent): + ControlPanelView("Post-Processing", parent) { + // Initialize parameters + pitch_shift_checkbox_ = new QCheckBox("Pitch shift", this); + pitch_shift_slider_ = new QSlider(Qt::Horizontal, this); + + pitch_override_checkbox_ = new QCheckBox("Pitch override", this); + pitch_override_slider_ = new QSlider(Qt::Horizontal, this); + + repeat_frames_checkbox_ = new QCheckBox("Repeat frames", this); + + gain_shift_checkbox_ = new QCheckBox("Gain shift", this); + gain_shift_slider_ = new QSlider(Qt::Horizontal, this); + + gain_normalization_checkbox_ = new QCheckBox("Gain normalization", this); - pitchShiftCheckbox = new QCheckBox("Pitch shift", this); - pitchShiftSlider = new QSlider(Qt::Horizontal, this); - pitchOverrideCheckbox = new QCheckBox("Pitch override", this); - pitchOverrideSlider = new QSlider(Qt::Horizontal, this); - repeatFramesCheckbox = new QCheckBox("Repeat frames", this); - gainShiftCheckbox = new QCheckBox("Gain shift", this); - gainShiftSlider = new QSlider(Qt::Horizontal, this); - gainNormalizationCheckbox = new QCheckBox("Gain normalization", this); auto maxUnvoicedGainLabel = new QLabel("Max unvoiced gain (dB)", this); - maxUnvoicedGainLine = new QLineEdit("37.5", this); + max_unvoiced_gain_line_ = new QLineEdit("37.5", this); + auto maxVoicedGainLabel = new QLabel("Max voiced gain (dB)", this); - maxVoicedGainLine = new QLineEdit("37.5", this); + max_voiced_gain_line_ = new QLineEdit("37.5", this); + + // Configure sliders based on TMS5220 coding table + pitch_shift_slider_->setRange( + -1 * static_cast(coding_table::tms5220::pitch.size()), + coding_table::tms5220::pitch.size()); - // Setup sliders based on TMS5220 coding table - pitchShiftSlider->setRange(-coding_table::tms5220::pitch.size(), coding_table::tms5220::pitch.size()); - pitchShiftSlider->setTickInterval(coding_table::tms5220::pitch.size() / 8); + pitch_shift_slider_->setTickInterval( + coding_table::tms5220::pitch.size() / 8); - pitchOverrideSlider->setRange(0, coding_table::tms5220::pitch.size()); - pitchOverrideSlider->setTickInterval(coding_table::tms5220::pitch.size() / 16); + pitch_override_slider_->setRange( + 0, + coding_table::tms5220::pitch.size()); - gainShiftSlider->setRange(-coding_table::tms5220::rms.size(), coding_table::tms5220::rms.size()); - gainShiftSlider->setTickInterval(coding_table::tms5220::rms.size() / 8); + pitch_override_slider_->setTickInterval( + coding_table::tms5220::pitch.size() / 16); + + gain_shift_slider_->setRange( + -1 * static_cast(coding_table::tms5220::rms.size()), + coding_table::tms5220::rms.size()); + + gain_shift_slider_->setTickInterval( + coding_table::tms5220::rms.size() / 8); // Construct layout auto row = grid->rowCount(); - grid->addWidget(pitchShiftCheckbox, row, 0); - grid->addWidget(pitchShiftSlider, row++, 1); + grid->addWidget(pitch_shift_checkbox_, row, 0); + grid->addWidget(pitch_shift_slider_, row++, 1); - grid->addWidget(pitchOverrideCheckbox, row, 0); - grid->addWidget(pitchOverrideSlider, row++, 1); + grid->addWidget(pitch_override_checkbox_, row, 0); + grid->addWidget(pitch_override_slider_, row++, 1); + auto line2 = new QFrame(this); + line2->setFrameShape(QFrame::HLine); grid->addWidget(line2, row++, 0, 1, 2); - grid->addWidget(repeatFramesCheckbox, row++, 0); + grid->addWidget(repeat_frames_checkbox_, row++, 0); + auto line3 = new QFrame(this); + line3->setFrameShape(QFrame::HLine); grid->addWidget(line3, row++, 0, 1, 2); - grid->addWidget(gainShiftCheckbox, row, 0); - grid->addWidget(gainShiftSlider, row++, 1); + grid->addWidget(gain_shift_checkbox_, row, 0); + grid->addWidget(gain_shift_slider_, row++, 1); - grid->addWidget(gainNormalizationCheckbox, row++, 0); + grid->addWidget(gain_normalization_checkbox_, row++, 0); grid->addWidget(maxUnvoicedGainLabel, row, 0); - grid->addWidget(maxUnvoicedGainLine, row++, 1); + grid->addWidget(max_unvoiced_gain_line_, row++, 1); grid->addWidget(maxVoicedGainLabel, row, 0); - grid->addWidget(maxVoicedGainLine, row, 1); + grid->addWidget(max_voiced_gain_line_, row, 1); } -void ControlPanelPostView::configureSlots() { - connect(pitchShiftCheckbox, &QCheckBox::released, this, &ControlPanelView::stateChanged); - connect(pitchShiftSlider, &QSlider::sliderReleased, this, &ControlPanelView::stateChanged); - connect(pitchOverrideCheckbox, &QCheckBox::released, this, &ControlPanelView::stateChanged); - connect(pitchOverrideSlider, &QSlider::sliderReleased, this, &ControlPanelView::stateChanged); - connect(repeatFramesCheckbox, &QCheckBox::released, this, &ControlPanelView::stateChanged); - connect(gainShiftCheckbox, &QCheckBox::released, this, &ControlPanelView::stateChanged); - connect(gainShiftSlider, &QSlider::sliderReleased, this, &ControlPanelView::stateChanged); - connect(gainNormalizationCheckbox, &QCheckBox::released, this, &ControlPanelView::stateChanged); - connect(maxUnvoicedGainLine, &QLineEdit::editingFinished, this, &ControlPanelView::stateChanged); - connect(maxVoicedGainLine, &QLineEdit::editingFinished, this, &ControlPanelView::stateChanged); -} +/////////////////////////////////////////////////////////////////////////////// +// Overloaded Methods ///////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// void ControlPanelPostView::reset() { - pitchShiftCheckbox->setChecked(false); - pitchShiftSlider->setSliderPosition(0); - pitchOverrideCheckbox->setChecked(false); - pitchOverrideSlider->setSliderPosition(coding_table::tms5220::pitch.size() / 2); - - repeatFramesCheckbox->setChecked(false); - - gainShiftCheckbox->setChecked(false); - gainShiftSlider->setSliderPosition(0); - //gainNormalizationCheckbox->setChecked(enableGainNormalization); - maxUnvoicedGainLine->setText("37.5"); - maxVoicedGainLine->setText("37.5"); + pitch_shift_checkbox_->setChecked(false); + pitch_shift_slider_->setSliderPosition(0); + pitch_override_checkbox_->setChecked(false); + pitch_override_slider_->setSliderPosition( + coding_table::tms5220::pitch.size() / 2); + + repeat_frames_checkbox_->setChecked(false); + + gain_shift_checkbox_->setChecked(false); + gain_shift_slider_->setSliderPosition(0); + // gain_normalization_checkbox_->setChecked(enableGainNormalization); + max_unvoiced_gain_line_->setText("37.5"); + max_voiced_gain_line_->setText("37.5"); } -bool ControlPanelPostView::pitchShiftEnabled() { - return pitchShiftCheckbox->isChecked(); +void ControlPanelPostView::configureSlots() { + connect(pitch_shift_checkbox_, &QCheckBox::released, this, + &ControlPanelView::stateChanged); + + connect(pitch_shift_slider_, &QSlider::sliderReleased, this, + &ControlPanelView::stateChanged); + + connect(pitch_override_checkbox_, &QCheckBox::released, this, + &ControlPanelView::stateChanged); + + connect(pitch_override_slider_, &QSlider::sliderReleased, this, + &ControlPanelView::stateChanged); + + connect(repeat_frames_checkbox_, &QCheckBox::released, this, + &ControlPanelView::stateChanged); + + connect(gain_shift_checkbox_, &QCheckBox::released, this, + &ControlPanelView::stateChanged); + + connect(gain_shift_slider_, &QSlider::sliderReleased, this, + &ControlPanelView::stateChanged); + + connect(gain_normalization_checkbox_, &QCheckBox::released, this, + &ControlPanelView::stateChanged); + + connect(max_unvoiced_gain_line_, &QLineEdit::editingFinished, this, + &ControlPanelView::stateChanged); + + connect(max_voiced_gain_line_, &QLineEdit::editingFinished, this, + &ControlPanelView::stateChanged); +} + +/////////////////////////////////////////////////////////////////////////////// +// Accessors ////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +bool ControlPanelPostView::getPitchShiftEnabled() const { + return pitch_shift_checkbox_->isChecked(); } -int ControlPanelPostView::pitchShift() { - return pitchShiftSlider->value(); +int ControlPanelPostView::getPitchShift() const { + return pitch_shift_slider_->value(); } -bool ControlPanelPostView::pitchOverrideEnabled() { - return pitchOverrideCheckbox->isChecked(); +bool ControlPanelPostView::getPitchOverrideEnabled() const { + return pitch_override_checkbox_->isChecked(); } -int ControlPanelPostView::pitchOverride() { - return pitchOverrideSlider->value(); +int ControlPanelPostView::getPitchOverride() const { + return pitch_override_slider_->value(); } -bool ControlPanelPostView::repeatFramesEnabled() { - return repeatFramesCheckbox->isChecked(); +bool ControlPanelPostView::getRepeatFramesEnabled() const { + return repeat_frames_checkbox_->isChecked(); } -bool ControlPanelPostView::gainShiftEnabled() { - return gainShiftCheckbox->isChecked(); +bool ControlPanelPostView::getGainShiftEnabled() const { + return gain_shift_checkbox_->isChecked(); } -int ControlPanelPostView::gainShift() { - return gainShiftSlider->value(); +int ControlPanelPostView::getGainShift() const { + return gain_shift_slider_->value(); } -bool ControlPanelPostView::gainNormalizationEnabled() { - return gainNormalizationCheckbox->isChecked(); +bool ControlPanelPostView::getGainNormalizationEnabled() const { + return gain_normalization_checkbox_->isChecked(); } -float ControlPanelPostView::maxUnvoicedGain() { - return maxUnvoicedGainLine->text().toFloat(); +float ControlPanelPostView::getMaxUnvoicedGain() const { + return max_unvoiced_gain_line_->text().toFloat(); } -float ControlPanelPostView::maxVoicedGain() { - return maxVoicedGainLine->text().toFloat(); +float ControlPanelPostView::getMaxVoicedGain() const { + return max_voiced_gain_line_->text().toFloat(); } -}; // namespace tms_express +}; // namespace tms_express::ui diff --git a/tms_express/User_Interfaces/Control_Panels/ControlPanelPostView.h b/tms_express/User_Interfaces/Control_Panels/ControlPanelPostView.h deleted file mode 100644 index 653c56a..0000000 --- a/tms_express/User_Interfaces/Control_Panels/ControlPanelPostView.h +++ /dev/null @@ -1,48 +0,0 @@ -// Author: Joseph Bellahcen - -#ifndef TMS_EXPRESS_CONTROLPANELPOSTVIEW_H -#define TMS_EXPRESS_CONTROLPANELPOSTVIEW_H - -#include "User_Interfaces/Control_Panels/ControlPanelView.hpp" - -#include -#include - -namespace tms_express::ui { - -class ControlPanelPostView: public ControlPanelView { -Q_OBJECT -public: - explicit ControlPanelPostView(QWidget *parent = nullptr); - - void reset() override; - void configureSlots() override; - - // Getters - bool pitchShiftEnabled(); - int pitchShift(); - bool pitchOverrideEnabled(); - int pitchOverride(); - bool repeatFramesEnabled(); - bool gainShiftEnabled(); - int gainShift(); - bool gainNormalizationEnabled(); - float maxUnvoicedGain(); - float maxVoicedGain(); - -private: - QCheckBox *pitchShiftCheckbox; - QSlider *pitchShiftSlider; - QCheckBox *pitchOverrideCheckbox; - QSlider *pitchOverrideSlider; - QCheckBox *repeatFramesCheckbox; - QCheckBox *gainShiftCheckbox; - QSlider *gainShiftSlider; - QCheckBox *gainNormalizationCheckbox; - QLineEdit *maxUnvoicedGainLine; - QLineEdit *maxVoicedGainLine; -}; - -}; // namespace tms_express - -#endif //TMS_EXPRESS_CONTROLPANELPOSTVIEW_H diff --git a/tms_express/User_Interfaces/Control_Panels/ControlPanelPostView.hpp b/tms_express/User_Interfaces/Control_Panels/ControlPanelPostView.hpp new file mode 100644 index 0000000..45d2b54 --- /dev/null +++ b/tms_express/User_Interfaces/Control_Panels/ControlPanelPostView.hpp @@ -0,0 +1,108 @@ +// Copyright 2023 Joseph Bellahcen + +#ifndef TMS_EXPRESS_USER_INTERFACES_CONTROL_PANELS_CONTROLPANELPOSTVIEW_HPP_ +#define TMS_EXPRESS_USER_INTERFACES_CONTROL_PANELS_CONTROLPANELPOSTVIEW_HPP_ + +#include +#include +#include +#include + +#include "User_Interfaces/Control_Panels/ControlPanelView.hpp" + +namespace tms_express::ui { + +class ControlPanelPostView: public ControlPanelView { + Q_OBJECT + + public: + /////////////////////////////////////////////////////////////////////////// + // Initializers /////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @brief Creates a new Control Panel View for LPC post-processing + /// parameters + /// @param parent Parent Qt widget + explicit ControlPanelPostView(QWidget *parent = nullptr); + + /////////////////////////////////////////////////////////////////////////// + // Overloaded Methods ///////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @copydoc ControlPanelView::reset() + void reset() override; + + /// @copydoc ControlPanelView::configureSlots() + void configureSlots() override; + + /////////////////////////////////////////////////////////////////////////// + // Accessors ////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @brief Checks if pitch shift should be applied to bitstream + /// @return true if pitch shift should be applied, false otherwise + bool getPitchShiftEnabled() const; + + /// @brief Accesses pitch shift (offset) slider + /// @return Pitch offset, from pitch entry in coding table + int getPitchShift() const; + + /// @brief Checks if pitch override (constant pitch) should be applied to + /// bitstream + /// @return true if pitch override should be applied, false otherwise + bool getPitchOverrideEnabled() const; + + /// @brief Accesses pitch override (constant pitch) + /// @return Index of coding table pitch entry to be applied to entire + /// bitstream + int getPitchOverride() const; + + /// @brief Checks if repeat frame detection should be applied to bitstream + /// @return true if repeat frame detection should be applied, false + /// otherwise + bool getRepeatFramesEnabled() const; + + /// @brief Checks if gain offset should be applied to bitstream + /// @return true if gain shift should be applied, false otherwise + bool getGainShiftEnabled() const; + + /// @brief Accesses gain shift (offset) slider + /// @return Gain offset, from gain/energy entry in coding table + int getGainShift() const; + + /// @brief Checks if gain normalization should be applied to bitstream + /// @return true if gain normalization should be applied, false otherwise + bool getGainNormalizationEnabled() const; + + /// @brief Accesses max unvoiced (consonant) gain line + /// @return Max unvoiced gain, in decibels + float getMaxUnvoicedGain() const; + + /// @brief Access max voiced (vowel) gain line + /// @return Max voiced gain, in decibels + float getMaxVoicedGain() const; + + private: + /////////////////////////////////////////////////////////////////////////// + // Members //////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + QCheckBox *pitch_shift_checkbox_; + QSlider *pitch_shift_slider_; + QCheckBox *pitch_override_checkbox_; + QSlider *pitch_override_slider_; + + QCheckBox *repeat_frames_checkbox_; + + QCheckBox *gain_shift_checkbox_; + QSlider *gain_shift_slider_; + + QCheckBox *gain_normalization_checkbox_; + + QLineEdit *max_unvoiced_gain_line_; + QLineEdit *max_voiced_gain_line_; +}; + +}; // namespace tms_express::ui + +#endif // TMS_EXPRESS_USER_INTERFACES_CONTROL_PANELS_CONTROLPANELPOSTVIEW_HPP_ diff --git a/tms_express/User_Interfaces/MainWindow.cpp b/tms_express/User_Interfaces/MainWindow.cpp index 9134a8b..8066348 100644 --- a/tms_express/User_Interfaces/MainWindow.cpp +++ b/tms_express/User_Interfaces/MainWindow.cpp @@ -13,7 +13,7 @@ #include "User_Interfaces/MainWindow.h" #include "User_Interfaces/Control_Panels/ControlPanelPitchView.h" #include "User_Interfaces/Control_Panels/ControlPanelLpcView.h" -#include "User_Interfaces/Control_Panels/ControlPanelPostView.h" +#include "User_Interfaces/Control_Panels/ControlPanelPostView.hpp" #include "CRC.h" @@ -486,28 +486,28 @@ void MainWindow::performPostProc() { framePostprocessor.reset(); // Re-configure post-processor - framePostprocessor.setMaxUnvoicedGainDB(postControl->maxUnvoicedGain()); - framePostprocessor.setMaxVoicedGainDB(postControl->maxVoicedGain()); + framePostprocessor.setMaxUnvoicedGainDB(postControl->getMaxUnvoicedGain()); + framePostprocessor.setMaxVoicedGainDB(postControl->getMaxVoicedGain()); - if (postControl->gainNormalizationEnabled()) { + if (postControl->getGainNormalizationEnabled()) { framePostprocessor.normalizeGain(); } // Perform either a pitch shift or a fixed-pitch offset - if (postControl->pitchShiftEnabled()) { - framePostprocessor.shiftPitch(postControl->pitchShift()); + if (postControl->getPitchShiftEnabled()) { + framePostprocessor.shiftPitch(postControl->getPitchShift()); - } else if (postControl->pitchOverrideEnabled()) { - framePostprocessor.overridePitch(postControl->pitchOverride()); + } else if (postControl->getPitchOverrideEnabled()) { + framePostprocessor.overridePitch(postControl->getPitchOverride()); } - if (postControl->repeatFramesEnabled()) { + if (postControl->getRepeatFramesEnabled()) { auto nRepeatFrames = framePostprocessor.detectRepeatFrames(); qDebug() << "Detected " << nRepeatFrames << " repeat frames"; } - if (postControl->gainShiftEnabled()) { - framePostprocessor.shiftGain(postControl->gainShift()); + if (postControl->getGainShiftEnabled()) { + framePostprocessor.shiftGain(postControl->getGainShift()); } synthesizer.synthesize(frameTable); diff --git a/tms_express/User_Interfaces/MainWindow.h b/tms_express/User_Interfaces/MainWindow.h index b9d0836..6fb3418 100644 --- a/tms_express/User_Interfaces/MainWindow.h +++ b/tms_express/User_Interfaces/MainWindow.h @@ -15,7 +15,7 @@ #include "User_Interfaces/Audio_Waveform/AudioWaveformView.hpp" #include "User_Interfaces/Control_Panels/ControlPanelPitchView.h" #include "User_Interfaces/Control_Panels/ControlPanelLpcView.h" -#include "User_Interfaces/Control_Panels/ControlPanelPostView.h" +#include "User_Interfaces/Control_Panels/ControlPanelPostView.hpp" #include #include From 4edb62f484e616f95fe3bc7a656e1272c755d1d8 Mon Sep 17 00:00:00 2001 From: Joseph Bellahcen Date: Sun, 6 Aug 2023 16:23:19 -0700 Subject: [PATCH 23/30] Re-factored Control Panel Pitch View --- .../Control_Panels/ControlPanelLpcView.cpp | 72 ++++---- .../Control_Panels/ControlPanelLpcView.h | 24 +-- .../Control_Panels/ControlPanelPitchView.cpp | 159 +++++++++++------- .../Control_Panels/ControlPanelPitchView.h | 47 ------ .../Control_Panels/ControlPanelPitchView.hpp | 98 +++++++++++ .../Control_Panels/ControlPanelPostView.hpp | 5 +- tms_express/User_Interfaces/MainWindow.cpp | 32 ++-- tms_express/User_Interfaces/MainWindow.h | 2 +- 8 files changed, 267 insertions(+), 172 deletions(-) delete mode 100644 tms_express/User_Interfaces/Control_Panels/ControlPanelPitchView.h create mode 100644 tms_express/User_Interfaces/Control_Panels/ControlPanelPitchView.hpp diff --git a/tms_express/User_Interfaces/Control_Panels/ControlPanelLpcView.cpp b/tms_express/User_Interfaces/Control_Panels/ControlPanelLpcView.cpp index 46ded94..dbc9550 100644 --- a/tms_express/User_Interfaces/Control_Panels/ControlPanelLpcView.cpp +++ b/tms_express/User_Interfaces/Control_Panels/ControlPanelLpcView.cpp @@ -17,74 +17,74 @@ namespace tms_express::ui { ControlPanelLpcView::ControlPanelLpcView(QWidget *parent): ControlPanelView("LPC Analysis", parent) { auto analysisWindowLabel = new QLabel("Analysis window (ms)", this); analysisWindowLine = new QLineEdit("25.0", this); - hpfCheckbox = new QCheckBox("Highpass filter (Hz)", this); - hpfLine = new QLineEdit("100", this); - lpfCheckbox = new QCheckBox("Lowpass filter (Hz)", this); - lpfLine = new QLineEdit("800", this); - preemphCheckbox = new QCheckBox("Pre-emphasis filter (alpha)", this); - preemphLine = new QLineEdit("0.9375", this); + hpf_checkbox_ = new QCheckBox("Highpass filter (Hz)", this); + hpf_line_ = new QLineEdit("100", this); + lpf_checkbox_ = new QCheckBox("Lowpass filter (Hz)", this); + lpf_line_ = new QLineEdit("800", this); + preemphasis_checkbox_ = new QCheckBox("Pre-emphasis filter (alpha)", this); + preemphasis_line_ = new QLineEdit("0.9375", this); // Construct layout auto row = grid->rowCount(); grid->addWidget(analysisWindowLabel, row, 0); grid->addWidget(analysisWindowLine, row++, 1); - grid->addWidget(hpfCheckbox, row, 0); - grid->addWidget(hpfLine, row++, 1); + grid->addWidget(hpf_checkbox_, row, 0); + grid->addWidget(hpf_line_, row++, 1); - grid->addWidget(lpfCheckbox, row, 0); - grid->addWidget(lpfLine, row++, 1); + grid->addWidget(lpf_checkbox_, row, 0); + grid->addWidget(lpf_line_, row++, 1); - grid->addWidget(preemphCheckbox, row, 0); - grid->addWidget(preemphLine, row, 1); + grid->addWidget(preemphasis_checkbox_, row, 0); + grid->addWidget(preemphasis_line_, row, 1); } void ControlPanelLpcView::configureSlots() { connect(analysisWindowLine, &QLineEdit::editingFinished, this, &ControlPanelView::stateChanged); - connect(hpfCheckbox, &QCheckBox::released, this, &ControlPanelView::stateChanged); - connect(hpfLine, &QLineEdit::editingFinished, this, &ControlPanelView::stateChanged); - connect(lpfCheckbox, &QCheckBox::released, this, &ControlPanelView::stateChanged); - connect(lpfLine, &QLineEdit::editingFinished, this, &ControlPanelView::stateChanged); - connect(preemphCheckbox, &QCheckBox::released, this, &ControlPanelView::stateChanged); - connect(preemphLine, &QLineEdit::editingFinished, this, &ControlPanelView::stateChanged); + connect(hpf_checkbox_, &QCheckBox::released, this, &ControlPanelView::stateChanged); + connect(hpf_line_, &QLineEdit::editingFinished, this, &ControlPanelView::stateChanged); + connect(lpf_checkbox_, &QCheckBox::released, this, &ControlPanelView::stateChanged); + connect(lpf_line_, &QLineEdit::editingFinished, this, &ControlPanelView::stateChanged); + connect(preemphasis_checkbox_, &QCheckBox::released, this, &ControlPanelView::stateChanged); + connect(preemphasis_line_, &QLineEdit::editingFinished, this, &ControlPanelView::stateChanged); } void ControlPanelLpcView::reset() { - hpfCheckbox->setChecked(false); - hpfLine->setText("100"); + hpf_checkbox_->setChecked(false); + hpf_line_->setText("100"); - lpfCheckbox->setChecked(false); - lpfLine->setText("800"); + lpf_checkbox_->setChecked(false); + lpf_line_->setText("800"); - preemphCheckbox->setChecked(true); - preemphLine->setText("0.9375"); + preemphasis_checkbox_->setChecked(true); + preemphasis_line_->setText("0.9375"); } float ControlPanelLpcView::analysisWindowWidth() { return analysisWindowLine->text().toFloat(); } -bool ControlPanelLpcView::hpfEnabled() { - return hpfCheckbox->isChecked(); +bool ControlPanelLpcView::getHpfEnabled() { + return hpf_checkbox_->isChecked(); } -int ControlPanelLpcView::hpfCutoff() { - return hpfLine->text().toInt(); +int ControlPanelLpcView::getHpfCutoff() { + return hpf_line_->text().toInt(); } -bool ControlPanelLpcView::lpfEnabled() { - return lpfCheckbox->isChecked(); +bool ControlPanelLpcView::getLpfEnabled() { + return lpf_checkbox_->isChecked(); } -int ControlPanelLpcView::lpfCutoff() { - return lpfLine->text().toInt(); +int ControlPanelLpcView::getLpfCutoff() { + return lpf_line_->text().toInt(); } -bool ControlPanelLpcView::preemphEnabled() { - return preemphCheckbox->isChecked(); +bool ControlPanelLpcView::getPreEmphasisEnabled() { + return preemphasis_checkbox_->isChecked(); } -float ControlPanelLpcView::preemphAlpha() { - return preemphLine->text().toFloat(); +float ControlPanelLpcView::getPreEmphasisAlpha() { + return preemphasis_line_->text().toFloat(); } }; // namespace tms_express diff --git a/tms_express/User_Interfaces/Control_Panels/ControlPanelLpcView.h b/tms_express/User_Interfaces/Control_Panels/ControlPanelLpcView.h index f807aae..e3729e7 100644 --- a/tms_express/User_Interfaces/Control_Panels/ControlPanelLpcView.h +++ b/tms_express/User_Interfaces/Control_Panels/ControlPanelLpcView.h @@ -20,21 +20,21 @@ Q_OBJECT // Getters float analysisWindowWidth(); - bool hpfEnabled(); - int hpfCutoff(); - bool lpfEnabled(); - int lpfCutoff(); - bool preemphEnabled(); - float preemphAlpha(); + bool getHpfEnabled(); + int getHpfCutoff(); + bool getLpfEnabled(); + int getLpfCutoff(); + bool getPreEmphasisEnabled(); + float getPreEmphasisAlpha(); private: QLineEdit *analysisWindowLine; - QCheckBox *hpfCheckbox; - QLineEdit *hpfLine; - QCheckBox *lpfCheckbox; - QLineEdit *lpfLine; - QCheckBox *preemphCheckbox; - QLineEdit *preemphLine; + QCheckBox *hpf_checkbox_; + QLineEdit *hpf_line_; + QCheckBox *lpf_checkbox_; + QLineEdit *lpf_line_; + QCheckBox *preemphasis_checkbox_; + QLineEdit *preemphasis_line_; }; }; // namespace tms_express diff --git a/tms_express/User_Interfaces/Control_Panels/ControlPanelPitchView.cpp b/tms_express/User_Interfaces/Control_Panels/ControlPanelPitchView.cpp index b1d33b3..ec226bb 100644 --- a/tms_express/User_Interfaces/Control_Panels/ControlPanelPitchView.cpp +++ b/tms_express/User_Interfaces/Control_Panels/ControlPanelPitchView.cpp @@ -6,103 +6,146 @@ // Author: Joseph Bellahcen /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#include "User_Interfaces/Control_Panels/ControlPanelPitchView.h" -#include "User_Interfaces/Control_Panels/ControlPanelView.hpp" +#include "User_Interfaces/Control_Panels/ControlPanelPitchView.hpp" + +#include +#include +#include +#include +#include #include -#include + +#include + +#include "User_Interfaces/Control_Panels/ControlPanelView.hpp" namespace tms_express::ui { -ControlPanelPitchView::ControlPanelPitchView(QWidget *parent): ControlPanelView("Pitch Analysis", parent) { - auto line2 = new QFrame(this); - line2->setFrameShape(QFrame::HLine); +/////////////////////////////////////////////////////////////////////////////// +// Initializers /////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// - hpfCheckbox = new QCheckBox("Highpass filter (Hz)", this); - hpfLine = new QLineEdit("100", this); - lpfCheckbox = new QCheckBox("Lowpass filter (Hz)", this); - lpfLine = new QLineEdit("800", this); - preemphCheckbox = new QCheckBox("Pre-emphasis filter (alpha)", this); - preemphLine = new QLineEdit("0.9375", this); - maxPitchLabel = new QLabel("Max pitch (Hz)", this); - maxPitchLine = new QLineEdit("500", this); - minPitchLabel = new QLabel("Min pitch (Hz)", this); - minPitchLine = new QLineEdit("50", this); +ControlPanelPitchView::ControlPanelPitchView(QWidget *parent): + ControlPanelView("Pitch Analysis", parent) { + // Initialize parameters + hpf_checkbox_ = new QCheckBox("Highpass filter (Hz)", this); + hpf_line_ = new QLineEdit("100", this); + lpf_checkbox_ = new QCheckBox("Lowpass filter (Hz)", this); + lpf_line_ = new QLineEdit("800", this); + + preemphasis_checkbox_ = new QCheckBox("Pre-emphasis filter (alpha)", this); + preemphasis_line_ = new QLineEdit("0.9375", this); + + auto maxPitchLabel = new QLabel("Max pitch (Hz)", this); + max_pitch_frq_line_ = new QLineEdit("500", this); + + auto minPitchLabel = new QLabel("Min pitch (Hz)", this); + min_pitch_frq_line_ = new QLineEdit("50", this); + + // Construct layout auto row = grid->rowCount(); - grid->addWidget(hpfCheckbox, row, 0); - grid->addWidget(hpfLine, row++, 1); + grid->addWidget(hpf_checkbox_, row, 0); + grid->addWidget(hpf_line_, row++, 1); - grid->addWidget(lpfCheckbox, row, 0); - grid->addWidget(lpfLine, row++, 1); + grid->addWidget(lpf_checkbox_, row, 0); + grid->addWidget(lpf_line_, row++, 1); - grid->addWidget(preemphCheckbox, row, 0); - grid->addWidget(preemphLine, row++, 1); + grid->addWidget(preemphasis_checkbox_, row, 0); + grid->addWidget(preemphasis_line_, row++, 1); + auto line2 = new QFrame(this); + line2->setFrameShape(QFrame::HLine); grid->addWidget(line2, row++, 0, 1, 2); - + grid->addWidget(maxPitchLabel, row, 0); - grid->addWidget(maxPitchLine, row++, 1); + grid->addWidget(max_pitch_frq_line_, row++, 1); grid->addWidget(minPitchLabel, row, 0); - grid->addWidget(minPitchLine, row, 1); + grid->addWidget(min_pitch_frq_line_, row, 1); } +/////////////////////////////////////////////////////////////////////////////// +// Overloaded Methods ///////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + + void ControlPanelPitchView::configureSlots() { - connect(hpfCheckbox, &QCheckBox::released, this, &ControlPanelView::stateChanged); - connect(hpfLine, &QLineEdit::editingFinished, this, &ControlPanelView::stateChanged); - connect(lpfCheckbox, &QCheckBox::released, this, &ControlPanelView::stateChanged); - connect(lpfLine, &QLineEdit::editingFinished, this, &ControlPanelView::stateChanged); - connect(preemphCheckbox, &QCheckBox::released, this, &ControlPanelView::stateChanged); - connect(preemphLine, &QLineEdit::editingFinished, this, &ControlPanelView::stateChanged); - connect(maxPitchLine, &QLineEdit::editingFinished, this, &ControlPanelView::stateChanged); - connect(minPitchLine, &QLineEdit::editingFinished, this, &ControlPanelView::stateChanged); + connect(hpf_checkbox_, &QCheckBox::released, this, + &ControlPanelView::stateChanged); + + connect(hpf_line_, &QLineEdit::editingFinished, this, + &ControlPanelView::stateChanged); + + connect(lpf_checkbox_, &QCheckBox::released, this, + &ControlPanelView::stateChanged); + + connect(lpf_line_, &QLineEdit::editingFinished, this, + &ControlPanelView::stateChanged); + + connect(preemphasis_checkbox_, &QCheckBox::released, this, + &ControlPanelView::stateChanged); + + connect(preemphasis_line_, &QLineEdit::editingFinished, this, + &ControlPanelView::stateChanged); + + connect(max_pitch_frq_line_, &QLineEdit::editingFinished, this, + &ControlPanelView::stateChanged); + + connect(min_pitch_frq_line_, &QLineEdit::editingFinished, this, + &ControlPanelView::stateChanged); } void ControlPanelPitchView::reset() { - hpfCheckbox->setChecked(false); - hpfLine->setText("100"); + hpf_checkbox_->setChecked(false); + hpf_line_->setText("100"); - lpfCheckbox->setChecked(true); - lpfLine->setText("800"); + lpf_checkbox_->setChecked(true); + lpf_line_->setText("800"); - preemphCheckbox->setChecked(false); - preemphLine->setText("0.9375"); + preemphasis_checkbox_->setChecked(false); + preemphasis_line_->setText("0.9375"); - maxPitchLine->setText("500"); - minPitchLine->setText("50"); + max_pitch_frq_line_->setText("500"); + min_pitch_frq_line_->setText("50"); } -bool ControlPanelPitchView::hpfEnabled() { - return hpfCheckbox->isChecked(); +/////////////////////////////////////////////////////////////////////////////// +// Accessors ////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + + +bool ControlPanelPitchView::getHpfEnabled() { + return hpf_checkbox_->isChecked(); } -int ControlPanelPitchView::hpfCutoff() { - return hpfLine->text().toInt(); +int ControlPanelPitchView::getHpfCutoff() { + return hpf_line_->text().toInt(); } -bool ControlPanelPitchView::lpfEnabled() { - return lpfCheckbox->isChecked(); +bool ControlPanelPitchView::getLpfEnabled() { + return lpf_checkbox_->isChecked(); } -int ControlPanelPitchView::lpfCutoff() { - return lpfLine->text().toInt(); +int ControlPanelPitchView::getLpfCutoff() { + return lpf_line_->text().toInt(); } -bool ControlPanelPitchView::preemphEnabled() { - return preemphCheckbox->isChecked(); +bool ControlPanelPitchView::getPreEmphasisEnabled() { + return preemphasis_checkbox_->isChecked(); } -float ControlPanelPitchView::preemphAlpha() { - return preemphLine->text().toFloat(); +float ControlPanelPitchView::getPreEmphasisAlpha() { + return preemphasis_line_->text().toFloat(); } -int ControlPanelPitchView::maxPitchFrq() { - return maxPitchLine->text().toInt(); +int ControlPanelPitchView::getMaxPitchFrq() { + return max_pitch_frq_line_->text().toInt(); } -int ControlPanelPitchView::minPitchFrq() { - return minPitchLine->text().toInt(); +int ControlPanelPitchView::getMinPitchFrq() { + return min_pitch_frq_line_->text().toInt(); } -}; // namespace tms_express +}; // namespace tms_express::ui diff --git a/tms_express/User_Interfaces/Control_Panels/ControlPanelPitchView.h b/tms_express/User_Interfaces/Control_Panels/ControlPanelPitchView.h deleted file mode 100644 index 70bbe85..0000000 --- a/tms_express/User_Interfaces/Control_Panels/ControlPanelPitchView.h +++ /dev/null @@ -1,47 +0,0 @@ -// Author: Joseph Bellahcen - -#ifndef TMS_EXPRESS_CONTROLPANELPITCHVIEW_H -#define TMS_EXPRESS_CONTROLPANELPITCHVIEW_H - -#include "User_Interfaces/Control_Panels/ControlPanelView.hpp" - -#include -#include -#include - -namespace tms_express::ui { - -class ControlPanelPitchView: public ControlPanelView { -Q_OBJECT -public: - explicit ControlPanelPitchView(QWidget *parent = nullptr); - - void reset() override; - void configureSlots() override; - - // Getters - bool hpfEnabled(); - int hpfCutoff(); - bool lpfEnabled(); - int lpfCutoff(); - bool preemphEnabled(); - float preemphAlpha(); - int maxPitchFrq(); - int minPitchFrq(); - -private: - QCheckBox *hpfCheckbox; - QLineEdit *hpfLine; - QCheckBox *lpfCheckbox; - QLineEdit *lpfLine; - QCheckBox *preemphCheckbox; - QLineEdit *preemphLine; - QLabel *maxPitchLabel; - QLineEdit *maxPitchLine; - QLabel *minPitchLabel; - QLineEdit *minPitchLine; -}; - -}; // namespace tms_express - -#endif //TMS_EXPRESS_CONTROLPANELPITCHVIEW_H diff --git a/tms_express/User_Interfaces/Control_Panels/ControlPanelPitchView.hpp b/tms_express/User_Interfaces/Control_Panels/ControlPanelPitchView.hpp new file mode 100644 index 0000000..703eac0 --- /dev/null +++ b/tms_express/User_Interfaces/Control_Panels/ControlPanelPitchView.hpp @@ -0,0 +1,98 @@ +// Copyright 2023 Joseph Bellahcen + +#ifndef TMS_EXPRESS_USER_INTERFACES_CONTROL_PANELS_CONTROLPANELPITCHVIEW_HPP_ +#define TMS_EXPRESS_USER_INTERFACES_CONTROL_PANELS_CONTROLPANELPITCHVIEW_HPP_ + +#include +#include +#include +#include + +#include + +#include "User_Interfaces/Control_Panels/ControlPanelView.hpp" + +namespace tms_express::ui { + +/// @brief Control Panel View for LPC pitch analysis and post-processing +/// parameters +class ControlPanelPitchView: public ControlPanelView { + Q_OBJECT + + public: + /////////////////////////////////////////////////////////////////////////// + // Initializers /////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @brief Creates a new Control Panel View for LPC pitch analysis and + //// post-processing parameters + /// @param parent Parent Qt widget + explicit ControlPanelPitchView(QWidget *parent = nullptr); + + /////////////////////////////////////////////////////////////////////////// + // Overloaded Methods ///////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @copydoc ControlPanelView::reset() + void reset() override; + + /// @copydoc ControlPanelView::configureSlots() + void configureSlots() override; + + /////////////////////////////////////////////////////////////////////////// + // Accessors ////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @brief Checks if highpass filter should be applied to pitch buffer + /// @return true if highpass filter should be applied, false otherwise + bool getHpfEnabled(); + + /// @brief Accesses highpass filter cutoff + /// @return Highpass filter cutoff, in Hertz + int getHpfCutoff(); + + /// @brief Checks if lowpass filter should be applied to pitch buffer + /// @return true if lowpass filter should be applied, false otherwise + bool getLpfEnabled(); + + /// @brief Accesses lowpass filter cutoff + /// @return Lowpass filter cutoff, in Hertz + int getLpfCutoff(); + + /// @brief Checks if pre-emphasis filter should be applied to pitch buffer + /// @return true if pre-emphasis filter should be applied, false otherwise + bool getPreEmphasisEnabled(); + + /// @brief Accesses pre-emphasis filter coefficient + /// @return Pre-emphasis filter coefficient + float getPreEmphasisAlpha(); + + /// @brief Accesses pitch analysis ceiling + /// @return Max pitch frequency, in Hertz + int getMaxPitchFrq(); + + /// @brief Accesses pitch analysis floor + /// @return Min pitch frequency, in Hertz + int getMinPitchFrq(); + + private: + /////////////////////////////////////////////////////////////////////////// + // Members //////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + QCheckBox *hpf_checkbox_; + QLineEdit *hpf_line_; + + QCheckBox *lpf_checkbox_; + QLineEdit *lpf_line_; + + QCheckBox *preemphasis_checkbox_; + QLineEdit *preemphasis_line_; + + QLineEdit *max_pitch_frq_line_; + QLineEdit *min_pitch_frq_line_; +}; + +}; // namespace tms_express::ui + +#endif // TMS_EXPRESS_USER_INTERFACES_CONTROL_PANELS_CONTROLPANELPITCHVIEW_HPP_ diff --git a/tms_express/User_Interfaces/Control_Panels/ControlPanelPostView.hpp b/tms_express/User_Interfaces/Control_Panels/ControlPanelPostView.hpp index 45d2b54..59c00b3 100644 --- a/tms_express/User_Interfaces/Control_Panels/ControlPanelPostView.hpp +++ b/tms_express/User_Interfaces/Control_Panels/ControlPanelPostView.hpp @@ -12,6 +12,7 @@ namespace tms_express::ui { +/// @brief Control Panel View for LPC bitstream post-processing parameters class ControlPanelPostView: public ControlPanelView { Q_OBJECT @@ -20,8 +21,8 @@ class ControlPanelPostView: public ControlPanelView { // Initializers /////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////// - /// @brief Creates a new Control Panel View for LPC post-processing - /// parameters + /// @brief Creates a new Control Panel View for LPC bitstream + /// post-processing parameters /// @param parent Parent Qt widget explicit ControlPanelPostView(QWidget *parent = nullptr); diff --git a/tms_express/User_Interfaces/MainWindow.cpp b/tms_express/User_Interfaces/MainWindow.cpp index 8066348..d044ec7 100644 --- a/tms_express/User_Interfaces/MainWindow.cpp +++ b/tms_express/User_Interfaces/MainWindow.cpp @@ -11,7 +11,7 @@ #include "LPC_Analysis/Autocorrelation.hpp" #include "User_Interfaces/Audio_Waveform/AudioWaveformView.hpp" #include "User_Interfaces/MainWindow.h" -#include "User_Interfaces/Control_Panels/ControlPanelPitchView.h" +#include "User_Interfaces/Control_Panels/ControlPanelPitchView.hpp" #include "User_Interfaces/Control_Panels/ControlPanelLpcView.h" #include "User_Interfaces/Control_Panels/ControlPanelPostView.hpp" @@ -383,7 +383,7 @@ unsigned int MainWindow::samplesChecksum(std::vector samples) { auto bufferSize = int(samples.size()); auto checksumBuffer = (float *) malloc(sizeof(float) * (bufferSize + 1)); memccpy(checksumBuffer, samples.data(), bufferSize, sizeof(float)); - checksumBuffer[bufferSize] = char(lpcControl->preemphAlpha() + pitchControl->preemphAlpha()); + checksumBuffer[bufferSize] = char(lpcControl->getPreEmphasisAlpha() + pitchControl->getPreEmphasisAlpha()); auto checksum = CRC::Calculate(checksumBuffer, sizeof(float), CRC::CRC_32()); @@ -403,20 +403,20 @@ void MainWindow::performPitchAnalysis() { pitchFrqTable.clear(); // Pre-process - if (pitchControl->hpfEnabled()) { - filter.applyHighpass(inputBuffer, pitchControl->hpfCutoff()); + if (pitchControl->getHpfEnabled()) { + filter.applyHighpass(inputBuffer, pitchControl->getHpfCutoff()); } - if (pitchControl->lpfEnabled()) { - filter.applyLowpass(inputBuffer, pitchControl->lpfCutoff()); + if (pitchControl->getLpfEnabled()) { + filter.applyLowpass(inputBuffer, pitchControl->getLpfCutoff()); } - if (pitchControl->preemphEnabled()) { - filter.applyPreEmphasis(inputBuffer, pitchControl->preemphAlpha()); + if (pitchControl->getPreEmphasisEnabled()) { + filter.applyPreEmphasis(inputBuffer, pitchControl->getPreEmphasisAlpha()); } - pitchEstimator.setMaxPeriod(pitchControl->minPitchFrq()); - pitchEstimator.setMinPeriod(pitchControl->maxPitchFrq()); + pitchEstimator.setMaxPeriod(pitchControl->getMinPitchFrq()); + pitchEstimator.setMinPeriod(pitchControl->getMaxPitchFrq()); for (const auto &segment : inputBuffer.getAllSegments()) { auto acf = tms_express::Autocorrelation(segment); @@ -448,20 +448,20 @@ void MainWindow::performLpcAnalysis() { } // Pre-process - if (lpcControl->hpfEnabled()) { + if (lpcControl->getHpfEnabled()) { qDebug() << "HPF"; - filter.applyHighpass(lpcBuffer, lpcControl->hpfCutoff()); + filter.applyHighpass(lpcBuffer, lpcControl->getHpfCutoff()); } - if (lpcControl->lpfEnabled()) { + if (lpcControl->getLpfEnabled()) { qDebug() << "LPF"; - filter.applyLowpass(lpcBuffer, lpcControl->lpfCutoff()); + filter.applyLowpass(lpcBuffer, lpcControl->getLpfCutoff()); } - if (lpcControl->preemphEnabled()) { + if (lpcControl->getPreEmphasisEnabled()) { qDebug() << "PEF"; qDebug() << (lpcBuffer.empty()); - filter.applyPreEmphasis(lpcBuffer, lpcControl->preemphAlpha()); + filter.applyPreEmphasis(lpcBuffer, lpcControl->getPreEmphasisAlpha()); } for (int i = 0; i < lpcBuffer.getNSegments(); i++) { diff --git a/tms_express/User_Interfaces/MainWindow.h b/tms_express/User_Interfaces/MainWindow.h index 6fb3418..501fa75 100644 --- a/tms_express/User_Interfaces/MainWindow.h +++ b/tms_express/User_Interfaces/MainWindow.h @@ -13,7 +13,7 @@ #include "LPC_Analysis/PitchEstimator.hpp" #include "LPC_Analysis/LinearPredictor.hpp" #include "User_Interfaces/Audio_Waveform/AudioWaveformView.hpp" -#include "User_Interfaces/Control_Panels/ControlPanelPitchView.h" +#include "User_Interfaces/Control_Panels/ControlPanelPitchView.hpp" #include "User_Interfaces/Control_Panels/ControlPanelLpcView.h" #include "User_Interfaces/Control_Panels/ControlPanelPostView.hpp" From b31144bfc5b22bd228926ba008f034dc73cd812a Mon Sep 17 00:00:00 2001 From: Joseph Bellahcen Date: Sun, 6 Aug 2023 16:38:18 -0700 Subject: [PATCH 24/30] Re-factored Control Panel LPC View --- .../Control_Panels/ControlPanelLpcView.cpp | 81 +++++++++++------ .../Control_Panels/ControlPanelLpcView.h | 42 --------- .../Control_Panels/ControlPanelLpcView.hpp | 90 +++++++++++++++++++ .../Control_Panels/ControlPanelPitchView.cpp | 38 ++++---- tms_express/User_Interfaces/MainWindow.cpp | 10 +-- tms_express/User_Interfaces/MainWindow.h | 3 +- 6 files changed, 167 insertions(+), 97 deletions(-) delete mode 100644 tms_express/User_Interfaces/Control_Panels/ControlPanelLpcView.h create mode 100644 tms_express/User_Interfaces/Control_Panels/ControlPanelLpcView.hpp diff --git a/tms_express/User_Interfaces/Control_Panels/ControlPanelLpcView.cpp b/tms_express/User_Interfaces/Control_Panels/ControlPanelLpcView.cpp index dbc9550..e2ba378 100644 --- a/tms_express/User_Interfaces/Control_Panels/ControlPanelLpcView.cpp +++ b/tms_express/User_Interfaces/Control_Panels/ControlPanelLpcView.cpp @@ -1,33 +1,40 @@ -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// Class: ControlPanelLpcView -// -// Description: The ControlPanelLpcView contains parameters which guide LPC analysis -// -// Author: Joseph Bellahcen -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -#include "User_Interfaces/Control_Panels/ControlPanelLpcView.h" -#include "User_Interfaces/Control_Panels/ControlPanelView.hpp" +// Copyright 2023 Joseph Bellahcen + +#include "User_Interfaces/Control_Panels/ControlPanelLpcView.hpp" +#include +#include +#include #include -#include + +#include "User_Interfaces/Control_Panels/ControlPanelView.hpp" namespace tms_express::ui { -ControlPanelLpcView::ControlPanelLpcView(QWidget *parent): ControlPanelView("LPC Analysis", parent) { +/////////////////////////////////////////////////////////////////////////////// +// Initializers /////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +ControlPanelLpcView::ControlPanelLpcView(QWidget *parent): + ControlPanelView("LPC Analysis", parent) { + // Initialize parameters auto analysisWindowLabel = new QLabel("Analysis window (ms)", this); - analysisWindowLine = new QLineEdit("25.0", this); + analysis_window_line_ = new QLineEdit("25.0", this); + hpf_checkbox_ = new QCheckBox("Highpass filter (Hz)", this); hpf_line_ = new QLineEdit("100", this); + lpf_checkbox_ = new QCheckBox("Lowpass filter (Hz)", this); lpf_line_ = new QLineEdit("800", this); + preemphasis_checkbox_ = new QCheckBox("Pre-emphasis filter (alpha)", this); preemphasis_line_ = new QLineEdit("0.9375", this); // Construct layout auto row = grid->rowCount(); + grid->addWidget(analysisWindowLabel, row, 0); - grid->addWidget(analysisWindowLine, row++, 1); + grid->addWidget(analysis_window_line_, row++, 1); grid->addWidget(hpf_checkbox_, row, 0); grid->addWidget(hpf_line_, row++, 1); @@ -39,15 +46,9 @@ ControlPanelLpcView::ControlPanelLpcView(QWidget *parent): ControlPanelView("LPC grid->addWidget(preemphasis_line_, row, 1); } -void ControlPanelLpcView::configureSlots() { - connect(analysisWindowLine, &QLineEdit::editingFinished, this, &ControlPanelView::stateChanged); - connect(hpf_checkbox_, &QCheckBox::released, this, &ControlPanelView::stateChanged); - connect(hpf_line_, &QLineEdit::editingFinished, this, &ControlPanelView::stateChanged); - connect(lpf_checkbox_, &QCheckBox::released, this, &ControlPanelView::stateChanged); - connect(lpf_line_, &QLineEdit::editingFinished, this, &ControlPanelView::stateChanged); - connect(preemphasis_checkbox_, &QCheckBox::released, this, &ControlPanelView::stateChanged); - connect(preemphasis_line_, &QLineEdit::editingFinished, this, &ControlPanelView::stateChanged); -} +/////////////////////////////////////////////////////////////////////////////// +// Overloaded Methods ///////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// void ControlPanelLpcView::reset() { hpf_checkbox_->setChecked(false); @@ -60,9 +61,37 @@ void ControlPanelLpcView::reset() { preemphasis_line_->setText("0.9375"); } -float ControlPanelLpcView::analysisWindowWidth() { - return analysisWindowLine->text().toFloat(); +void ControlPanelLpcView::configureSlots() { + connect(analysis_window_line_, &QLineEdit::editingFinished, this, + &ControlPanelView::stateChanged); + + connect(hpf_checkbox_, &QCheckBox::released, this, + &ControlPanelView::stateChanged); + + connect(hpf_line_, &QLineEdit::editingFinished, this, + &ControlPanelView::stateChanged); + + connect(lpf_checkbox_, &QCheckBox::released, this, + &ControlPanelView::stateChanged); + + connect(lpf_line_, &QLineEdit::editingFinished, this, + &ControlPanelView::stateChanged); + + connect(preemphasis_checkbox_, &QCheckBox::released, this, + &ControlPanelView::stateChanged); + + connect(preemphasis_line_, &QLineEdit::editingFinished, this, + &ControlPanelView::stateChanged); } + +/////////////////////////////////////////////////////////////////////////////// +// Accessors ////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +float ControlPanelLpcView::getAnalysisWindowWidth() { + return analysis_window_line_->text().toFloat(); +} + bool ControlPanelLpcView::getHpfEnabled() { return hpf_checkbox_->isChecked(); } @@ -87,4 +116,4 @@ float ControlPanelLpcView::getPreEmphasisAlpha() { return preemphasis_line_->text().toFloat(); } -}; // namespace tms_express +}; // namespace tms_express::ui diff --git a/tms_express/User_Interfaces/Control_Panels/ControlPanelLpcView.h b/tms_express/User_Interfaces/Control_Panels/ControlPanelLpcView.h deleted file mode 100644 index e3729e7..0000000 --- a/tms_express/User_Interfaces/Control_Panels/ControlPanelLpcView.h +++ /dev/null @@ -1,42 +0,0 @@ -// Author: Joseph Bellahcen - -#ifndef TMS_EXPRESS_CONTROLPANELLPCVIEW_H -#define TMS_EXPRESS_CONTROLPANELLPCVIEW_H - -#include "User_Interfaces/Control_Panels/ControlPanelView.hpp" - -#include -#include - -namespace tms_express::ui { - -class ControlPanelLpcView: public ControlPanelView { -Q_OBJECT -public: - explicit ControlPanelLpcView(QWidget *parent = nullptr); - - void reset() override; - void configureSlots() override; - - // Getters - float analysisWindowWidth(); - bool getHpfEnabled(); - int getHpfCutoff(); - bool getLpfEnabled(); - int getLpfCutoff(); - bool getPreEmphasisEnabled(); - float getPreEmphasisAlpha(); - -private: - QLineEdit *analysisWindowLine; - QCheckBox *hpf_checkbox_; - QLineEdit *hpf_line_; - QCheckBox *lpf_checkbox_; - QLineEdit *lpf_line_; - QCheckBox *preemphasis_checkbox_; - QLineEdit *preemphasis_line_; -}; - -}; // namespace tms_express - -#endif //TMS_EXPRESS_CONTROLPANELLPCVIEW_H diff --git a/tms_express/User_Interfaces/Control_Panels/ControlPanelLpcView.hpp b/tms_express/User_Interfaces/Control_Panels/ControlPanelLpcView.hpp new file mode 100644 index 0000000..55ecf6e --- /dev/null +++ b/tms_express/User_Interfaces/Control_Panels/ControlPanelLpcView.hpp @@ -0,0 +1,90 @@ +// Copyright 2023 Joseph Bellahcen + +#ifndef TMS_EXPRESS_USER_INTERFACES_CONTROL_PANELS_CONTROLPANELLPCVIEW_HPP_ +#define TMS_EXPRESS_USER_INTERFACES_CONTROL_PANELS_CONTROLPANELLPCVIEW_HPP_ + +#include "User_Interfaces/Control_Panels/ControlPanelView.hpp" + +#include +#include +#include + +namespace tms_express::ui { + +/// @brief Control Panel View for LPC analysis parameters +class ControlPanelLpcView: public ControlPanelView { + Q_OBJECT + + public: + /////////////////////////////////////////////////////////////////////////// + // Initializers /////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @brief Creates a new Control Panel View for LPC analysis parameters + /// @param parent Parent Qt widget + explicit ControlPanelLpcView(QWidget *parent = nullptr); + + /////////////////////////////////////////////////////////////////////////// + // Overloaded Methods ///////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @copydoc ControlPanelView::reset() + void reset() override; + + /// @copydoc ControlPanelView::configureSlots() + void configureSlots() override; + + /////////////////////////////////////////////////////////////////////////// + // Accessors ////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @brief Accesses LPC analysis (segmentation) window width + /// @return Analysis window width, in milliseconds + /// @note The analysis window width guides both pitch and LPC analysis by + /// establishing Frame segmentation boundaries + float getAnalysisWindowWidth(); + + /// @brief Checks if highpass filter should be applied to pitch buffer + /// @return true if highpass filter should be applied, false otherwise + bool getHpfEnabled(); + + /// @brief Accesses highpass filter cutoff + /// @return Highpass filter cutoff, in Hertz + int getHpfCutoff(); + + /// @brief Checks if lowpass filter should be applied to pitch buffer + /// @return true if lowpass filter should be applied, false otherwise + bool getLpfEnabled(); + + /// @brief Accesses lowpass filter cutoff + /// @return Lowpass filter cutoff, in Hertz + int getLpfCutoff(); + + /// @brief Checks if pre-emphasis filter should be applied to pitch buffer + /// @return true if pre-emphasis filter should be applied, false otherwise + bool getPreEmphasisEnabled(); + + /// @brief Accesses pre-emphasis filter coefficient + /// @return Pre-emphasis filter coefficient + float getPreEmphasisAlpha(); + + private: + /////////////////////////////////////////////////////////////////////////// + // Members //////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + QLineEdit *analysis_window_line_; + + QCheckBox *hpf_checkbox_; + QLineEdit *hpf_line_; + + QCheckBox *lpf_checkbox_; + QLineEdit *lpf_line_; + + QCheckBox *preemphasis_checkbox_; + QLineEdit *preemphasis_line_; +}; + +}; // namespace tms_express::ui + +#endif // TMS_EXPRESS_USER_INTERFACES_CONTROL_PANELS_CONTROLPANELLPCVIEW_HPP_ diff --git a/tms_express/User_Interfaces/Control_Panels/ControlPanelPitchView.cpp b/tms_express/User_Interfaces/Control_Panels/ControlPanelPitchView.cpp index ec226bb..9edeef9 100644 --- a/tms_express/User_Interfaces/Control_Panels/ControlPanelPitchView.cpp +++ b/tms_express/User_Interfaces/Control_Panels/ControlPanelPitchView.cpp @@ -1,10 +1,4 @@ -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// Class: ControlPanelPitchView -// -// Description: The ControlPanelPitchView contains parameters which guide pitch analysis -// -// Author: Joseph Bellahcen -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Copyright 2023 Joseph Bellahcen #include "User_Interfaces/Control_Panels/ControlPanelPitchView.hpp" @@ -23,7 +17,7 @@ namespace tms_express::ui { /////////////////////////////////////////////////////////////////////////////// // Initializers /////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// ControlPanelPitchView::ControlPanelPitchView(QWidget *parent): ControlPanelView("Pitch Analysis", parent) { @@ -70,6 +64,19 @@ ControlPanelPitchView::ControlPanelPitchView(QWidget *parent): // Overloaded Methods ///////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// +void ControlPanelPitchView::reset() { + hpf_checkbox_->setChecked(false); + hpf_line_->setText("100"); + + lpf_checkbox_->setChecked(true); + lpf_line_->setText("800"); + + preemphasis_checkbox_->setChecked(false); + preemphasis_line_->setText("0.9375"); + + max_pitch_frq_line_->setText("500"); + min_pitch_frq_line_->setText("50"); +} void ControlPanelPitchView::configureSlots() { connect(hpf_checkbox_, &QCheckBox::released, this, @@ -97,25 +104,10 @@ void ControlPanelPitchView::configureSlots() { &ControlPanelView::stateChanged); } -void ControlPanelPitchView::reset() { - hpf_checkbox_->setChecked(false); - hpf_line_->setText("100"); - - lpf_checkbox_->setChecked(true); - lpf_line_->setText("800"); - - preemphasis_checkbox_->setChecked(false); - preemphasis_line_->setText("0.9375"); - - max_pitch_frq_line_->setText("500"); - min_pitch_frq_line_->setText("50"); -} - /////////////////////////////////////////////////////////////////////////////// // Accessors ////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// - bool ControlPanelPitchView::getHpfEnabled() { return hpf_checkbox_->isChecked(); } diff --git a/tms_express/User_Interfaces/MainWindow.cpp b/tms_express/User_Interfaces/MainWindow.cpp index d044ec7..c96d314 100644 --- a/tms_express/User_Interfaces/MainWindow.cpp +++ b/tms_express/User_Interfaces/MainWindow.cpp @@ -12,7 +12,7 @@ #include "User_Interfaces/Audio_Waveform/AudioWaveformView.hpp" #include "User_Interfaces/MainWindow.h" #include "User_Interfaces/Control_Panels/ControlPanelPitchView.hpp" -#include "User_Interfaces/Control_Panels/ControlPanelLpcView.h" +#include "User_Interfaces/Control_Panels/ControlPanelLpcView.hpp" #include "User_Interfaces/Control_Panels/ControlPanelPostView.hpp" #include "CRC.h" @@ -207,7 +207,7 @@ void MainWindow::onOpenFile() { // Enable gain normalization by default //ui->postGainNormalizeEnable->setChecked(true); - auto input_buffer_ptr = AudioBuffer::Create(filePath.toStdString(), 8000, lpcControl->analysisWindowWidth()); + auto input_buffer_ptr = AudioBuffer::Create(filePath.toStdString(), 8000, lpcControl->getAnalysisWindowWidth()); if (input_buffer_ptr == nullptr) { qDebug() << "NULL"; @@ -439,9 +439,9 @@ void MainWindow::performLpcAnalysis() { frameTable.clear(); // Re-trigger pitch analysis if window width has changed - if (lpcControl->analysisWindowWidth() != inputBuffer.getWindowWidthMs()) { - inputBuffer.setWindowWidthMs(lpcControl->analysisWindowWidth()); - lpcBuffer.setWindowWidthMs(lpcControl->analysisWindowWidth()); + if (lpcControl->getAnalysisWindowWidth() != inputBuffer.getWindowWidthMs()) { + inputBuffer.setWindowWidthMs(lpcControl->getAnalysisWindowWidth()); + lpcBuffer.setWindowWidthMs(lpcControl->getAnalysisWindowWidth()); qDebug() << "Adjusting window width for pitch and LPC buffers"; performPitchAnalysis(); diff --git a/tms_express/User_Interfaces/MainWindow.h b/tms_express/User_Interfaces/MainWindow.h index 501fa75..f01fc18 100644 --- a/tms_express/User_Interfaces/MainWindow.h +++ b/tms_express/User_Interfaces/MainWindow.h @@ -14,12 +14,13 @@ #include "LPC_Analysis/LinearPredictor.hpp" #include "User_Interfaces/Audio_Waveform/AudioWaveformView.hpp" #include "User_Interfaces/Control_Panels/ControlPanelPitchView.hpp" -#include "User_Interfaces/Control_Panels/ControlPanelLpcView.h" +#include "User_Interfaces/Control_Panels/ControlPanelLpcView.hpp" #include "User_Interfaces/Control_Panels/ControlPanelPostView.hpp" #include #include #include +#include #include From 3229afdfebf9a7416730c55b62e17290c71faf25 Mon Sep 17 00:00:00 2001 From: Joseph Bellahcen Date: Sun, 6 Aug 2023 17:48:09 -0700 Subject: [PATCH 25/30] Refactored Command Line App --- .../User_Interfaces/CommandLineApp.cpp | 130 ++++++++++++------ tms_express/User_Interfaces/CommandLineApp.h | 40 ------ .../User_Interfaces/CommandLineApp.hpp | 100 ++++++++++++++ .../Control_Panels/ControlPanelLpcView.cpp | 4 +- .../Control_Panels/ControlPanelLpcView.hpp | 4 +- tms_express/User_Interfaces/MainWindow.cpp | 2 +- tms_express/main.cpp | 4 +- 7 files changed, 193 insertions(+), 91 deletions(-) delete mode 100644 tms_express/User_Interfaces/CommandLineApp.h create mode 100644 tms_express/User_Interfaces/CommandLineApp.hpp diff --git a/tms_express/User_Interfaces/CommandLineApp.cpp b/tms_express/User_Interfaces/CommandLineApp.cpp index 1322c75..bea76a0 100644 --- a/tms_express/User_Interfaces/CommandLineApp.cpp +++ b/tms_express/User_Interfaces/CommandLineApp.cpp @@ -1,28 +1,30 @@ -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// Class: CommandLineApp -// -// Description: The CommandLineApp wraps a CLI11 application for executing TMS Express from the command line -// -// Author: Joseph Bellahcen -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Copyright 2023 Joseph Bellahcen + +#include "User_Interfaces/CommandLineApp.hpp" + +#include "lib/CLI11.hpp" #include "Bitstream_Generation/BitstreamGenerator.hpp" #include "Bitstream_Generation/PathUtils.hpp" -#include "User_Interfaces/CommandLineApp.h" -#include "CLI11.hpp" +namespace tms_express::ui { -namespace tms_express { +/////////////////////////////////////////////////////////////////////////////// +// Initializers /////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// -/// Setup a CLI11 interface for accessing TMS Express from the command line CommandLineApp::CommandLineApp() { - encoder = add_subcommand("encode", "Convert audio file(s) to TMS5220 bitstream(s)"); - require_subcommand(1); + encoder = add_subcommand("encode", + "Converts audio file(s) to TMS5220 bitstream(s)"); + require_subcommand(1); setupEncoder(); } -/// Run TMS Express from the command line +/////////////////////////////////////////////////////////////////////////////// +// Interface ////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + int CommandLineApp::run(int argc, char** argv) { try { parse(argc, argv); @@ -32,38 +34,47 @@ int CommandLineApp::run(int argc, char** argv) { if (got_subcommand(encoder)) { // Open input and output files for inspection - auto input = PathUtils(inputPath); - auto output = PathUtils(outputPath); + auto input = PathUtils(input_path_); + auto output = PathUtils(output_path_); if (!input.exists()) { std::cerr << "Input file does not exist or is empty" << std::endl; return 1; } - if (input.isDirectory() && !bitstreamFormat && (!output.isDirectory() && output.exists())) { - std::cerr << "Batch mode requires a directory for ASCII bitstreams" << std::endl; + if (input.isDirectory() && !bitstream_format_ && + (!output.isDirectory() && output.exists())) { + std::cerr << + "Batch mode requires a directory for ASCII bitstreams" << + std::endl; + return 0; } if (!input.isDirectory() && output.isDirectory()) { - std::cerr << "Single-file encode requires a single-file output" << std::endl; + std::cerr << + "Single-file encode requires a single-file output" << std::endl; return 1; } // Extract IO paths and encode - auto bitGen = BitstreamGenerator(windowWidthMs, highpassCutoff, lowpassCutoff, - preEmphasisAlpha, bitstreamFormat, !noStopFrame, - gainShift, maxVoicedGain,maxUnvoicedGain, - useRepeatFrames, maxPitchFrq,minPitchFrq); - auto inputPaths = input.getPaths(); - auto inputFilenames = input.getFilenames(); - auto outputPathOrDirectory = output.getPaths().at(0); + auto bitstream_generator = BitstreamGenerator(analysis_window_ms_, + hpf_cutoff_, lpf_cutoff_, preemphasis_alpha_, bitstream_format_, + !no_stop_frame_, gain_shift_, max_voiced_gain_, max_unvoiced_gain_, + repeat_frames_, max_pitch_frq_, min_pitch_frq_); + + auto input_paths = input.getPaths(); + auto input_filenames = input.getFilenames(); + auto output_path_directory = output.getPaths().at(0); try { if (input.isDirectory()) { - bitGen.encodeBatch(inputPaths, inputFilenames, outputPathOrDirectory); + bitstream_generator.encodeBatch(input_paths, input_filenames, + output_path_directory); + } else { - bitGen.encode(inputPaths.at(0), inputFilenames.at(0), outputPathOrDirectory); + bitstream_generator.encode(input_paths.at(0), + input_filenames.at(0), output_path_directory); } } catch (const std::exception &e) { std::cerr << "Error: " << e.what() << std::endl; @@ -74,22 +85,53 @@ int CommandLineApp::run(int argc, char** argv) { return 0; } -/// Attach CommandLineApp class members with command line arguments and flags +/////////////////////////////////////////////////////////////////////////////// +// Helper Methods ///////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + void CommandLineApp::setupEncoder() { - encoder->add_option("-i,--input,input", inputPath, "Path to audio file")->required(); - encoder->add_option("-w,--window", windowWidthMs, "Window width/speed (ms)"); - encoder->add_option("-b,--highpass", highpassCutoff, "Highpass filter cutoff for upper tract analysis (Hz)"); - encoder->add_option("-l,--lowpass", lowpassCutoff, "Lowpass filter cutoff for lower tract analysis (Hz)"); - encoder->add_option("-a,--alpha", preEmphasisAlpha, "Pre-emphasis filter coefficient for upper tract analysis"); - encoder->add_option("-f,--format", bitstreamFormat, "Bitstream format: ascii (0), c (1), arduino (2), JSON (3)")->check(CLI::Range(0, 3)); - encoder->add_flag("-n,--no-stop-frame", noStopFrame, "Do not end bitstream with stop frame"); - encoder->add_option("-g,--gain-shift", gainShift, "Quantized gain shift"); - encoder->add_option("-v,--max-voiced-gain", maxVoicedGain, "Max voiced/vowel gain (dB)"); - encoder->add_option("-u,--max-unvoiced-gain", maxUnvoicedGain, "Max unvoiced/consonant gain (dB)"); - encoder->add_flag("-r,--use-repeat-frames", useRepeatFrames, "Compress bitstream by detecting and repeating similar frames"); - encoder->add_option("-M,--max-pitch", maxPitchFrq, "Max pitch frequency (Hz)"); - encoder->add_option("-m,--min-pitch", minPitchFrq, "Min pitch frequency (Hz)"); - encoder->add_option("-o,--output,output", outputPath, "Path to output file")->required(); + encoder->add_option("-i,--input,input", input_path_, + "Path to audio file")->required(); + + encoder->add_option("-w,--window", analysis_window_ms_, + "Window width/speed (ms)"); + + encoder->add_option("-b,--highpass", hpf_cutoff_, + "Highpass filter cutoff for upper tract analysis (Hz)"); + + encoder->add_option("-l,--lowpass", lpf_cutoff_, + "Lowpass filter cutoff for lower tract analysis (Hz)"); + + encoder->add_option("-a,--alpha", preemphasis_alpha_, + "Pre-emphasis filter coefficient for upper tract analysis"); + + encoder->add_option("-f,--format", bitstream_format_, + "Bitstream format: ascii (0), c (1), arduino (2), JSON (3)")-> + check(CLI::Range(0, 3)); + + encoder->add_flag("-n,--no-stop-frame", no_stop_frame_, + "Do not end bitstream with stop frame"); + + encoder->add_option("-g,--gain-shift", gain_shift_, + "Quantized gain shift"); + + encoder->add_option("-v,--max-voiced-gain", max_voiced_gain_, + "Max voiced/vowel gain (dB)"); + + encoder->add_option("-u,--max-unvoiced-gain", max_unvoiced_gain_, + "Max unvoiced/consonant gain (dB)"); + + encoder->add_flag("-r,--use-repeat-frames", repeat_frames_, + "Compress bitstream by detecting and repeating similar frames"); + + encoder->add_option("-M,--max-pitch", max_pitch_frq_, + "Max pitch frequency (Hz)"); + + encoder->add_option("-m,--min-pitch", min_pitch_frq_, + "Min pitch frequency (Hz)"); + + encoder->add_option("-o,--output,output", output_path_, + "Path to output file")->required(); } -}; // namespace tms_express +}; // namespace tms_express::ui diff --git a/tms_express/User_Interfaces/CommandLineApp.h b/tms_express/User_Interfaces/CommandLineApp.h deleted file mode 100644 index 870e877..0000000 --- a/tms_express/User_Interfaces/CommandLineApp.h +++ /dev/null @@ -1,40 +0,0 @@ -// Author: Joseph Bellahcen - -#ifndef TMS_EXPRESS_COMMANDLINEAPP_H -#define TMS_EXPRESS_COMMANDLINEAPP_H - -#include "Bitstream_Generation/BitstreamGenerator.hpp" -#include "CLI11.hpp" - -namespace tms_express { - -class CommandLineApp : public CLI::App { -public: - CommandLineApp(); - - int run(int argc, char** argv); - -private: - CLI::App* encoder; - - std::string inputPath; - float windowWidthMs = 25.0f; - int highpassCutoff = 1000; - int lowpassCutoff = 800; - float preEmphasisAlpha = -0.9375f; - BitstreamGenerator::EncoderStyle bitstreamFormat = BitstreamGenerator::EncoderStyle::ENCODERSTYLE_ASCII; - bool noStopFrame = false; - int gainShift = 2; - float maxVoicedGain = 37.5f; - float maxUnvoicedGain = 30.0f; - bool useRepeatFrames = false; - int maxPitchFrq = 500; - int minPitchFrq = 50; - std::string outputPath; - - void setupEncoder(); -}; - -}; // namespace tms_express - -#endif //TMS_EXPRESS_COMMANDLINEAPP_H diff --git a/tms_express/User_Interfaces/CommandLineApp.hpp b/tms_express/User_Interfaces/CommandLineApp.hpp new file mode 100644 index 0000000..ead4c51 --- /dev/null +++ b/tms_express/User_Interfaces/CommandLineApp.hpp @@ -0,0 +1,100 @@ +// Copyright 2023 Joseph Bellahcen + +#ifndef TMS_EXPRESS_USER_INTERFACES_COMMANDLINEAPP_HPP_ +#define TMS_EXPRESS_USER_INTERFACES_COMMANDLINEAPP_HPP_ + +#include + +#include "lib/CLI11.hpp" + +#include "Bitstream_Generation/BitstreamGenerator.hpp" + +namespace tms_express::ui { + +/// @brief Exposes command-line interface (CLI) for application +class CommandLineApp: public CLI::App { + public: + /////////////////////////////////////////////////////////////////////////// + // Initializers /////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @brief Creates and configures a new CLI for application + CommandLineApp(); + + /////////////////////////////////////////////////////////////////////////// + // Interface ////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @brief Runs command-line application + /// @param argc Number of command-line arguments, passed from main() + /// @param argv Command line arguments, passed from main() + /// @return Zero if exitted successfully, non-zero otherwise + int run(int argc, char** argv); + + private: + /////////////////////////////////////////////////////////////////////////// + // Helper Methods ///////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @brief Attaches command-line arguments to Encoder application + void setupEncoder(); + + /////////////////////////////////////////////////////////////////////////// + // Command-Line Applications ////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @brief Encoder application, exposed as "encode" command, which converts + /// audio files to LPC bitstreams + CLI::App* encoder; + + /////////////////////////////////////////////////////////////////////////// + // Encoder Application Members //////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @brief Path to existing input audio file + std::string input_path_; + + /// @brief Path to new output bitstream file + std::string output_path_; + + /// @brief Analysis (segmentation) window width, in milliseconds + float analysis_window_ms_ = 25.0f; + + /// @brief Highpass filter cutoff, in Hertz + int hpf_cutoff_ = 1000; + + /// @brief Lowpass filter cutoff, in Hertz + int lpf_cutoff_ = 800; + + /// @brief Pre-emphasis filter coefficient + float preemphasis_alpha_ = -0.9375f; + + /// @brief Bitstream format + BitstreamGenerator::EncoderStyle bitstream_format_ = + BitstreamGenerator::EncoderStyle::ENCODERSTYLE_ASCII; + + /// @brief true to append stop frame to end of bitstream, false otherwise + bool no_stop_frame_ = false; + + /// @brief Gain shift (offset) from gain/energy entry in coding table + int gain_shift_ = 2; + + /// @brief Max voiced (vowel) gain, in decibels + float max_voiced_gain_ = 37.5f; + + /// @brief Max unvoiced (vowel) gain, in decibels + float max_unvoiced_gain_ = 30.0f; + + /// @brief true to detect repeat Frames, false otherwise + bool repeat_frames_ = false; + + /// @brief Pitch analysis ceiling frequency, in Hertz + int max_pitch_frq_ = 500; + + /// @brief Pitch analysis floor frequency, in Hertz + int min_pitch_frq_ = 50; +}; + +}; // namespace tms_express::ui + +#endif // TMS_EXPRESS_USER_INTERFACES_COMMANDLINEAPP_HPP_ diff --git a/tms_express/User_Interfaces/Control_Panels/ControlPanelLpcView.cpp b/tms_express/User_Interfaces/Control_Panels/ControlPanelLpcView.cpp index e2ba378..018a53d 100644 --- a/tms_express/User_Interfaces/Control_Panels/ControlPanelLpcView.cpp +++ b/tms_express/User_Interfaces/Control_Panels/ControlPanelLpcView.cpp @@ -18,7 +18,7 @@ namespace tms_express::ui { ControlPanelLpcView::ControlPanelLpcView(QWidget *parent): ControlPanelView("LPC Analysis", parent) { // Initialize parameters - auto analysisWindowLabel = new QLabel("Analysis window (ms)", this); + auto analysis_window_label = new QLabel("Analysis window (ms)", this); analysis_window_line_ = new QLineEdit("25.0", this); hpf_checkbox_ = new QCheckBox("Highpass filter (Hz)", this); @@ -33,7 +33,7 @@ ControlPanelLpcView::ControlPanelLpcView(QWidget *parent): // Construct layout auto row = grid->rowCount(); - grid->addWidget(analysisWindowLabel, row, 0); + grid->addWidget(analysis_window_label, row, 0); grid->addWidget(analysis_window_line_, row++, 1); grid->addWidget(hpf_checkbox_, row, 0); diff --git a/tms_express/User_Interfaces/Control_Panels/ControlPanelLpcView.hpp b/tms_express/User_Interfaces/Control_Panels/ControlPanelLpcView.hpp index 55ecf6e..1b24db2 100644 --- a/tms_express/User_Interfaces/Control_Panels/ControlPanelLpcView.hpp +++ b/tms_express/User_Interfaces/Control_Panels/ControlPanelLpcView.hpp @@ -3,12 +3,12 @@ #ifndef TMS_EXPRESS_USER_INTERFACES_CONTROL_PANELS_CONTROLPANELLPCVIEW_HPP_ #define TMS_EXPRESS_USER_INTERFACES_CONTROL_PANELS_CONTROLPANELLPCVIEW_HPP_ -#include "User_Interfaces/Control_Panels/ControlPanelView.hpp" - #include #include #include +#include "User_Interfaces/Control_Panels/ControlPanelView.hpp" + namespace tms_express::ui { /// @brief Control Panel View for LPC analysis parameters diff --git a/tms_express/User_Interfaces/MainWindow.cpp b/tms_express/User_Interfaces/MainWindow.cpp index c96d314..d75cc26 100644 --- a/tms_express/User_Interfaces/MainWindow.cpp +++ b/tms_express/User_Interfaces/MainWindow.cpp @@ -15,7 +15,7 @@ #include "User_Interfaces/Control_Panels/ControlPanelLpcView.hpp" #include "User_Interfaces/Control_Panels/ControlPanelPostView.hpp" -#include "CRC.h" +#include "lib/CRC.h" #include #include diff --git a/tms_express/main.cpp b/tms_express/main.cpp index 163a5b5..9d53259 100644 --- a/tms_express/main.cpp +++ b/tms_express/main.cpp @@ -1,6 +1,6 @@ // Author: Joseph Bellahcen -#include "User_Interfaces/CommandLineApp.h" +#include "User_Interfaces/CommandLineApp.hpp" #include "User_Interfaces/MainWindow.h" #include @@ -17,7 +17,7 @@ int main(int argc, char **argv) { // Otherwise, use command-line interface if (argc > 1) { - auto cli = tms_express::CommandLineApp(); + auto cli = tms_express::ui::CommandLineApp(); int status = cli.run(argc, argv); exit(status); } From e2fcf75795da193091a06dcbe3d0192046ce7bab Mon Sep 17 00:00:00 2001 From: Joseph Bellahcen Date: Sun, 6 Aug 2023 19:59:02 -0700 Subject: [PATCH 26/30] Re-factored Main Window --- .../BitstreamGenerator.cpp | 10 +- tms_express/User_Interfaces/MainWindow.cpp | 629 +++++++++--------- tms_express/User_Interfaces/MainWindow.h | 116 ---- tms_express/User_Interfaces/MainWindow.hpp | 211 ++++++ tms_express/main.cpp | 7 +- 5 files changed, 553 insertions(+), 420 deletions(-) delete mode 100644 tms_express/User_Interfaces/MainWindow.h create mode 100644 tms_express/User_Interfaces/MainWindow.hpp diff --git a/tms_express/Bitstream_Generation/BitstreamGenerator.cpp b/tms_express/Bitstream_Generation/BitstreamGenerator.cpp index ca5c84e..885d007 100644 --- a/tms_express/Bitstream_Generation/BitstreamGenerator.cpp +++ b/tms_express/Bitstream_Generation/BitstreamGenerator.cpp @@ -120,8 +120,8 @@ std::vector BitstreamGenerator::generateFrames( auto sample_rate = lpc_buffer.getSampleRateHz(); // Initialize analysis objects and data structures - auto linearPredictor = LinearPredictor(); - auto pitchEstimator = PitchEstimator(sample_rate, min_pitch_hz_, + auto linear_predictor = LinearPredictor(); + auto pitch_estimator = PitchEstimator(sample_rate, min_pitch_hz_, max_pitch_hz_); auto frames = std::vector(); @@ -142,11 +142,11 @@ std::vector BitstreamGenerator::generateFrames( auto pitch_acf = tms_express::Autocorrelation(pitch_segment); // Extract LPC reflector coefficients and compute the predictor gain - auto coeffs = linearPredictor.computeCoeffs(lpc_acf); - auto gain = linearPredictor.gain(); + auto coeffs = linear_predictor.computeCoeffs(lpc_acf); + auto gain = linear_predictor.gain(); // Estimate pitch - auto pitch_period = pitchEstimator.estimatePeriod(pitch_acf); + auto pitch_period = pitch_estimator.estimatePeriod(pitch_acf); // Decide whether the segment is voiced or unvoiced auto segment_is_voiced = coeffs[0] < 0; diff --git a/tms_express/User_Interfaces/MainWindow.cpp b/tms_express/User_Interfaces/MainWindow.cpp index d75cc26..72f4ac4 100644 --- a/tms_express/User_Interfaces/MainWindow.cpp +++ b/tms_express/User_Interfaces/MainWindow.cpp @@ -1,225 +1,179 @@ -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// Class: MainWindow -// -// Description: The GUI frontend of TMS Express -// -// Author: Joseph Bellahcen -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Copyright 2023 Joseph Bellahcen -#include "Audio/AudioBuffer.hpp" -#include "Frame_Encoding/FramePostprocessor.hpp" -#include "LPC_Analysis/Autocorrelation.hpp" -#include "User_Interfaces/Audio_Waveform/AudioWaveformView.hpp" -#include "User_Interfaces/MainWindow.h" -#include "User_Interfaces/Control_Panels/ControlPanelPitchView.hpp" -#include "User_Interfaces/Control_Panels/ControlPanelLpcView.hpp" -#include "User_Interfaces/Control_Panels/ControlPanelPostView.hpp" - -#include "lib/CRC.h" +#include "User_Interfaces/MainWindow.hpp" +#include #include +#include +#include +#include #include +#include +#include + #include #include #include +#include + +#include "lib/CRC.h" + +#include "Audio/AudioBuffer.hpp" +#include "Frame_Encoding/FramePostprocessor.hpp" +#include "LPC_Analysis/Autocorrelation.hpp" +#include "User_Interfaces/Audio_Waveform/AudioWaveformView.hpp" +#include "User_Interfaces/Control_Panels/ControlPanelPitchView.hpp" +#include "User_Interfaces/Control_Panels/ControlPanelLpcView.hpp" +#include "User_Interfaces/Control_Panels/ControlPanelPostView.hpp" namespace tms_express::ui { -/// Setup the main window of the application +/////////////////////////////////////////////////////////////////////////////// +// Initializers /////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { // Set the minimum requirements for window dimensions and margins - setMinimumSize(TMS_EXPRESS_WINDOW_MIN_WIDTH, TMS_EXPRESS_WINDOW_MIN_HEIGHT); - setContentsMargins(TMS_EXPRESS_WINDOW_MARGINS, TMS_EXPRESS_WINDOW_MARGINS, TMS_EXPRESS_WINDOW_MARGINS, TMS_EXPRESS_WINDOW_MARGINS); + setMinimumSize(TE_WINDOW_MIN_WIDTH, TE_WINDOW_MIN_HEIGHT); + + setContentsMargins(TE_WINDOW_MARGINS, TE_WINDOW_MARGINS, + TE_WINDOW_MARGINS, TE_WINDOW_MARGINS); // The main widget will hold all contents of the Main Window - mainWidget = new QWidget(this); - setCentralWidget(mainWidget); + main_widget_ = new QWidget(this); + setCentralWidget(main_widget_); // The main layout separates the main widget into rows - mainLayout = new QVBoxLayout(mainWidget); + main_layout_ = new QVBoxLayout(main_widget_); - // The control panel group holds a horizontal layout, and each column is occupied by a control panel - controlPanelGroup = new QGroupBox("Control Panel", this); + // The control panel group holds a horizontal layout, and each column is + // occupied by a control panel + control_panel_group_ = new QGroupBox("Control Panel", this); - // The control panel layout must never be allowed to become smaller than its contents - controlPanelLayout = new QHBoxLayout(controlPanelGroup); - controlPanelLayout->setSizeConstraint(QLayout::SetMinimumSize); + // The control panel layout must never be allowed to become smaller than + // its contents + control_panel_layout_ = new QHBoxLayout(control_panel_group_); + control_panel_layout_->setSizeConstraint(QLayout::SetMinimumSize); - pitchControl = new ControlPanelPitchView(this); - pitchControl->configureSlots(); - pitchControl->reset(); + pitch_control_ = new ControlPanelPitchView(this); + pitch_control_->configureSlots(); + pitch_control_->reset(); - lpcControl = new ControlPanelLpcView(this); - lpcControl->configureSlots(); - lpcControl->reset(); + lpc_control_ = new ControlPanelLpcView(this); + lpc_control_->configureSlots(); + lpc_control_->reset(); - postControl = new ControlPanelPostView(this); - postControl->configureSlots(); - postControl->reset(); + post_control_ = new ControlPanelPostView(this); + post_control_->configureSlots(); + post_control_->reset(); - controlPanelLayout->addWidget(pitchControl); - controlPanelLayout->addWidget(lpcControl); - controlPanelLayout->addWidget(postControl); - mainLayout->addWidget(controlPanelGroup); + control_panel_layout_->addWidget(pitch_control_); + control_panel_layout_->addWidget(lpc_control_); + control_panel_layout_->addWidget(post_control_); + main_layout_->addWidget(control_panel_group_); // The main layouts final two rows are occupied by waveforms - inputWaveform = new AudioWaveformView("Input Signal", 750, 150, this); - lpcWaveform = new AudioWaveformView("Synthesized Signal", 750, 150, this); + input_waveform_ = new AudioWaveformView("Input Signal", 750, 150, this); + lpc_waveform_ = new AudioWaveformView("Synthesized Signal", 750, 150, this); // The waveforms may be - inputWaveform->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - lpcWaveform->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + input_waveform_->setSizePolicy(QSizePolicy::Expanding, + QSizePolicy::Expanding); + + lpc_waveform_->setSizePolicy(QSizePolicy::Expanding, + QSizePolicy::Expanding); - mainLayout->addWidget(inputWaveform); - mainLayout->addWidget(lpcWaveform); + main_layout_->addWidget(input_waveform_); + main_layout_->addWidget(lpc_waveform_); // Menu Bar - actionExport = new QAction(this); - actionExport->setText("Export"); - actionExport->setShortcut(QKeySequence("Ctrl+E")); + action_export_ = new QAction(this); + action_export_->setText("Export"); + action_export_->setShortcut(QKeySequence("Ctrl+E")); - actionOpen = new QAction(this); - actionOpen->setText("Open"); - actionOpen->setShortcut(QKeySequence("Ctrl+O")); + action_open_ = new QAction(this); + action_open_->setText("Open"); + action_open_->setShortcut(QKeySequence("Ctrl+O")); - actionSave = new QAction(this); - actionSave->setText("Save"); - actionSave->setShortcut(QKeySequence("Ctrl+S")); + action_save_ = new QAction(this); + action_save_->setText("Save"); + action_save_->setShortcut(QKeySequence("Ctrl+S")); - menuBar = new QMenuBar(this); - auto menuFile = new QMenu(menuBar); - menuFile->setTitle("File"); - setMenuBar(menuBar); + menu_bar_ = new QMenuBar(this); + auto menu_file = new QMenu(menu_bar_); + menu_file->setTitle("File"); + setMenuBar(menu_bar_); - menuBar->addAction(menuFile->menuAction()); - menuFile->addAction(actionOpen); - menuFile->addAction(actionSave); - menuFile->addAction(actionExport); + menu_bar_->addAction(menu_file->menuAction()); + menu_file->addAction(action_open_); + menu_file->addAction(action_save_); + menu_file->addAction(action_export_); player = new QMediaPlayer(this); - audioOutput = new QAudioOutput(this); + audio_output_ = new QAudioOutput(this); - inputBuffer = AudioBuffer(); - lpcBuffer = AudioBuffer(); + input_buffer_ = AudioBuffer(); + lpc_buffer_ = AudioBuffer(); - frameTable = {}; - pitchPeriodTable = {}; - pitchFrqTable = {}; + frame_table_ = {}; + pitch_period_table_ = {}; + pitch_curve_table_ = {}; configureUiSlots(); configureUiState(); } -/// Free pointers associated with UI -MainWindow::~MainWindow() { -} - -/////////////////////////////////////////////////////////////////////////////// -// UI Helpers -/////////////////////////////////////////////////////////////////////////////// - -/// Connect UI elements to member functions -void MainWindow::configureUiSlots() { - // Menu bar - connect(actionOpen, &QAction::triggered, this, &MainWindow::onOpenFile); - connect(actionSave, &QAction::triggered, this, &MainWindow::onSaveBitstream); - connect(actionExport, &QAction::triggered, this, &MainWindow::onExportAudio); - - // Control panels - connect(pitchControl, &ControlPanelPitchView::stateChangeSignal, this, &MainWindow::onPitchParamEdit); - connect(lpcControl, &ControlPanelLpcView::stateChangeSignal, this, &MainWindow::onLpcParamEdit); - connect(postControl, &ControlPanelPostView::stateChangeSignal, this, &MainWindow::onPostProcEdit); - - // Play buttons - connect(inputWaveform, &AudioWaveformView::signalPlayButtonPressed, this, &MainWindow::onInputAudioPlay); - connect(lpcWaveform, &AudioWaveformView::signalPlayButtonPressed, this, &MainWindow::onLpcAudioPlay); -} - -/// Toggle UI elements based on the state of the application -void MainWindow::configureUiState() { - // Get UI state - auto disableAudioDependentObject = (inputBuffer.empty()); - auto disableBitstreamDependentObject = frameTable.empty(); - - // Menu bar - actionSave->setDisabled(disableAudioDependentObject); - actionSave->setDisabled(disableBitstreamDependentObject); - actionExport->setDisabled(disableAudioDependentObject); - - // Control panels - pitchControl->setDisabled(disableAudioDependentObject); - lpcControl->setDisabled(disableAudioDependentObject); - postControl->setDisabled(disableBitstreamDependentObject); -} - -/// Draw the input and output signal waveforms, along with an abstract representation of their associated pitch data -void MainWindow::drawPlots() { - inputWaveform->setSamples(inputBuffer.getSamples()); - - if (!frameTable.empty()) { - auto samples = synthesizer.synthesize(frameTable); - lpcWaveform->setSamples(samples); - - auto framePitchTable = std::vector(frameTable.size()); - for (int i = 0; i < frameTable.size(); i++) - // TODO: Parameterize - framePitchTable[i] = (8000.0f / float(frameTable[i].quantizedPitch())) / float(pitchEstimator.getMaxFrq()); - - lpcWaveform->setPitchCurve(framePitchTable); - } else { - lpcWaveform->setSamples({}); - } -} - /////////////////////////////////////////////////////////////////////////////// -// UI Slots +// Qt Slots /////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// void MainWindow::onOpenFile() { - auto filePath = QFileDialog::getOpenFileName(this, "Open file", - QDir::homePath(), - "Audio Files (*.wav *.aif *.aiff *.raw *.wav *.caf *.flac);;" \ - "ASCII Bitstream (*.lpc);;" \ - "Binary Bitstream (*.bin)" - ); + auto filepath = QFileDialog::getOpenFileName(this, + "Open file", + QDir::homePath(), + "Audio Files (*.wav *.aif *.aiff *.raw *.wav *.caf *.flac);;" \ + "ASCII Bitstream (*.lpc);;" \ + "Binary Bitstream (*.bin)"); // Do nothing if user cancels request - if (filePath.isNull()) { + if (filepath.isNull()) { qDebug() << "Open file canceled"; return; } - if (!inputBuffer.empty()) { - inputBuffer = AudioBuffer(); + if (!input_buffer_.empty()) { + input_buffer_ = AudioBuffer(); } - if (!lpcBuffer.empty()) { - lpcBuffer = AudioBuffer(); + if (!lpc_buffer_.empty()) { + lpc_buffer_ = AudioBuffer(); } - frameTable.clear(); - pitchPeriodTable.clear(); - pitchFrqTable.clear(); + frame_table_.clear(); + pitch_period_table_.clear(); + pitch_curve_table_.clear(); // Import audio file - if (filePath.endsWith(".wav", Qt::CaseInsensitive)) { + if (filepath.endsWith(".wav", Qt::CaseInsensitive)) { // Enable gain normalization by default - //ui->postGainNormalizeEnable->setChecked(true); + // ui->postGainNormalizeEnable->setChecked(true); - auto input_buffer_ptr = AudioBuffer::Create(filePath.toStdString(), 8000, lpcControl->getAnalysisWindowWidth()); + auto input_buffer_ptr = AudioBuffer::Create( + filepath.toStdString(), 8000, + lpc_control_->getAnalysisWindowWidth()); if (input_buffer_ptr == nullptr) { qDebug() << "NULL"; return; } - inputBuffer = input_buffer_ptr->copy(); - lpcBuffer = input_buffer_ptr->copy(); + input_buffer_ = input_buffer_ptr->copy(); + lpc_buffer_ = input_buffer_ptr->copy(); performPitchAnalysis(); performLpcAnalysis(); - framePostprocessor = FramePostprocessor(&frameTable); + frame_postprocessor_ = FramePostprocessor(&frame_table_); performPostProc(); configureUiState(); @@ -227,12 +181,13 @@ void MainWindow::onOpenFile() { return; } - if (filePath.endsWith(".lpc", Qt::CaseInsensitive) || filePath.endsWith(".bin", Qt::CaseInsensitive)) { + if (filepath.endsWith(".lpc", Qt::CaseInsensitive) || + filepath.endsWith(".bin", Qt::CaseInsensitive)) { // Disable gain normalization to preserve original bitstream gain - //ui->postGainNormalizeEnable->setChecked(false); + // ui->postGainNormalizeEnable->setChecked(false); - performBitstreamParsing(filePath.toStdString()); - framePostprocessor = FramePostprocessor(&frameTable); + importBitstream(filepath.toStdString()); + frame_postprocessor_ = FramePostprocessor(&frame_table_); performPostProc(); configureUiState(); @@ -241,101 +196,110 @@ void MainWindow::onOpenFile() { } } -/// Save bitstream to disk void MainWindow::onSaveBitstream() { - auto filePath = QFileDialog::getSaveFileName(this, "Save bitstream", - QDir::homePath(), - "ASCII Bitstream (*.lpc);;" \ - "Binary Bitstream (*.bin)" - ); + auto filepath = QFileDialog::getSaveFileName(this, + "Save bitstream", + QDir::homePath(), + "ASCII Bitstream (*.lpc);;" \ + "Binary Bitstream (*.bin)"); - if (filePath.isNull()) { + if (filepath.isNull()) { qDebug() << "Save bitstream canceled"; return; } - exportBitstream(filePath.toStdString()); + exportBitstream(filepath.toStdString()); } /// Export synthesized bitstream to disk void MainWindow::onExportAudio() { // Display file picker - auto filePath = QFileDialog::getSaveFileName(this, "Export audio file", - QDir::homePath(), - "Audio Files (*.wav *.aif *.aiff *.raw *.wav *.caf *.flac)" - ); + auto filepath = QFileDialog::getSaveFileName(this, + "Export audio file", + QDir::homePath(), + "Audio Files (*.wav *.aif *.aiff *.raw *.wav *.caf *.flac)"); - if (filePath.isNull()) { + if (filepath.isNull()) { qDebug() << "Export audio canceled"; return; } - synthesizer.render(synthesizer.getSamples(), filePath.toStdString(), lpcBuffer.getSampleRateHz(), lpcBuffer.getWindowWidthMs()); + synthesizer_.render(synthesizer_.getSamples(), + filepath.toStdString(), lpc_buffer_.getSampleRateHz(), + lpc_buffer_.getWindowWidthMs()); } -/// Play contents of input buffer void MainWindow::onInputAudioPlay() { - if (inputBuffer.empty()) { + if (input_buffer_.empty()) { qDebug() << "Requested play, but input buffer is empty"; return; } // Generate checksum of buffer to produce unique temporary filename // - // The pre-emphasis alpha coefficient will be included in this computation, as its impact on the buffer may not - // be significant enough to modify the buffer checksum alone + // The pre-emphasis alpha coefficient will be included in this computation, + // as its impact on the buffer may not be significant enough to modify the + // buffer checksum alone char filename[31]; - auto checksum = samplesChecksum(inputBuffer.getSamples()); - snprintf(filename, 31, "tmsexpress_render_%x.wav", checksum); + auto checksum = samplesChecksum(input_buffer_.getSamples()); + snprintf(filename, sizeof(filename), "tmsexpress_render_%x.wav", checksum); // Only render audio if this particular buffer does not exist - auto tempDir = std::filesystem::temp_directory_path(); - tempDir.append(filename); - qDebug() << "Playing " << tempDir.c_str(); - inputBuffer.render(tempDir); + auto temp_dir = std::filesystem::temp_directory_path(); + temp_dir.append(filename); + qDebug() << "Playing " << temp_dir.c_str(); + input_buffer_.render(temp_dir); // Setup player and play - player->setAudioOutput(audioOutput); - player->setSource(QUrl::fromLocalFile(tempDir.c_str())); - audioOutput->setVolume(100); + player->setAudioOutput(audio_output_); + player->setSource(QUrl::fromLocalFile(temp_dir.c_str())); + audio_output_->setVolume(100); player->play(); } /// Play synthesized bitstream audio void MainWindow::onLpcAudioPlay() { - if (frameTable.empty()) { + if (frame_table_.empty()) { return; } - synthesizer.synthesize(frameTable); + synthesizer_.synthesize(frame_table_); // Generate checksum of buffer to produce unique temporary filename // - // The pre-emphasis alpha coefficient will be included in this computation, as its impact on the buffer may not - // be significant enough to modify the buffer checksum alone + // The pre-emphasis alpha coefficient will be included in this computation, + // as its impact on the buffer may not be significant enough to modify the + // buffer checksum alone char filename[35]; - uint checksum = (!lpcBuffer.empty()) ? samplesChecksum(lpcBuffer.getSamples()) : samplesChecksum(synthesizer.getSamples()); - snprintf(filename, 35, "tmsexpress_lpc_render_%x.wav", checksum); + uint checksum = (!lpc_buffer_.empty()) ? + samplesChecksum(lpc_buffer_.getSamples()) : + samplesChecksum(synthesizer_.getSamples()); + + snprintf(filename, sizeof(filename), "tmsexpress_lpc_render_%x.wav", + checksum); // Only render audio if this particular buffer does not exist - auto tempDir = std::filesystem::temp_directory_path(); - tempDir.append(filename); - qDebug() << "Playing " << tempDir.c_str(); - synthesizer.render(synthesizer.getSamples(), tempDir, lpcBuffer.getSampleRateHz(), lpcBuffer.getWindowWidthMs()); + auto temp_dir = std::filesystem::temp_directory_path(); + temp_dir.append(filename); + + qDebug() << "Playing " << temp_dir.c_str(); + + synthesizer_.render(synthesizer_.getSamples(), + temp_dir, lpc_buffer_.getSampleRateHz(), + lpc_buffer_.getWindowWidthMs()); // Setup player and play - player->setAudioOutput(audioOutput); - player->setSource(QUrl::fromLocalFile(tempDir.c_str())); - audioOutput->setVolume(100); + player->setAudioOutput(audio_output_); + player->setSource(QUrl::fromLocalFile(temp_dir.c_str())); + audio_output_->setVolume(100); player->play(); } -/// Re-perform pitch and LPC analysis when pitch controls are changed void MainWindow::onPitchParamEdit() { configureUiState(); - if (!inputBuffer.empty()) { + if (!input_buffer_.empty()) { performPitchAnalysis(); performLpcAnalysis(); @@ -343,11 +307,10 @@ void MainWindow::onPitchParamEdit() { } } -/// Re-perform LPC analysis when LPC controls are changed void MainWindow::onLpcParamEdit() { configureUiState(); - if (!lpcBuffer.empty()) { + if (!lpc_buffer_.empty()) { performLpcAnalysis(); performPostProc(); @@ -355,11 +318,10 @@ void MainWindow::onLpcParamEdit() { } } -/// Re-perform LPC analysis when bitstream controls are changed void MainWindow::onPostProcEdit() { configureUiState(); - if (!frameTable.empty()) { + if (!frame_table_.empty()) { performPostProc(); drawPlots(); @@ -367,187 +329,262 @@ void MainWindow::onPostProcEdit() { } /////////////////////////////////////////////////////////////////////////////// -// Metadata +// UI Helper Methods ////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// -/// Compute checksum of a vector of floating-point samples to uniquely identify it -/// -/// \note The checksum is used to generate a unique filename, which is stored temporarily to facilitate playback of -/// rendered audio. In addition to the samples from the vector, this checksum also incorporates the pre-emphasis -/// filter coefficients of both the input audio and synthesized bitstream buffers, as applying the small -/// pre-emphasis filter to floating point samples may not impact the checksum. -/// -/// \param samples Samples of which to compute checksum -/// \return Checksum -unsigned int MainWindow::samplesChecksum(std::vector samples) { - auto bufferSize = int(samples.size()); - auto checksumBuffer = (float *) malloc(sizeof(float) * (bufferSize + 1)); - memccpy(checksumBuffer, samples.data(), bufferSize, sizeof(float)); - checksumBuffer[bufferSize] = char(lpcControl->getPreEmphasisAlpha() + pitchControl->getPreEmphasisAlpha()); - - auto checksum = CRC::Calculate(checksumBuffer, sizeof(float), CRC::CRC_32()); - - free(checksumBuffer); - return checksum; +void MainWindow::configureUiSlots() { + // Menu bar + connect(action_open_, &QAction::triggered, this, + &MainWindow::onOpenFile); + + connect(action_save_, &QAction::triggered, this, + &MainWindow::onSaveBitstream); + + connect(action_export_, &QAction::triggered, this, + &MainWindow::onExportAudio); + + // Control panels + connect(pitch_control_, &ControlPanelPitchView::stateChangeSignal, this, + &MainWindow::onPitchParamEdit); + + connect(lpc_control_, &ControlPanelLpcView::stateChangeSignal, this, + &MainWindow::onLpcParamEdit); + + connect(post_control_, &ControlPanelPostView::stateChangeSignal, this, + &MainWindow::onPostProcEdit); + + // Play buttons + connect(input_waveform_, &AudioWaveformView::signalPlayButtonPressed, this, + &MainWindow::onInputAudioPlay); + + connect(lpc_waveform_, &AudioWaveformView::signalPlayButtonPressed, this, + &MainWindow::onLpcAudioPlay); } /////////////////////////////////////////////////////////////////////////////// -// Data Manipulation +// UI Helper Methods ////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// -/// Apply filters to input buffer and perform pitch analysis to populate the pitch tables +void MainWindow::configureUiState() { + // Get UI state + auto disableAudioDependentObject = (input_buffer_.empty()); + auto disableBitstreamDependentObject = frame_table_.empty(); + + // Menu bar + action_save_->setDisabled(disableAudioDependentObject); + action_save_->setDisabled(disableBitstreamDependentObject); + action_export_->setDisabled(disableAudioDependentObject); + + // Control panels + pitch_control_->setDisabled(disableAudioDependentObject); + lpc_control_->setDisabled(disableAudioDependentObject); + post_control_->setDisabled(disableBitstreamDependentObject); +} + +void MainWindow::drawPlots() { + input_waveform_->setSamples(input_buffer_.getSamples()); + + if (!frame_table_.empty()) { + auto samples = synthesizer_.synthesize(frame_table_); + lpc_waveform_->setSamples(samples); + + auto tmp_pitch_curve_table = std::vector(frame_table_.size()); + const auto max_pitch = static_cast(pitch_estimator_.getMaxFrq()); + + for (int i = 0; i < frame_table_.size(); i++) { + auto quantized_pitch = static_cast( + frame_table_[i].quantizedPitch()); + + tmp_pitch_curve_table[i] = + (TE_AUDIO_SAMPLE_RATE / quantized_pitch) / max_pitch; + } + + lpc_waveform_->setPitchCurve(tmp_pitch_curve_table); + + } else { + lpc_waveform_->setSamples({}); + } +} + +/////////////////////////////////////////////////////////////////////////// +// LPC Routines /////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////// + void MainWindow::performPitchAnalysis() { // Clear tables - inputBuffer.reset(); - pitchPeriodTable.clear(); - pitchFrqTable.clear(); + input_buffer_.reset(); + pitch_period_table_.clear(); + pitch_curve_table_.clear(); // Pre-process - if (pitchControl->getHpfEnabled()) { - filter.applyHighpass(inputBuffer, pitchControl->getHpfCutoff()); + if (pitch_control_->getHpfEnabled()) { + filter_.applyHighpass(input_buffer_, pitch_control_->getHpfCutoff()); } - if (pitchControl->getLpfEnabled()) { - filter.applyLowpass(inputBuffer, pitchControl->getLpfCutoff()); + if (pitch_control_->getLpfEnabled()) { + filter_.applyLowpass(input_buffer_, pitch_control_->getLpfCutoff()); } - if (pitchControl->getPreEmphasisEnabled()) { - filter.applyPreEmphasis(inputBuffer, pitchControl->getPreEmphasisAlpha()); + if (pitch_control_->getPreEmphasisEnabled()) { + filter_.applyPreEmphasis(input_buffer_, + pitch_control_->getPreEmphasisAlpha()); } - pitchEstimator.setMaxPeriod(pitchControl->getMinPitchFrq()); - pitchEstimator.setMinPeriod(pitchControl->getMaxPitchFrq()); + pitch_estimator_.setMaxPeriod(pitch_control_->getMinPitchFrq()); + pitch_estimator_.setMinPeriod(pitch_control_->getMaxPitchFrq()); - for (const auto &segment : inputBuffer.getAllSegments()) { + const auto max_pitch = static_cast(pitch_estimator_.getMaxFrq()); + + for (const auto &segment : input_buffer_.getAllSegments()) { auto acf = tms_express::Autocorrelation(segment); - auto pitchPeriod = pitchEstimator.estimatePeriod(acf); - // TODO: Parameterize - auto pitchFrq = pitchEstimator.estimateFrequency(acf) / float(pitchEstimator.getMaxFrq()); + auto period = pitch_estimator_.estimatePeriod(acf); + auto frq = pitch_estimator_.estimateFrequency(acf) / max_pitch; - pitchPeriodTable.push_back(pitchPeriod); - pitchFrqTable.push_back(pitchFrq); + pitch_period_table_.push_back(period); + pitch_curve_table_.push_back(frq); } } -/// Apply filters to input buffer and perform LPC analysis to populate the frame table -/// -/// \note This function may re-trigger pitch analysis if the window width has been modified, as both the pitch and -/// frame tables must share segment boundaries void MainWindow::performLpcAnalysis() { // Clear tables - lpcBuffer.reset(); - frameTable.clear(); + lpc_buffer_.reset(); + frame_table_.clear(); // Re-trigger pitch analysis if window width has changed - if (lpcControl->getAnalysisWindowWidth() != inputBuffer.getWindowWidthMs()) { - inputBuffer.setWindowWidthMs(lpcControl->getAnalysisWindowWidth()); - lpcBuffer.setWindowWidthMs(lpcControl->getAnalysisWindowWidth()); + if (lpc_control_->getAnalysisWindowWidth() != + input_buffer_.getWindowWidthMs()) { + // + input_buffer_.setWindowWidthMs(lpc_control_->getAnalysisWindowWidth()); + lpc_buffer_.setWindowWidthMs(lpc_control_->getAnalysisWindowWidth()); qDebug() << "Adjusting window width for pitch and LPC buffers"; performPitchAnalysis(); } // Pre-process - if (lpcControl->getHpfEnabled()) { + if (lpc_control_->getHpfEnabled()) { qDebug() << "HPF"; - filter.applyHighpass(lpcBuffer, lpcControl->getHpfCutoff()); + filter_.applyHighpass(lpc_buffer_, lpc_control_->getHpfCutoff()); } - if (lpcControl->getLpfEnabled()) { + if (lpc_control_->getLpfEnabled()) { qDebug() << "LPF"; - filter.applyLowpass(lpcBuffer, lpcControl->getLpfCutoff()); + filter_.applyLowpass(lpc_buffer_, lpc_control_->getLpfCutoff()); } - if (lpcControl->getPreEmphasisEnabled()) { + if (lpc_control_->getPreEmphasisEnabled()) { qDebug() << "PEF"; - qDebug() << (lpcBuffer.empty()); - filter.applyPreEmphasis(lpcBuffer, lpcControl->getPreEmphasisAlpha()); + qDebug() << (lpc_buffer_.empty()); + filter_.applyPreEmphasis(lpc_buffer_, + lpc_control_->getPreEmphasisAlpha()); } - for (int i = 0; i < lpcBuffer.getNSegments(); i++) { - auto segment = lpcBuffer.getSegment(i); + for (int i = 0; i < lpc_buffer_.getNSegments(); i++) { + auto segment = lpc_buffer_.getSegment(i); auto acf = tms_express::Autocorrelation(segment); - auto coeffs = linearPredictor.computeCoeffs(acf); - auto gain = linearPredictor.gain(); + auto coeffs = linear_predictor_.computeCoeffs(acf); + auto gain = linear_predictor_.gain(); - auto pitchPeriod = pitchPeriodTable[i]; - auto isVoiced = coeffs[0] < 0; + auto period = pitch_period_table_[i]; + auto is_voiced = coeffs[0] < 0; - frameTable.emplace_back(pitchPeriod, isVoiced, gain, coeffs); + frame_table_.emplace_back(period, is_voiced, gain, coeffs); } - framePostprocessor = FramePostprocessor(&frameTable); + frame_postprocessor_ = FramePostprocessor(&frame_table_); } -/// Perform post-processing on and synthesize bitstream from frame table void MainWindow::performPostProc() { // Clear tables - framePostprocessor.reset(); + frame_postprocessor_.reset(); // Re-configure post-processor - framePostprocessor.setMaxUnvoicedGainDB(postControl->getMaxUnvoicedGain()); - framePostprocessor.setMaxVoicedGainDB(postControl->getMaxVoicedGain()); + frame_postprocessor_.setMaxUnvoicedGainDB( + post_control_->getMaxUnvoicedGain()); + + frame_postprocessor_.setMaxVoicedGainDB(post_control_->getMaxVoicedGain()); - if (postControl->getGainNormalizationEnabled()) { - framePostprocessor.normalizeGain(); + if (post_control_->getGainNormalizationEnabled()) { + frame_postprocessor_.normalizeGain(); } // Perform either a pitch shift or a fixed-pitch offset - if (postControl->getPitchShiftEnabled()) { - framePostprocessor.shiftPitch(postControl->getPitchShift()); + if (post_control_->getPitchShiftEnabled()) { + frame_postprocessor_.shiftPitch(post_control_->getPitchShift()); - } else if (postControl->getPitchOverrideEnabled()) { - framePostprocessor.overridePitch(postControl->getPitchOverride()); + } else if (post_control_->getPitchOverrideEnabled()) { + frame_postprocessor_.overridePitch(post_control_->getPitchOverride()); } - if (postControl->getRepeatFramesEnabled()) { - auto nRepeatFrames = framePostprocessor.detectRepeatFrames(); + if (post_control_->getRepeatFramesEnabled()) { + auto nRepeatFrames = frame_postprocessor_.detectRepeatFrames(); qDebug() << "Detected " << nRepeatFrames << " repeat frames"; } - if (postControl->getGainShiftEnabled()) { - framePostprocessor.shiftGain(postControl->getGainShift()); + if (post_control_->getGainShiftEnabled()) { + frame_postprocessor_.shiftGain(post_control_->getGainShift()); } - synthesizer.synthesize(frameTable); + synthesizer_.synthesize(frame_table_); } -/// Import bitstream file from the disk and populate the frame table -void MainWindow::performBitstreamParsing(const std::string &path) { +void MainWindow::importBitstream(const std::string &path) { // Determine file extension - auto filePath = QString::fromStdString(path); - auto frameEncoder = FrameEncoder(); + auto filepath = QString::fromStdString(path); + auto frame_encoder = FrameEncoder(); - if (filePath.endsWith(".lpc")) { - auto frame_count = frameEncoder.importASCIIFromFile(path); + if (filepath.endsWith(".lpc")) { + auto frame_count = frame_encoder.importASCIIFromFile(path); } else { - // TODO: Binary parsing return; } - frameTable = frameEncoder.getFrameTable(); + frame_table_ = frame_encoder.getFrameTable(); } void MainWindow::exportBitstream(const std::string& path) { - auto filePath = QString::fromStdString(path); - auto frameEncoder = FrameEncoder(frameTable, true); + auto filepath = QString::fromStdString(path); + auto frame_encoder = FrameEncoder(frame_table_, true); - if (filePath.endsWith(".lpc")) { - auto hex = frameEncoder.toHex(); + if (filepath.endsWith(".lpc")) { + auto hex = frame_encoder.toHex(); std::ofstream lpcOut; lpcOut.open(path); lpcOut << hex; lpcOut.close(); - } else if (filePath.endsWith(".bin")) { - auto bin = frameEncoder.toBytes(); + } else if (filepath.endsWith(".bin")) { + auto bin = frame_encoder.toBytes(); std::ofstream binOut(path, std::ios::out | std::ios::binary); - binOut.write((char *)(bin.data()), long(bin.size())); + binOut.write( + reinterpret_cast(bin.data()), + static_cast(bin.size())); } } -}; // namespace tms_express +/////////////////////////////////////////////////////////////////////////////// +// Helper Methods ///////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +int MainWindow::samplesChecksum(std::vector samples) { + auto buffer_size = samples.size(); + + auto checksum_buffer = new float[buffer_size + 1]; + memccpy(checksum_buffer, samples.data(), buffer_size, sizeof(float)); + + checksum_buffer[buffer_size] = + static_cast(lpc_control_->getPreEmphasisAlpha() + + pitch_control_->getPreEmphasisAlpha()); + + auto checksum = CRC::Calculate(checksum_buffer, sizeof(float), + CRC::CRC_32()); + + delete[] checksum_buffer; + return checksum; +} + +}; // namespace tms_express::ui diff --git a/tms_express/User_Interfaces/MainWindow.h b/tms_express/User_Interfaces/MainWindow.h deleted file mode 100644 index f01fc18..0000000 --- a/tms_express/User_Interfaces/MainWindow.h +++ /dev/null @@ -1,116 +0,0 @@ -// Author: Joseph Bellahcen - -#ifndef TMS_EXPRESS_MAINWINDOW_H -#define TMS_EXPRESS_MAINWINDOW_H - -#include "Audio/AudioBuffer.hpp" -#include "Audio/AudioFilter.hpp" -#include "Bitstream_Generation/BitstreamGenerator.hpp" -#include "Frame_Encoding/Frame.hpp" -#include "Frame_Encoding/FrameEncoder.hpp" -#include "Frame_Encoding/FramePostprocessor.hpp" -#include "Frame_Encoding/Synthesizer.hpp" -#include "LPC_Analysis/PitchEstimator.hpp" -#include "LPC_Analysis/LinearPredictor.hpp" -#include "User_Interfaces/Audio_Waveform/AudioWaveformView.hpp" -#include "User_Interfaces/Control_Panels/ControlPanelPitchView.hpp" -#include "User_Interfaces/Control_Panels/ControlPanelLpcView.hpp" -#include "User_Interfaces/Control_Panels/ControlPanelPostView.hpp" - -#include -#include -#include -#include - -#include - -#define TMS_EXPRESS_WINDOW_MIN_WIDTH 1000 -#define TMS_EXPRESS_WINDOW_MIN_HEIGHT 800 -#define TMS_EXPRESS_WINDOW_MARGINS 5 -#define TMS_EXPRESS_AUDIO_SAMPLE_RATE 8000 - -namespace tms_express::ui { - -class MainWindow : public QMainWindow -{ -Q_OBJECT - -public: - explicit MainWindow(QWidget *parent = nullptr); - ~MainWindow() override; - -public slots: - void onOpenFile(); - void onSaveBitstream(); - void onExportAudio(); - - // Play buttons - void onInputAudioPlay(); - void onLpcAudioPlay(); - - // Control panels - void onPitchParamEdit(); - void onLpcParamEdit(); - void onPostProcEdit(); - -private: - // Layouts - QWidget *mainWidget; - QVBoxLayout *mainLayout; - QGroupBox *controlPanelGroup; - QHBoxLayout *controlPanelLayout; - - // Control panels - ControlPanelPitchView *pitchControl; - ControlPanelLpcView *lpcControl; - ControlPanelPostView *postControl; - - // Menu bar - QMenuBar *menuBar; - QAction *actionExport; - QAction *actionOpen; - QAction *actionSave; - - // Multimedia - QMediaPlayer *player; - QAudioOutput *audioOutput; - - // Custom Qt widgets - AudioWaveformView *inputWaveform; - AudioWaveformView *lpcWaveform; - - // Audio buffers - AudioBuffer inputBuffer; - AudioBuffer lpcBuffer; - - // Data tables - std::vector frameTable; - std::vector pitchPeriodTable; - std::vector pitchFrqTable; - - // Analysis objects - Synthesizer synthesizer = Synthesizer(); - AudioFilter filter = AudioFilter(); - PitchEstimator pitchEstimator = PitchEstimator(TMS_EXPRESS_AUDIO_SAMPLE_RATE); - LinearPredictor linearPredictor = LinearPredictor(); - FramePostprocessor framePostprocessor = FramePostprocessor(&frameTable); - - // UI helpers - void configureUiSlots(); - void configureUiState(); - void drawPlots(); - - // Data manipulation - void performBitstreamParsing(const std::string& path); - void performPitchAnalysis(); - void performLpcAnalysis(); - void performPostProc(); - void exportBitstream(const std::string& path); - - // Metadata - unsigned int samplesChecksum(std::vector samples); -}; - -}; // namespace tms_express - -#endif //TMS_EXPRESS_MAINWINDOW_H diff --git a/tms_express/User_Interfaces/MainWindow.hpp b/tms_express/User_Interfaces/MainWindow.hpp new file mode 100644 index 0000000..844bed7 --- /dev/null +++ b/tms_express/User_Interfaces/MainWindow.hpp @@ -0,0 +1,211 @@ +// Copyright 2023 Joseph Bellahcen + +#ifndef TMS_EXPRESS_USER_INTERFACES_MAINWINDOW_HPP_ +#define TMS_EXPRESS_USER_INTERFACES_MAINWINDOW_HPP_ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include "Audio/AudioBuffer.hpp" +#include "Audio/AudioFilter.hpp" +#include "Bitstream_Generation/BitstreamGenerator.hpp" +#include "Frame_Encoding/Frame.hpp" +#include "Frame_Encoding/FrameEncoder.hpp" +#include "Frame_Encoding/FramePostprocessor.hpp" +#include "Frame_Encoding/Synthesizer.hpp" +#include "LPC_Analysis/PitchEstimator.hpp" +#include "LPC_Analysis/LinearPredictor.hpp" +#include "User_Interfaces/Audio_Waveform/AudioWaveformView.hpp" +#include "User_Interfaces/Control_Panels/ControlPanelPitchView.hpp" +#include "User_Interfaces/Control_Panels/ControlPanelLpcView.hpp" +#include "User_Interfaces/Control_Panels/ControlPanelPostView.hpp" + +#define TE_WINDOW_MIN_WIDTH 1000 +#define TE_WINDOW_MIN_HEIGHT 800 +#define TE_WINDOW_MARGINS 5 +#define TE_AUDIO_SAMPLE_RATE 8000 + +namespace tms_express::ui { + +/// @brief GUI frontend for application +class MainWindow : public QMainWindow { + Q_OBJECT + + public: + /////////////////////////////////////////////////////////////////////////// + // Initializers /////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @brief Creates and sets-up a new GUI window for application + /// @param parent Parent Qt widget + explicit MainWindow(QWidget *parent = nullptr); + + public slots: + /////////////////////////////////////////////////////////////////////////// + // Qt Slots /////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @brief Triggers audio import, pre-processing, lpc & pitch analysis, and + /// post-processing + void onOpenFile(); + + /// @brief Triggers bitstream export + void onSaveBitstream(); + + /// @brief Triggers synthesized audio export + void onExportAudio(); + + /// @brief Triggers input audio file playback + void onInputAudioPlay(); + + /// @brief Triggers synthesized audio playback + void onLpcAudioPlay(); + + /// @brief Triggers pitch & LPC analysis and post-processing + void onPitchParamEdit(); + + /// @brief Triggers LPC analysis and post-processing + void onLpcParamEdit(); + + /// @brief Triggers post-processing + void onPostProcEdit(); + + private: + /////////////////////////////////////////////////////////////////////////// + // UI Helper Methods ////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @brief Assigns Qt slots and signals to UI elements + /// @note This function must only be called once + void configureUiSlots(); + + /// @brief Updates the state of all UI elements, including Control Panel + /// and Waveform Views + void configureUiState(); + + /// @brief Re-draws Waveform View plots + void drawPlots(); + + /////////////////////////////////////////////////////////////////////////// + // LPC Routines /////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @brief Applies pre-processing to audio buffer and perform pitch analysis + /// to populate pitch curve table + void performPitchAnalysis(); + + /// @brief Applies pre-processing to LPC buffer and perform LPC analysis + /// to populate Frame table + /// @note This function may trigger a call to + /// MainWindow::performPitchAnalysis() if the analysis/segmentation + /// window width has been changed since pitch analysis was last + /// performed + void performLpcAnalysis(); + + /// @brief Applies post-processing to Frame table + void performPostProc(); + + /////////////////////////////////////////////////////////////////////////// + // Bitstream I/O ////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @brief Imports bitstream from disk and populates Frame table + /// @param path Path to existing bitstream file + void importBitstream(const std::string& path); + + /// @brief Exports Frame table to disk as bitstream file + /// @param path Path to new bitstream file + void exportBitstream(const std::string& path); + + /////////////////////////////////////////////////////////////////////////// + // Helper Methods ///////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /// @brief Computes checksum of vector of floating-point samples + /// @param samples Samples to uniquely identify via checksum + /// @return Checksum of vector + /// @details The checksum is used to generate a unique filename, which is + /// stored temporarily to facilitate playback of audio. In + /// addition to the samples from the vector, this checksum also + /// incorporates the pre-emphasis filter_ coefficients of both + /// the audio and LPC buffers, as applying the pre-emphasis + /// filter_ to floating point samples may not impact the checksum + int samplesChecksum(std::vector samples); + + /////////////////////////////////////////////////////////////////////////// + // Qt Layout Members ////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + QWidget *main_widget_; + QVBoxLayout *main_layout_; + QGroupBox *control_panel_group_; + QHBoxLayout *control_panel_layout_; + + /////////////////////////////////////////////////////////////////////////// + // Qt Menu Bar Members //////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + QMenuBar *menu_bar_; + QAction *action_export_; + QAction *action_open_; + QAction *action_save_; + + /////////////////////////////////////////////////////////////////////////// + // Qt Multimedia Members ////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + QMediaPlayer *player; + QAudioOutput *audio_output_; + + /////////////////////////////////////////////////////////////////////////// + // Control Panel View Members ///////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + ControlPanelPitchView *pitch_control_; + ControlPanelLpcView *lpc_control_; + ControlPanelPostView *post_control_; + + /////////////////////////////////////////////////////////////////////////// + // Audio Waveform View Members //////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + AudioWaveformView *input_waveform_; + AudioWaveformView *lpc_waveform_; + + /////////////////////////////////////////////////////////////////////////// + // Audio Buffer Members /////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + AudioBuffer input_buffer_; + AudioBuffer lpc_buffer_; + + /////////////////////////////////////////////////////////////////////////// + // Data Tables //////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + std::vector frame_table_; + std::vector pitch_period_table_; + std::vector pitch_curve_table_; + + /////////////////////////////////////////////////////////////////////////// + // LPC Analysis Object Members //////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + Synthesizer synthesizer_ = Synthesizer(); + AudioFilter filter_ = AudioFilter(); + PitchEstimator pitch_estimator_ = PitchEstimator(TE_AUDIO_SAMPLE_RATE); + LinearPredictor linear_predictor_ = LinearPredictor(); + FramePostprocessor frame_postprocessor_ = FramePostprocessor(&frame_table_); +}; + +}; // namespace tms_express::ui + +#endif // TMS_EXPRESS_USER_INTERFACES_MAINWINDOW_HPP_ diff --git a/tms_express/main.cpp b/tms_express/main.cpp index 9d53259..0566f9a 100644 --- a/tms_express/main.cpp +++ b/tms_express/main.cpp @@ -1,9 +1,10 @@ -// Author: Joseph Bellahcen +// Copyright 2023 Joseph Bellahcen -#include "User_Interfaces/CommandLineApp.hpp" -#include "User_Interfaces/MainWindow.h" #include +#include "User_Interfaces/CommandLineApp.hpp" +#include "User_Interfaces/MainWindow.hpp" + int main(int argc, char **argv) { // If no arguments are passed, launch GUI if (argc == 1) { From 11bdd582b9151eca8e112ca1e75c1704074488bd Mon Sep 17 00:00:00 2001 From: Joseph Bellahcen Date: Sun, 6 Aug 2023 20:15:38 -0700 Subject: [PATCH 27/30] Re-factored tests --- test/AutocorrelatorTests.cpp | 41 ++++++----- test/FrameEncoderTests.cpp | 132 +++++++++++++++++++---------------- test/FrameTests.cpp | 111 ++++++++++++++--------------- 3 files changed, 145 insertions(+), 139 deletions(-) diff --git a/test/AutocorrelatorTests.cpp b/test/AutocorrelatorTests.cpp index dcd0888..98b3b2c 100644 --- a/test/AutocorrelatorTests.cpp +++ b/test/AutocorrelatorTests.cpp @@ -1,20 +1,26 @@ -// -// Created by Joseph Bellahcen on 6/1/22. -// +// Copyright 2023 Joseph Bellahcen + +#include -#include "LPC_Analysis/Autocorrelation.hpp" #include #include -#include #include +#include "LPC_Analysis/Autocorrelation.hpp" + namespace tms_express { -// Compute the autocorrelation of a decaying cosine signal with amplitude 20 and period 50 +/// @brief Produces test subject, which is the autocorrelation of a decaying +/// cosine signal with amplitude 20 and period 50 +/// @return Test subject std::vector acfTestSubject() { auto signal = std::vector(); for (int i = 0; i < 200; i ++) { - float sample = 20.0f * cosf(2.0f * float(M_PI) * float(i) / 50.0f) * expf(-0.02f * float(i)); + float sample = 20.0f * + cosf(2.0f * static_cast(M_PI) * + static_cast(i) / 50.0f) * + expf(-0.02f * static_cast(i)); + signal.push_back(sample); } @@ -22,27 +28,24 @@ std::vector acfTestSubject() { return acf; } -// The autocorrelation should be maximized at time zero, or pitch detection will fail TEST(AutocorrelatorTests, AutocorrelationIsMaxAtIndexZero) { auto acf = acfTestSubject(); - auto maxElement = std::max_element(acf.begin(), acf.end()); - auto maxIdx = std::distance(acf.begin(), maxElement); - EXPECT_EQ(maxIdx, 0); + auto max_element = std::max_element(acf.begin(), acf.end()); + auto max_idx = std::distance(acf.begin(), max_element); + EXPECT_EQ(max_idx, 0); } -// For a periodic signal, the autocorrelation should have its second-largest local max at the period. This property -// is the basis of many pitch estimation algorithms TEST(AutocorrelatorTests, AutocorrelationHasLocalMaxAtOriginalSignalPeriod) { auto acf = acfTestSubject(); - auto maxElement = std::max_element(acf.begin(), acf.end()); - auto maxIdx = std::distance(acf.begin(), maxElement); + auto max_element = std::max_element(acf.begin(), acf.end()); + auto max_idx = std::distance(acf.begin(), max_element); - auto minElement = std::min_element(acf.begin(), acf.end()); - auto nextMaxElement = std::max_element(minElement, acf.end()); - auto periodIdx = std::distance(acf.begin(), nextMaxElement); - EXPECT_NEAR(periodIdx, 50, 2); + auto min_element = std::min_element(acf.begin(), acf.end()); + auto next_max_element = std::max_element(min_element, acf.end()); + auto period_idx = std::distance(acf.begin(), next_max_element); + EXPECT_NEAR(period_idx, 50, 2); } }; // namespace tms_express diff --git a/test/FrameEncoderTests.cpp b/test/FrameEncoderTests.cpp index aadb5c5..ae59c6b 100644 --- a/test/FrameEncoderTests.cpp +++ b/test/FrameEncoderTests.cpp @@ -1,128 +1,136 @@ -#include "Frame_Encoding/Frame.hpp" -#include "Frame_Encoding/FrameEncoder.hpp" +// Copyright 2023 Joseph Bellahcen #include -#include #include +#include "Frame_Encoding/Frame.hpp" +#include "Frame_Encoding/FrameEncoder.hpp" + namespace tms_express { -// The hex stream should, at minimum, contain a StopFrame TEST(FrameEncoderTests, StopFrame) { - auto frameEncoder = FrameEncoder(); + auto frame_encoder = FrameEncoder(); - auto hex = frameEncoder.toHex(); + auto hex = frame_encoder.toHex(); EXPECT_EQ(hex, "0f"); } TEST(FrameEncoderTests, AsciiStopFrame) { - auto frameEncoder = FrameEncoder(); + auto frame_encoder = FrameEncoder(); - frameEncoder.importASCIIFromString("0x0f"); + frame_encoder.importASCIIFromString("0x0f"); - auto hex = frameEncoder.toHex(); + auto hex = frame_encoder.toHex(); EXPECT_EQ(hex, "0f"); } -// A silent frame will result in four zero bits -// -// Expected: silent frame, stop frame -// 0000 1111 -// 00001111 -> 11110000 -// f0 TEST(FrameEncoderTests, SilentFrame) { - auto frameEncoder = FrameEncoder(); - auto silentFrame = Frame(0, false, 0.0f, std::vector()); + // A silent frame will result in four zero bits + // + // Expected: silent frame, stop frame + // 0000 1111 + // 00001111 -> 11110000 + // f0 - frameEncoder.append(silentFrame); + auto frame_encoder = FrameEncoder(); + const auto silent_frame = Frame(0, false, 0.0f, {}); - auto hex = frameEncoder.toHex(); + frame_encoder.append(silent_frame); + + auto hex = frame_encoder.toHex(); EXPECT_EQ(hex, "f0"); } TEST(FrameEncoderTests, AsciiSilentFrame) { - auto frameEncoder = FrameEncoder(); - frameEncoder.importASCIIFromString("0xf0"); + auto frame_encoder = FrameEncoder(); + frame_encoder.importASCIIFromString("0xf0"); - auto hex = frameEncoder.toHex(); + auto hex = frame_encoder.toHex(); EXPECT_EQ(hex, "f0"); } -// A voiced Frame will produce 50 data bits. The output will also contain 4 stop bits. The Frame is "flattened" into a -// binary string representing its contents, which is then sliced into binary. Each byte is then reversed, as the TMS5100 -// Voice Synthesis Memory units which normally feed data into the TMS5220 send byte-wise data LSB first TEST(FrameEncoderTests, VoicedFrame) { - auto voicedFrame = Frame(38, true, 56.850773, - {-0.753234, 0.939525, -0.342255, -0.172317, 0.108887, 0.679660, 0.056874, 0.433271, - -0.220355, 0.17028}); + // A voiced Frame will produce 50 data bits. The output will also contain 4 + // stop bits. The Frame is "flattened" into a binary string representing + // its contents, which is then sliced into binary. Each byte is then + // reversed, as the TMS5100 Voice Synthesis Memory units which normally + // feed data into the TMS5220 send byte-wise data LSB first + + auto voiced_frame = Frame(38, true, 56.850773, + {-0.753234, 0.939525, -0.342255, -0.172317, 0.108887, 0.679660, + 0.056874, 0.433271, -0.220355, 0.17028}); - auto frameEncoder = FrameEncoder(); - frameEncoder.append(voicedFrame); + auto frame_encoder = FrameEncoder(); + frame_encoder.append(voiced_frame); - auto bin = frameEncoder.toHex(); + auto bin = frame_encoder.toHex(); EXPECT_EQ(bin, "c8,88,4f,25,ce,ab,3c"); } TEST(FrameEncoderTests, AsciiVoicedFrame) { - auto frameEncoder = FrameEncoder(); - frameEncoder.importASCIIFromString("0xc8,0x88,0x4f,0x25,0xce,0xab,0x3c"); + auto frame_encoder = FrameEncoder(); + frame_encoder.importASCIIFromString("0xc8,0x88,0x4f,0x25,0xce,0xab,0x3c"); - auto bin = frameEncoder.toHex(); + auto bin = frame_encoder.toHex(); EXPECT_EQ(bin, "c8,88,4f,25,ce,ab,3c"); } -// An unvoiced Frame will produce 29 data bits. The output will also contain 4 stop bits. TEST(FrameEncoderTests, UnvoicedFrame) { - auto unVoicedFrame = Frame(38, false, 56.850773, - {-0.753234, 0.939525, -0.342255, -0.172317, 0.108887, 0.679660, 0.056874, 0.433271, - -0.220355, 0.17028}); + // An unvoiced Frame will produce 29 data bits. The output will also + // contain 4 stop bits. + + auto unvoiced_frame = Frame(38, false, 56.850773, + {-0.753234, 0.939525, -0.342255, -0.172317, 0.108887, 0.679660, + 0.056874, 0.433271, -0.220355, 0.17028}); - auto frameEncoder = FrameEncoder(); - frameEncoder.append(unVoicedFrame); + auto frame_encoder = FrameEncoder(); + frame_encoder.append(unvoiced_frame); - auto bin = frameEncoder.toHex(); + auto bin = frame_encoder.toHex(); EXPECT_EQ(bin, "08,88,4f,e5,01"); } TEST(FrameEncoderTests, AsciiUnvoicedFrame) { - auto frameEncoder = FrameEncoder(); - frameEncoder.importASCIIFromString("0x08,0x88,0x4f,0xe5,0x01"); + auto frame_encoder = FrameEncoder(); + frame_encoder.importASCIIFromString("0x08,0x88,0x4f,0xe5,0x01"); - auto bin = frameEncoder.toHex(); + auto bin = frame_encoder.toHex(); EXPECT_EQ(bin, "08,88,4f,e5,01"); } TEST(FrameEncoderTests, MixtureOfFrames) { auto frames = std::vector( - { - Frame(0, false, 0, {-0.753234, 0.939525, -0.342255, -0.172317, 0.108887, 0.679660, 0.056874, 0.433271, - -0.220355, 0.17028}), + { + Frame(0, false, 0, {-0.753234, 0.939525, -0.342255, -0.172317, + 0.108887, 0.679660, 0.056874, 0.433271, -0.220355, + 0.17028}), - Frame(38, true, 142.06, {-0.653234, 0.139525, 0.342255, -0.172317, 0.108887, 0.679660, 0.056874, 0.433271, - -0.220355, 0.17028}), + Frame(38, true, 142.06, {-0.653234, 0.139525, 0.342255, -0.172317, + 0.108887, 0.679660, 0.056874, 0.433271, -0.220355, + 0.17028}), - Frame(38, true, 142.06, {-0.653234, 0.139525, 0.342255, -0.172317, 0.108887, 0.679660, 0.056874, 0.433271, - -0.220355, 0.17028}), + Frame(38, true, 142.06, {-0.653234, 0.139525, 0.342255, -0.172317, + 0.108887, 0.679660, 0.056874, 0.433271, -0.220355, + 0.17028}), - Frame(38, false, 56.850773, {-0.753234, 0.939525, -0.342255, -0.172317, 0.108887, 0.679660, 0.056874, 0.433271, - -0.220355, 0.17028}) - } - ); + Frame(38, false, 56.850773, {-0.753234, 0.939525, -0.342255, + -0.172317, 0.108887, 0.679660, 0.056874, 0.433271, + -0.220355, 0.17028}) + }); + auto frame_encoder = FrameEncoder(frames); - auto frameEncoder = FrameEncoder(frames); - - auto bin = frameEncoder.toHex(); + auto bin = frame_encoder.toHex(); EXPECT_EQ(bin, "c0,8c,a4,5b,e2,bc,0a,33,92,6e,89,f3,2a,08,88,4f,e5,01"); - } TEST(FrameEncoderTests, AsciiMixtureOfFrames) { - auto frameEncoder = FrameEncoder(); - frameEncoder.importASCIIFromString("0xc0,0x8c,0xa4,0x5b,0xe2,0xbc,0x0a,0x33,0x92,0x6e,0x89,0xf3,0x2a,0x08,0x88,0x4f,0xe5,0x01"); + auto frame_encoder = FrameEncoder(); + frame_encoder.importASCIIFromString("0xc0,0x8c,0xa4,0x5b,0xe2,0xbc,0x0a," \ + "0x33,0x92,0x6e,0x89,0xf3,0x2a,0x08,0x88,0x4f,0xe5,0x01"); - auto bin = frameEncoder.toHex(); + auto bin = frame_encoder.toHex(); EXPECT_EQ(bin, "c0,8c,a4,5b,e2,bc,0a,33,92,6e,89,f3,2a,08,88,4f,e5,01"); } diff --git a/test/FrameTests.cpp b/test/FrameTests.cpp index 6b7284f..78243d7 100644 --- a/test/FrameTests.cpp +++ b/test/FrameTests.cpp @@ -1,17 +1,20 @@ -#include "Frame_Encoding/Frame.hpp" +// Copyright 2023 Joseph Bellahcen + #include + #include +#include "Frame_Encoding/Frame.hpp" + namespace tms_express { Frame frameTestSubject() { - return Frame(38, true, 56.850773, {-0.753234, 0.939525, -0.342255, -0.172317, - 0.108887, 0.679660, 0.056874, 0.433271, - -0.220355, 0.17028}); + return Frame(38, true, 56.850773, + {-0.753234, 0.939525, -0.342255, -0.172317, 0.108887, 0.679660, + 0.056874, 0.433271, -0.220355, 0.17028}); } -// A silent frame should contain only an energy parameter -TEST(FrameTests, SilentFrame) { +TEST(FrameTests, SilentFrameContainsOnlyEnergyParameter) { auto frame = frameTestSubject(); frame.setGain(0.0f); @@ -19,110 +22,102 @@ TEST(FrameTests, SilentFrame) { EXPECT_EQ(frame.toBinary(), "0000"); } -/* -TEST(FrameTests, RepeatFrame) { - -} - */ - -// An unvoiced frame has an energy parameter, zero pitch, a repeat bit, and four reflector coefficients -TEST(FrameTests, UnvoicedFrame) { +TEST(FrameTests, UnvoicedFrameHasEnergyNoPitchRepeatBitAndCoeffs) { auto frame = frameTestSubject(); frame.setVoicing(false); - auto bin = frame.toBinary(); - // energy[4] + repeat[1] + pitch[6] + K1[5] + K2[5] + K3[4] + K3[4] = 29 bits + // energy[4] + repeat[1] + pitch[6] + + // K1[5] + K2[5] + K3[4] + K3[4] = 29 bits EXPECT_EQ(bin.size(), 29); // Energy = 38 ~= 52 @ [1] - auto energyBin = bin.substr(0, 4); - EXPECT_EQ(energyBin, "0001"); + auto energy_bin = bin.substr(0, 4); + EXPECT_EQ(energy_bin, "0001"); // Repeat = 0 - auto repeatBin = bin.substr(4, 1); - EXPECT_EQ(repeatBin, "0"); + auto repeat_bin = bin.substr(4, 1); + EXPECT_EQ(repeat_bin, "0"); // Pitch = 38 ~= 38 @ [23] // For unvoiced frames, pitch is ALWAYS b000000 - auto pitchBin = bin.substr(5, 6); - EXPECT_EQ(pitchBin, "000000"); + auto pitch_bin = bin.substr(5, 6); + EXPECT_EQ(pitch_bin, "000000"); // K1 = -0.753234 ~= -0.74058 @ [17] - auto k1Bin = bin.substr(11, 5); - EXPECT_EQ(k1Bin, "10001"); + auto k1_bin = bin.substr(11, 5); + EXPECT_EQ(k1_bin, "10001"); // K2 = 0.939525 ~= 0.92988 @ [30] - auto k2Bin = bin.substr(16, 5); - EXPECT_EQ(k2Bin, "11110"); + auto k2_bin = bin.substr(16, 5); + EXPECT_EQ(k2_bin, "11110"); // K3 = -0.342255 ~= -0.33333 @ [5] - auto k3Bin = bin.substr(21, 4); - EXPECT_EQ(k3Bin, "0101"); + auto k3_bin = bin.substr(21, 4); + EXPECT_EQ(k3_bin, "0101"); // K4 = -0.172317 ~= 0.20579 @ [4] - auto k4Bin = bin.substr(25, 4); - EXPECT_EQ(k4Bin, "0100"); + auto k4_bin = bin.substr(25, 4); + EXPECT_EQ(k4_bin, "0100"); } -// A voiced frame will have a full set of parameters -TEST(FrameTests, VoicedFrame) { +TEST(FrameTests, VoicedFrameHasFullParameterSet) { auto frame = frameTestSubject(); auto bin = frame.toBinary(); EXPECT_EQ(bin.size(), 50); // Energy = 38 ~= 52 @ [1] - auto energyBin = bin.substr(0, 4); - EXPECT_EQ(energyBin, "0001"); + auto energy_bin = bin.substr(0, 4); + EXPECT_EQ(energy_bin, "0001"); // Repeat = 0 - auto repeatBin = bin.substr(4, 1); - EXPECT_EQ(repeatBin, "0"); + auto repeat_bin = bin.substr(4, 1); + EXPECT_EQ(repeat_bin, "0"); // Pitch = 38 ~= 38 @ [24] - auto pitchBin = bin.substr(5, 6); - EXPECT_EQ(pitchBin, "011000"); + auto pitch_bin = bin.substr(5, 6); + EXPECT_EQ(pitch_bin, "011000"); // K1 = -0.753234 ~= -0.74058 @ [17] - auto k1Bin = bin.substr(11, 5); - EXPECT_EQ(k1Bin, "10001"); + auto k1_bin = bin.substr(11, 5); + EXPECT_EQ(k1_bin, "10001"); // K2 = 0.939525 ~= 0.92988 @ [30] - auto k2Bin = bin.substr(16, 5); - EXPECT_EQ(k2Bin, "11110"); + auto k2_bin = bin.substr(16, 5); + EXPECT_EQ(k2_bin, "11110"); // K3 = -0.342255 ~= -0.33333 @ [5] - auto k3Bin = bin.substr(21, 4); - EXPECT_EQ(k3Bin, "0101"); + auto k3_bin = bin.substr(21, 4); + EXPECT_EQ(k3_bin, "0101"); // K4 = -0.172317 ~= 0.20579 @ [4] - auto k4Bin = bin.substr(25, 4); - EXPECT_EQ(k4Bin, "0100"); + auto k4_bin = bin.substr(25, 4); + EXPECT_EQ(k4_bin, "0100"); // K5 = 0.108887 ~= 0.08533 @ [8] - auto k5Bin = bin.substr(29, 4); - EXPECT_EQ(k5Bin, "1000"); + auto k5_bin = bin.substr(29, 4); + EXPECT_EQ(k5_bin, "1000"); // K6 = 0.679660 ~= 0.71333 @ [14] - auto k6Bin = bin.substr(33, 4); - EXPECT_EQ(k6Bin, "1110"); + auto k6_bin = bin.substr(33, 4); + EXPECT_EQ(k6_bin, "1110"); // K7 = 0.056874 ~= 0.05333 @ [7] - auto k7Bin = bin.substr(37, 4); - EXPECT_EQ(k7Bin, "0111"); + auto k7_bin = bin.substr(37, 4); + EXPECT_EQ(k7_bin, "0111"); // K8 = 0.433271 ~= 0.42857 @ [5] - auto k8Bin = bin.substr(41, 3); - EXPECT_EQ(k8Bin, "101"); + auto k8_bin = bin.substr(41, 3); + EXPECT_EQ(k8_bin, "101"); // K9 = -0.220355 ~= -0.18571 @ [2] - auto k9Bin = bin.substr(44, 3); - EXPECT_EQ(k9Bin, "010"); + auto k9_bin = bin.substr(44, 3); + EXPECT_EQ(k9_bin, "010"); // K10 = 0.17028 ~= 0.17143 @ [4] - auto k10Bin = bin.substr(47, 3); - EXPECT_EQ(k10Bin, "100"); + auto k10_bin = bin.substr(47, 3); + EXPECT_EQ(k10_bin, "100"); } }; // namespace tms_express From 66b4387e8458c8ba79a6c9928f824a2757faf7e6 Mon Sep 17 00:00:00 2001 From: Joseph Bellahcen Date: Sun, 6 Aug 2023 20:34:42 -0700 Subject: [PATCH 28/30] Re-factored CMakeLists --- CMakeLists.txt | 51 +++++++++++++++++++++---------------- test/CMakeLists.txt | 62 +++++++++++++++++++++++++++++++++++++++++++++ test/TmsTest.cmake | 42 ------------------------------ 3 files changed, 91 insertions(+), 64 deletions(-) create mode 100644 test/CMakeLists.txt delete mode 100644 test/TmsTest.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index f6a0b8e..be73a7b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,32 +1,36 @@ -# Author: Joseph Bellahcen +# Copyright (C) 2023 Joseph Bellahcen + +############################################################################### +# CMake Configuration ######################################################### +############################################################################### cmake_minimum_required(VERSION 3.14) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) -# Run Qt's moc, rcc, and uic tools +# Required for Qt set(CMAKE_AUTOUIC ON) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) +############################################################################### +# Project Configuration ####################################################### +############################################################################### + project(TMS-Express) option(TMSEXPRESS_BUILD_TESTS "Build test programs" ON) -# ==================================== -# PROGRAM FILES -# ==================================== +############################################################################### +# Project Sources & Includes ################################################## +############################################################################### + include_directories( - # For lib directory ${CMAKE_CURRENT_LIST_DIR} - - # For project sources tms_express ) add_executable(${PROJECT_NAME} - # ==================================== - # BACKEND SOURCES - # ==================================== + # Backend ################################################################# tms_express/Audio/AudioBuffer.cpp tms_express/Audio/AudioFilter.cpp @@ -42,13 +46,9 @@ add_executable(${PROJECT_NAME} tms_express/Bitstream_Generation/BitstreamGenerator.cpp tms_express/Bitstream_Generation/PathUtils.cpp - # ==================================== - # FRONTEND SOURCES - # ==================================== + # Frontend ############################################################### tms_express/User_Interfaces/CommandLineApp.cpp - tms_express/User_Interfaces/MainWindow.cpp - tms_express/User_Interfaces/Audio_Waveform/AudioWaveform.cpp tms_express/User_Interfaces/Audio_Waveform/AudioWaveformView.cpp @@ -57,15 +57,19 @@ add_executable(${PROJECT_NAME} tms_express/User_Interfaces/Control_Panels/ControlPanelLpcView.cpp tms_express/User_Interfaces/Control_Panels/ControlPanelPostView.cpp + tms_express/User_Interfaces/MainWindow.cpp + + # Main #################################################################### tms_express/main.cpp ) -# ==================================== -# DYNAMIC LIBS -# ==================================== -find_package(Qt6 COMPONENTS Core Gui Multimedia Widgets REQUIRED) +############################################################################### +# Project Dependencies ######################################################## +############################################################################### +find_package(Qt6 COMPONENTS Core Gui Multimedia Widgets REQUIRED) find_package(PkgConfig REQUIRED) + pkg_check_modules(SndFile REQUIRED IMPORTED_TARGET sndfile) pkg_check_modules(SampleRate REQUIRED IMPORTED_TARGET samplerate) @@ -79,10 +83,13 @@ target_link_libraries(${PROJECT_NAME} Qt::Multimedia ) -# Rename executable +############################################################################### +# Artifacts & Sub-Targets ##################################################### +############################################################################### + set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME tmsexpress) if (TMSEXPRESS_BUILD_TESTS) message(STATUS "Building TMS Express test suite") - include(test/TmsTest.cmake) + add_subdirectory(test) endif() diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..30699e3 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,62 @@ +# Copyright (C) 2023 Joseph Bellahcen + +############################################################################### +# CMake Configuration ######################################################### +############################################################################### + +cmake_minimum_required(VERSION 3.14) +set(CMAKE_CXX_STANDARD 17) + +############################################################################### +# Project Configuration ####################################################### +############################################################################### + +project(TMS-Test) + +############################################################################### +# GoogleTest Framework ######################################################## +############################################################################### + +include(FetchContent) +FetchContent_Declare(googletest + GIT_REPOSITORY "https://github.com/google/googletest.git" + GIT_TAG "release-1.12.1" +) + +set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) +FetchContent_MakeAvailable(googletest) +enable_testing() + +############################################################################### +# Project Sources & Includes ################################################## +############################################################################### + +include_directories( + ${CMAKE_PROJECT_TOP_LEVEL_INCLUDES} +) + +set (TEST_TARGET_DIR ${CMAKE_CURRENT_LIST_DIR}/../tms_express) +message(${TEST_TARGET_DIR}) + +add_executable(${PROJECT_NAME} + ${TEST_TARGET_DIR}/LPC_Analysis/Autocorrelation.cpp + ${CMAKE_CURRENT_LIST_DIR}/AutocorrelatorTests.cpp + + ${TEST_TARGET_DIR}/Frame_Encoding/Frame.cpp + ${CMAKE_CURRENT_LIST_DIR}/FrameTests.cpp + + ${TEST_TARGET_DIR}/Frame_Encoding/FrameEncoder.cpp + ${CMAKE_CURRENT_LIST_DIR}/FrameEncoderTests.cpp +) + +############################################################################### +# Project Dependencies ######################################################## +############################################################################### + +target_link_libraries( + ${PROJECT_NAME} + gtest_main +) + +include(GoogleTest) +gtest_discover_tests(TMS-Test) diff --git a/test/TmsTest.cmake b/test/TmsTest.cmake deleted file mode 100644 index 6ecd02d..0000000 --- a/test/TmsTest.cmake +++ /dev/null @@ -1,42 +0,0 @@ -cmake_minimum_required(VERSION 3.14) -set(CMAKE_CXX_STANDARD 17) - -# ==================================== -# Google Test Framework -# ==================================== -include(FetchContent) -FetchContent_Declare(googletest - GIT_REPOSITORY "https://github.com/google/googletest.git" - GIT_TAG "release-1.12.1" -) - -set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) -FetchContent_MakeAvailable(googletest) -enable_testing() - -# ==================================== -# PROGRAM FILES -# ==================================== -include_directories( - inc - lib -) - -add_executable(TMS-Test - tms_express/LPC_Analysis/Autocorrelation.cpp - test/AutocorrelatorTests.cpp - - tms_express/Frame_Encoding/Frame.cpp - test/FrameTests.cpp - - tms_express/Frame_Encoding/FrameEncoder.cpp - test/FrameEncoderTests.cpp - ) - -target_link_libraries( - TMS-Test - gtest_main -) - -include(GoogleTest) -gtest_discover_tests(TMS-Test) From 360cc555858a8c01fef748b3b80574ede967b927 Mon Sep 17 00:00:00 2001 From: Joseph Bellahcen Date: Sun, 6 Aug 2023 20:38:52 -0700 Subject: [PATCH 29/30] Re-factored README --- README.md | 63 ++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 44 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 3971f77..ce8331b 100644 --- a/README.md +++ b/README.md @@ -3,12 +3,14 @@ TMS Express generates bitstreams for the TMS5220 Voice Synthesis Processor. ![TMS Express GUI Screenshot](doc/screenshot.png) -The TMS5220 hardware and its software analogues (i.e. Arduino Talkie) alike may be driven by the output of TMS Express. -The program accepts audio files in a variety of formats, applies linear predictive coding (LPC) to compress the data, +The TMS5220 hardware and its software analogues (i.e. Arduino Talkie) alike may +be driven by the output of TMS Express. The program accepts audio files in a +variety of formats, applies linear predictive coding (LPC) to compress the data, and outputs either an ASCII bitstream or C header with the encoded data. Compared to existing encoders, TMS Express has the following advantages: -- Implements the original Levinson-Durbin recursion to determine LPC coefficients +- Implements the original Levinson-Durbin recursion to determine LPC + coefficients - Supports repeat frames for enhanced audio compression - Automatically downsamples and mixes audio files of any format - Performs batch encoding of multiple files @@ -39,33 +41,56 @@ $ cmake --build . -j ## Usage ## GUI -To launch the TMS Express GUI frontend, simply invoke the program with no arguments +To launch the TMS Express GUI frontend, simply invoke the program with no +arguments ```shell $ tmsexpress ``` ## The Encode Command -The `encode` command accepts audio file(s) and a variety of parameters which affect how they are processed, analyzed, -and formatted for output. TMS Express automatically detects when the input path is a directory and performs a batch job. +The `encode` command accepts audio file(s) and a variety of parameters which +affect how they are processed, analyzed, and formatted for output. TMS Express +automatically detects when the input path is a directory and performs a batch +job. ```shell $ tmsexpress encode [OPTIONS] input output ``` ### Explanation of Options -- `window`: Speech data is separated into small windows/segments, each of which are analyzed individually. This is because small enough segments of speech data are roughly periodic and their behavior may be generalized. An ideal window width is between 22.5-25 ms - - Values above and below the recommendation will artificially speed up and slow down speech, respectively -- `highpass` and `lowpass`: Speech data occupies a relatively small frequency band compared to what digital audio files are capable of representing. Filtering out unnecessary frequencies may lead to more accurate LPC analysis - - Lowering the highpass filter cutoff will improve the bass response of the audio +- `window`: Speech data is separated into small windows/segments, each of which + are analyzed individually. This is because small enough segments of speech + data are roughly periodic and their behavior may be generalized. An ideal + window width is between 22.5-25 ms + - Values above and below the recommendation will artificially speed up and + slow down speech, respectively +- `highpass` and `lowpass`: Speech data occupies a relatively small frequency + band compared to what digital audio files are capable of representing. + Filtering out unnecessary frequencies may lead to more accurate LPC analysis + - Lowering the highpass filter cutoff will improve the bass response of the + audio - Adjusting the lowpass cutoff may have minor effects of pitch estimation -- `alpha`: While the pitch of speech is characterized by the lower frequency band, LPC algorithms which characterize the upper vocal tract benefit from an exaggeration of high frequency data. A pre-emphasis filter will exaggerate this part of the spectrum and lead to crisper, more accurate synthesis -- `format`: ASCII format is ideal for testing and visualization of single files. The C and Arduino format produce C headers for use with TMS5220 emulations -- `no-stop-frame`: An explicit stop frame signals to the TMS5220 that the Speak External command has finished executing -- `gain`: Increases the gain of the synthesized signal by adjusting the index of the coding table element. Gain offsets greater than the max size of the coding table will hit the ceiling -- `max-voiced-gain`: Specifies the maximum gain (dB) of the output signal for voiced frames (vowels) -- `max-unvoiced-gain`: Specifies the maximum gain (dB) of the output signal for unvoiced frames (consonants) - - Ensuring that this value hovers around `0.8 * max-voiced-gain` will result in the most accurate synthesis of consonant sounds +- `alpha`: While the pitch of speech is characterized by the lower frequency + band, LPC algorithms which characterize the upper vocal tract benefit from an + exaggeration of high frequency data. A pre-emphasis filter will exaggerate + this part of the spectrum and lead to crisper, more accurate synthesis +- `format`: ASCII format is ideal for testing and visualization of single + files. The C and Arduino format produce C headers for use with TMS5220 + emulations +- `no-stop-frame`: An explicit stop frame signals to the TMS5220 that the + Speak External command has finished executing +- `gain`: Increases the gain of the synthesized signal by adjusting the index + of the coding table element. Gain offsets greater than the max size of the + coding table will hit the ceiling +- `max-voiced-gain`: Specifies the maximum gain (dB) of the output signal for + voiced frames (vowels) +- `max-unvoiced-gain`: Specifies the maximum gain (dB) of the output signal for + unvoiced frames (consonants) + - Ensuring that this value hovers around `0.8 * max-voiced-gain` will result + in the most accurate synthesis of consonant sounds - `use-repeat-frames`: Detect repeat frames to reduce the size of the bitstream -- `max-frq`: Specifies the maximum representable pitch frequency of the output signal -- `min-frq`: Specifies the minimum representable pitch frequency of the output signal +- `max-frq`: Specifies the maximum representable pitch frequency of the output + signal +- `min-frq`: Specifies the minimum representable pitch frequency of the output + signal From bb2f180784390283271c0a9209eebcbddd5753cc Mon Sep 17 00:00:00 2001 From: Joseph Bellahcen Date: Sun, 6 Aug 2023 20:48:37 -0700 Subject: [PATCH 30/30] Added comment block after multi-line function declaration --- tms_express/Audio/AudioBuffer.cpp | 4 ++++ .../BitstreamGenerator.cpp | 3 ++- .../Bitstream_Generation/PathUtils.cpp | 1 + tms_express/Frame_Encoding/Frame.cpp | 1 + tms_express/Frame_Encoding/FrameEncoder.cpp | 24 +++++++++++-------- .../Frame_Encoding/FramePostprocessor.cpp | 1 + tms_express/Frame_Encoding/Synthesizer.cpp | 1 + tms_express/LPC_Analysis/LinearPredictor.cpp | 3 ++- tms_express/LPC_Analysis/PitchEstimator.cpp | 1 + .../Audio_Waveform/AudioWaveformView.cpp | 2 +- .../Control_Panels/ControlPanelLpcView.cpp | 1 + .../Control_Panels/ControlPanelPitchView.cpp | 1 + .../Control_Panels/ControlPanelPostView.cpp | 1 + .../Control_Panels/ControlPanelView.cpp | 1 + 14 files changed, 32 insertions(+), 13 deletions(-) diff --git a/tms_express/Audio/AudioBuffer.cpp b/tms_express/Audio/AudioBuffer.cpp index da26046..1839028 100644 --- a/tms_express/Audio/AudioBuffer.cpp +++ b/tms_express/Audio/AudioBuffer.cpp @@ -17,6 +17,7 @@ namespace tms_express { std::shared_ptr AudioBuffer::Create(const std::string &path, int sample_rate_hz, float window_width_ms) { + // // Attempt to open an audio file via libsndfile, aborting initialization if // the given path does not exist, is invalid, or is not a suported format auto audio_file = SndfileHandle(path); @@ -55,6 +56,7 @@ std::shared_ptr AudioBuffer::Create(const std::string &path, AudioBuffer::AudioBuffer(std::vector samples, int sample_rate_hz, float window_width_ms) { + // n_segments_ = 0; n_samples_per_segment_ = 0; sample_rate_hz_ = sample_rate_hz; @@ -214,6 +216,7 @@ void AudioBuffer::reset() { std::vector AudioBuffer::mixToMono(std::vector samples, int n_channels) { + // int mono_size = samples.size() / n_channels; auto mono_samples = std::vector(mono_size, 0); @@ -231,6 +234,7 @@ std::vector AudioBuffer::mixToMono(std::vector samples, // Resample the audio buffer to the target sample rate std::vector AudioBuffer::resample(std::vector samples, int src_sample_rate_hz, int target_sample_rate_hz) { + // // Resampler parameters // NOTE: If a future version of this codebase requires // compatibility with stereo audio, compute the diff --git a/tms_express/Bitstream_Generation/BitstreamGenerator.cpp b/tms_express/Bitstream_Generation/BitstreamGenerator.cpp index 885d007..144d3cb 100644 --- a/tms_express/Bitstream_Generation/BitstreamGenerator.cpp +++ b/tms_express/Bitstream_Generation/BitstreamGenerator.cpp @@ -24,7 +24,7 @@ BitstreamGenerator::BitstreamGenerator(float window_width_ms, EncoderStyle style, bool include_stop_frame, int gain_shift, float max_voiced_gain_db, float max_unvoiced_gain_db, bool detect_repeat_frames, int max_pitch_hz, int min_pitch_hz) { - + // window_width_ms_ = window_width_ms; highpass_cutoff_hz_ = highpass_cutoff_hz; lowpass_cutoff_hz_ = lowpass_cutoff_hz; @@ -171,6 +171,7 @@ std::vector BitstreamGenerator::generateFrames( std::string BitstreamGenerator::serializeFrames( const std::vector& frames, const std::string &filename) const { + // // Encode frames to hex bitstreams auto encoder = FrameEncoder(frames, style_ != ENCODERSTYLE_ASCII); std::string bitstream; diff --git a/tms_express/Bitstream_Generation/PathUtils.cpp b/tms_express/Bitstream_Generation/PathUtils.cpp index 7755c9b..42d9985 100644 --- a/tms_express/Bitstream_Generation/PathUtils.cpp +++ b/tms_express/Bitstream_Generation/PathUtils.cpp @@ -80,6 +80,7 @@ std::string PathUtils::extractFilenameFromPath(const std::string &path) { std::vector PathUtils::splitString(const std::string& str, const std::string& delim) { + // auto result = std::vector(); auto delim_size = delim.length(); diff --git a/tms_express/Frame_Encoding/Frame.cpp b/tms_express/Frame_Encoding/Frame.cpp index b5540fb..69ea24d 100644 --- a/tms_express/Frame_Encoding/Frame.cpp +++ b/tms_express/Frame_Encoding/Frame.cpp @@ -18,6 +18,7 @@ namespace tms_express { Frame::Frame(int pitch_period, bool is_voiced, float gain_db, std::vector coeffs) { + // gain_db_ = gain_db; pitch_period_ = pitch_period; coeffs_ = coeffs; diff --git a/tms_express/Frame_Encoding/FrameEncoder.cpp b/tms_express/Frame_Encoding/FrameEncoder.cpp index 23811c5..77bbe1c 100644 --- a/tms_express/Frame_Encoding/FrameEncoder.cpp +++ b/tms_express/Frame_Encoding/FrameEncoder.cpp @@ -24,6 +24,7 @@ FrameEncoder::FrameEncoder(bool include_hex_prefix) { FrameEncoder::FrameEncoder(const std::vector &frames, bool include_hex_prefix) { + // binary_bitstream_ = std::vector(1, ""); frames_ = std::vector(); include_hex_prefix_ = include_hex_prefix; @@ -214,6 +215,7 @@ std::vector FrameEncoder::getFrameTable() const { std::string FrameEncoder::binToHex(const std::string &bin_str, bool include_hex_prefix) { + // // TODO(Joseph Bellahcen): Handle exception int value = std::stoi(bin_str, nullptr, 2); @@ -256,20 +258,22 @@ std::string FrameEncoder::reverseHexBytes(std::string bitstream) { void FrameEncoder::extractUnvoicedCoeffs(const std::string &chunk, float *k1, float *k2, float *k3, float *k4) { - auto k1_idx = std::stoul(chunk.substr(11, 5), nullptr, 2); - auto k2_idx = std::stoul(chunk.substr(16, 5), nullptr, 2); - auto k3_idx = std::stoul(chunk.substr(21, 4), nullptr, 2); - auto k4_idx = std::stoul(chunk.substr(25, 4), nullptr, 2); - - // TODO(Joseph Bellahcen): Guard against nullptr dereference - *k1 = coding_table::tms5220::k1.at(k1_idx); - *k2 = coding_table::tms5220::k2.at(k2_idx); - *k3 = coding_table::tms5220::k3.at(k3_idx); - *k4 = coding_table::tms5220::k4.at(k4_idx); + // + auto k1_idx = std::stoul(chunk.substr(11, 5), nullptr, 2); + auto k2_idx = std::stoul(chunk.substr(16, 5), nullptr, 2); + auto k3_idx = std::stoul(chunk.substr(21, 4), nullptr, 2); + auto k4_idx = std::stoul(chunk.substr(25, 4), nullptr, 2); + + // TODO(Joseph Bellahcen): Guard against nullptr dereference + *k1 = coding_table::tms5220::k1.at(k1_idx); + *k2 = coding_table::tms5220::k2.at(k2_idx); + *k3 = coding_table::tms5220::k3.at(k3_idx); + *k4 = coding_table::tms5220::k4.at(k4_idx); } void FrameEncoder::extractVoicedCoeffs(const std::string &chunk, float *k5, float *k6, float *k7, float *k8, float *k9, float *k10) { + // auto k5_idx = std::stoul(chunk.substr(29, 4), nullptr, 2); auto k6_idx = std::stoul(chunk.substr(33, 4), nullptr, 2); auto k7_idx = std::stoul(chunk.substr(37, 4), nullptr, 2); diff --git a/tms_express/Frame_Encoding/FramePostprocessor.cpp b/tms_express/Frame_Encoding/FramePostprocessor.cpp index 56c2f73..3c90a17 100644 --- a/tms_express/Frame_Encoding/FramePostprocessor.cpp +++ b/tms_express/Frame_Encoding/FramePostprocessor.cpp @@ -15,6 +15,7 @@ namespace tms_express { FramePostprocessor::FramePostprocessor(std::vector *frames, float max_voiced_gain_db, float max_unvoiced_gain_db) { + // original_frame_table_ = std::vector(frames->begin(), frames->end()); frame_table_ = frames; max_unvoiced_gain_db_ = max_unvoiced_gain_db; diff --git a/tms_express/Frame_Encoding/Synthesizer.cpp b/tms_express/Frame_Encoding/Synthesizer.cpp index 8a3db2d..4c97577 100644 --- a/tms_express/Frame_Encoding/Synthesizer.cpp +++ b/tms_express/Frame_Encoding/Synthesizer.cpp @@ -69,6 +69,7 @@ std::vector Synthesizer::getSamples() const { /// Export synthesized samples as an audio file void Synthesizer::render(const std::vector &samples, const std::string& path, int sample_rate_hz, float frame_rate_ms) { + // AudioBuffer(samples, sample_rate_hz, frame_rate_ms).render(path); } diff --git a/tms_express/LPC_Analysis/LinearPredictor.cpp b/tms_express/LPC_Analysis/LinearPredictor.cpp index dc40528..c244cb3 100644 --- a/tms_express/LPC_Analysis/LinearPredictor.cpp +++ b/tms_express/LPC_Analysis/LinearPredictor.cpp @@ -1,4 +1,4 @@ - +// Copyright 2023 Joseph Bellahcen #include "LPC_Analysis/LinearPredictor.hpp" #include @@ -21,6 +21,7 @@ LinearPredictor::LinearPredictor(int model_order) { std::vector LinearPredictor::computeCoeffs( const std::vector& acf) { + // // Reference: "Levinson–Durbin Algorithm" (Castiglioni) // Autocorrelation alias diff --git a/tms_express/LPC_Analysis/PitchEstimator.cpp b/tms_express/LPC_Analysis/PitchEstimator.cpp index 481255b..63575a4 100644 --- a/tms_express/LPC_Analysis/PitchEstimator.cpp +++ b/tms_express/LPC_Analysis/PitchEstimator.cpp @@ -13,6 +13,7 @@ namespace tms_express { PitchEstimator::PitchEstimator(int sample_rate_hz, int min_frq_hz, int max_frq_hz) { + // max_period_ = sample_rate_hz / min_frq_hz; min_period_ = sample_rate_hz / max_frq_hz; sample_rate_hz_ = sample_rate_hz; diff --git a/tms_express/User_Interfaces/Audio_Waveform/AudioWaveformView.cpp b/tms_express/User_Interfaces/Audio_Waveform/AudioWaveformView.cpp index b5af434..252cc77 100644 --- a/tms_express/User_Interfaces/Audio_Waveform/AudioWaveformView.cpp +++ b/tms_express/User_Interfaces/Audio_Waveform/AudioWaveformView.cpp @@ -17,7 +17,7 @@ namespace tms_express::ui { AudioWaveformView::AudioWaveformView(std::string title, int base_width, int base_height, QWidget *parent): QWidget(parent) { - + // // Widget properties setMinimumSize(base_width, base_height); diff --git a/tms_express/User_Interfaces/Control_Panels/ControlPanelLpcView.cpp b/tms_express/User_Interfaces/Control_Panels/ControlPanelLpcView.cpp index 018a53d..402ec0d 100644 --- a/tms_express/User_Interfaces/Control_Panels/ControlPanelLpcView.cpp +++ b/tms_express/User_Interfaces/Control_Panels/ControlPanelLpcView.cpp @@ -17,6 +17,7 @@ namespace tms_express::ui { ControlPanelLpcView::ControlPanelLpcView(QWidget *parent): ControlPanelView("LPC Analysis", parent) { + // // Initialize parameters auto analysis_window_label = new QLabel("Analysis window (ms)", this); analysis_window_line_ = new QLineEdit("25.0", this); diff --git a/tms_express/User_Interfaces/Control_Panels/ControlPanelPitchView.cpp b/tms_express/User_Interfaces/Control_Panels/ControlPanelPitchView.cpp index 9edeef9..1a32a12 100644 --- a/tms_express/User_Interfaces/Control_Panels/ControlPanelPitchView.cpp +++ b/tms_express/User_Interfaces/Control_Panels/ControlPanelPitchView.cpp @@ -21,6 +21,7 @@ namespace tms_express::ui { ControlPanelPitchView::ControlPanelPitchView(QWidget *parent): ControlPanelView("Pitch Analysis", parent) { + // // Initialize parameters hpf_checkbox_ = new QCheckBox("Highpass filter (Hz)", this); hpf_line_ = new QLineEdit("100", this); diff --git a/tms_express/User_Interfaces/Control_Panels/ControlPanelPostView.cpp b/tms_express/User_Interfaces/Control_Panels/ControlPanelPostView.cpp index 11db386..19b3a5e 100644 --- a/tms_express/User_Interfaces/Control_Panels/ControlPanelPostView.cpp +++ b/tms_express/User_Interfaces/Control_Panels/ControlPanelPostView.cpp @@ -18,6 +18,7 @@ namespace tms_express::ui { ControlPanelPostView::ControlPanelPostView(QWidget *parent): ControlPanelView("Post-Processing", parent) { + // // Initialize parameters pitch_shift_checkbox_ = new QCheckBox("Pitch shift", this); pitch_shift_slider_ = new QSlider(Qt::Horizontal, this); diff --git a/tms_express/User_Interfaces/Control_Panels/ControlPanelView.cpp b/tms_express/User_Interfaces/Control_Panels/ControlPanelView.cpp index 3bc4a9e..1fd0cc3 100644 --- a/tms_express/User_Interfaces/Control_Panels/ControlPanelView.cpp +++ b/tms_express/User_Interfaces/Control_Panels/ControlPanelView.cpp @@ -14,6 +14,7 @@ namespace tms_express::ui { ControlPanelView::ControlPanelView(const std::string &title, QWidget *parent): QWidget(parent) { + // grid = new QGridLayout(this); auto panel_title = new QLabel(title.c_str(), this);