diff --git a/CMakeLists.txt b/CMakeLists.txt index 2b9390b..9c5c933 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -197,11 +197,10 @@ set(SOURCE_FILES src/phoneExtraction.cpp src/phoneExtraction.h src/platformTools.cpp src/platformTools.h src/tools.cpp src/tools.h - src/audio/AudioStream.cpp src/audio/AudioStream.h - src/audio/AudioStreamSegment.cpp src/audio/AudioStreamSegment.h + src/audio/AudioClip.cpp src/audio/AudioClip.h + src/audio/AudioSegment.cpp src/audio/AudioSegment.h src/audio/DCOffset.cpp src/audio/DCOffset.h src/audio/SampleRateConverter.cpp src/audio/SampleRateConverter.h - src/audio/UnboundedStream.cpp src/audio/UnboundedStream.h src/audio/voiceActivityDetection.cpp src/audio/voiceActivityDetection.h src/audio/WaveFileReader.cpp src/audio/WaveFileReader.h src/audio/waveFileWriting.cpp src/audio/waveFileWriting.h diff --git a/src/audio/AudioClip.cpp b/src/audio/AudioClip.cpp new file mode 100644 index 0000000..6c83ff3 --- /dev/null +++ b/src/audio/AudioClip.cpp @@ -0,0 +1,65 @@ +#include "AudioClip.h" +#include + +using std::invalid_argument; + +TimeRange AudioClip::getTruncatedRange() const { + return TimeRange(0cs, centiseconds(100 * size() / getSampleRate())); +} + +class SafeSampleReader { +public: + SafeSampleReader(SampleReader unsafeRead, AudioClip::size_type size); + AudioClip::value_type operator()(AudioClip::size_type index); +private: + SampleReader unsafeRead; + AudioClip::size_type size; + AudioClip::size_type lastIndex = -1; + AudioClip::value_type lastSample = 0; +}; + +SafeSampleReader::SafeSampleReader(SampleReader unsafeRead, AudioClip::size_type size) : + unsafeRead(unsafeRead), + size(size) +{} + +inline AudioClip::value_type SafeSampleReader::operator()(AudioClip::size_type index) { + if (index < 0) { + throw invalid_argument(fmt::format("Cannot read from sample index {}. Index < 0.", index)); + } + if (index >= size) { + throw invalid_argument(fmt::format("Cannot read from sample index {}. Clip size is {}.", index, size)); + } + if (index == lastIndex) { + return lastSample; + } + + lastIndex = index; + lastSample = unsafeRead(index); + return lastSample; +} + +SampleReader AudioClip::createSampleReader() const { + return SafeSampleReader(createUnsafeSampleReader(), size()); +} + +AudioClip::iterator AudioClip::begin() const { + return SampleIterator(*this, 0); +} + +AudioClip::iterator AudioClip::end() const { + return SampleIterator(*this, size()); +} + +std::unique_ptr operator|(std::unique_ptr clip, AudioEffect effect) { + return effect(std::move(clip)); +} + +SampleIterator::SampleIterator() : + sampleIndex(0) +{} + +SampleIterator::SampleIterator(const AudioClip& audioClip, size_type sampleIndex) : + sampleReader([&audioClip] { return audioClip.createSampleReader(); }), + sampleIndex(sampleIndex) +{} diff --git a/src/audio/AudioClip.h b/src/audio/AudioClip.h new file mode 100644 index 0000000..b707d19 --- /dev/null +++ b/src/audio/AudioClip.h @@ -0,0 +1,141 @@ +#pragma once +#include +#include "TimeRange.h" +#include +#include "Lazy.h" + +class AudioClip; +class SampleIterator; + +class AudioClip { +public: + using value_type = float; + using size_type = int64_t; + using difference_type = int64_t; + using iterator = SampleIterator; + using SampleReader = std::function; + + virtual ~AudioClip() {} + virtual std::unique_ptr clone() const = 0; + virtual int getSampleRate() const = 0; + virtual size_type size() const = 0; + TimeRange getTruncatedRange() const; + SampleReader createSampleReader() const; + iterator begin() const; + iterator end() const; +private: + virtual SampleReader createUnsafeSampleReader() const = 0; +}; + +using AudioEffect = std::function(std::unique_ptr)>; + +std::unique_ptr operator|(std::unique_ptr clip, AudioEffect effect); + +using SampleReader = AudioClip::SampleReader; + +class SampleIterator { +public: + using value_type = AudioClip::value_type; + using size_type = AudioClip::size_type; + using difference_type = AudioClip::difference_type; + + SampleIterator(); + + size_type getSampleIndex() const; + void seek(size_type sampleIndex); + value_type operator*() const; + value_type operator[](difference_type n) const; + +private: + friend AudioClip; + SampleIterator(const AudioClip& audioClip, size_type sampleIndex); + + Lazy sampleReader; + size_type sampleIndex; +}; + +inline SampleIterator::size_type SampleIterator::getSampleIndex() const { + return sampleIndex; +} + +inline void SampleIterator::seek(size_type sampleIndex) { + this->sampleIndex = sampleIndex; +} + +inline SampleIterator::value_type SampleIterator::operator*() const { + return (*sampleReader)(sampleIndex); +} + +inline SampleIterator::value_type SampleIterator::operator[](difference_type n) const { + return (*sampleReader)(sampleIndex + n); +} + +inline bool operator==(const SampleIterator& lhs, const SampleIterator& rhs) { + return lhs.getSampleIndex() == rhs.getSampleIndex(); +} + +inline bool operator!=(const SampleIterator& lhs, const SampleIterator& rhs) { + return lhs.getSampleIndex() != rhs.getSampleIndex(); +} + +inline bool operator<(const SampleIterator& lhs, const SampleIterator& rhs) { + return lhs.getSampleIndex() < rhs.getSampleIndex(); +} + +inline bool operator>(const SampleIterator& lhs, const SampleIterator& rhs) { + return lhs.getSampleIndex() > rhs.getSampleIndex(); +} + +inline bool operator<=(const SampleIterator& lhs, const SampleIterator& rhs) { + return lhs.getSampleIndex() <= rhs.getSampleIndex(); +} + +inline bool operator>=(const SampleIterator& lhs, const SampleIterator& rhs) { + return lhs.getSampleIndex() >= rhs.getSampleIndex(); +} + +inline SampleIterator& operator+=(SampleIterator& it, SampleIterator::difference_type n) { + it.seek(it.getSampleIndex() + n); + return it; +} + +inline SampleIterator& operator-=(SampleIterator& it, SampleIterator::difference_type n) { + it.seek(it.getSampleIndex() - n); + return it; +} + +inline SampleIterator& operator++(SampleIterator& it) { + return operator+=(it, 1); +} + +inline SampleIterator operator++(SampleIterator& it, int) { + SampleIterator tmp(it); + operator++(it); + return tmp; +} + +inline SampleIterator& operator--(SampleIterator& it) { + return operator-=(it, 1); +} + +inline SampleIterator operator--(SampleIterator& it, int) { + SampleIterator tmp(it); + operator--(it); + return tmp; +} + +inline SampleIterator operator+(const SampleIterator& it, SampleIterator::difference_type n) { + SampleIterator result(it); + result += n; + return result; +} + +inline SampleIterator operator-(const SampleIterator& it, SampleIterator::difference_type n) { + SampleIterator result(it); + result -= n; + return result; +} + +inline SampleIterator::difference_type operator-(const SampleIterator& lhs, const SampleIterator& rhs) { + return lhs.getSampleIndex() - rhs.getSampleIndex(); +} diff --git a/src/audio/AudioSegment.cpp b/src/audio/AudioSegment.cpp new file mode 100644 index 0000000..36ac39d --- /dev/null +++ b/src/audio/AudioSegment.cpp @@ -0,0 +1,30 @@ +#include "AudioSegment.h" + +using std::unique_ptr; +using std::make_unique; + +AudioSegment::AudioSegment(std::unique_ptr inputClip, const TimeRange& range) : + inputClip(std::move(inputClip)), + sampleOffset(static_cast(range.getStart().count()) * this->inputClip->getSampleRate() / 100), + sampleCount(static_cast(range.getLength().count()) * this->inputClip->getSampleRate() / 100) +{ + if (sampleOffset < 0 || sampleOffset + sampleCount > this->inputClip->size()) { + throw std::invalid_argument("Segment extends beyond input clip."); + } +} + +unique_ptr AudioSegment::clone() const { + return make_unique(*this); +} + +SampleReader AudioSegment::createUnsafeSampleReader() const { + return [read = inputClip->createSampleReader(), sampleOffset = sampleOffset](size_type index) { + return read(index + sampleOffset); + }; +} + +AudioEffect segment(const TimeRange& range) { + return [range](unique_ptr inputClip) { + return make_unique(std::move(inputClip), range); + }; +} diff --git a/src/audio/AudioSegment.h b/src/audio/AudioSegment.h new file mode 100644 index 0000000..7faf435 --- /dev/null +++ b/src/audio/AudioSegment.h @@ -0,0 +1,26 @@ +#pragma once +#include "AudioClip.h" + +class AudioSegment : public AudioClip { +public: + AudioSegment(std::unique_ptr inputClip, const TimeRange& range); + std::unique_ptr clone() const override; + int getSampleRate() const override; + size_type size() const override; + +private: + SampleReader createUnsafeSampleReader() const override; + + std::shared_ptr inputClip; + size_type sampleOffset, sampleCount; +}; + +inline int AudioSegment::getSampleRate() const { + return inputClip->getSampleRate(); +} + +inline AudioClip::size_type AudioSegment::size() const { + return sampleCount; +} + +AudioEffect segment(const TimeRange& range); diff --git a/src/audio/AudioStream.cpp b/src/audio/AudioStream.cpp deleted file mode 100644 index 8a405af..0000000 --- a/src/audio/AudioStream.cpp +++ /dev/null @@ -1,9 +0,0 @@ -#include "AudioStream.h" - -TimeRange AudioStream::getTruncatedRange() const { - return TimeRange(0cs, centiseconds(100 * getSampleCount() / getSampleRate())); -} - -bool AudioStream::endOfStream() const { - return getSampleIndex() >= getSampleCount(); -} diff --git a/src/audio/AudioStream.h b/src/audio/AudioStream.h deleted file mode 100644 index d120f47..0000000 --- a/src/audio/AudioStream.h +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once - -#include -#include "TimeRange.h" - -// A mono stream of floating-point samples. -class AudioStream { -public: - virtual ~AudioStream() {} - virtual std::unique_ptr clone(bool reset) const = 0; - virtual int getSampleRate() const = 0; - virtual int64_t getSampleCount() const = 0; - TimeRange getTruncatedRange() const; - virtual int64_t getSampleIndex() const = 0; - virtual void seek(int64_t sampleIndex) = 0; - bool endOfStream() const; - virtual float readSample() = 0; -}; diff --git a/src/audio/AudioStreamSegment.cpp b/src/audio/AudioStreamSegment.cpp deleted file mode 100644 index 1b703a9..0000000 --- a/src/audio/AudioStreamSegment.cpp +++ /dev/null @@ -1,50 +0,0 @@ -#include "AudioStreamSegment.h" -#include - -AudioStreamSegment::AudioStreamSegment(std::unique_ptr audioStream, const TimeRange& range) : - audioStream(std::move(audioStream)), - sampleOffset(static_cast(range.getStart().count()) * this->audioStream->getSampleRate() / 100), - sampleCount(static_cast(range.getLength().count()) * this->audioStream->getSampleRate() / 100) -{ - seek(0); - - if (sampleOffset < 0 || sampleOffset + sampleCount > this->audioStream->getSampleCount()) { - throw std::invalid_argument("Segment extends beyond input stream."); - } -} - -AudioStreamSegment::AudioStreamSegment(const AudioStreamSegment& rhs, bool reset) : - audioStream(rhs.audioStream->clone(false)), - sampleOffset(rhs.sampleOffset), - sampleCount(rhs.sampleCount) -{ - if (reset) seek(0); -} - -std::unique_ptr AudioStreamSegment::clone(bool reset) const { - return std::make_unique(*this, reset); -} - -int AudioStreamSegment::getSampleRate() const { - return audioStream->getSampleRate(); -} - -int64_t AudioStreamSegment::getSampleCount() const { - return sampleCount; -} - -int64_t AudioStreamSegment::getSampleIndex() const { - return audioStream->getSampleIndex() - sampleOffset; -} - -void AudioStreamSegment::seek(int64_t sampleIndex) { - audioStream->seek(sampleIndex + sampleOffset); -} - -float AudioStreamSegment::readSample() { - return audioStream->readSample(); -} - -std::unique_ptr createSegment(std::unique_ptr audioStream, const TimeRange& range) { - return std::make_unique(std::move(audioStream), range); -} diff --git a/src/audio/AudioStreamSegment.h b/src/audio/AudioStreamSegment.h deleted file mode 100644 index 07476ab..0000000 --- a/src/audio/AudioStreamSegment.h +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once -#include