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);
|
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:
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
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);
|
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;
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user