Tabula Sonora 0.1.0
A native C++20 implementation of the Roland Sound Canvas VA synth voice
Loading...
Searching...
No Matches
pitch_ramp.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <algorithm>
4#include <cmath>
5#include <cstdint>
6#include <span>
7
8namespace ts {
9
25class PitchRamp {
26public:
28 static constexpr int samples_per_slot = 8;
29
31 static constexpr int unity = 0x38000;
32
34 static constexpr int units_per_octave = 0x4000;
35 static constexpr int milli_semitone_divisor = 375;
36
38 static constexpr int domain_max = 0x3FFFF;
39
41 explicit PitchRamp(std::span<const std::int32_t> ramp_exp) noexcept : table_(ramp_exp) {}
42
47 [[nodiscard]] static int domain_of(double ratio) noexcept
48 {
49 if (!(ratio > 0.0)) {
50 return 0;
51 }
52 const double units = unity + (std::log2(ratio) * units_per_octave);
53 return std::clamp(static_cast<int>(std::lround(units)), 0, domain_max) & ~1;
54 }
55
60 [[nodiscard]] std::int32_t increment_of(int value) const noexcept
61 {
62 if (value == 0) {
63 return 0;
64 }
65 const auto index = static_cast<std::size_t>((value >> 6) & 0xFF);
66 const int fraction = value & 0x3F;
67 const std::int64_t interpolated =
68 (static_cast<std::int64_t>(table_[index]) * (0x40 - fraction))
69 + (static_cast<std::int64_t>(table_[index + 1]) * fraction);
70 const std::int64_t rounded = (interpolated + ((interpolated >> 63) & 0x3F)) >> 6;
71 return static_cast<std::int32_t>(rounded >> (0xF - ((value >> 0xE) & 0xF)));
72 }
73
80 void arm(double from_ratio, double to_ratio) noexcept
81 {
82 current_ = domain_of(from_ratio);
83 target_ = domain_of(to_ratio);
84 const auto delta = static_cast<std::int64_t>(target_) - current_;
85 step_ = static_cast<int>((delta * max_rate) >> 13);
86 if (step_ == 0 && current_ < target_) {
87 step_ = 1;
88 }
89 active_ = current_ != target_ && step_ != 0;
90 }
91
93 [[nodiscard]] bool is_active() const noexcept { return active_; }
94
96 [[nodiscard]] std::int32_t next_slot() noexcept
97 {
98 if (!active_) {
99 return increment_of(target_);
100 }
101
102 current_ = step_ < 0 ? std::max(target_, current_ + step_)
103 : std::min(target_, current_ + step_);
104 if (current_ == target_) {
105 active_ = false;
106 }
107 return increment_of(current_);
108 }
109
110private:
113 static constexpr int max_rate = 0xFFF;
114
115 std::span<const std::int32_t> table_;
116 int current_ = unity;
117 int target_ = unity;
118 int step_ = 0;
119 bool active_ = false;
120};
121
122} // namespace ts
static constexpr int milli_semitone_divisor
Definition pitch_ramp.hpp:35
PitchRamp(std::span< const std::int32_t > ramp_exp) noexcept
Creates a ramp over the shared exponential table, which must outlive it.
Definition pitch_ramp.hpp:41
std::int32_t increment_of(int value) const noexcept
The 16.16 sampler increment a domain value decodes to.
Definition pitch_ramp.hpp:60
std::int32_t next_slot() noexcept
Advances one slot and returns the increment that serves the next eight samples.
Definition pitch_ramp.hpp:96
static constexpr int unity
The ramp domain's value for unity rate.
Definition pitch_ramp.hpp:31
static constexpr int units_per_octave
Units per octave, and the divisor that puts milli-semitones into them.
Definition pitch_ramp.hpp:34
void arm(double from_ratio, double to_ratio) noexcept
Arms the ramp to glide from one ratio to another across the coming block.
Definition pitch_ramp.hpp:80
static constexpr int samples_per_slot
Samples one increment serves; the engine writes four of these per 32-sample chunk.
Definition pitch_ramp.hpp:28
static int domain_of(double ratio) noexcept
Converts a playback ratio into the ramp domain.
Definition pitch_ramp.hpp:47
bool is_active() const noexcept
Whether the ramp still has ground to cover.
Definition pitch_ramp.hpp:93
static constexpr int domain_max
Largest value the exponential decode accepts.
Definition pitch_ramp.hpp:38
Definition control_decode.hpp:7