From c102ebb6dab66b8dc97187bdfeff45c01b3d056b Mon Sep 17 00:00:00 2001 From: James Grogan Date: Thu, 1 Dec 2022 10:52:48 +0000 Subject: [PATCH] Clean up some tests. --- .../text_editor/TextEditorController.cpp | 14 +-- src/audio/AudioDevice.cpp | 59 +++++---- src/audio/AudioDevice.h | 26 ++-- src/audio/AudioManager.cpp | 16 +-- src/audio/AudioManager.h | 15 ++- src/audio/AudioSample.cpp | 10 +- src/audio/AudioSample.h | 13 +- src/audio/AudioSynth.cpp | 20 ++-- src/audio/AudioSynth.h | 5 +- src/audio/AudioTrack.h | 1 - src/audio/AudioWriter.cpp | 32 +++-- src/audio/AudioWriter.h | 10 +- src/audio/audio_interfaces/AlsaInterface.cpp | 86 ++++++++----- src/audio/audio_interfaces/AlsaInterface.h | 31 +++-- src/audio/audio_interfaces/IAudioInterface.h | 10 +- .../audio_interfaces/NullAudioInterface.cpp | 4 +- .../audio_interfaces/NullAudioInterface.h | 8 +- .../midi/reader/MidiChannelEventAdapter.cpp | 6 +- .../midi/reader/MidiMetaEventAdapter.cpp | 2 +- src/audio/midi/reader/MidiReader.cpp | 56 ++++----- src/audio/midi/reader/MidiReader.h | 17 +-- src/audio/midi/reader/MidiTimeAdapter.cpp | 12 +- src/compiler/TemplateFile.cpp | 6 +- src/console/MainApplication.cpp | 8 +- src/core/file_utilities/File.cpp | 113 +++++++++++------- src/core/file_utilities/File.h | 34 +++--- src/core/serializers/TomlReader.cpp | 8 +- src/core/serializers/TomlReader.h | 56 ++++----- src/database/Database.cpp | 4 +- src/database/Database.h | 14 ++- src/database/DatabaseManager.cpp | 14 +-- src/database/DatabaseManager.h | 14 ++- .../database_interfaces/SqliteInterface.cpp | 8 +- .../database_interfaces/SqliteInterface.h | 12 +- src/fonts/FontReader.cpp | 56 ++++----- src/image/png/PngReader.cpp | 36 +++--- src/image/png/PngWriter.cpp | 7 +- src/web/DocumentConverter.cpp | 33 +++-- src/web/DocumentConverter.h | 9 +- src/web/markdown/MarkdownParser.cpp | 8 +- src/web/markdown/MarkdownParser.h | 15 ++- test/CMakeLists.txt | 8 ++ test/audio/CMakeLists.txt | 32 +++-- test/audio/TestAudioWriter.cpp | 34 ------ test/audio/TestMidiReader.cpp | 16 --- test/audio/integration/TestAlsaInterface.cpp | 0 .../audio/integration/TestWasapiInterface.cpp | 13 ++ test/audio/unit/TestAudioWriter.cpp | 17 +++ test/audio/unit/TestMidiReader.cpp | 12 ++ test/compiler/TestTemplatingEngine.cpp | 13 +- test/core/TestTomlReader.cpp | 11 +- test/data/index.png | Bin 0 -> 6273 bytes test/data/test.mid | Bin 0 -> 8444 bytes test/data/test.png | Bin 0 -> 50081 bytes test/data/test_fixed.png | Bin 0 -> 64 bytes test/database/TestDatabase.cpp | 5 +- test/fonts/TestFontReader.cpp | 2 - test/image/TestPngReader.cpp | 5 +- test/image/TestPngWriter.cpp | 18 ++- test/publishing/TestPdfWriter.cpp | 8 +- test/test_utils/TestUtils.h | 19 +++ test/video/TestVideoDecoder.cpp | 8 +- test/web/TestMarkdownParser.cpp | 16 ++- test/web/TestXmlParser.cpp | 11 +- 64 files changed, 615 insertions(+), 541 deletions(-) delete mode 100644 test/audio/TestAudioWriter.cpp delete mode 100644 test/audio/TestMidiReader.cpp create mode 100644 test/audio/integration/TestAlsaInterface.cpp create mode 100644 test/audio/integration/TestWasapiInterface.cpp create mode 100644 test/audio/unit/TestAudioWriter.cpp create mode 100644 test/audio/unit/TestMidiReader.cpp create mode 100644 test/data/index.png create mode 100644 test/data/test.mid create mode 100644 test/data/test.png create mode 100644 test/data/test_fixed.png create mode 100644 test/test_utils/TestUtils.h diff --git a/apps/sample-gui/text_editor/TextEditorController.cpp b/apps/sample-gui/text_editor/TextEditorController.cpp index d950311..36adacb 100644 --- a/apps/sample-gui/text_editor/TextEditorController.cpp +++ b/apps/sample-gui/text_editor/TextEditorController.cpp @@ -28,20 +28,18 @@ void TextEditorController::OnSave() { if(mSavePath.empty()) return; File outfile(mSavePath); - outfile.SetAccessMode(File::AccessMode::Write); - outfile.Open(); - outfile.WriteText(mModel->GetDocument()->GetContent()); - outfile.Close(); + + outfile.open(File::AccessMode::Write); + outfile.writeText(mModel->GetDocument()->GetContent()); } void TextEditorController::OnLoad() { if(mLoadPath.empty()) return; File infile(mLoadPath); - infile.SetAccessMode(File::AccessMode::Read); - infile.Open(); - mModel->GetDocument()->SetContent(infile.ReadText()); - infile.Close(); + + infile.open(File::AccessMode::Read); + mModel->GetDocument()->SetContent(infile.readText()); } void TextEditorController::SetSavePath(const std::filesystem::path& path) diff --git a/src/audio/AudioDevice.cpp b/src/audio/AudioDevice.cpp index 7394194..3670110 100644 --- a/src/audio/AudioDevice.cpp +++ b/src/audio/AudioDevice.cpp @@ -20,52 +20,63 @@ std::unique_ptr AudioDevice::Create() return std::make_unique(); } -void AudioDevice::SetNumChannels(unsigned numChannels) +bool AudioDevice::getIsOpen() const { - mNumChannels = numChannels; + return mIsOpen; } -void AudioDevice::SetPeriod(unsigned period) -{ - mPeriod = period; -} - -void AudioDevice::SetBufferSize(std::size_t bufferSize) -{ - mBufferSize = bufferSize; -} - -unsigned AudioDevice::GetNumChannels() const +unsigned AudioDevice::getNumChannels() const { return mNumChannels; } -unsigned AudioDevice::GetPeriod() const +unsigned AudioDevice::getPeriod() const { return mPeriod; } -std::size_t AudioDevice::GetBufferSize() const +std::size_t AudioDevice::getBufferSize() const { return mBufferSize; } -void AudioDevice::SetSampleRate(unsigned rate) -{ - mSampleRate = rate; -} - -unsigned AudioDevice::GetSampleRate() const +unsigned AudioDevice::getSampleRate() const { return mSampleRate; } -void AudioDevice::SetName(const std::string& name) +std::string AudioDevice::getName() const +{ + return mName; +} + +void AudioDevice::setSampleRate(unsigned rate) +{ + mSampleRate = rate; +} + +void AudioDevice::setName(const std::string& name) { mName = name; } -std::string AudioDevice::GetName() const +void AudioDevice::setNumChannels(unsigned numChannels) { - return mName; + mNumChannels = numChannels; } + +void AudioDevice::setPeriod(unsigned period) +{ + mPeriod = period; +} + +void AudioDevice::setBufferSize(std::size_t bufferSize) +{ + mBufferSize = bufferSize; +} + +void AudioDevice::setIsOpen(bool isOpen) +{ + mIsOpen = isOpen; +} + diff --git a/src/audio/AudioDevice.h b/src/audio/AudioDevice.h index c28297e..57e19d8 100644 --- a/src/audio/AudioDevice.h +++ b/src/audio/AudioDevice.h @@ -7,40 +7,42 @@ class AudioDevice { public: - AudioDevice(); ~AudioDevice(); static std::unique_ptr Create(); - void SetSampleRate(unsigned rate); + unsigned getSampleRate() const; - unsigned GetSampleRate() const; + unsigned getNumChannels() const; - void SetName(const std::string& name); + unsigned getPeriod() const; - void SetNumChannels(unsigned numChannels); + std::size_t getBufferSize() const; - void SetPeriod(unsigned period); + bool getIsOpen() const; - void SetBufferSize(std::size_t bufferSize); + std::string getName() const; - unsigned GetNumChannels() const; + void setName(const std::string& name); - unsigned GetPeriod() const; + void setNumChannels(unsigned numChannels); - std::size_t GetBufferSize() const; + void setPeriod(unsigned period); - std::string GetName() const; + void setBufferSize(std::size_t bufferSize); + void setSampleRate(unsigned rate); + + void setIsOpen(bool isOpen); private: - std::string mName {"unset"}; unsigned mSampleRate {44100}; unsigned mNumChannels {1}; unsigned mPeriod {2}; std::size_t mBufferSize{0}; + bool mIsOpen{false}; }; using AudioDevicePtr = std::unique_ptr; diff --git a/src/audio/AudioManager.cpp b/src/audio/AudioManager.cpp index 4e744e2..41cb17a 100644 --- a/src/audio/AudioManager.cpp +++ b/src/audio/AudioManager.cpp @@ -31,22 +31,17 @@ std::unique_ptr AudioManager::Create() return std::make_unique(); } -void AudioManager::AddAudioDevice(AudioDevicePtr device) +void AudioManager::addAudioDevice(AudioDevicePtr device) { mAudioDevices.push_back(std::move(device)); } -IAudioInterface* AudioManager::GetAudioInterface() -{ - return mAudioInterface.get(); -} - -std::size_t AudioManager::GetNumAudioDevices() const +std::size_t AudioManager::getNumAudioDevices() const { return mAudioDevices.size(); } -AudioDevice* AudioManager::GetAudioDevice(unsigned idx) const +AudioDevice* AudioManager::getAudioDevice(unsigned idx) const { if (idx < mAudioDevices.size()) { @@ -55,12 +50,11 @@ AudioDevice* AudioManager::GetAudioDevice(unsigned idx) const return nullptr; } -void AudioManager::Play() +void AudioManager::play(AudioSample* sample, unsigned duration) { if (mAudioDevices.size() == 0) { mAudioDevices.push_back(AudioDevice::Create()); } - mAudioInterface->OpenDevice(mAudioDevices[0]); - mAudioInterface->Play(mAudioDevices[0]); + mAudioInterface->play(mAudioDevices[0].get(), sample, duration); } diff --git a/src/audio/AudioManager.h b/src/audio/AudioManager.h index 78fd23e..dacef02 100644 --- a/src/audio/AudioManager.h +++ b/src/audio/AudioManager.h @@ -6,30 +6,29 @@ #include #include +class AudioSample; + class AudioManager { public: - AudioManager(); ~AudioManager(); static std::unique_ptr Create(); - void AddAudioDevice(AudioDevicePtr device); + void addAudioDevice(AudioDevicePtr device); - std::size_t GetNumAudioDevices() const; + std::size_t getNumAudioDevices() const; - AudioDevice* GetAudioDevice(unsigned idx) const; + AudioDevice* getAudioDevice(unsigned idx) const; - IAudioInterface* GetAudioInterface(); - - void Play(); + void play(AudioSample* sample, unsigned duration); private: std::vector mAudioDevices; - IAudioInterfaceUPtr mAudioInterface; + IAudioInterfacePtr mAudioInterface; }; using AudioManagerUPtr = std::unique_ptr; diff --git a/src/audio/AudioSample.cpp b/src/audio/AudioSample.cpp index a301e61..7a21f5c 100644 --- a/src/audio/AudioSample.cpp +++ b/src/audio/AudioSample.cpp @@ -10,22 +10,22 @@ std::unique_ptr AudioSample::Create() return std::make_unique(); } -std::size_t AudioSample::GetNumChannels() const +std::size_t AudioSample::getNumChannels() const { return mData.size(); } -unsigned AudioSample::GetSampleRate() const +unsigned AudioSample::getSampleRate() const { return mSampleRate; } -unsigned AudioSample::GetBitDepth() const +unsigned AudioSample::getBitDepth() const { return mBitDepth; } -void AudioSample::SetChannelData(const std::vector& data, std::size_t channel) +void AudioSample::setChannelData(const ChannelData& data, std::size_t channel) { if (mData.size() == channel) { @@ -37,7 +37,7 @@ void AudioSample::SetChannelData(const std::vector& data, std::size_t cha } } -std::vector AudioSample::GetChannelData(std::size_t channel) const +AudioSample::ChannelData AudioSample::getChannelData(std::size_t channel) const { if(mData.size() > channel) { diff --git a/src/audio/AudioSample.h b/src/audio/AudioSample.h index 66aba07..08cfc8c 100644 --- a/src/audio/AudioSample.h +++ b/src/audio/AudioSample.h @@ -6,22 +6,23 @@ class AudioSample { public: + using ChannelData = std::vector; AudioSample(); static std::unique_ptr Create(); - std::vector GetChannelData(std::size_t channel) const; - void SetChannelData(const std::vector& data, std::size_t channel); + ChannelData getChannelData(std::size_t channel) const; + std::size_t getNumChannels() const; + unsigned getSampleRate() const; + unsigned getBitDepth() const; - std::size_t GetNumChannels() const; - unsigned GetSampleRate() const; - unsigned GetBitDepth() const; + void setChannelData(const ChannelData& data, std::size_t channel); private: unsigned mSampleRate { 44100 }; unsigned mBitDepth{ 16 }; - std::vector > mData; + std::vector mData; }; using AudioSamplePtr = std::unique_ptr; diff --git a/src/audio/AudioSynth.cpp b/src/audio/AudioSynth.cpp index 9d9d742..dfe3b3c 100644 --- a/src/audio/AudioSynth.cpp +++ b/src/audio/AudioSynth.cpp @@ -1,39 +1,39 @@ #include "AudioSynth.h" + #define _USE_MATH_DEFINES #include #include #include -#include AudioSynth::AudioSynth() { } -AudioSamplePtr AudioSynth::GetConstant(unsigned amplitude, unsigned duration) +AudioSamplePtr AudioSynth::getConstant(unsigned amplitude, unsigned duration) const { auto sample = AudioSample::Create(); - auto num_samples = duration * sample->GetSampleRate(); - sample->SetChannelData(std::vector(num_samples, amplitude), 0); + auto num_samples = duration * sample->getSampleRate(); + sample->setChannelData(std::vector(num_samples, amplitude), 0); return sample; } -AudioSamplePtr AudioSynth::GetSineWave(double freq, unsigned duration) +AudioSamplePtr AudioSynth::getSineWave(double freq, unsigned duration) const { auto sample = AudioSample::Create(); - const auto sample_rate = sample->GetSampleRate(); + const auto sample_rate = sample->getSampleRate(); const auto num_samples = sample_rate*duration; std::vector data(num_samples, 0); - double tick_duration = 1.0/sample_rate; - double pi_2 = 2.0 * M_PI; - short max_short = std::numeric_limits::max(); + const double tick_duration = 1.0/sample_rate; + const double pi_2 = 2.0 * M_PI; + const short max_short = std::numeric_limits::max(); for(unsigned idx=0; idxSetChannelData(data, 0); + sample->setChannelData(data, 0); return sample; } diff --git a/src/audio/AudioSynth.h b/src/audio/AudioSynth.h index 5500786..e3479ce 100644 --- a/src/audio/AudioSynth.h +++ b/src/audio/AudioSynth.h @@ -7,10 +7,9 @@ class AudioSynth { public: - AudioSynth(); - AudioSamplePtr GetConstant(unsigned amplitude, unsigned duration); + AudioSamplePtr getConstant(unsigned amplitude, unsigned duration) const; - AudioSamplePtr GetSineWave(double freq, unsigned duration); + AudioSamplePtr getSineWave(double freq, unsigned duration) const; }; diff --git a/src/audio/AudioTrack.h b/src/audio/AudioTrack.h index 05fc3e1..42f013d 100644 --- a/src/audio/AudioTrack.h +++ b/src/audio/AudioTrack.h @@ -3,6 +3,5 @@ class AudioTrack { public: - AudioTrack(); }; diff --git a/src/audio/AudioWriter.cpp b/src/audio/AudioWriter.cpp index da8d46c..55ad1a5 100644 --- a/src/audio/AudioWriter.cpp +++ b/src/audio/AudioWriter.cpp @@ -3,36 +3,41 @@ #include "File.h" #include "BinaryStream.h" #include "AudioSample.h" +#include "FileLogger.h" AudioWriter::AudioWriter() { } -void AudioWriter::SetPath(const std::string& path) +void AudioWriter::setPath(const Path& path) { mPath = path; } -void AudioWriter::Write(const AudioSamplePtr& sample) +bool AudioWriter::write(const AudioSamplePtr& sample) { if (mPath.empty()) { - return; + MLOG_ERROR("Attempted to write audio with no output file path, aborting Write."); + return false; } - const auto sample_rate = sample->GetSampleRate(); - const auto num_channels = sample->GetNumChannels(); - const auto bytes_per_sample = sample->GetBitDepth() / 8; + File outfile(mPath); + if (!outfile.open(File::AccessMode::Write)) + { + MLOG_ERROR("Failed to open audio output file, aborting Write."); + return false; + } + auto handle = outfile.getOutHandle(); + + const auto sample_rate = sample->getSampleRate(); + const auto num_channels = sample->getNumChannels(); + const auto bytes_per_sample = sample->getBitDepth() / 8; unsigned byte_rate = sample_rate*num_channels*bytes_per_sample; - File outfile(mPath); - outfile.SetAccessMode(File::AccessMode::Write); - outfile.Open(true); - auto handle = outfile.GetOutHandle(); - handle->write("RIFF", 4); - auto data = sample->GetChannelData(0); + auto data = sample->getChannelData(0); const auto num_samples = data.size(); unsigned content_size = 36 + bytes_per_sample* num_samples*num_channels; @@ -54,5 +59,6 @@ void AudioWriter::Write(const AudioSamplePtr& sample) BinaryStream::write(handle, bytes_per_sample* num_samples*num_channels); // bits/sample handle->write(reinterpret_cast(&data[0]), data.size()*sizeof(short)); - outfile.Close(); + + return true; } diff --git a/src/audio/AudioWriter.h b/src/audio/AudioWriter.h index e55f33f..a23164b 100644 --- a/src/audio/AudioWriter.h +++ b/src/audio/AudioWriter.h @@ -2,20 +2,20 @@ #include #include +#include class AudioSample; using AudioSamplePtr = std::unique_ptr; +using Path = std::filesystem::path; class AudioWriter { public: AudioWriter(); - void SetPath(const std::string& path); - - void Write(const AudioSamplePtr& sample); + void setPath(const Path& path); + bool write(const AudioSamplePtr& sample); private: - - std::string mPath; + Path mPath; }; diff --git a/src/audio/audio_interfaces/AlsaInterface.cpp b/src/audio/audio_interfaces/AlsaInterface.cpp index de413a5..08e9a4a 100644 --- a/src/audio/audio_interfaces/AlsaInterface.cpp +++ b/src/audio/audio_interfaces/AlsaInterface.cpp @@ -1,5 +1,7 @@ #include "AlsaInterface.h" + #include "FileLogger.h" +#include "AudioDevice.h" #include "AudioSynth.h" @@ -23,13 +25,13 @@ std::unique_ptr AlsaInterface::Create() return std::make_unique(); } -void AlsaInterface::OpenDevice(const AudioDevicePtr& device) +void AlsaInterface::openDevice(AudioDevice* device) { MLOG_INFO("Opening Device"); snd_pcm_stream_t stream = SND_PCM_STREAM_PLAYBACK; - if (snd_pcm_open(&mHandle, device->GetName().c_str(), stream, 0) < 0) + if (snd_pcm_open(&mHandle, device->getName().c_str(), stream, 0) < 0) { - MLOG_ERROR("Error opening PCM device: " + device->GetName()); + MLOG_ERROR("Error opening PCM device: " + device->getName()); return; } @@ -40,92 +42,110 @@ void AlsaInterface::OpenDevice(const AudioDevicePtr& device) return; } - SetAccessType(device); - SetSampleFormat(device); - SetSampleRate(device); - SetPeriod(device); - SetBufferSize(device); - SetChannelNumber(device); + setAccessType(device); + setSampleFormat(device); + setSampleRate(device); + setPeriod(device); + setBufferSize(device); + setChannelNumber(device); /* Apply HW parameter settings to */ /* PCM device and prepare device */ - if (snd_pcm_hw_params(mHandle, mHardwareParams) < 0) { + if (snd_pcm_hw_params(mHandle, mHardwareParams) < 0) + { MLOG_ERROR("Error setting HW params."); return; } } -void AlsaInterface::SetAccessType(const AudioDevicePtr& device) +void AlsaInterface::setAccessType(AudioDevice* device) { - if (snd_pcm_hw_params_set_access(mHandle, mHardwareParams, SND_PCM_ACCESS_RW_INTERLEAVED) < 0) { + if (snd_pcm_hw_params_set_access(mHandle, mHardwareParams, SND_PCM_ACCESS_RW_INTERLEAVED) < 0) + { MLOG_ERROR("Error setting device access."); return; } } -void AlsaInterface::SetSampleFormat(const AudioDevicePtr& device) +bool AlsaInterface::setSampleFormat(AudioDevice* device) { /* Set sample format */ - if (snd_pcm_hw_params_set_format(mHandle, mHardwareParams, SND_PCM_FORMAT_S16_LE) < 0) { + if (snd_pcm_hw_params_set_format(mHandle, mHardwareParams, SND_PCM_FORMAT_S16_LE) < 0) + { MLOG_ERROR("Error setting format. "); - return; + return false; } + return true; } -void AlsaInterface::SetSampleRate(const AudioDevicePtr& device) +bool AlsaInterface::setSampleRate(AudioDevice* device) { - unsigned rate = device->GetSampleRate(); + unsigned rate = device->getSampleRate(); unsigned exact_rate = rate; if (snd_pcm_hw_params_set_rate_near(mHandle, mHardwareParams, &exact_rate, 0) < 0) { MLOG_ERROR("Error setting rate. "); - return; + return false; } - if (rate != exact_rate) { + + if (rate != exact_rate) + { MLOG_ERROR("The rate is not supported by your hardware."); + return false; } + return true; } -void AlsaInterface::SetPeriod(const AudioDevicePtr& device) +bool AlsaInterface::setPeriod(AudioDevice* device) { /* Set number of periods. Periods used to be called fragments. */ - if (snd_pcm_hw_params_set_periods(mHandle, mHardwareParams, device->GetPeriod(), 0) < 0) + if (snd_pcm_hw_params_set_periods(mHandle, mHardwareParams, device->getPeriod(), 0) < 0) { MLOG_ERROR("Error setting periods. "); - return; + return false; } + return true; } -void AlsaInterface::SetBufferSize(const AudioDevicePtr& device) +bool AlsaInterface::setBufferSize(AudioDevice* device) { - int periods = static_cast(device->GetPeriod()); + int periods = static_cast(device->getPeriod()); /* Set buffer size (in frames). The resulting latency is given by */ /* latency = periodsize * periods / (rate * bytes_per_frame) */ if (snd_pcm_hw_params_set_buffer_size(mHandle, mHardwareParams, (mPeriodSize * periods)>>2) < 0) { MLOG_ERROR("Error setting buffersize. "); - return; + return false; } + return true; } -void AlsaInterface::SetChannelNumber(const AudioDevicePtr& device) +bool AlsaInterface::setChannelNumber(AudioDevice* device) { /* Set number of channels */ - if (snd_pcm_hw_params_set_channels(mHandle, mHardwareParams, device->GetNumChannels()) < 0) + if (snd_pcm_hw_params_set_channels(mHandle, mHardwareParams, device->getNumChannels()) < 0) { MLOG_ERROR("Error setting channels"); - return; + return false; } + return true; } -void AlsaInterface::Play(const AudioDevicePtr& device) +void AlsaInterface::play(AudioDevice* device, AudioSample* sample, unsigned duration) { + if (!device->getIsOpen()) + { + openDevice(device); + } + + if (!device->getIsOpen()) + { + return; + } + MLOG_INFO("Playing audio"); - AudioSynth synth; - const unsigned duration = 100; - double freq = 440; - auto data = synth.GetSineWave(freq, duration)->GetChannelData(0); + const auto data = sample->getChannelData(0); int numFrames = mPeriodSize >> 2; for(int count = 0; count < duration; count++) diff --git a/src/audio/audio_interfaces/AlsaInterface.h b/src/audio/audio_interfaces/AlsaInterface.h index fe0eb2c..06c82ba 100644 --- a/src/audio/audio_interfaces/AlsaInterface.h +++ b/src/audio/audio_interfaces/AlsaInterface.h @@ -1,40 +1,39 @@ #pragma once #include "IAudioInterface.h" -#include "AudioDevice.h" -#include #include +#include class AlsaInterface : public IAudioInterface { - snd_pcm_t* mHandle; - snd_pcm_hw_params_t* mHardwareParams; - snd_pcm_uframes_t mPeriodSize; - public: - AlsaInterface(); ~AlsaInterface(); static std::unique_ptr Create(); - void OpenDevice(const AudioDevicePtr& device) override; + void play(AudioDevice* device, AudioSample* sample, unsigned duration) override; - void SetAccessType(const AudioDevicePtr& device); +private: + void openDevice(AudioDevice* device) override; - void SetSampleFormat(const AudioDevicePtr& device); + void setAccessType(AudioDevice* device); - void SetSampleRate(const AudioDevicePtr& device); + bool setSampleFormat(AudioDevice* device); - void SetPeriod(const AudioDevicePtr& device); + bool setSampleRate(AudioDevice* device); - void SetBufferSize(const AudioDevicePtr& device); + bool setPeriod(AudioDevice* device); - void SetChannelNumber(const AudioDevicePtr& device); + bool setBufferSize(AudioDevice* device); - void Play(const AudioDevicePtr& device) override; + bool setChannelNumber(AudioDevice* device); + + snd_pcm_t* mHandle; + snd_pcm_hw_params_t* mHardwareParams; + snd_pcm_uframes_t mPeriodSize; }; -using AlsaInterfacePtr = std::shared_ptr; +using AlsaInterfacePtr = std::unique_ptr; diff --git a/src/audio/audio_interfaces/IAudioInterface.h b/src/audio/audio_interfaces/IAudioInterface.h index 1cfad01..8c9aa18 100644 --- a/src/audio/audio_interfaces/IAudioInterface.h +++ b/src/audio/audio_interfaces/IAudioInterface.h @@ -3,19 +3,19 @@ #include class AudioDevice; -using AudioDevicePtr = std::unique_ptr; +class AudioSample; class IAudioInterface { public: - IAudioInterface() = default; virtual ~IAudioInterface() = default; - virtual void OpenDevice(const AudioDevicePtr& device) = 0; + virtual void play(AudioDevice* device, AudioSample* sample, unsigned duration) = 0; - virtual void Play(const AudioDevicePtr& device) = 0; +protected: + virtual void openDevice(AudioDevice* device) = 0; }; -using IAudioInterfaceUPtr = std::unique_ptr; +using IAudioInterfacePtr = std::unique_ptr; diff --git a/src/audio/audio_interfaces/NullAudioInterface.cpp b/src/audio/audio_interfaces/NullAudioInterface.cpp index fd5532f..68f01a5 100644 --- a/src/audio/audio_interfaces/NullAudioInterface.cpp +++ b/src/audio/audio_interfaces/NullAudioInterface.cpp @@ -20,12 +20,12 @@ std::unique_ptr NullAudioInterface::Create() return std::make_unique(); } -void NullAudioInterface::OpenDevice(const AudioDevicePtr& device) +void NullAudioInterface::openDevice(AudioDevice* device) { } -void NullAudioInterface::Play(const AudioDevicePtr& device) +void NullAudioInterface::play(AudioDevice* device, AudioSample* sample, unsigned duration) { } diff --git a/src/audio/audio_interfaces/NullAudioInterface.h b/src/audio/audio_interfaces/NullAudioInterface.h index 4d70cf9..bbe13c4 100644 --- a/src/audio/audio_interfaces/NullAudioInterface.h +++ b/src/audio/audio_interfaces/NullAudioInterface.h @@ -9,16 +9,18 @@ class NullAudioInterface : public IAudioInterface { public: - NullAudioInterface(); ~NullAudioInterface(); static std::unique_ptr Create(); - void OpenDevice(const AudioDevicePtr& device) override; + void openDevice(AudioDevice* device) override; - void Play(const AudioDevicePtr& device) override; + void play(AudioDevice* device, AudioSample* sample, unsigned duration) override; + +private: + void openDevice(const AudioDevicePtr& device) override; }; using NullAudioInterfacePtr = std::shared_ptr; diff --git a/src/audio/midi/reader/MidiChannelEventAdapter.cpp b/src/audio/midi/reader/MidiChannelEventAdapter.cpp index f06d540..fc37dd7 100644 --- a/src/audio/midi/reader/MidiChannelEventAdapter.cpp +++ b/src/audio/midi/reader/MidiChannelEventAdapter.cpp @@ -13,7 +13,7 @@ int MidiChannelEventAdapter::ReadEvent(std::ifstream* file, char firstByte, Midi int event_type = (firstByte & first_four_bits) >> 4; int midi_channel = (firstByte & second_four_bits) >> 4; unsigned byteCount = 0; - std::cout << "Channel: " << midi_channel << std::endl; + //std::cout << "Channel: " << midi_channel << std::endl; const bool isStatusByte = ByteUtils::MostSignificantBitIsOne(firstByte); if(isStatusByte) @@ -25,7 +25,7 @@ int MidiChannelEventAdapter::ReadEvent(std::ifstream* file, char firstByte, Midi { event->SetType(lastEventType); } - std::cout << "MC Type " << static_cast(event->GetType()) << std::endl; + //std::cout << "MC Type " << static_cast(event->GetType()) << std::endl; switch(event->GetType()) { case MidiChannelEvent::Type::NOTE_ON: @@ -49,7 +49,7 @@ int MidiChannelEventAdapter::ReadEvent(std::ifstream* file, char firstByte, Midi break; } default: - std::cout << "Unknown status event: " << std::bitset<8>(firstByte) << "|" << event_type <(firstByte) << "|" << event_type <SetType(c); - std::cout << "Meta event type: " << std::hex << int(c) << std::dec<GetType()) { diff --git a/src/audio/midi/reader/MidiReader.cpp b/src/audio/midi/reader/MidiReader.cpp index f6e8160..142bdca 100644 --- a/src/audio/midi/reader/MidiReader.cpp +++ b/src/audio/midi/reader/MidiReader.cpp @@ -22,32 +22,32 @@ MidiReader::MidiReader() } -MidiDocument* MidiReader::GetDocument() const +MidiDocument* MidiReader::getDocument() const { return mDocument.get(); } -bool MidiReader::ProcessHeader() +bool MidiReader::processHeader() { - if(!BinaryStream::checkNextDWord(mFile->GetInHandle(), HeaderLabel)) + if(!BinaryStream::checkNextDWord(mFile->getInHandle(), HeaderLabel)) { return false; } - const auto length = BinaryStream::getNextDWord(mFile->GetInHandle()); + const auto length = BinaryStream::getNextDWord(mFile->getInHandle()); if(!length) { return false; } - const auto formatType = BinaryStream::getNextWord(mFile->GetInHandle()); + const auto formatType = BinaryStream::getNextWord(mFile->getInHandle()); if(!formatType) { return false; } mDocument->SetFormatType(*formatType); - const auto expectedTracks = BinaryStream::getNextWord(mFile->GetInHandle()); + const auto expectedTracks = BinaryStream::getNextWord(mFile->getInHandle()); if(!expectedTracks) { return false; @@ -55,52 +55,52 @@ bool MidiReader::ProcessHeader() mDocument->SetExpectedTracks(*expectedTracks); MidiTimeDivision timeDivision; - MidiTimeAdapter::ReadTimeDivision(mFile->GetInHandle(), timeDivision); + MidiTimeAdapter::ReadTimeDivision(mFile->getInHandle(), timeDivision); mDocument->SetTimeDivision(timeDivision); return true; } -int MidiReader::ProcessEvent(MidiTrack* track) +int MidiReader::processEvent(MidiTrack* track) { int timeDelta {0}; unsigned byteCount {0}; - byteCount += MidiTimeAdapter::ReadEventTimeDelta(mFile->GetInHandle(), timeDelta); + byteCount += MidiTimeAdapter::ReadEventTimeDelta(mFile->getInHandle(), timeDelta); char c; - mFile->GetInHandle()->get(c); - std::cout << "Event check: " << std::bitset<8>(c) << std::endl; + mFile->getInHandle()->get(c); + //std::cout << "Event check: " << std::bitset<8>(c) << std::endl; byteCount++; if(MidiEvent::IsMetaEvent(c)) { auto event = std::make_unique(); event->SetTimeDelta(timeDelta); - std::cout << "Meta event " <GetInHandle(), event.get(), mLastMidiChannel); + //std::cout << "Meta event " <getInHandle(), event.get(), mLastMidiChannel); track->AddEvent(std::move(event)); } else if(MidiEvent::IsSysExEvent(c)) { - std::cout << "Sysex event" << std::endl; + //std::cout << "Sysex event" << std::endl; } else { // Midi event auto event = std::make_unique(); event->SetTimeDelta(timeDelta); - std::cout << "Midi event" << std::endl; - byteCount += MidiChannelEventAdapter::ReadEvent(mFile->GetInHandle(), c, event.get(), mLastChannelEventType); + //std::cout << "Midi event" << std::endl; + byteCount += MidiChannelEventAdapter::ReadEvent(mFile->getInHandle(), c, event.get(), mLastChannelEventType); track->AddEvent(std::move(event)); } return byteCount; } -bool MidiReader::ProcessTrackChunk(bool debug) +bool MidiReader::processTrackChunk(bool debug) { - if(!BinaryStream::checkNextDWord(mFile->GetInHandle(), TrackChunkLabel)) + if(!BinaryStream::checkNextDWord(mFile->getInHandle(), TrackChunkLabel)) { return false; } - const auto chunkSize = BinaryStream::getNextDWord(mFile->GetInHandle()); + const auto chunkSize = BinaryStream::getNextDWord(mFile->getInHandle()); if(!chunkSize) { return false; @@ -111,9 +111,9 @@ bool MidiReader::ProcessTrackChunk(bool debug) unsigned iter_count = 0; while(byteCount < static_cast(*chunkSize)) { - std::cout << "-------------" << std::endl; - byteCount += ProcessEvent(track.get()); - std::cout << "Track byte count: " << byteCount << " of " << *chunkSize << std::endl; + //std::cout << "-------------" << std::endl; + byteCount += processEvent(track.get()); + //std::cout << "Track byte count: " << byteCount << " of " << *chunkSize << std::endl; if(debug && iter_count == 40) { return true; @@ -124,30 +124,30 @@ bool MidiReader::ProcessTrackChunk(bool debug) return true; } -void MidiReader::Read(const std::string& path) +void MidiReader::read(const Path& path) { mFile = std::make_unique(path); - mFile->Open(true); - if(!ProcessHeader()) + mFile->open(File::AccessMode::Read); + if(!processHeader()) { MLOG_ERROR("Problem processing header"); return; } int trackCount = 0; - if(!ProcessTrackChunk(false)) + if(!processTrackChunk(false)) { MLOG_ERROR("Problem processing track chunk"); return; } trackCount++; - if(!ProcessTrackChunk(true)) + if(!processTrackChunk(true)) { MLOG_ERROR("Problem processing track chunk"); return; } trackCount++; - mFile->Close(); + mFile->close(); } diff --git a/src/audio/midi/reader/MidiReader.h b/src/audio/midi/reader/MidiReader.h index 57db097..ea7219f 100644 --- a/src/audio/midi/reader/MidiReader.h +++ b/src/audio/midi/reader/MidiReader.h @@ -5,29 +5,30 @@ #include "MidiTrack.h" #include "MidiChannelEvent.h" #include "File.h" + +#include #include +using Path = std::filesystem::path; + class MidiReader { static constexpr const char TrackChunkLabel[] = "MTrk"; static constexpr const char HeaderLabel[] = "MThd"; public: - MidiReader(); - void Read(const std::string& path); + MidiDocument* getDocument() const; - MidiDocument* GetDocument() const; + void read(const Path& path); private: - - bool ProcessHeader(); - bool ProcessTrackChunk(bool debug=false); - int ProcessEvent(MidiTrack* track); + bool processHeader(); + bool processTrackChunk(bool debug=false); + int processEvent(MidiTrack* track); private: - std::unique_ptr mFile; MidiDocumentPtr mDocument; int mLastMidiChannel {0}; diff --git a/src/audio/midi/reader/MidiTimeAdapter.cpp b/src/audio/midi/reader/MidiTimeAdapter.cpp index 3ce2978..106ebae 100644 --- a/src/audio/midi/reader/MidiTimeAdapter.cpp +++ b/src/audio/midi/reader/MidiTimeAdapter.cpp @@ -16,14 +16,14 @@ int MidiTimeAdapter::ReadEventTimeDelta(std::ifstream* file, int& delta) if(!ByteUtils::MostSignificantBitIsOne(c)) { delta = int(c); - std::cout << "Time delta final: " << delta << std::endl; + //std::cout << "Time delta final: " << delta << std::endl; return byteCount; } int working_c = c; int final_c = 0; unsigned count = 0; - std::cout << "Working " << std::bitset<8>(working_c) << std::endl; + //std::cout << "Working " << std::bitset<8>(working_c) << std::endl; while(unsigned(working_c >> 7) != 0) { char corrected = (working_c &= ~(1UL << 7)); @@ -33,16 +33,16 @@ int MidiTimeAdapter::ReadEventTimeDelta(std::ifstream* file, int& delta) file->get(file_c); byteCount++; working_c = int(file_c); - std::cout << "Working " << std::bitset<8>(working_c) << std::endl; + //std::cout << "Working " << std::bitset<8>(working_c) << std::endl; count++; } - std::cout << "Time delta start: " << std::bitset<16>(final_c) << std::endl; + //std::cout << "Time delta start: " << std::bitset<16>(final_c) << std::endl; final_c <<= 7; - std::cout << "Time delta pre: " << std::bitset<16>(final_c) << std::endl; + //std::cout << "Time delta pre: " << std::bitset<16>(final_c) << std::endl; final_c |= (working_c << 7*(count-1)); delta = int(final_c); - std::cout << "Time delta final: " << delta << "|" << std::bitset<16>(final_c)<< std::endl; + //std::cout << "Time delta final: " << delta << "|" << std::bitset<16>(final_c)<< std::endl; return byteCount; } diff --git a/src/compiler/TemplateFile.cpp b/src/compiler/TemplateFile.cpp index e6fd90c..113f6a6 100644 --- a/src/compiler/TemplateFile.cpp +++ b/src/compiler/TemplateFile.cpp @@ -21,7 +21,7 @@ std::string TemplateFile::getName() const void TemplateFile::loadContent() { - std::cout << "Trying to load file at: " << mPath << std::endl; + //std::cout << "Trying to load file at: " << mPath << std::endl; mRawContent = File(mPath).readLines(); mWorkingLine = 0; mWorkingNode = mRootNode.get(); @@ -183,5 +183,5 @@ void TemplateFile::onFoundExpression(const std::string& expression_string) void TemplateFile::dumpContent() { auto content = mRootNode->getRawContent(); - std::cout << content << std::endl; -} \ No newline at end of file + //std::cout << content << std::endl; +} diff --git a/src/console/MainApplication.cpp b/src/console/MainApplication.cpp index 51f98f4..dc61a19 100644 --- a/src/console/MainApplication.cpp +++ b/src/console/MainApplication.cpp @@ -49,7 +49,7 @@ void MainApplication::initialize(CommandLineArgsUPtr commandLineArgs, std::uniqu MLOG_INFO("Launched"); mDatabaseManager = DatabaseManager::Create(); - mDatabaseManager->CreateDatabase(launch_path + "/database.db"); + mDatabaseManager->openDatabase(launch_path + "/database.db"); MLOG_INFO("Created DB"); mNetworkManager = NetworkManager::Create(); @@ -122,7 +122,7 @@ void MainApplication::playAudio() { //MidiReader reader; //reader.Read("/home/james/sample.mid"); - mAudioManager->Play(); + //mAudioManager->Play(); } void MainApplication::convertDocument(const std::string& inputPath, const std::string& outputPath) @@ -131,7 +131,7 @@ void MainApplication::convertDocument(const std::string& inputPath, const std::s auto output_file = File(std::filesystem::path(outputPath)); MLOG_INFO("Converting: " + inputPath + " to " + outputPath); DocumentConverter converter; - converter.Convert(&input_file, &output_file); + converter.convert(&input_file, &output_file); } CommandLineArgs* MainApplication::getCommandLineArgs() const @@ -141,7 +141,7 @@ CommandLineArgs* MainApplication::getCommandLineArgs() const void MainApplication::shutDown() { - mDatabaseManager->OnShutDown(); + mDatabaseManager->onShutDown(); mNetworkManager->ShutDown(); MLOG_INFO("Shut down"); FileLogger::GetInstance().Close(); diff --git a/src/core/file_utilities/File.cpp b/src/core/file_utilities/File.cpp index b904a8b..e570fb4 100644 --- a/src/core/file_utilities/File.cpp +++ b/src/core/file_utilities/File.cpp @@ -4,32 +4,29 @@ #include "ByteUtils.h" #include -#include #include File::File(std::filesystem::path path) : mFullPath(path), mInHandle(), - mOutHandle(), - mAccessMode(AccessMode::Read) + mOutHandle() { } -std::string File::GetExtension() const +File::~File() +{ + close(); +} + +std::string File::getExtension() const { return mFullPath.extension().string(); } -void File::SetAccessMode(AccessMode mode) -{ - mAccessMode = mode; -} - std::string File::dumpBinary() { - mAccessMode = AccessMode::Read; - Open(); + open(AccessMode::Read); std::stringstream sstr; sstr << "Count | Binary | Decimal | ASCII \n"; @@ -46,22 +43,30 @@ std::string File::dumpBinary() count++; } const auto out = sstr.str(); - Close(); + close(); return out; } -std::ifstream* File::GetInHandle() const +std::ifstream* File::getInHandle() const { return mInHandle.get(); } -std::ofstream* File::GetOutHandle() const +std::ofstream* File::getOutHandle() const { return mOutHandle.get(); } std::optional File::readNextByte() { + if (!mInHandle) + { + if (!open(AccessMode::Read)) + { + return std::nullopt; + } + } + if (mInHandle->good()) { if (auto val = mInHandle->get(); val == EOF) @@ -79,31 +84,39 @@ std::optional File::readNextByte() } } -void File::Open(bool asBinary) +bool File::open(AccessMode accessMode) { - if(mAccessMode == AccessMode::Read) + if (mFullPath.is_absolute() && !std::filesystem::exists(mFullPath.parent_path())) + { + std::filesystem::create_directories(mFullPath.parent_path()); + } + + if(accessMode == AccessMode::Read) { auto flags = std::ifstream::in; - if (asBinary) - { - //flags |= std::ifstream::binary; - } mInHandle = std::make_unique(); mInHandle->open(mFullPath, flags); + if (!mInHandle->is_open()) + { + MLOG_ERROR("Failed to open file at :" << mFullPath); + return false; + } } else { auto flags = std::ofstream::out; - if (asBinary) - { - //flags |= std::ofstream::binary; - } mOutHandle = std::make_unique(); mOutHandle->open(mFullPath, flags); + if (!mOutHandle->is_open()) + { + MLOG_ERROR("Failed to open file at :" << mFullPath); + return false; + } } + return true; } -void File::Close() +void File::close() { if(mOutHandle) { @@ -115,27 +128,29 @@ void File::Close() } } -FileFormat::Format File::InferFormat() const +FileFormat::Format File::inferFormat() const { - const auto extension = GetExtension(); + const auto extension = getExtension(); return FileFormat::InferFormat(extension); } -void File::WriteText(const std::string& text) +void File::writeText(const std::string& text) { - bool had_to_open{ false }; + bool had_to_open{false}; if (!mOutHandle) { had_to_open = true; - SetAccessMode(File::AccessMode::Write); - Open(); + if (!open(File::AccessMode::Write)) + { + return; + } } (*mOutHandle) << text; if (had_to_open) { - Close(); + close(); } } @@ -143,12 +158,15 @@ std::vector File::readLines() { std::vector content; - if (!PathExists()) + if (!pathExists()) { - return content; + return {}; } - Open(false); + if(!open(AccessMode::Read)) + { + return {}; + } std::string str; while(std::getline(*mInHandle, str)) @@ -156,23 +174,26 @@ std::vector File::readLines() content.push_back(str); } - Close(); + close(); return content; } std::string File::read() { - if (!PathExists()) + if (!pathExists()) { return {}; } - Open(false); + if(!open(AccessMode::Read)) + { + return {}; + } std::stringstream buffer; buffer << mInHandle->rdbuf(); - Close(); + close(); return buffer.str(); } @@ -181,14 +202,24 @@ std::string File::getBaseFilename(const Path& path) return path.stem().string(); } -std::string File::ReadText() +std::string File::readText() { + if (!pathExists()) + { + return {}; + } + + if(!open(AccessMode::Read)) + { + return {}; + } + std::string str((std::istreambuf_iterator(*mInHandle)), std::istreambuf_iterator()); return str; } -bool File::PathExists() const +bool File::pathExists() const { return std::filesystem::exists(mFullPath); } diff --git a/src/core/file_utilities/File.h b/src/core/file_utilities/File.h index 2509be7..b5acba0 100644 --- a/src/core/file_utilities/File.h +++ b/src/core/file_utilities/File.h @@ -23,42 +23,38 @@ public: File(std::filesystem::path fullPath); - std::string GetExtension() const; + ~File(); - FileFormat::Format InferFormat() const; + void close(); - std::ifstream* GetInHandle() const; + std::string dumpBinary(); - std::ofstream* GetOutHandle() const; + static std::string getBaseFilename(const Path& path); - void WriteText(const std::string& text); + std::string getExtension() const; - std::string ReadText(); + std::ifstream* getInHandle() const; + + std::ofstream* getOutHandle() const; + + FileFormat::Format inferFormat() const; + + std::string readText(); std::vector readLines(); std::string read(); - static std::string getBaseFilename(const Path& path); + bool pathExists() const; - bool PathExists() const; - - void SetAccessMode(AccessMode mode); - - void Open(bool asBinary = false); - - void Close(); + bool open(AccessMode mode); std::optional readNextByte(); - std::string dumpBinary(); - + void writeText(const std::string& text); private: - std::filesystem::path mFullPath; std::unique_ptr mInHandle; std::unique_ptr mOutHandle; - AccessMode mAccessMode; - }; diff --git a/src/core/serializers/TomlReader.cpp b/src/core/serializers/TomlReader.cpp index ff40272..3693a4f 100644 --- a/src/core/serializers/TomlReader.cpp +++ b/src/core/serializers/TomlReader.cpp @@ -119,17 +119,15 @@ void TomlReader::processLine(const std::string& line) if (in_comment) { mWorkingTable->addComment({ mLastSectionOffset, working_string }); - } + } else if (in_header) { onHeader(working_string); } else if (found_key) { - std::locale locale; - key_string.erase(std::remove_if(key_string.begin(), key_string.end(), [locale](unsigned char c) {return std::isspace(c, locale); }), key_string.end()); - working_string.erase(std::remove_if(working_string.begin(), working_string.end(), [locale](unsigned char c) {return std::isspace(c, locale); }), working_string.end()); - + key_string.erase(std::remove_if(key_string.begin(), key_string.end(), [](char c) {return std::isspace(c); }), key_string.end()); + working_string.erase(std::remove_if(working_string.begin(), working_string.end(), [](char c) {return std::isspace(c); }), working_string.end()); if (working_string.size()>2 && working_string[0] == '"' && working_string[working_string.size() - 1] == '"') { working_string = working_string.substr(1, working_string.size() - 2); diff --git a/src/core/serializers/TomlReader.h b/src/core/serializers/TomlReader.h index 1f4a2de..346fd98 100644 --- a/src/core/serializers/TomlReader.h +++ b/src/core/serializers/TomlReader.h @@ -12,61 +12,61 @@ using Path = std::filesystem::path; class TomlTable { public: - using Comment = std::pair; - using KeyValuePairs = std::unordered_map; + using Comment = std::pair; + using KeyValuePairs = std::unordered_map; - TomlTable(const std::string& header); + TomlTable(const std::string& header); - void addComment(const Comment& comment); + void addComment(const Comment& comment); - void addTable(std::unique_ptr table); + void addTable(std::unique_ptr table); - void addKeyValuePair(const std::string& key, const std::string& value); + void addKeyValuePair(const std::string& key, const std::string& value); - std::string getHeader() const; + std::string getHeader() const; - TomlTable* getTable(const std::string& path); + TomlTable* getTable(const std::string& path); - KeyValuePairs getKeyValuePairs() const; + KeyValuePairs getKeyValuePairs() const; private: - unsigned mLineOffset{ 0 }; - std::string mHeader; - std::unordered_map > mTables; - KeyValuePairs mMap; - std::vector mComments; + unsigned mLineOffset{ 0 }; + std::string mHeader; + std::unordered_map > mTables; + KeyValuePairs mMap; + std::vector mComments; }; class TomlContent { public: - TomlContent(); + TomlContent(); - TomlTable* getRootTable() const; + TomlTable* getRootTable() const; - TomlTable* getTable(const std::string& path) const; + TomlTable* getTable(const std::string& path) const; private: - std::unique_ptr mRootTable; + std::unique_ptr mRootTable; }; class TomlReader { public: - TomlReader(); + TomlReader(); - TomlContent* getContent() const; + TomlContent* getContent() const; - void read(const Path& input_path); + void read(const Path& input_path); - void processLine(const std::string& line); + void processLine(const std::string& line); - void onHeader(const std::string& header); + void onHeader(const std::string& header); - void onKeyValuePair(const std::string key, const std::string value); + void onKeyValuePair(const std::string key, const std::string value); private: - unsigned mLastSectionOffset{ 0 }; - std::unique_ptr mContent; - TomlTable* mWorkingTable{nullptr}; -}; \ No newline at end of file + unsigned mLastSectionOffset{ 0 }; + std::unique_ptr mContent; + TomlTable* mWorkingTable{nullptr}; +}; diff --git a/src/database/Database.cpp b/src/database/Database.cpp index 33d067b..e99e037 100644 --- a/src/database/Database.cpp +++ b/src/database/Database.cpp @@ -16,12 +16,12 @@ std::unique_ptr Database::Create() return std::make_unique(); } -void Database::SetPath(const std::string& path) +void Database::setPath(const Path& path) { mPath = path; } -std::string Database::GetPath() const +const Path& Database::getPath() const { return mPath; } diff --git a/src/database/Database.h b/src/database/Database.h index a645ec1..29e3928 100644 --- a/src/database/Database.h +++ b/src/database/Database.h @@ -2,23 +2,25 @@ #include #include +#include + +using Path = std::filesystem::path; class Database { - - std::string mPath; - public: - Database(); ~Database(); static std::unique_ptr Create(); - void SetPath(const std::string& path); + const Path& getPath() const; - std::string GetPath() const; + void setPath(const Path& path); + +private: + Path mPath; }; using DatabasePtr = std::unique_ptr; diff --git a/src/database/DatabaseManager.cpp b/src/database/DatabaseManager.cpp index 025f790..4ef3017 100644 --- a/src/database/DatabaseManager.cpp +++ b/src/database/DatabaseManager.cpp @@ -18,24 +18,24 @@ std::unique_ptr DatabaseManager::Create() } -void DatabaseManager::CreateDatabase(const std::string& path) +void DatabaseManager::openDatabase(const Path& path) { mDatabase = Database::Create(); - mDatabase->SetPath(path); + mDatabase->setPath(path); mDatabaseInterface = SqliteInterface::Create(); - mDatabaseInterface->Open(mDatabase); + mDatabaseInterface->open(mDatabase.get()); } -void DatabaseManager::Run(const std::string& statement) +void DatabaseManager::run(const std::string& statement) { - mDatabaseInterface->Run(statement); + mDatabaseInterface->run(statement); } -void DatabaseManager::OnShutDown() +void DatabaseManager::onShutDown() { if(mDatabaseInterface) { - mDatabaseInterface->Close(); + mDatabaseInterface->close(); } } diff --git a/src/database/DatabaseManager.h b/src/database/DatabaseManager.h index 239763e..b8e2d61 100644 --- a/src/database/DatabaseManager.h +++ b/src/database/DatabaseManager.h @@ -1,25 +1,27 @@ #pragma once -#include - #include "SqliteInterface.h" #include "Database.h" +#include +#include + +using Path = std::filesystem::path; + class DatabaseManager { public: - DatabaseManager(); ~DatabaseManager(); static std::unique_ptr Create(); - void CreateDatabase(const std::string& path); + void openDatabase(const Path& path); - void Run(const std::string& statement); + void run(const std::string& statement); - void OnShutDown(); + void onShutDown(); private: DatabasePtr mDatabase; diff --git a/src/database/database_interfaces/SqliteInterface.cpp b/src/database/database_interfaces/SqliteInterface.cpp index 2c28fe6..955a437 100644 --- a/src/database/database_interfaces/SqliteInterface.cpp +++ b/src/database/database_interfaces/SqliteInterface.cpp @@ -18,9 +18,9 @@ std::unique_ptr SqliteInterface::Create() return std::make_unique(); } -void SqliteInterface::Open(const DatabasePtr& db) +void SqliteInterface::open(Database* db) { - int rc = sqlite3_open(db->GetPath().c_str(), &mSqliteDb); + int rc = sqlite3_open(db->getPath().c_str(), &mSqliteDb); if( rc ) { MLOG_ERROR("Can't open database: %s\n" << sqlite3_errmsg(mSqliteDb)); @@ -39,7 +39,7 @@ static int callback(void *NotUsed, int argc, char **argv, char **azColName) return 0; } -void SqliteInterface::Run(const std::string& statement) +void SqliteInterface::run(const std::string& statement) { char *zErrMsg = 0; int rc = sqlite3_exec(mSqliteDb, statement.c_str(), callback, 0, &zErrMsg); @@ -50,7 +50,7 @@ void SqliteInterface::Run(const std::string& statement) } } -void SqliteInterface::Close() +void SqliteInterface::close() { sqlite3_close(mSqliteDb); } diff --git a/src/database/database_interfaces/SqliteInterface.h b/src/database/database_interfaces/SqliteInterface.h index f43ba39..5eb9bef 100644 --- a/src/database/database_interfaces/SqliteInterface.h +++ b/src/database/database_interfaces/SqliteInterface.h @@ -6,21 +6,21 @@ class SqliteInterface { - sqlite3* mSqliteDb; - public: - SqliteInterface(); ~SqliteInterface(); static std::unique_ptr Create(); - void Open(const DatabasePtr& db); + void open(Database* db); - void Close(); + void close(); - void Run(const std::string& statement); + void run(const std::string& statement); + +private: + sqlite3* mSqliteDb; }; using SqliteInterfacePtr = std::unique_ptr; diff --git a/src/fonts/FontReader.cpp b/src/fonts/FontReader.cpp index b1c1385..8e67d65 100644 --- a/src/fonts/FontReader.cpp +++ b/src/fonts/FontReader.cpp @@ -19,19 +19,19 @@ void FontReader::setPath(const std::string& path) bool FontReader::readOffsetSubtable() { - mOffsetSubtable.scaler_type = *BinaryStream::getNextDWord(mFile->GetInHandle()); + mOffsetSubtable.scaler_type = *BinaryStream::getNextDWord(mFile->getInHandle()); mCurrentOffset += 4; - mOffsetSubtable.num_tables = *BinaryStream::getNextWord(mFile->GetInHandle()); + mOffsetSubtable.num_tables = *BinaryStream::getNextWord(mFile->getInHandle()); mCurrentOffset += 2; - mOffsetSubtable.search_range = *BinaryStream::getNextWord(mFile->GetInHandle()); + mOffsetSubtable.search_range = *BinaryStream::getNextWord(mFile->getInHandle()); mCurrentOffset += 2; - mOffsetSubtable.entry_selector = *BinaryStream::getNextWord(mFile->GetInHandle()); + mOffsetSubtable.entry_selector = *BinaryStream::getNextWord(mFile->getInHandle()); mCurrentOffset += 2; - mOffsetSubtable.range_shift = *BinaryStream::getNextWord(mFile->GetInHandle()); + mOffsetSubtable.range_shift = *BinaryStream::getNextWord(mFile->getInHandle()); mCurrentOffset += 2; return true; @@ -60,16 +60,16 @@ void FontReader::readTableDirectory() for (unsigned idx=0; idxGetInHandle(), table.name, 4); + BinaryStream::getNextString(mFile->getInHandle(), table.name, 4); mCurrentOffset += 4; - table.checksum = *BinaryStream::getNextDWord(mFile->GetInHandle()); + table.checksum = *BinaryStream::getNextDWord(mFile->getInHandle()); mCurrentOffset += 4; - table.offset = *BinaryStream::getNextDWord(mFile->GetInHandle()); + table.offset = *BinaryStream::getNextDWord(mFile->getInHandle()); mCurrentOffset += 4; - table.length = *BinaryStream::getNextDWord(mFile->GetInHandle()); + table.length = *BinaryStream::getNextDWord(mFile->getInHandle()); mCurrentOffset += 4; logTable(table); @@ -117,29 +117,29 @@ void FontReader::readHeadTable() TrueTypeFont::HeadTable table; - table.version = *BinaryStream::getNextDWord(mFile->GetInHandle()); - table.fontRevision = *BinaryStream::getNextDWord(mFile->GetInHandle()); - table.checksumAdjustment = *BinaryStream::getNextDWord(mFile->GetInHandle()); - table.magicNumber = *BinaryStream::getNextDWord(mFile->GetInHandle()); + table.version = *BinaryStream::getNextDWord(mFile->getInHandle()); + table.fontRevision = *BinaryStream::getNextDWord(mFile->getInHandle()); + table.checksumAdjustment = *BinaryStream::getNextDWord(mFile->getInHandle()); + table.magicNumber = *BinaryStream::getNextDWord(mFile->getInHandle()); mCurrentOffset += 16; - table.flags = *BinaryStream::getNextWord(mFile->GetInHandle()); - table.unitsPerEm = *BinaryStream::getNextWord(mFile->GetInHandle()); + table.flags = *BinaryStream::getNextWord(mFile->getInHandle()); + table.unitsPerEm = *BinaryStream::getNextWord(mFile->getInHandle()); mCurrentOffset += 4; - table.created = *BinaryStream::getNextQWord(mFile->GetInHandle()); - table.modified = *BinaryStream::getNextQWord(mFile->GetInHandle()); + table.created = *BinaryStream::getNextQWord(mFile->getInHandle()); + table.modified = *BinaryStream::getNextQWord(mFile->getInHandle()); mCurrentOffset += 16; - table.xMin = *BinaryStream::getNextWord(mFile->GetInHandle()); - table.yMin = *BinaryStream::getNextWord(mFile->GetInHandle()); - table.xMax = *BinaryStream::getNextWord(mFile->GetInHandle()); - table.yMax = *BinaryStream::getNextWord(mFile->GetInHandle()); - table.macStyle = *BinaryStream::getNextWord(mFile->GetInHandle()); - table.lowestRecPPEM = *BinaryStream::getNextWord(mFile->GetInHandle()); - table.fontDirectionHint = *BinaryStream::getNextWord(mFile->GetInHandle()); - table.indexToLocFormat = *BinaryStream::getNextWord(mFile->GetInHandle()); - table.glyphDataFormat = *BinaryStream::getNextWord(mFile->GetInHandle()); + table.xMin = *BinaryStream::getNextWord(mFile->getInHandle()); + table.yMin = *BinaryStream::getNextWord(mFile->getInHandle()); + table.xMax = *BinaryStream::getNextWord(mFile->getInHandle()); + table.yMax = *BinaryStream::getNextWord(mFile->getInHandle()); + table.macStyle = *BinaryStream::getNextWord(mFile->getInHandle()); + table.lowestRecPPEM = *BinaryStream::getNextWord(mFile->getInHandle()); + table.fontDirectionHint = *BinaryStream::getNextWord(mFile->getInHandle()); + table.indexToLocFormat = *BinaryStream::getNextWord(mFile->getInHandle()); + table.glyphDataFormat = *BinaryStream::getNextWord(mFile->getInHandle()); mCurrentOffset += 18; @@ -154,7 +154,7 @@ std::unique_ptr FontReader::read() mWorkingFont = std::make_unique(); mFile = std::make_unique(mPath); - mFile->Open(true); + mFile->open(File::AccessMode::Read); readOffsetSubtable(); //std::cout << "Current offset: " << mCurrentOffset << std::endl; @@ -171,7 +171,7 @@ std::unique_ptr FontReader::read() break; } - mFile->Close(); + mFile->close(); diff --git a/src/image/png/PngReader.cpp b/src/image/png/PngReader.cpp index 72e2cac..34b22b2 100644 --- a/src/image/png/PngReader.cpp +++ b/src/image/png/PngReader.cpp @@ -25,14 +25,14 @@ void PngReader::setPath(const Path& path) bool PngReader::checkSignature() { const int highBitCheck = 0x89; - const auto firstPos = mFile->GetInHandle()->get(); + const auto firstPos = mFile->getInHandle()->get(); if (firstPos != highBitCheck) { return false; } std::string fileType; - BinaryStream::getNextString(mFile->GetInHandle(), fileType, 3); + BinaryStream::getNextString(mFile->getInHandle(), fileType, 3); if (fileType != "PNG") { return false; @@ -41,7 +41,7 @@ bool PngReader::checkSignature() std::vector sequence{13, 10, 26, 10}; for (auto c : sequence) { - if (mFile->GetInHandle()->get() != c) + if (mFile->getInHandle()->get() != c) { return false; } @@ -51,10 +51,10 @@ bool PngReader::checkSignature() bool PngReader::readChunk() { - unsigned length = *BinaryStream::getNextDWord(mFile->GetInHandle()); + unsigned length = *BinaryStream::getNextDWord(mFile->getInHandle()); std::string chunkType; - BinaryStream::getNextString(mFile->GetInHandle(), chunkType, 4); + BinaryStream::getNextString(mFile->getInHandle(), chunkType, 4); //std::cout << "Got chunk with type: " << chunkType << " and length: " << length << std::endl; bool lastChunk = false; @@ -72,7 +72,7 @@ bool PngReader::readChunk() { decodeData(); } - unsigned crcCheck = *BinaryStream::getNextDWord(mFile->GetInHandle()); + unsigned crcCheck = *BinaryStream::getNextDWord(mFile->getInHandle()); } else if(chunkType == "IDAT") { @@ -91,31 +91,31 @@ bool PngReader::readChunk() for(unsigned idx=0;idxGetInHandle()->get(); + mFile->getInHandle()->get(); } - unsigned crcCheck = *BinaryStream::getNextDWord(mFile->GetInHandle()); + unsigned crcCheck = *BinaryStream::getNextDWord(mFile->getInHandle()); } return !lastChunk; } bool PngReader::readHeaderChunk() { - auto width = *BinaryStream::getNextDWord(mFile->GetInHandle()); - auto height = *BinaryStream::getNextDWord(mFile->GetInHandle()); - auto bitDepth = mFile->GetInHandle()->get(); + auto width = *BinaryStream::getNextDWord(mFile->getInHandle()); + auto height = *BinaryStream::getNextDWord(mFile->getInHandle()); + auto bitDepth = mFile->getInHandle()->get(); mHeader.setImageData(width, height, bitDepth); PngInfo info; - info.mColorType = static_cast(mFile->GetInHandle()->get()); - info.mCompressionMethod = static_cast(mFile->GetInHandle()->get()); - info.mFilterMethod = static_cast(mFile->GetInHandle()->get()); - info.mInterlaceMethod = static_cast(mFile->GetInHandle()->get()); + info.mColorType = static_cast(mFile->getInHandle()->get()); + info.mCompressionMethod = static_cast(mFile->getInHandle()->get()); + info.mFilterMethod = static_cast(mFile->getInHandle()->get()); + info.mInterlaceMethod = static_cast(mFile->getInHandle()->get()); mHeader.setPngInfo(info); mHeader.updateData(); - uint32_t file_crc = *BinaryStream::getNextDWord(mFile->GetInHandle()); + uint32_t file_crc = *BinaryStream::getNextDWord(mFile->getInHandle()); auto crc_calc = mHeader.getCrc(); //std::cout << mHeader.toString() << "*************\n"; @@ -147,7 +147,7 @@ bool PngReader::readIDATChunk(unsigned length) } mInputStream->clearChecksumCalculator(); - uint32_t file_crc = *BinaryStream::getNextDWord(mFile->GetInHandle()); + uint32_t file_crc = *BinaryStream::getNextDWord(mFile->getInHandle()); auto crc_calc = crc_check->getChecksum(); if (file_crc != crc_calc) @@ -166,7 +166,7 @@ std::unique_ptr > PngReader::read() auto image = std::make_unique >(5, 5); mFile = std::make_unique(mPath); - mFile->Open(true); + mFile->open(File::AccessMode::Read); if (!checkSignature()) { diff --git a/src/image/png/PngWriter.cpp b/src/image/png/PngWriter.cpp index 2b1e638..b95cbe2 100644 --- a/src/image/png/PngWriter.cpp +++ b/src/image/png/PngWriter.cpp @@ -161,9 +161,8 @@ void PngWriter::write(const std::unique_ptr >& image) if (!mPath.empty()) { mWorkingFile = std::make_unique(mPath); - mWorkingFile->SetAccessMode(File::AccessMode::Write); - mWorkingFile->Open(true); - mOutStream = std::make_unique(mWorkingFile->GetOutHandle()); + mWorkingFile->open(File::AccessMode::Write); + mOutStream = std::make_unique(mWorkingFile->getOutHandle()); } else { @@ -218,6 +217,6 @@ void PngWriter::write(const std::unique_ptr >& image) if (mWorkingFile) { - mWorkingFile->Close(); + mWorkingFile->close(); } } diff --git a/src/web/DocumentConverter.cpp b/src/web/DocumentConverter.cpp index 114e268..43a79ae 100644 --- a/src/web/DocumentConverter.cpp +++ b/src/web/DocumentConverter.cpp @@ -2,6 +2,7 @@ #include "MarkdownParser.h" #include "HtmlWriter.h" #include "FileLogger.h" +#include "File.h" #include DocumentConverter::DocumentConverter() @@ -9,19 +10,19 @@ DocumentConverter::DocumentConverter() } -void DocumentConverter::Convert(File* input, File* output) +void DocumentConverter::convert(File* input, File* output) { if(!input || !output) { return; } - const auto input_format = input->InferFormat(); + const auto input_format = input->inferFormat(); switch (input_format) { case FileFormat::Format::Markdown: { - ConvertMarkdown(input, output); + convertMarkdown(input, output); break; } default: @@ -31,14 +32,14 @@ void DocumentConverter::Convert(File* input, File* output) } } -void DocumentConverter::ConvertMarkdown(File* input, File* output) +void DocumentConverter::convertMarkdown(File* input, File* output) { - const auto output_format = output->InferFormat(); + const auto output_format = output->inferFormat(); switch (output_format) { case FileFormat::Format::Html: { - MarkdownToHtml(input, output); + markdownToHtml(input, output); break; } default: @@ -48,29 +49,27 @@ void DocumentConverter::ConvertMarkdown(File* input, File* output) } } -void DocumentConverter::MarkdownToHtml(File* input, File* output) +void DocumentConverter::markdownToHtml(File* input, File* output) { MLOG_INFO("Converting Markdown to Html"); - input->SetAccessMode(File::AccessMode::Read); - input->Open(); + input->open(File::AccessMode::Read); MarkdownParser parser; - auto handle = input->GetInHandle(); + auto handle = input->getInHandle(); while(handle->good()) { std::string line; std::getline(*handle, line); - parser.ProcessLine(line); + parser.processLine(line); }; - input->Close(); + input->close(); - auto html_document = parser.GetHtml(); + auto html_document = parser.getHtml(); HtmlWriter writer; std::string html_string = writer.ToString(html_document); - output->SetAccessMode(File::AccessMode::Write); - output->Open(); - *(output->GetOutHandle()) << html_string; - output->Close(); + output->open(File::AccessMode::Write); + *(output->getOutHandle()) << html_string; + output->close(); } diff --git a/src/web/DocumentConverter.h b/src/web/DocumentConverter.h index c987b2f..02b6341 100644 --- a/src/web/DocumentConverter.h +++ b/src/web/DocumentConverter.h @@ -1,16 +1,17 @@ #pragma once #include -#include "File.h" + +class File; class DocumentConverter { public: DocumentConverter(); - void Convert(File* input, File* output); + void convert(File* input, File* output); - void ConvertMarkdown(File* input, File* output); + void convertMarkdown(File* input, File* output); - void MarkdownToHtml(File* input, File* output); + void markdownToHtml(File* input, File* output); }; diff --git a/src/web/markdown/MarkdownParser.cpp b/src/web/markdown/MarkdownParser.cpp index 295a25e..736ac40 100644 --- a/src/web/markdown/MarkdownParser.cpp +++ b/src/web/markdown/MarkdownParser.cpp @@ -9,22 +9,22 @@ MarkdownParser::MarkdownParser() } -void MarkdownParser::ProcessLine(const std::string& line) +void MarkdownParser::processLine(const std::string& line) { } -void MarkdownParser::Run(const std::string& content) +void MarkdownParser::run(const std::string& content) { std::stringstream ss(content); std::string line; while (std::getline(ss, line, '\n')) { - ProcessLine(line); + processLine(line); } } -HtmlDocumentPtr MarkdownParser::GetHtml() +HtmlDocumentPtr MarkdownParser::getHtml() { return mHtmlDocument; } diff --git a/src/web/markdown/MarkdownParser.h b/src/web/markdown/MarkdownParser.h index 762847a..37c2381 100644 --- a/src/web/markdown/MarkdownParser.h +++ b/src/web/markdown/MarkdownParser.h @@ -14,17 +14,16 @@ class MarkdownParser None }; - - DocumentState mDocumentState {DocumentState::None}; - HtmlDocumentPtr mHtmlDocument; - public: - MarkdownParser(); - HtmlDocumentPtr GetHtml(); + HtmlDocumentPtr getHtml(); - void ProcessLine(const std::string& line); + void processLine(const std::string& line); - void Run(const std::string& content); + void run(const std::string& content); + +private: + DocumentState mDocumentState {DocumentState::None}; + HtmlDocumentPtr mHtmlDocument; }; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 3491714..3828f1f 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,5 +1,7 @@ add_subdirectory(test_utils) +file(COPY data/ DESTINATION ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/test_data) + set(TEST_MODULES audio compiler @@ -21,7 +23,13 @@ foreach(module ${TEST_MODULES}) string(TOUPPER ${module} MODULE_UPPER) list(APPEND UNIT_TEST_FILES ${${MODULE_UPPER}_UNIT_TEST_FILES}) list(APPEND UNIT_TEST_DEPENDENCIES ${${MODULE_UPPER}_UNIT_TEST_DEPENDENCIES}) + + list(APPEND INTEGRATION_TEST_FILES ${${MODULE_UPPER}_INTEGRATION_TEST_FILES}) + list(APPEND INTEGRATION_TEST_DEPENDENCIES ${${MODULE_UPPER}_INTEGRATION_TEST_DEPENDENCIES}) endforeach() add_executable(unit_tests test_runner.cpp ${UNIT_TEST_FILES}) target_link_libraries(unit_tests PUBLIC test_utils ${UNIT_TEST_DEPENDENCIES}) + +add_executable(integration_tests test_runner.cpp ${INTEGRATION_TEST_FILES}) +target_link_libraries(integration_tests PUBLIC test_utils ${INTEGRATION_TEST_DEPENDENCIES}) diff --git a/test/audio/CMakeLists.txt b/test/audio/CMakeLists.txt index fcd13b2..6015545 100644 --- a/test/audio/CMakeLists.txt +++ b/test/audio/CMakeLists.txt @@ -1,10 +1,22 @@ -set(AUDIO_UNIT_TEST_FILES - audio/TestAudioWriter.cpp - audio/TestMidiReader.cpp - PARENT_SCOPE - ) - -set(AUDIO_UNIT_TEST_DEPENDENCIES - audio - PARENT_SCOPE - ) \ No newline at end of file +set(MODULE_NAME audio) + +set(UNIT_TESTS + ${MODULE_NAME}/unit/TestAudioWriter.cpp + ${MODULE_NAME}/unit/TestMidiReader.cpp +) + +if(UNIX) +set(INTEGETATION_TESTS + ${MODULE_NAME}/integration/TestAlsaInterface.cpp +) +else() +set(INTEGETATION_TESTS + ${MODULE_NAME}/integration/TestWasapiInterface.cpp +) +endif() + +set(AUDIO_UNIT_TEST_FILES ${UNIT_TESTS} PARENT_SCOPE) +set(AUDIO_INTEGRATION_TEST_FILES ${INTEGETATION_TESTS} PARENT_SCOPE) + +set(AUDIO_UNIT_TEST_DEPENDENCIES ${MODULE_NAME} PARENT_SCOPE) +set(AUDIO_INTEGRATION_TEST_DEPENDENCIES ${MODULE_NAME} PARENT_SCOPE) \ No newline at end of file diff --git a/test/audio/TestAudioWriter.cpp b/test/audio/TestAudioWriter.cpp deleted file mode 100644 index 43c927a..0000000 --- a/test/audio/TestAudioWriter.cpp +++ /dev/null @@ -1,34 +0,0 @@ -#include "AudioSample.h" -#include "AudioSynth.h" -#include "AudioWriter.h" -#include "WasapiInterface.h" - -#include "TestFramework.h" - -#include -#include -#ifdef _WIN32 -#include -#endif -#include - -TEST_CASE(TestWriteWav, "audio") -{ - AudioWriter writer; - writer.SetPath("test.wav"); - - AudioSynth synth; - auto sample = synth.GetSineWave(240, 5); - - writer.Write(sample); -}; - -TEST_CASE(TestAudioRender, "audio") -{ - -#ifdef _WIN32 - WasapiInterface audio_interface; - auto device = AudioDevice::Create(); - audio_interface.Play(device); -#endif -}; diff --git a/test/audio/TestMidiReader.cpp b/test/audio/TestMidiReader.cpp deleted file mode 100644 index 9f8ee6a..0000000 --- a/test/audio/TestMidiReader.cpp +++ /dev/null @@ -1,16 +0,0 @@ -#include "MidiReader.h" - -#include "TestFramework.h" - -#include -#include -#include - -TEST_CASE(TestReadMidi, "audio") -{ - MidiReader reader; - reader.Read("/home/jmsgrogan/Downloads/test.mid"); - - auto document = reader.GetDocument(); - // std::cout << document->Serialize() << std::endl; -}; diff --git a/test/audio/integration/TestAlsaInterface.cpp b/test/audio/integration/TestAlsaInterface.cpp new file mode 100644 index 0000000..e69de29 diff --git a/test/audio/integration/TestWasapiInterface.cpp b/test/audio/integration/TestWasapiInterface.cpp new file mode 100644 index 0000000..cdd3f3f --- /dev/null +++ b/test/audio/integration/TestWasapiInterface.cpp @@ -0,0 +1,13 @@ +#include "TestFramework.h" +#include "TestUtils.h" + +#include "AudioSample.h" +#include "AudioSynth.h" +#include "WasapiInterface.h" + +TEST_CASE(TestWasapiInterface, "audio") +{ + WasapiInterface audio_interface; + auto device = AudioDevice::Create(); + audio_interface.Play(device); +}; diff --git a/test/audio/unit/TestAudioWriter.cpp b/test/audio/unit/TestAudioWriter.cpp new file mode 100644 index 0000000..de46732 --- /dev/null +++ b/test/audio/unit/TestAudioWriter.cpp @@ -0,0 +1,17 @@ +#include "TestFramework.h" +#include "TestUtils.h" + +#include "AudioSample.h" +#include "AudioSynth.h" +#include "AudioWriter.h" + +TEST_CASE(TestAudioWriterWav, "audio") +{ + AudioWriter writer; + writer.setPath(TestUtils::getTestOutputDir() / "TestAudioWriterWav.wav"); + + AudioSynth synth; + const auto sample = synth.getSineWave(240, 5); + + writer.write(sample); +}; diff --git a/test/audio/unit/TestMidiReader.cpp b/test/audio/unit/TestMidiReader.cpp new file mode 100644 index 0000000..6fd872c --- /dev/null +++ b/test/audio/unit/TestMidiReader.cpp @@ -0,0 +1,12 @@ +#include "MidiReader.h" + +#include "TestFramework.h" +#include "TestUtils.h" + +TEST_CASE(TestReadMidi, "audio") +{ + MidiReader reader; + reader.read(TestUtils::getTestDataDir() / "test.mid"); + + auto document = reader.getDocument(); +}; diff --git a/test/compiler/TestTemplatingEngine.cpp b/test/compiler/TestTemplatingEngine.cpp index 9eb422a..3649455 100644 --- a/test/compiler/TestTemplatingEngine.cpp +++ b/test/compiler/TestTemplatingEngine.cpp @@ -1,19 +1,16 @@ #include "TemplatingEngine.h" #include "File.h" -#include "TestFramework.h" -#include -#include +#include "TestFramework.h" +#include "TestUtils.h" TEST_CASE(TestTemplatingEngine, "compiler") { - const auto data_loc = std::filesystem::path(__FILE__) / "../../data"; - - auto engine = TemplatingEngine(data_loc); + auto engine = TemplatingEngine(TestUtils::getTestDataDir()); engine.loadTemplateFiles(); const auto content = engine.processTemplate("index"); - File outfile("index.html"); - outfile.WriteText(content); + File outfile(TestUtils::getTestOutputDir() / "index.html"); + outfile.writeText(content); } diff --git a/test/core/TestTomlReader.cpp b/test/core/TestTomlReader.cpp index c49766c..e081e17 100644 --- a/test/core/TestTomlReader.cpp +++ b/test/core/TestTomlReader.cpp @@ -1,17 +1,12 @@ #include "TomlReader.h" #include "TestFramework.h" - -#include -#include +#include "TestUtils.h" TEST_CASE(TestTomlReader, "core") { - const auto data_loc = std::filesystem::path(__FILE__) / "../../data"; - const auto sample_toml_file = data_loc / "sample_toml.toml"; - auto reader = TomlReader(); - reader.read(sample_toml_file); + reader.read(TestUtils::getTestDataDir() / "sample_toml.toml"); auto themes_table = reader.getContent()->getTable("themes"); @@ -19,6 +14,6 @@ TEST_CASE(TestTomlReader, "core") for (const auto& items : themes_table->getKeyValuePairs()) { - std::cout << "Got entry with key: " << items.first << " and val " << items.second << std::endl; + //std::cout << "Got entry with key: " << items.first << " and val " << items.second << std::endl; } } diff --git a/test/data/index.png b/test/data/index.png new file mode 100644 index 0000000000000000000000000000000000000000..8656d34f190fecd64168706f3a306d9b713024d2 GIT binary patch literal 6273 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 literal 0 HcmV?d00001 diff --git a/test/data/test.mid b/test/data/test.mid new file mode 100644 index 0000000000000000000000000000000000000000..a52838c62bc5c43bd53d34fe98152901cb0d3c57 GIT binary patch literal 8444 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 literal 0 HcmV?d00001 diff --git a/test/data/test_fixed.png b/test/data/test_fixed.png new file mode 100644 index 0000000000000000000000000000000000000000..5cf1c2b9b4d1a2daa27a4643e7ffbd487cd941a0 GIT binary patch literal 64 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2VkYHF5IUx~9v3t5WhE&X9W?*4p*}bvV6(r^9 L>gTe~DWM4fP4f&~ literal 0 HcmV?d00001 diff --git a/test/database/TestDatabase.cpp b/test/database/TestDatabase.cpp index bc1392f..1d4aece 100644 --- a/test/database/TestDatabase.cpp +++ b/test/database/TestDatabase.cpp @@ -1,12 +1,13 @@ #include "DatabaseManager.h" #include "TestFramework.h" +#include "TestUtils.h" TEST_CASE(TestDatabaseManager, "database") { DatabaseManager db_manager; - db_manager.CreateDatabase("test.db"); + db_manager.openDatabase(TestUtils::getTestOutputDir() / "test.db"); std::string statement = "CREATE TABLE corporation;"; - db_manager.Run(statement); + db_manager.run(statement); } diff --git a/test/fonts/TestFontReader.cpp b/test/fonts/TestFontReader.cpp index d47dd17..d18f819 100644 --- a/test/fonts/TestFontReader.cpp +++ b/test/fonts/TestFontReader.cpp @@ -1,8 +1,6 @@ #include "TrueTypeFont.h" #include "FontReader.h" -#include - #include "TestFramework.h" TEST_CASE(TestFontReader, "fonts") diff --git a/test/image/TestPngReader.cpp b/test/image/TestPngReader.cpp index f705cc2..a31f884 100644 --- a/test/image/TestPngReader.cpp +++ b/test/image/TestPngReader.cpp @@ -1,6 +1,7 @@ #include "PngReader.h" #include "BitStream.h" +#include "TestUtils.h" #include "Image.h" #include @@ -9,7 +10,7 @@ TEST_CASE(TestThirdPartyPng, "image") { - const auto path = "/home/jmsgrogan/Downloads/test.png"; + const auto path = TestUtils::getTestDataDir() / "test.png"; //const auto path = "/home/jmsgrogan/Downloads/index.png"; @@ -30,7 +31,7 @@ TEST_CASE(TestThirdPartyPng, "image") TEST_CASE(TestFxedCodePng, "image") { - const auto path = "/home/jmsgrogan/code/MediaTool-build/bin/test_fixed.png"; + const auto path = TestUtils::getTestDataDir() / "test_fixed.png"; //File file(path); //std::cout << file.dumpBinary(); diff --git a/test/image/TestPngWriter.cpp b/test/image/TestPngWriter.cpp index 7ad5e27..bdcaf1e 100644 --- a/test/image/TestPngWriter.cpp +++ b/test/image/TestPngWriter.cpp @@ -7,7 +7,7 @@ #include "ImagePrimitives.h" #include "TestFramework.h" - +#include "TestUtils.h" #include TEST_CASE(TestCompressedPng, "image") @@ -29,20 +29,18 @@ TEST_CASE(TestCompressedPng, "image") image->setData(data); PngWriter writer; - writer.setPath("test_compressed.png"); + writer.setPath(TestUtils::getTestOutputDir() / "test_compressed.png"); writer.setCompressionMethod(Deflate::CompressionMethod::NONE); writer.write(image); return; - File test_file("test_compressed.png"); - test_file.SetAccessMode(File::AccessMode::Read); - test_file.Open(true); + File test_file(TestUtils::getTestOutputDir() / "test_compressed.png"); + test_file.open(File::AccessMode::Read); while(auto byte = test_file.readNextByte()) { //std::cout << static_cast(*byte) << std::endl; } - test_file.Close(); } TEST_CASE(TestFixedPng, "image") @@ -65,12 +63,12 @@ TEST_CASE(TestFixedPng, "image") image->setData(data); PngWriter writer; - writer.setPath("test_fixed.png"); + writer.setPath(TestUtils::getTestOutputDir() / "test_fixed.png"); writer.setCompressionMethod(Deflate::CompressionMethod::FIXED_HUFFMAN); writer.write(image); //return; - File test_file("test_fixed.png"); + File test_file(TestUtils::getTestOutputDir() / "test_fixed.png"); //std::cout << test_file.dumpBinary(); } @@ -95,10 +93,10 @@ TEST_CASE(TestDynamicCompressedPng, "image") image->setData(data); PngWriter writer; - writer.setPath("test_dynamic.png"); + writer.setPath(TestUtils::getTestOutputDir() / "test_dynamic.png"); writer.write(image); //return; - File test_file("test_dynamic.png"); + File test_file(TestUtils::getTestOutputDir() / "test_dynamic.png"); //std::cout << test_file.dumpBinary(); } diff --git a/test/publishing/TestPdfWriter.cpp b/test/publishing/TestPdfWriter.cpp index dad2d8e..e7793cc 100644 --- a/test/publishing/TestPdfWriter.cpp +++ b/test/publishing/TestPdfWriter.cpp @@ -4,6 +4,7 @@ #include "File.h" #include "TestFramework.h" +#include "TestUtils.h" TEST_CASE(TestPdfWriter, "publishing") { @@ -12,8 +13,7 @@ TEST_CASE(TestPdfWriter, "publishing") PdfWriter writer; const auto output = writer.ToString(document); - File pdf_file("TestPdfWriter.pdf"); - pdf_file.SetAccessMode(File::AccessMode::Write); - pdf_file.Open(); - pdf_file.WriteText(output); + File pdf_file(TestUtils::getTestOutputDir() / "TestPdfWriter.pdf"); + pdf_file.open(File::AccessMode::Write); + pdf_file.writeText(output); } diff --git a/test/test_utils/TestUtils.h b/test/test_utils/TestUtils.h new file mode 100644 index 0000000..1524595 --- /dev/null +++ b/test/test_utils/TestUtils.h @@ -0,0 +1,19 @@ +#pragma once + +#include + +using Path = std::filesystem::path; + +class TestUtils +{ +public: + static Path getTestOutputDir() + { + return std::filesystem::current_path() / "test_output"; + } + + static Path getTestDataDir() + { + return std::filesystem::current_path() / "test_data"; + } +}; diff --git a/test/video/TestVideoDecoder.cpp b/test/video/TestVideoDecoder.cpp index 9ecdd7d..361d48e 100644 --- a/test/video/TestVideoDecoder.cpp +++ b/test/video/TestVideoDecoder.cpp @@ -2,24 +2,22 @@ #include "Image.h" #include "FfmpegInterface.h" #include "PngWriter.h" -#include #include "TestFramework.h" TEST_CASE(TestVideoDecoder, "video") { auto video = Video::Create(); - video->SetPath("/home/jmsgrogan/test.mp4"); + video->SetPath(TestUtils::getTestDataDir() / "test.mp4"); FfmpegInterface decoder; auto images = decoder.decodeToImages(video, 4); - std::cout << "Got " << std::to_string(images.size()) << std::endl; PngWriter writer; for(unsigned idx=0; idx<20; idx++) { auto filename = "test_out" + std::to_string(idx) + ".png"; - writer.SetPath(filename); - writer.Write(images[idx]); + writer.setPath(filename); + writer.write(images[idx]); } } diff --git a/test/web/TestMarkdownParser.cpp b/test/web/TestMarkdownParser.cpp index 9a2b667..5d5a170 100644 --- a/test/web/TestMarkdownParser.cpp +++ b/test/web/TestMarkdownParser.cpp @@ -3,23 +3,21 @@ #include "HtmlWriter.h" #include "TestFramework.h" +#include "TestUtils.h" TEST_CASE(TestMarkdownParser, "web") { - File md_file("/home/jmsgrogan/code/MediaTool/test/data/sample_markdown.md"); - md_file.Open(); - const auto md_content = md_file.ReadText(); + File md_file(TestUtils::getTestDataDir() / "sample_markdown.md"); + const auto md_content = md_file.readText(); MarkdownParser parser; - parser.Run(md_content); + parser.run(md_content); - auto html = parser.GetHtml(); + auto html = parser.getHtml(); HtmlWriter writer; const auto html_string = writer.ToString(html); - File html_file("TestMarkdownParserOut.html"); - html_file.SetAccessMode(File::AccessMode::Write); - html_file.Open(); - html_file.WriteText(html_string); + File html_file(TestUtils::getTestOutputDir() / "TestMarkdownParserOut.html"); + html_file.writeText(html_string); } diff --git a/test/web/TestXmlParser.cpp b/test/web/TestXmlParser.cpp index 79ace09..a96041e 100644 --- a/test/web/TestXmlParser.cpp +++ b/test/web/TestXmlParser.cpp @@ -6,13 +6,14 @@ #include "File.h" #include "TestFramework.h" +#include "TestUtils.h" TEST_CASE(TestXmlParser, "web") { XmlParser parser; std::ifstream xml_file; - xml_file.open("/home/jmsgrogan/test.xml", std::ifstream::in); + xml_file.open(TestUtils::getTestDataDir() / "test.xml", std::ifstream::in); while(xml_file.good()) { std::string line; @@ -21,11 +22,9 @@ TEST_CASE(TestXmlParser, "web") } xml_file.close(); - auto outFile = std::make_unique("test_out.xml"); - outFile->SetAccessMode(File::AccessMode::Write); - outFile->Open(); - XmlWriter writer; auto content = writer.ToString(parser.GetDocument().get()); - outFile->WriteText(content); + + auto outFile = std::make_unique(TestUtils::getTestOutputDir() / "test_out.xml"); + outFile->writeText(content); }