58#include "../Core/AudioSpec.h"
59#include "../Core/AudioBuffer.h"
60#include "../Core/DenormalGuard.h"
102 const char*
name =
"DSPark Plugin";
135 float minValue,
float maxValue,
float defValue,
136 const char* unit)
noexcept
138 return Param { id, name, minValue, maxValue, defValue, unit, 0 };
142constexpr Param toggle(
const char*
id,
const char* name,
bool defaultOn)
noexcept
144 return Param { id, name, 0.0f, 1.0f, defaultOn ? 1.0f : 0.0f,
"", 1 };
153 float minValue,
float maxValue,
float defValue,
154 int steps,
const char* unit =
"") noexcept
156 return Param { id, name, minValue, maxValue, defValue, unit,
157 steps > 1 ? steps : 1,
nullptr };
172 const char*
const (&labels)[N],
int defaultIndex = 0) noexcept
174 static_assert(N >= 2,
"a choice needs at least two labels");
175 const int def = defaultIndex < 0 ? 0
176 : (defaultIndex >
static_cast<int>(N) - 1 ?
static_cast<int>(N) - 1
178 return Param { id, name, 0.0f,
static_cast<float>(N - 1),
static_cast<float>(def),
179 "",
static_cast<int>(N - 1), labels };
185 return p.
steps == 1 && p.labels ==
nullptr;
189template <
typename... Ps>
190constexpr std::array<
Param,
sizeof...(Ps)>
params(Ps... ps)
noexcept
200 const double range =
static_cast<double>(p.maxValue) - p.
minValue;
201 if (range <= 0.0)
return 0.0;
202 const double n = (plain - p.minValue) / range;
205 return !(n > 0.0) ? 0.0 : (n > 1.0 ? 1.0 : n);
213 double n = !(normalized > 0.0) ? 0.0 : (normalized > 1.0 ? 1.0 : normalized);
218 const double scaled = n * p.steps + 0.5;
219 const double idx =
static_cast<double>(
static_cast<long long>(scaled));
221 n = n < 0.0 ? 0.0 : (n > 1.0 ? 1.0 : n);
223 return p.minValue + n * (
static_cast<double>(p.maxValue) - p.minValue);
232 auto lower = [](
char c) {
return c >=
'A' && c <= 'Z' ? static_cast<char>(c + 32) : c; };
233 if (text ==
nullptr)
return -1;
234 if (lower(text[0]) ==
'o' && lower(text[1]) ==
'n' && text[2] ==
'\0')
return 1;
235 if (lower(text[0]) ==
'o' && lower(text[1]) ==
'f' && lower(text[2]) ==
'f'
236 && text[3] ==
'\0')
return 0;
243 if (p.steps <= 0)
return 0;
244 const double scaled =
toNormalized(p, plain) * p.steps + 0.5;
245 const int idx =
static_cast<int>(scaled);
246 return idx < 0 ? 0 : (idx > p.steps ? p.steps : idx);
252 if (out ==
nullptr || outSize <= 0)
return;
253 const size_t cap =
static_cast<size_t>(outSize);
254 if (p.labels !=
nullptr && p.steps > 0)
256 const char* label = p.labels[
stepIndex(p, plain)];
257 std::snprintf(out, cap,
"%s", label !=
nullptr ? label :
"");
262 std::snprintf(out, cap,
"%s",
263 plain >= 0.5 * (p.minValue + p.maxValue) ?
"On" :
"Off");
270 const double step = (
static_cast<double>(p.maxValue) - p.minValue) / p.steps;
271 auto isWhole = [](
double v) {
272 const double r =
static_cast<double>(
static_cast<long long>(v < 0.0 ? v - 0.5
274 return v - r < 1e-9 && r - v < 1e-9;
276 whole = isWhole(step) && isWhole(p.minValue);
280 std::snprintf(number,
sizeof(number),
"%.0f", plain);
282 std::snprintf(number,
sizeof(number),
"%.2f", plain);
285 auto put = [&](
const char* text)
noexcept {
286 for (; text !=
nullptr && *text !=
'\0' && n + 1 < cap; ++text) out[n++] = *text;
289 if (p.unit !=
nullptr && p.unit[0] !=
'\0')
306 if (text ==
nullptr)
return false;
307 while (*text ==
' ' || *text ==
'\t') ++text;
308 if (p.labels !=
nullptr && p.steps > 0)
310 auto lower = [](
char c) {
311 return c >=
'A' && c <= 'Z' ? static_cast<char>(c + 32) : c;
313 for (
int i = 0; i <= p.steps; ++i)
315 const char* label = p.labels[i];
316 if (label ==
nullptr)
continue;
318 while (label[k] !=
'\0' && lower(label[k]) == lower(text[k])) ++k;
319 if (label[k] ==
'\0' && text[k] ==
'\0')
321 plain =
toPlain(p,
static_cast<double>(i) / p.steps);
336 const double v = std::strtod(text, &end);
337 if (end == text || v != v)
return false;
345constexpr uint32_t
hash32(
const char* s)
noexcept
347 uint32_t h = 2166136261u;
350 h ^=
static_cast<uint8_t
>(*s++);
357constexpr uint64_t
hash64(
const char* s, uint64_t salt)
noexcept
359 uint64_t h = 14695981039346656037ull ^ salt;
362 h ^=
static_cast<uint8_t
>(*s++);
363 h *= 1099511628211ull;
377 constexpr size_t n = P::parameters.size();
378 for (
size_t i = 0; i < n; ++i)
380 const uint32_t h =
hash32(P::parameters[i].
id);
381 if (h == 0x5052474Du || h == 0x42595053u)
return false;
382 for (
size_t j = i + 1; j < n; ++j)
383 if (
hash32(P::parameters[j].
id) == h)
return false;
392constexpr std::array<uint8_t, 16>
makeUid(
const char* productId, uint64_t salt)
noexcept
394 const uint64_t a =
hash64(productId, salt);
395 const uint64_t b =
hash64(productId, salt ^ 0x9E3779B97F4A7C15ull);
396 std::array<uint8_t, 16> uid {};
397 for (
int i = 0; i < 8; ++i)
399 uid[
static_cast<size_t>(i)] =
static_cast<uint8_t
>(a >> (8 * i));
400 uid[
static_cast<size_t>(i + 8)] =
static_cast<uint8_t
>(b >> (8 * i));
411concept HasLatency =
requires(
const P p) { { p.getLatency() } -> std::convertible_to<int>; };
414concept HasTail =
requires(
const P p) { { p.getTailSeconds() } -> std::convertible_to<double>; };
418 { p.getState() } -> std::convertible_to<std::vector<uint8_t>>;
423 { p.setState(d, n) } -> std::convertible_to<bool>;
431concept HasEditor =
requires { P::hasEditor; } && P::hasEditor;
453 AudioBufferView<float> sc) {
454 p.processBlock(io, sc);
553concept HasMidi =
requires(P p,
const MidiEvent& e) {
554 p.handleMidiEvent(e);
569 p.setOfflineRendering(offline);
592 { P::channels } -> std::convertible_to<ChannelSupport>;
629template <
size_t NumValues>
638template <
typename... Vs>
641 return PresetDef<
sizeof...(Vs)> { name, {
static_cast<float>(values)... } };
647template <
typename First,
typename... Rest>
648constexpr std::array<First, 1 +
sizeof...(Rest)>
presets(First first, Rest... rest)
noexcept
650 static_assert((std::is_same_v<First, Rest> && ...),
651 "every preset must list one value per parameter");
652 return { first, rest... };
664 { P::factoryPresets.size() } -> std::convertible_to<size_t>;
665 { P::factoryPresets[0].name } -> std::convertible_to<const char*>;
666 { P::factoryPresets[0].values.size() } -> std::convertible_to<size_t>;
675 static_assert(P::factoryPresets[0].values.size() == P::parameters.size(),
676 "factoryPresets must list one PLAIN value per parameter, "
677 "in parameter-table order");
678 return static_cast<int>(P::factoryPresets.size());
691 P::factoryPresets[
static_cast<size_t>(presetIndex)]
692 .values[paramIndex]);
713 { P::sampleAccurateAutomation } -> std::convertible_to<bool>;
720 return P::sampleAccurateAutomation;
753 for (
int i = 1; i < count; ++i)
757 while (j >= 0 && events[j].offset > key.
offset)
759 events[j + 1] = events[j];
787 const int need = (maxLatency > 0 ? maxLatency : 0) + (maxBlock > 0 ? maxBlock : 0) + 1;
789 while (size_ < need) size_ <<= 1;
790 for (
auto& b : buf_) b.assign(
static_cast<size_t>(size_), 0.0f);
797 for (
auto& b : buf_) std::fill(b.begin(), b.end(), 0.0f);
802 [[nodiscard]]
int capacity(
int n)
const noexcept {
return size_ - 1 - n; }
809 void process(
float*
const* dry,
int numChannels,
int n,
int latency)
noexcept
811 if (size_ == 0 || n <= 0)
return;
812 const int mask = size_ - 1;
814 const int d = latency < 0 ? 0 : (latency > cap ? (cap > 0 ? cap : 0) : latency);
815 for (
int ch = 0; ch < numChannels && ch < 2; ++ch)
817 float* ring = buf_[
static_cast<size_t>(ch)].data();
819 for (
int i = 0; i < n; ++i)
821 const int w = (write_ + i) & mask;
823 x[i] = ring[(w - d) & mask];
826 write_ = (write_ + n) & mask;
830 std::array<std::vector<float>, 2> buf_;
866 { P::editorHtml() } -> std::convertible_to<const char*>;
872 { P::editorSize.width } -> std::convertible_to<int>;
873 { P::editorSize.height } -> std::convertible_to<int>;
881 return EditorSize {
static_cast<int>(P::editorSize.width),
882 static_cast<int>(P::editorSize.height) };
896 { P::editorResize } -> std::convertible_to<EditorResize>;
904 return P::editorResize;
942 if (!(value >
static_cast<double>(lo)))
return lo;
943 if (value >=
static_cast<double>(hi))
return hi;
944 return static_cast<int>(value);
962 width = logical.
width * scale;
963 height = logical.
height * scale;
971 width = width < minW ? minW : (width > maxW ? maxW : width);
972 height = height < minH ? minH : (height > maxH ? maxH : height);
975 const double ratio =
static_cast<double>(logical.
width) / logical.
height;
977 if (height * ratio < fitW) fitW = height * ratio;
978 fitW = fitW < minW ? minW : (fitW > maxW ? maxW : fitW);
980 height = fitW / ratio;
987 width = !(width > 1.0) ? 1.0
989 height = !(height > 1.0) ? 1.0
1002template <
typename P>
1004 { P::editorDevFile() } -> std::convertible_to<const char*>;
1042template <
typename Derived>
1078 [[nodiscard]] std::vector<uint8_t>
getState()
const {
return {}; }
1081 bool setState(
const uint8_t*,
size_t) {
return false; }
1133template <
typename P>
1134inline std::vector<uint8_t>
buildState(
const P& user,
const double* normalized,
1135 size_t numParams,
int programIndex = -1,
1136 int bypassState = -1)
1138 auto push32 = [](std::vector<uint8_t>& v, uint32_t x) {
1139 v.push_back(
static_cast<uint8_t
>(x));
1140 v.push_back(
static_cast<uint8_t
>(x >> 8));
1141 v.push_back(
static_cast<uint8_t
>(x >> 16));
1142 v.push_back(
static_cast<uint8_t
>(x >> 24));
1144 auto pushValue = [&push32](std::vector<uint8_t>& v,
double value) {
1146 static_assert(
sizeof(bits) ==
sizeof(value));
1147 std::memcpy(&bits, &value,
sizeof(bits));
1148 push32(v,
static_cast<uint32_t
>(bits));
1149 push32(v,
static_cast<uint32_t
>(bits >> 32));
1151 std::vector<uint8_t> blob;
1154 push32(blob,
static_cast<uint32_t
>(numParams) + (programIndex >= 0 ? 1u : 0u)
1155 + (bypassState >= 0 ? 1u : 0u));
1156 for (
size_t i = 0; i < numParams; ++i)
1158 push32(blob,
hash32(P::parameters[i].
id));
1159 pushValue(blob, normalized[i]);
1161 if (programIndex >= 0)
1166 pushValue(blob,
static_cast<double>(programIndex));
1168 if (bypassState >= 0)
1171 pushValue(blob, bypassState > 0 ? 1.0 : 0.0);
1175 const std::vector<uint8_t> userBlob = user.getState();
1176 push32(blob,
static_cast<uint32_t
>(userBlob.size()));
1177 blob.insert(blob.end(), userBlob.begin(), userBlob.end());
1188template <
typename P>
1189inline bool applyState(P& user,
const uint8_t* data,
size_t size,
double* normalized,
1190 int* programIndex =
nullptr,
int* bypassState =
nullptr)
1192 auto read32 = [&](
size_t& pos, uint32_t& out) {
1193 if (pos + 4 > size)
return false;
1194 out =
static_cast<uint32_t
>(data[pos])
1195 |
static_cast<uint32_t
>(data[pos + 1]) << 8
1196 |
static_cast<uint32_t
>(data[pos + 2]) << 16
1197 |
static_cast<uint32_t
>(data[pos + 3]) << 24;
1202 uint32_t magic = 0, version = 0, count = 0;
1203 if (!read32(pos, magic) || magic !=
kStateMagic)
return false;
1204 if (!read32(pos, version) || version == 0)
return false;
1205 if (!read32(pos, count))
return false;
1207 constexpr size_t numParams = P::parameters.size();
1208 for (uint32_t i = 0; i < count; ++i)
1210 uint32_t
id = 0, lo = 0, hi = 0;
1211 if (!read32(pos,
id) || !read32(pos, lo) || !read32(pos, hi))
return false;
1212 const uint64_t bits =
static_cast<uint64_t
>(lo) | (
static_cast<uint64_t
>(hi) << 32);
1214 std::memcpy(&v, &bits,
sizeof(v));
1218 if (v != v)
continue;
1227 if (programIndex !=
nullptr && v >= 0.0)
1229 const double rounded = v + 0.5;
1230 constexpr double intLimit =
1231 static_cast<double>(std::numeric_limits<int>::max());
1232 *programIndex = rounded < intLimit
1233 ?
static_cast<int>(rounded)
1234 : std::numeric_limits<int>::max();
1240 if (bypassState !=
nullptr)
1241 *bypassState = v >= 0.5 ? 1 : 0;
1244 for (
size_t k = 0; k < numParams; ++k)
1245 if (
hash32(P::parameters[k].
id) ==
id)
1247 normalized[k] = v < 0.0 ? 0.0 : (v > 1.0 ? 1.0 : v);
1253 uint32_t userSize = 0;
1257 if (read32(pos, userSize) && userSize > 0 && userSize <= size - pos)
1260 user.setState(data + pos, userSize);
The dry signal of the wrappers' soft bypass, delayed by the plugin's latency.
int capacity(int n) const noexcept
void prepare(int maxLatency, int maxBlock)
void process(float *const *dry, int numChannels, int n, int latency) noexcept
Optional static constexpr bool editorDebug = true; - enables the browser DevTools in the editor (WebV...
Optional static const char* editorDevFile() - absolute path of an HTML file to load INSTEAD of editor...
static const char* editorHtml() - the editor page (HTML/CSS/JS), usually a raw string literal....
Optional static constexpr bool editorResizable = true; - shorthand for editorResize = EditorResize::F...
Optional static constexpr EditorResize editorResize = ...; - full resize policy; takes precedence ove...
Optional static constexpr EditorSize editorSize { w, h };.
Custom-editor switch. With hasEditor = false (or absent) hosts show their generic parameter UI....
Factory-preset capability: a static constexpr auto factoryPresets table built with presets()....
MIDI capability: void handleMidiEvent(const MidiEvent&) noexcept. Its presence grows a note/event inp...
Render-mode capability: void setOfflineRendering(bool) noexcept. The host flips it to true for non-re...
Sample-accurate opt-out: static constexpr bool sampleAccurateAutomation = false;. By default the wrap...
Sidechain capability. Implement the two-buffer process - the same shape DSPark's own dynamics take:
Transport capability: void setTransport(const TransportInfo&) noexcept. Called on the audio thread,...
constexpr bool supportsChannelCount(int numChannels) noexcept
True when P accepts a bus width of numChannels (1 or 2).
constexpr std::array< Param, sizeof...(Ps)> params(Ps... ps) noexcept
Builds the parameter table (use inside static constexpr auto).
constexpr double kEditorMinSizeFactor
Resize bounds: hosts may shrink to half and grow to 3x the declared size.
EditorResize
How the host may resize the editor window. Declare static constexpr EditorResize editorResize = ....
@ Fixed
The window is exactly editorSize; hosts cannot drag-resize it.
@ KeepAspect
Drag-resizable, locked to the declared width:height ratio.
@ Free
Drag-resizable between 0.5x and 3x the declared size.
constexpr bool paramIdsUnique() noexcept
Compile-time guarantee that every parameter id hashes uniquely and collides with neither reserved sta...
constexpr ChannelSupport channelSupportOf() noexcept
The declared channel support of P (default: mono+stereo).
constexpr uint32_t kProgramStateId
Reserved entry id persisting the active factory-preset index ('PRGM'); never a user parameter....
constexpr uint32_t kStateVersion
Category
Plugin category (reflected into each format's class metadata).
@ Instrument
Audio generator (MIDI in -> audio out, no audio input).
@ Fx
Audio effect (audio in -> audio out).
constexpr double kEditorMaxPixels
Pixel ceiling for a negotiated editor size.
constexpr Param stepped(const char *id, const char *name, float minValue, float maxValue, float defValue, int steps, const char *unit="") noexcept
Discrete parameter helper: steps + 1 evenly spaced positions from minValue to maxValue,...
std::vector< uint8_t > buildState(const P &user, const double *normalized, size_t numParams, int programIndex=-1, int bypassState=-1)
constexpr Param toggle(const char *id, const char *name, bool defaultOn) noexcept
On/off parameter helper.
constexpr int stepIndex(const Param &p, double plain) noexcept
Position index (0..steps) of a plain value of a discrete parameter.
constexpr int kDryDelayMinCapacity
Ring headroom of the bypass dry path: latencies up to this (or twice the latency at activation,...
constexpr bool sampleAccurateOf() noexcept
constexpr uint64_t hash64(const char *s, uint64_t salt) noexcept
FNV-1a 64-bit with a salt (two runs build a 128-bit class UID).
constexpr Param param(const char *id, const char *name, float minValue, float maxValue, float defValue, const char *unit) noexcept
Continuous parameter helper.
constexpr std::array< uint8_t, 16 > makeUid(const char *productId, uint64_t salt) noexcept
Deterministic 16-byte class UID from the stable productId. Each backend salts differently so VST3/CLA...
constexpr double toNormalized(const Param &p, double plain) noexcept
Plain [min, max] -> normalized [0, 1] (steps snap on the way back).
constexpr double toPlain(const Param &p, double normalized) noexcept
Normalized [0, 1] -> plain [min, max], snapped for stepped params.
int parseToggleText(const char *text) noexcept
Case-insensitive "On"/"Off" recognition - the inverse of the toggle display below....
constexpr double presetNormalized(int presetIndex, size_t paramIndex) noexcept
Normalized value of parameter paramIndex in factory preset presetIndex (bounds already validated by t...
constexpr int kAutomationQuantum
Sub-block grain: automation/event splits snap to this many frames (bounds the per-call overhead; ~0....
constexpr uint32_t hash32(const char *s) noexcept
FNV-1a 32-bit over a C string - the per-parameter host id.
constexpr Param choice(const char *id, const char *name, const char *const (&labels)[N], int defaultIndex=0) noexcept
Named-choice helper: one position per label, plain values 0..N-1 (the label index),...
constexpr EditorResize editorResizeOf() noexcept
The effective resize policy for plugin class P.
constexpr uint32_t kStateMagic
constexpr void constrainEditorSize(double &width, double &height, double scale) noexcept
Applies the resize policy of P to a size the host proposes: Fixed pins it, Free clamps each axis to t...
constexpr double kEditorMaxSizeFactor
constexpr int kMaxBlockEvents
Hard cap of timestamped events handled per block; the (very unlikely) excess applies at the position ...
constexpr bool isToggle(const Param &p) noexcept
True for an on/off parameter (two unnamed positions).
constexpr PresetDef< sizeof...(Vs)> preset(const char *name, Vs... values) noexcept
Builds one factory preset: preset("Warm", 6.0f, 0.8f, ...) - one PLAIN value per parameter,...
constexpr EditorSize editorSizeOf() noexcept
The declared (logical) editor size, or the 480x320 default.
constexpr int factoryPresetCountOf() noexcept
Number of factory presets of P (0 when none declared).
constexpr int defaultChannelCount() noexcept
The width every backend starts in before the host negotiates.
void formatValue(const Param &p, double plain, char *out, int outSize) noexcept
Formats a plain value for host display ("3.5 dB", "On", "Saw", "4").
void sortBlockEvents(BlockEvent *events, int count) noexcept
Stable insertion sort by offset (tiny N, allocation-free - audio-thread safe; equal offsets keep arri...
ChannelSupport
Bus widths a plugin offers to the host. The DSPark contract is channel-agnostic by construction (prep...
@ MonoOnly
Strictly 1->1 (rare; metering/utility plugins).
@ StereoOnly
Strictly 2->2 (inherently stereo DSP).
@ MonoAndStereo
Negotiates 1->1 or 2->2 with the host (default).
constexpr uint32_t kBypassStateId
Reserved entry id persisting the host bypass ('BYPS' - the same value every backend uses as the bypas...
constexpr int toBoundedInt(double value, int lo, int hi) noexcept
A floating value from outside converted to an integer, with the range settled BEFORE the conversion.
constexpr std::array< First, 1+sizeof...(Rest)> presets(First first, Rest... rest) noexcept
Builds the factory preset table (all presets must cover the same parameter count - enforced here; mat...
bool applyState(P &user, const uint8_t *data, size_t size, double *normalized, int *programIndex=nullptr, int *bypassState=nullptr)
Parses a state blob; fills normalized (defaults pre-loaded by the caller), reports a persisted progra...
bool parseValue(const Param &p, const char *text, double &plain) noexcept
Host text -> plain value, the inverse of formatValue(): a choice's label (any case),...
One timestamped in-block event, normalised across formats: a parameter point, a bypass point,...
uint32_t paramId
hash32 id (Kind::Param only).
int32_t offset
Frame position within the block.
double value
Normalized value / bypass >= 0.5.
MidiEvent midi
Kind::Midi payload.
Static identity of a plugin. All fields are plain literals so the whole descriptor can live in a stat...
Editor window size in logical pixels (physical = logical x host scale).
One incoming MIDI-ish event, normalised across formats.
@ NoteOn
note = key, value = velocity 0..1.
@ PolyPressure
note = key, value = pressure 0..1.
@ NoteOff
note = key, value = release velocity 0..1.
@ ControlChange
note = controller number, value = 0..1.
@ ChannelPressure
value = pressure 0..1 (note unused).
@ PitchBend
value = bend -1..+1 (note unused).
float value
Velocity / CC / pressure 0..1; bend -1..+1.
int sampleOffset
Frames into the NEXT processBlock call.
uint8_t note
Key 0..127, or controller number for CC.
One automatable parameter. Plain values run [min, max]; hosts see the normalized [0,...
const char * name
Display name.
const char *const * labels
steps+1 position names, or null.
const char * unit
Display unit ("dB", "Hz", "%", ...).
int steps
0 continuous, 1 toggle, N discrete.
Optional convenience base that makes EVERY contract method visible and overridable from one place - I...
void setOfflineRendering(bool) noexcept
void setTransport(const TransportInfo &) noexcept
static constexpr bool hasEditor
double getTailSeconds() const noexcept
std::vector< uint8_t > getState() const
bool setState(const uint8_t *, size_t)
- restore side. Return false on a foreign blob.
int getLatency() const noexcept
One factory preset: a name plus a PLAIN value for every parameter, in parameter-table order....
std::array< float, NumValues > values
Host transport snapshot, delivered once per audio block.
bool playing
Transport is rolling.
double secondsPerBeat() const noexcept
Seconds per quarter note at the current tempo.
double tempoBpm
Current tempo (when tempoValid).
double loopStartPpq
Cycle start (when loopValid).
double samplesPerBeat(double sampleRate) const noexcept
Samples per quarter note - the basis for tempo-synced delays/LFOs.
bool timeSigValid
Time signature came from the host.
bool tempoValid
tempoBpm came from the host.
bool positionValid
ppqPosition/barStartPpq came from the host.
double ppqPosition
Musical position of frame 0, in quarter notes.
double loopEndPpq
Cycle end (when loopValid).
double barStartPpq
Quarter-note position of the current bar start.
bool loopValid
Loop points came from the host.
int timeSigNumerator
e.g. 3 in 3/4 (when timeSigValid).
bool looping
Cycle/loop mode is engaged.
bool recording
Transport is recording.
int timeSigDenominator
e.g. 4 in 3/4 (when timeSigValid).