Fix embed use-after-free, restore golden fixture + refs tests, drop dead note_entry
This commit is contained in:
@@ -212,7 +212,6 @@ slider couldn't. Two pure modules split the forward (draw) and inverse (edit) ma
|
||||
- `component_state_io` (`core/instrument/map`) — the `ComponentState` envelope + params-payload binary codec (envelope v1…v11, params payload v1…v8), split out of `sample_map` (Q-W2v, T4-13 ≡ T2-07) so BOTH artifacts can link the codec without the extension pulling in the whole voice engine to serialize one preset blob — the extension's `instrument_drop` and the instrument's processor read/write the identical bytes, so the cross-artifact contract cannot drift. Payload v1…v7 are the RETIRED per-zone lists: still read, lifting by adopting zone one's capture + parameters (that first zone is what the old first-match resolve actually played, so it is also what supersedes the envelope's stored selection id).
|
||||
- `bank_sync` — generation change-detection + assignment-request consume: owns the yes/no decision logic so the rules are provable without a host. The processor shell owns cadence and side effects.
|
||||
- `bridge_marshal` — pure marshalling helper for the REAPER VST-host bridge read: interprets the `GetProjExtState` int return against its filled buffer.
|
||||
- `note_entry` — parses a raw string into a clamped MIDI note [0,127]; accepts plain decimal integers or note names (C4==60, DAW convention).
|
||||
- `trigger_seam` — pure Trigger frames↔fraction converter: owns the shared formula for converting between engine source-frame fade counts and the overlay's fractional representation, threading `startFrame` correctly through pack and unpack directions.
|
||||
|
||||
### `ui/`
|
||||
|
||||
@@ -1,111 +0,0 @@
|
||||
// note_entry.cpp — see note_entry.h.
|
||||
|
||||
#include "core/instrument/map/note_entry.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
|
||||
namespace reasampler::instrument::map {
|
||||
|
||||
namespace {
|
||||
char asciiUpper(char c) {
|
||||
return static_cast<char>(std::toupper(static_cast<unsigned char>(c)));
|
||||
}
|
||||
|
||||
std::string trim(const std::string& s) {
|
||||
std::size_t a = 0;
|
||||
std::size_t b = s.size();
|
||||
while (a < b && std::isspace(static_cast<unsigned char>(s[a]))) ++a;
|
||||
while (b > a && std::isspace(static_cast<unsigned char>(s[b - 1]))) --b;
|
||||
return s.substr(a, b - a);
|
||||
}
|
||||
|
||||
int clampNote(long long n) {
|
||||
if (n < 0) return 0;
|
||||
if (n > 127) return 127;
|
||||
return static_cast<int>(n);
|
||||
}
|
||||
|
||||
// Semitone offset within an octave for a note letter (C..B), or -1 for a non-letter.
|
||||
int letterSemitone(char up) {
|
||||
switch (up) {
|
||||
case 'C': return 0;
|
||||
case 'D': return 2;
|
||||
case 'E': return 4;
|
||||
case 'F': return 5;
|
||||
case 'G': return 7;
|
||||
case 'A': return 9;
|
||||
case 'B': return 11;
|
||||
default: return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse a note name like "C4", "F#3", "Bb-1" (case-insensitive, DAW convention:
|
||||
// MIDI 0 == C-1, 60 == C4). Returns nullopt if it is not a note name.
|
||||
std::optional<int> parseNoteName(const std::string& s) {
|
||||
if (s.empty()) return std::nullopt;
|
||||
std::size_t i = 0;
|
||||
const int base = letterSemitone(asciiUpper(s[i]));
|
||||
if (base < 0) return std::nullopt; // not a letter -> not a note name
|
||||
++i;
|
||||
int semitone = base;
|
||||
// Optional accidental(s): # / b only (not 's'/'f').
|
||||
while (i < s.size() && (s[i] == '#' || s[i] == 'b' || s[i] == 'B')) {
|
||||
if (s[i] == '#') ++semitone;
|
||||
else --semitone;
|
||||
++i;
|
||||
}
|
||||
// The octave: an optional sign then digits, running to the end.
|
||||
if (i >= s.size()) return std::nullopt; // a bare "C" has no octave -> reject (ambiguous)
|
||||
bool neg = false;
|
||||
if (s[i] == '+' || s[i] == '-') {
|
||||
neg = (s[i] == '-');
|
||||
++i;
|
||||
}
|
||||
if (i >= s.size()) return std::nullopt;
|
||||
int octave = 0;
|
||||
bool anyDigit = false;
|
||||
for (; i < s.size(); ++i) {
|
||||
if (!std::isdigit(static_cast<unsigned char>(s[i]))) return std::nullopt;
|
||||
octave = octave * 10 + (s[i] - '0');
|
||||
anyDigit = true;
|
||||
}
|
||||
if (!anyDigit) return std::nullopt;
|
||||
if (neg) octave = -octave;
|
||||
// MIDI note = (octave + 1) * 12 + semitone (C-1 == 0, C4 == 60).
|
||||
const long long note = static_cast<long long>(octave + 1) * 12 + semitone;
|
||||
return clampNote(note);
|
||||
}
|
||||
|
||||
std::optional<int> parseInteger(const std::string& s) {
|
||||
if (s.empty()) return std::nullopt;
|
||||
std::size_t i = 0;
|
||||
bool neg = false;
|
||||
if (s[i] == '+' || s[i] == '-') {
|
||||
neg = (s[i] == '-');
|
||||
++i;
|
||||
}
|
||||
if (i >= s.size()) return std::nullopt;
|
||||
long long v = 0;
|
||||
for (; i < s.size(); ++i) {
|
||||
if (!std::isdigit(static_cast<unsigned char>(s[i]))) return std::nullopt;
|
||||
v = v * 10 + (s[i] - '0');
|
||||
if (v > 1000000) v = 1000000; // saturate; clampNote takes it to 127 anyway
|
||||
}
|
||||
if (neg) v = -v;
|
||||
return clampNote(v);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
std::optional<int> parseNoteEntry(const std::string& text) {
|
||||
const std::string s = trim(text);
|
||||
if (s.empty()) return std::nullopt;
|
||||
// Try a plain integer first (the common MIDI-number case); fall back to a note name.
|
||||
if (std::isdigit(static_cast<unsigned char>(s[0])) || s[0] == '+' ||
|
||||
(s[0] == '-' && s.size() > 1 && std::isdigit(static_cast<unsigned char>(s[1])))) {
|
||||
if (auto n = parseInteger(s)) return n;
|
||||
}
|
||||
return parseNoteName(s);
|
||||
}
|
||||
|
||||
} // namespace reasampler::instrument::map
|
||||
@@ -1,18 +0,0 @@
|
||||
// note_entry — parse + clamp for direct numeric/note-name entry of a zone's low/high/root
|
||||
// MIDI note (a drag on the keyboard strip can't hit a precise note reliably).
|
||||
//
|
||||
// Accepts a plain decimal integer ("60", "+5") or a note name ("C4", "f#3", "Bb-1", DAW
|
||||
// convention: MIDI 0 == C-1, 60 == C4). Out-of-range CLAMPS to [0,127] rather than
|
||||
// rejecting; unparseable input returns nullopt (shell keeps the old value).
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
namespace reasampler::instrument::map {
|
||||
|
||||
// Leading/trailing whitespace ignored. Empty or unparseable input returns nullopt.
|
||||
std::optional<int> parseNoteEntry(const std::string& text);
|
||||
|
||||
} // namespace reasampler::instrument::map
|
||||
@@ -6,7 +6,7 @@
|
||||
// focused sub-editor, not a view change): width/height each clamp to a fraction of the
|
||||
// window within min/max bounds. A title row sits over the curve box. The curve box rect
|
||||
// here is the border rect — the shell derives the mapping box via its curveBoxFromRect
|
||||
// formula, so the popup editor and the Zone-panel inline editor share coordinates.
|
||||
// formula.
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
// outside the DAW; the shell draws handles, captures the grab, and feeds pixel deltas back in.
|
||||
//
|
||||
// envelope_overlay owns the params->polyline forward (draw) map; this module owns the inverse
|
||||
// (edit) map + hit-test. Both read/write the same AmpEnvelope fields (shell re-reads the zone
|
||||
// every paint), so a node drag and a slider edit are two views on one source of truth.
|
||||
// (edit) map + hit-test. Both read/write the same AmpEnvelope fields (shell re-reads the one
|
||||
// parameter set every paint), so a node drag and a slider edit are two views on one source of
|
||||
// truth.
|
||||
//
|
||||
// A drag can never produce a param a slider couldn't: nodes are monotonic in time (clamped
|
||||
// between time predecessor/successor) and range-clamped to the same per-param [min,max] the
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
// envelope_overlay.h — amp-envelope -> polyline geometry for the Sample-view envelope overlay.
|
||||
// Engine-free by design (no sample_map/sampler_core dependency); mirror of waveform_view /
|
||||
// param_slider. The shell packs the zone's AdsrSeconds/TriggerParams into AmpEnvelope and draws
|
||||
// the polyline plus a handle at each node (envelope_edit does the hit-test).
|
||||
// param_slider. The shell packs the one parameter set's AdsrSeconds/TriggerParams into
|
||||
// AmpEnvelope and draws the polyline plus a handle at each node (envelope_edit does the
|
||||
// hit-test).
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
Reference in New Issue
Block a user