Working note resolution

This commit is contained in:
Robert Bendun 2022-05-24 01:40:16 +02:00
parent 7d2ba379f7
commit 142680ab77
3 changed files with 96 additions and 7 deletions

View File

@ -447,6 +447,27 @@ struct Block
Result<Value> index(Interpreter &i, unsigned position); 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> template<typename T, typename ...XS>
constexpr auto is_one_of = (std::is_same_v<T, XS> || ...); constexpr auto is_one_of = (std::is_same_v<T, XS> || ...);
@ -466,7 +487,8 @@ struct Value
Number, Number,
Symbol, Symbol,
Intrinsic, Intrinsic,
Block Block,
Music
}; };
Value() = default; Value() = default;
@ -480,10 +502,11 @@ struct Value
} }
Type type = Type::Nil; Type type = Type::Nil;
bool b{}; bool b;
Number n{}; Number n;
Intrinsic intr{}; Intrinsic intr;
Block blk; Block blk;
Note note;
// TODO Most strings should not be allocated by Value, but reference to string allocated previously // 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: // Wrapper for std::string is needed that will allocate only when needed, middle ground between:

View File

@ -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 = [] { suite value_test = [] {
"Value"_test = [] { "Value"_test = [] {
should("be properly created using Value::from") = [] { should("be properly created using Value::from") = [] {
@ -74,4 +91,24 @@ suite value_test = [] {
expect(eq("<intrinsic>"sv, str(Value(Intrinsic(nullptr))))); 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);
}
};
};
}; };

View File

@ -11,6 +11,8 @@ constexpr u16 hash_note(Indexable<usize, char> auto const& note)
return u8(note[0]) | (note[1] << 8); 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) constexpr u8 note_index(Indexable<usize, char> auto const& note)
{ {
switch (hash_note(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 2;
case hash_note("d#"): return 3; case hash_note("d#"): return 3;
case hash_note("e"): return 4; 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 5;
case hash_note("f#"): return 6; case hash_note("f#"): return 6;
case hash_note("g"): return 7; 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; case hash_note("b#"): return 12;
} }
// This should be unreachable since parser limits what character can pass as notes // 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) Result<Value> Value::from(Token t)
@ -113,6 +116,7 @@ bool Value::truthy() const
case Type::Number: return n != Number(0); case Type::Number: return n != Number(0);
case Type::Block: case Type::Block:
case Type::Intrinsic: case Type::Intrinsic:
case Type::Music:
case Type::Symbol: return true; case Type::Symbol: return true;
} }
unreachable(); unreachable();
@ -135,6 +139,7 @@ bool Value::operator==(Value const& other) const
case Type::Intrinsic: return intr == other.intr; case Type::Intrinsic: return intr == other.intr;
case Type::Block: return false; // TODO Reconsider if functions are comparable case Type::Block: return false; // TODO Reconsider if functions are comparable
case Type::Bool: return b == other.b; case Type::Bool: return b == other.b;
case Type::Music: unimplemented();
} }
unreachable(); unreachable();
@ -160,6 +165,9 @@ std::ostream& operator<<(std::ostream& os, Value const& v)
case Value::Type::Block: case Value::Type::Block:
return os << "<block>"; return os << "<block>";
case Value::Type::Music:
unimplemented();
} }
unreachable(); unreachable();
} }
@ -167,9 +175,10 @@ std::ostream& operator<<(std::ostream& os, Value const& v)
std::string_view type_name(Value::Type t) std::string_view type_name(Value::Type t)
{ {
switch (t) { switch (t) {
case Value::Type::Block: return "block";
case Value::Type::Bool: return "bool"; case Value::Type::Bool: return "bool";
case Value::Type::Intrinsic: return "intrinsic"; 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::Nil: return "nil";
case Value::Type::Number: return "number"; case Value::Type::Number: return "number";
case Value::Type::Symbol: return "symbol"; 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) assert(position < body.arguments.size(), "Out of range"); // TODO(assert)
return i.eval((Ast)body.arguments[position]); 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;
}