summaryrefslogtreecommitdiff
path: root/src/audio/resample.cpp
diff options
context:
space:
mode:
authorjacqueline <me@jacqueline.id.au>2023-08-04 20:07:44 +1000
committerjacqueline <me@jacqueline.id.au>2023-08-04 20:07:44 +1000
commit60f767713227b5405b855e6e6e2a0475ecd96bcc (patch)
treefe55b7048e9e7f1f587f465a1845aef9d3b7b731 /src/audio/resample.cpp
parent3b240d1cd5c52caf189ca036a1a841f7e6d84ccd (diff)
downloadtangara-fw-60f767713227b5405b855e6e6e2a0475ecd96bcc.tar.gz
Do our own resampling
Diffstat (limited to 'src/audio/resample.cpp')
-rw-r--r--src/audio/resample.cpp260
1 files changed, 260 insertions, 0 deletions
diff --git a/src/audio/resample.cpp b/src/audio/resample.cpp
new file mode 100644
index 00000000..93ea1034
--- /dev/null
+++ b/src/audio/resample.cpp
@@ -0,0 +1,260 @@
+#include "resample.hpp"
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <algorithm>
+#include <numeric>
+
+#include "esp_log.h"
+
+#include "sample.hpp"
+#include "stream_info.hpp"
+
+namespace audio {
+
+static constexpr size_t kFilterSize = 1536;
+
+constexpr auto calc_deltas(const std::array<int32_t, kFilterSize>& filter)
+ -> std::array<int32_t, kFilterSize> {
+ std::array<int32_t, kFilterSize> deltas;
+ for (size_t n = 0; n < kFilterSize - 1; n++)
+ deltas[n] = filter[n + 1] - filter[n];
+ return deltas;
+}
+
+static const std::array<int32_t, kFilterSize> kFilter{
+#include "fir.h"
+};
+
+static const std::array<int32_t, kFilterSize> kFilterDeltas =
+ calc_deltas(kFilter);
+
+class Channel {
+ public:
+ Channel(uint32_t src_rate,
+ uint32_t dest_rate,
+ size_t chunk_size,
+ size_t skip);
+ ~Channel();
+
+ auto output_chunk_size() -> size_t { return output_chunk_size_; }
+
+ auto FlushSamples(cpp::span<sample::Sample> out) -> size_t;
+ auto AddSample(sample::Sample, cpp::span<sample::Sample> out) -> std::size_t;
+ auto ApplyFilter() -> sample::Sample;
+
+ private:
+ size_t output_chunk_size_;
+ size_t skip_;
+
+ uint32_t factor_; /* factor */
+
+ uint32_t time_; /* time */
+
+ uint32_t time_per_filter_iteration_; /* output step */
+ uint32_t filter_step_; /* filter step */
+ uint32_t filter_end_; /* filter end */
+
+ int32_t unity_scale_; /* unity scale */
+
+ int32_t samples_per_filter_wing_; /* extra samples */
+ int32_t latest_sample_; /* buffer index */
+ cpp::span<int32_t> sample_buffer_; /* the buffer */
+};
+
+enum {
+ Nl = 8, /* 2^Nl samples per zero crossing in fir */
+ Nη = 8, /* phase bits for filter interpolation */
+ kPhaseBits = Nl + Nη, /* phase bits (fract of fixed point) */
+ One = 1 << kPhaseBits,
+};
+
+Channel::Channel(uint32_t irate, uint32_t orate, size_t count, size_t skip)
+ : skip_(skip) {
+ factor_ = ((uint64_t)orate << kPhaseBits) / irate;
+ if (factor_ != One) {
+ time_per_filter_iteration_ = ((uint64_t)irate << kPhaseBits) / orate;
+ filter_step_ = 1 << (Nl + Nη);
+ filter_end_ = kFilterSize << Nη;
+ samples_per_filter_wing_ = 1 + (filter_end_ / filter_step_);
+ unity_scale_ = 13128; /* unity scale factor for fir */
+ if (factor_ < One) {
+ unity_scale_ *= factor_;
+ unity_scale_ >>= kPhaseBits;
+ filter_step_ *= factor_;
+ filter_step_ >>= kPhaseBits;
+ samples_per_filter_wing_ *= time_per_filter_iteration_;
+ samples_per_filter_wing_ >>= kPhaseBits;
+ }
+ latest_sample_ = samples_per_filter_wing_;
+ time_ = latest_sample_ << kPhaseBits;
+
+ size_t buf_size = samples_per_filter_wing_ * 2 + count;
+ int32_t* buf = new int32_t[buf_size];
+ sample_buffer_ = {buf, buf_size};
+ count += buf_size; /* account for buffer accumulation */
+ }
+ output_chunk_size_ = ((uint64_t)count * factor_) >> kPhaseBits;
+}
+
+Channel::~Channel() {
+ delete sample_buffer_.data();
+}
+
+auto Channel::ApplyFilter() -> sample::Sample {
+ uint32_t iteration, p, i;
+ int32_t *sample, a;
+
+ int64_t value = 0;
+
+ // I did my best, but I'll be honest with you I've no idea about any of this
+ // maths stuff.
+
+ // Left wing of the filter.
+ sample = &sample_buffer_[time_ >> kPhaseBits];
+ p = time_ & ((1 << kPhaseBits) - 1);
+ iteration = factor_ < One ? (factor_ * p) >> kPhaseBits : p;
+ while (iteration < filter_end_) {
+ i = iteration >> Nη;
+ a = iteration & ((1 << Nη) - 1);
+ iteration += filter_step_;
+ a *= kFilterDeltas[i];
+ a >>= Nη;
+ a += kFilter[i];
+ value += static_cast<int64_t>(*--sample) * a;
+ }
+
+ // Right wing of the filter.
+ sample = &sample_buffer_[time_ >> kPhaseBits];
+ p = (One - p) & ((1 << kPhaseBits) - 1);
+ iteration = factor_ < One ? (factor_ * p) >> kPhaseBits : p;
+ if (p == 0) /* skip h[0] as it was already been summed above if p == 0 */
+ iteration += filter_step_;
+ while (iteration < filter_end_) {
+ i = iteration >> Nη;
+ a = iteration & ((1 << Nη) - 1);
+ iteration += filter_step_;
+ a *= kFilterDeltas[i];
+ a >>= Nη;
+ a += kFilter[i];
+ value += static_cast<int64_t>(*sample++) * a;
+ }
+
+ /* scale */
+ value >>= 2;
+ value *= unity_scale_;
+ value >>= 27;
+
+ return sample::Clip(value);
+}
+
+auto Channel::FlushSamples(cpp::span<sample::Sample> out) -> size_t {
+ size_t zeroes_needed = (2 * samples_per_filter_wing_) - latest_sample_;
+ size_t produced = 0;
+ while (zeroes_needed > 0) {
+ produced += AddSample(0, out.subspan(produced));
+ zeroes_needed--;
+ }
+ return produced;
+}
+
+auto Channel::AddSample(sample::Sample in, cpp::span<sample::Sample> out)
+ -> size_t {
+ // Add the latest sample to our working buffer.
+ sample_buffer_[latest_sample_++] = in;
+
+ // If we don't have enough samples to run the filter, then bail out and wait
+ // for more.
+ if (latest_sample_ < 2 * samples_per_filter_wing_) {
+ return 0;
+ }
+
+ // Apply the filter to the buffered samples. First, we work out how long (in
+ // samples) we can run the filter for before running out. This isn't as
+ // trivial as it might look; e.g. depending on the resampling factor we might
+ // be doubling the number of samples, or halving them.
+ uint32_t max_time = (latest_sample_ - samples_per_filter_wing_) << kPhaseBits;
+ size_t samples_output = 0;
+ while (time_ < max_time) {
+ out[skip_ * samples_output++] = ApplyFilter();
+ time_ += time_per_filter_iteration_;
+ }
+
+ // If we are approaching the end of our buffer, we need to shift all the data
+ // in it down to the front to make room for more samples.
+ int32_t current_sample = time_ >> kPhaseBits;
+ if (current_sample >= (sample_buffer_.size() - samples_per_filter_wing_)) {
+ // NB: bit shifting back and forth means we're only modifying `time` by
+ // whole samples.
+ time_ -= current_sample << kPhaseBits;
+ time_ += samples_per_filter_wing_ << kPhaseBits;
+
+ int32_t new_current_sample = time_ >> kPhaseBits;
+ new_current_sample -= samples_per_filter_wing_;
+ current_sample -= samples_per_filter_wing_;
+
+ int32_t samples_to_move = latest_sample_ - current_sample;
+ if (samples_to_move > 0) {
+ auto samples = sample_buffer_.subspan(current_sample, samples_to_move);
+ std::copy_backward(samples.begin(), samples.end(),
+ sample_buffer_.first(new_current_sample).end());
+ latest_sample_ = new_current_sample + samples_to_move;
+ } else {
+ latest_sample_ = new_current_sample;
+ }
+ }
+
+ return samples_output;
+}
+
+static const size_t kChunkSizeSamples = 256;
+
+Resampler::Resampler(uint32_t source_sample_rate,
+ uint32_t target_sample_rate,
+ uint8_t num_channels)
+ : source_sample_rate_(source_sample_rate),
+ target_sample_rate_(target_sample_rate),
+ factor_(((uint64_t)target_sample_rate << kPhaseBits) /
+ source_sample_rate),
+ num_channels_(num_channels),
+ channels_() {
+ for (int i = 0; i < num_channels; i++) {
+ channels_.emplace_back(source_sample_rate, target_sample_rate,
+ kChunkSizeSamples, num_channels);
+ }
+}
+
+Resampler::~Resampler() {}
+
+auto Resampler::Process(cpp::span<const sample::Sample> input,
+ cpp::span<sample::Sample> output,
+ bool end_of_data) -> std::pair<size_t, size_t> {
+ size_t samples_used = 0;
+ std::vector<size_t> samples_produced = {num_channels_, 0};
+ size_t total_samples_produced = 0;
+
+ size_t slop = (factor_ >> kPhaseBits) + 1;
+
+ uint_fast8_t cur_channel = 0;
+
+ while (input.size() > samples_used &&
+ output.size() > total_samples_produced + slop) {
+ // Work out where the next set of samples should be placed.
+ size_t next_output_index =
+ (samples_produced[cur_channel] * num_channels_) + cur_channel;
+
+ // Generate the next samples
+ size_t new_samples = channels_[cur_channel].AddSample(
+ input[samples_used++], output.subspan(next_output_index));
+
+ samples_produced[cur_channel] += new_samples;
+ total_samples_produced += new_samples;
+
+ cur_channel = (cur_channel + 1) % num_channels_;
+ }
+
+ return {samples_used, total_samples_produced};
+}
+
+} // namespace audio