Working note resolution
This commit is contained in:
parent
7d2ba379f7
commit
142680ab77
@ -447,6 +447,27 @@ struct Block
|
||||
Result<Value> index(Interpreter &i, unsigned position);
|
||||
};
|
||||
|
||||
struct Note
|
||||
{
|
||||
/// Base of a note, like `c` (=0), `c#` (=1) `d` (=2)
|
||||
u8 base;
|
||||
|
||||
/// Octave in MIDI acceptable range (from -1 to 9 inclusive)
|
||||
std::optional<i8> octave = std::nullopt;
|
||||
|
||||
/// Length of playing note
|
||||
std::optional<Number> length = std::nullopt;
|
||||
|
||||
/// Create Note from string
|
||||
static std::optional<Note> from(std::string_view note);
|
||||
|
||||
/// Extract midi note number
|
||||
std::optional<u8> into_midi_note() const;
|
||||
|
||||
/// Extract midi note number, but when octave is not present use provided default
|
||||
u8 into_midi_note(i8 default_octave) const;
|
||||
};
|
||||
|
||||
template<typename T, typename ...XS>
|
||||
constexpr auto is_one_of = (std::is_same_v<T, XS> || ...);
|
||||
|
||||
@ -466,7 +487,8 @@ struct Value
|
||||
Number,
|
||||
Symbol,
|
||||
Intrinsic,
|
||||
Block
|
||||
Block,
|
||||
Music
|
||||
};
|
||||
|
||||
Value() = default;
|
||||
@ -480,10 +502,11 @@ struct Value
|
||||
}
|
||||
|
||||
Type type = Type::Nil;
|
||||
bool b{};
|
||||
Number n{};
|
||||
Intrinsic intr{};
|
||||
bool b;
|
||||
Number n;
|
||||
Intrinsic intr;
|
||||
Block blk;
|
||||
Note note;
|
||||
|
||||
// TODO Most strings should not be allocated by Value, but reference to string allocated previously
|
||||
// Wrapper for std::string is needed that will allocate only when needed, middle ground between:
|
||||
|
@ -34,6 +34,23 @@ static void either_truthy_or_falsy(
|
||||
}
|
||||
}
|
||||
|
||||
static void test_note_resolution(
|
||||
std::string_view name,
|
||||
i8 octave,
|
||||
u8 expected,
|
||||
reflection::source_location sl = reflection::source_location::current())
|
||||
{
|
||||
auto const maybe_note = Note::from(name);
|
||||
expect(maybe_note.has_value(), sl) << "Note::from didn't recognized " << name << " as a note";
|
||||
if (maybe_note) {
|
||||
auto note = *maybe_note;
|
||||
note.octave = octave;
|
||||
auto const midi_note = note.into_midi_note();
|
||||
expect(midi_note.has_value(), sl) << "Note::into_midi_note returned nullopt, but should not";
|
||||
expect(eq(int(*midi_note), int(expected)), sl) << "Note::into_midi_note returned wrong value";
|
||||
}
|
||||
}
|
||||
|
||||
suite value_test = [] {
|
||||
"Value"_test = [] {
|
||||
should("be properly created using Value::from") = [] {
|
||||
@ -74,4 +91,24 @@ suite value_test = [] {
|
||||
|
||||
expect(eq("<intrinsic>"sv, str(Value(Intrinsic(nullptr)))));
|
||||
};
|
||||
|
||||
"Note"_test = [] {
|
||||
should("properly resolve notes") = [] {
|
||||
for (i8 i = 0; i < 9; ++i) {
|
||||
test_note_resolution("c", i + -1, i * 12 + 0);
|
||||
test_note_resolution("c#", i + -1, i * 12 + 1);
|
||||
test_note_resolution("d", i + -1, i * 12 + 2);
|
||||
test_note_resolution("d#", i + -1, i * 12 + 3);
|
||||
test_note_resolution("e", i + -1, i * 12 + 4);
|
||||
test_note_resolution("e#", i + -1, i * 12 + 5);
|
||||
test_note_resolution("f", i + -1, i * 12 + 5);
|
||||
test_note_resolution("f#", i + -1, i * 12 + 6);
|
||||
test_note_resolution("g", i + -1, i * 12 + 7);
|
||||
test_note_resolution("g#", i + -1, i * 12 + 8);
|
||||
test_note_resolution("a", i + -1, i * 12 + 9);
|
||||
test_note_resolution("a#", i + -1, i * 12 + 10);
|
||||
test_note_resolution("b", i + -1, i * 12 + 11);
|
||||
}
|
||||
};
|
||||
};
|
||||
};
|
||||
|
35
src/value.cc
35
src/value.cc
@ -11,6 +11,8 @@ constexpr u16 hash_note(Indexable<usize, char> auto const& note)
|
||||
return u8(note[0]) | (note[1] << 8);
|
||||
}
|
||||
|
||||
/// Finds numeric value of note. This form is later used as in
|
||||
/// note to midi resolution in formula octave * 12 + note_index
|
||||
constexpr u8 note_index(Indexable<usize, char> auto const& note)
|
||||
{
|
||||
switch (hash_note(note)) {
|
||||
@ -19,7 +21,7 @@ constexpr u8 note_index(Indexable<usize, char> auto const& note)
|
||||
case hash_note("d"): return 2;
|
||||
case hash_note("d#"): return 3;
|
||||
case hash_note("e"): return 4;
|
||||
case hash_note("e#"): return 4;
|
||||
case hash_note("e#"): return 5;
|
||||
case hash_note("f"): return 5;
|
||||
case hash_note("f#"): return 6;
|
||||
case hash_note("g"): return 7;
|
||||
@ -32,7 +34,8 @@ constexpr u8 note_index(Indexable<usize, char> auto const& note)
|
||||
case hash_note("b#"): return 12;
|
||||
}
|
||||
// This should be unreachable since parser limits what character can pass as notes
|
||||
unreachable();
|
||||
// but just to be sure return special value
|
||||
return -1;
|
||||
}
|
||||
|
||||
Result<Value> Value::from(Token t)
|
||||
@ -113,6 +116,7 @@ bool Value::truthy() const
|
||||
case Type::Number: return n != Number(0);
|
||||
case Type::Block:
|
||||
case Type::Intrinsic:
|
||||
case Type::Music:
|
||||
case Type::Symbol: return true;
|
||||
}
|
||||
unreachable();
|
||||
@ -135,6 +139,7 @@ bool Value::operator==(Value const& other) const
|
||||
case Type::Intrinsic: return intr == other.intr;
|
||||
case Type::Block: return false; // TODO Reconsider if functions are comparable
|
||||
case Type::Bool: return b == other.b;
|
||||
case Type::Music: unimplemented();
|
||||
}
|
||||
|
||||
unreachable();
|
||||
@ -160,6 +165,9 @@ std::ostream& operator<<(std::ostream& os, Value const& v)
|
||||
|
||||
case Value::Type::Block:
|
||||
return os << "<block>";
|
||||
|
||||
case Value::Type::Music:
|
||||
unimplemented();
|
||||
}
|
||||
unreachable();
|
||||
}
|
||||
@ -167,9 +175,10 @@ std::ostream& operator<<(std::ostream& os, Value const& v)
|
||||
std::string_view type_name(Value::Type t)
|
||||
{
|
||||
switch (t) {
|
||||
case Value::Type::Block: return "block";
|
||||
case Value::Type::Bool: return "bool";
|
||||
case Value::Type::Intrinsic: return "intrinsic";
|
||||
case Value::Type::Block: return "block";
|
||||
case Value::Type::Music: return "music";
|
||||
case Value::Type::Nil: return "nil";
|
||||
case Value::Type::Number: return "number";
|
||||
case Value::Type::Symbol: return "symbol";
|
||||
@ -207,3 +216,23 @@ Result<Value> Block::index(Interpreter &i, unsigned position)
|
||||
assert(position < body.arguments.size(), "Out of range"); // TODO(assert)
|
||||
return i.eval((Ast)body.arguments[position]);
|
||||
}
|
||||
|
||||
std::optional<Note> Note::from(std::string_view literal)
|
||||
{
|
||||
if (auto note = note_index(literal); note != u8(-1)) {
|
||||
return Note { .base = note };
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<u8> Note::into_midi_note() const
|
||||
{
|
||||
return octave ? std::optional(into_midi_note(0)) : std::nullopt;
|
||||
}
|
||||
|
||||
u8 Note::into_midi_note(i8 default_octave) const
|
||||
{
|
||||
auto const octave = this->octave.has_value() ? *this->octave : default_octave;
|
||||
// octave is in range [-1, 9] where Note { .base = 0, .octave = -1 } is midi note 0
|
||||
return (octave + 1) * 12 + base;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user