Fix embed use-after-free, restore golden fixture + refs tests, drop dead note_entry

This commit is contained in:
2026-07-30 08:01:22 -04:00
parent 8d4ccbf841
commit e304f2b031
13 changed files with 241 additions and 244 deletions
-111
View File
@@ -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
-18
View File
@@ -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