DSPark 1.8.0
Header-only C++20 DSP for real-time and offline audio
Loading...
Searching...
No Matches
OfflineSinc.h
1// DSPark - Professional Audio DSP Framework
2// Copyright (c) 2026 Cristian Moresi - MIT License
3
4#pragma once
5
12#include "OfflineProduct.h"
13#if DSPARK_HAS_OFFLINE
14#include <algorithm>
15#include <array>
16#include <cmath>
17#include <cstdint>
18#include <memory>
19#include <numbers>
20#include <optional>
21#include <span>
22#include <utility>
23#include <vector>
24
25// Finite cardinal-sinc phase, using the existing
26// Core finite convolution window and the same source's shared Cauchy moments.
28{
30{
32 double shift_, sine_ = 0;
33 std::optional<OfflineConvolutionWindow> near_;
34
35 public:
36 SincPhase(OfflineProductWorkspace &work, double shift) : work_(work), shift_(shift)
37 {
38 if (!std::isfinite(shift) || std::abs(shift) >= 1)
40 if (shift == 0)
41 return;
42 // Preserve a shift one ulp from an integer: multiplying that shift by
43 // pi first loses relative accuracy at the nearest sinc numerator zero.
44 const double reduced = shift > .5 ? 1 - shift : shift < -.5 ? -1 - shift : shift;
45 sine_ = std::sin(std::numbers::pi * reduced);
46 near_.emplace(work.job(), work.block(), [this](int lag)
47 { return (lag % 2 ? -sine_ : sine_) / (std::numbers::pi * (lag + shift_)); });
48 }
49 SincPhase(const SincPhase &) = delete;
50 SincPhase &operator=(const SincPhase &) = delete;
51 template <class Reader>
52 void evaluate(const OfflineHilbertMap &map, std::size_t leaf, Reader &source, double *out)
53 {
54 const int b = work_.block();
55 if (leaf >= work_.leaves())
57 const auto first = static_cast<std::int64_t>(leaf) * b;
58 if (shift_ == 0)
59 {
60 work_.read(source, first, b, out);
61 return;
62 }
63 map.evaluateCauchyFar(leaf, shift_, out, true);
64 work_.read(source, first - b, 3 * b, near_->input());
65 const auto *close = near_->process();
66 for (int i = 0; i < b; ++i)
67 out[i] = close[i] + (i % 2 ? -sine_ : sine_) * out[i];
68 }
69};
70
71// Project a finite high-rate sequence onto |f|<=1/(2*factor) and sample at
72// multiples of factor. h[k]=sin(pi*k/factor)/(pi*k) gives a single real
73// Cauchy transform of sin(pi*j/factor)*source[j], plus its diagonal term.
74template <class Reader> class SincProjection
75{
76 Reader source_;
78 int factor_;
79 std::array<double, 32> sine_{};
80 struct Modulated
81 {
82 SincProjection *owner;
83 void operator()(std::int64_t first, int count, double *out)
84 {
85 owner->source_(first, count, out);
86 for (int i = 0; i < count; ++i)
87 out[i] *=
88 owner->sine_[static_cast<std::size_t>((first + i) % (2 * owner->factor_))];
89 }
90 } modulated_{this};
91 std::optional<OfflineHilbertMap> map_;
92 OfflineScratchArray<double> transform_, raw_, output_;
93
94 public:
95 SincProjection(OfflineSession &job, std::int64_t frames, int factor, Reader source, int block)
96 : source_(std::move(source)),
97 work_(job, frames, block, OfflineProductWorkspace::NearFields::External), factor_(factor)
98 {
99 if (factor < 1 || factor > 16 || (factor & (factor - 1)))
101 for (int p = 1; p < factor; ++p)
102 {
103 sine_[p] = std::sin(std::numbers::pi * p / factor);
104 sine_[p + factor] = -sine_[p];
105 }
106 map_.emplace(work_, modulated_);
107 transform_ = job.allocateScratch<double>(block);
108 raw_ = job.allocateScratch<double>(block);
109 output_ = job.allocateScratch<double>(block / factor);
110 work_.prepareCauchy();
111 }
114 void rebuild()
115 {
116 map_->rebuild(work_, modulated_);
117 }
118 [[nodiscard]] std::size_t leaves() const
119 {
120 return work_.leaves();
121 }
122 [[nodiscard]] std::span<const double> get(std::size_t leaf)
123 {
124 if (leaf >= leaves())
126 const int block = work_.block();
127 const auto first = static_cast<std::int64_t>(leaf) * block;
128 const int count = static_cast<int>(std::min<std::int64_t>(block, work_.frames() - first));
129 map_->evaluateCauchy(work_, leaf, modulated_, transform_.get());
130 work_.read(source_, first, count, raw_.get());
131 const int written = (count + factor_ - 1) / factor_;
132 for (int k = 0; k < written; ++k)
133 {
134 const auto frame = first / factor_ + k;
135 output_[k] =
136 raw_[k * factor_] / factor_ + (frame % 2 ? 1 : -1) * transform_[k * factor_];
137 }
138 return {output_.get(), static_cast<std::size_t>(written)};
139 }
140};
141template <class Reader, class Consumer>
142void projectSinc(OfflineSession &job, std::int64_t frames, int factor, Reader source,
143 Consumer consume, int block = 4096)
144{
145 SincProjection<Reader> projection(job, frames, factor, std::move(source), block);
146 for (std::size_t leaf = 0; leaf < projection.leaves(); ++leaf)
147 {
148 const auto data = projection.get(leaf);
149 consume(static_cast<std::int64_t>(leaf) * (block / factor), static_cast<int>(data.size()),
150 data.data());
151 }
152}
153} // namespace dspark::detail::offline_sinc
154
155#endif // DSPARK_HAS_OFFLINE
void evaluateCauchyFar(std::size_t leaf, double shift, double *output, bool alternating=false) const
OfflineSession & job() noexcept
std::int64_t frames() const noexcept
std::size_t leaves() const noexcept
void read(Reader &source, std::int64_t first, int count, double *out) const
OfflineScratchArray< U > allocateScratch(std::uint64_t count)
SincPhase & operator=(const SincPhase &)=delete
void evaluate(const OfflineHilbertMap &map, std::size_t leaf, Reader &source, double *out)
Definition OfflineSinc.h:52
SincPhase(OfflineProductWorkspace &work, double shift)
Definition OfflineSinc.h:36
SincPhase(const SincPhase &)=delete
SincProjection(OfflineSession &job, std::int64_t frames, int factor, Reader source, int block)
Definition OfflineSinc.h:95
SincProjection & operator=(const SincProjection &)=delete
SincProjection(const SincProjection &)=delete
std::span< const double > get(std::size_t leaf)
void projectSinc(OfflineSession &job, std::int64_t frames, int factor, Reader source, Consumer consume, int block=4096)
void offlineFail(OfflineStatus status)
std::unique_ptr< T[], OfflineScratchDeleter< T > > OfflineScratchArray