Extended chord literal syntax with flats
This commit is contained in:
parent
dc1322e4ea
commit
a665d95692
@ -102,10 +102,9 @@ auto Lexer::next_token() -> Result<Token>
|
||||
|
||||
// lex chord declaration
|
||||
if (consume_if(Notes_Symbols)) {
|
||||
// Allow `c#`
|
||||
consume_if('#');
|
||||
while (consume_if("#sfb")) {}
|
||||
|
||||
while (consume_if('_') || consume_if(unicode::is_digit)) {}
|
||||
while (consume_if(unicode::is_digit)) {}
|
||||
|
||||
// If we encounter any letter that is not part of chord declaration,
|
||||
// then we have symbol, not chord declaration
|
||||
|
@ -134,6 +134,12 @@ suite lexer_test = [] {
|
||||
expect_token_type_and_value(Token::Type::Chord, "c1");
|
||||
expect_token_type_and_value(Token::Type::Chord, "d1257");
|
||||
expect_token_type_and_value(Token::Type::Chord, "e#5");
|
||||
expect_token_type_and_value(Token::Type::Chord, "ef5");
|
||||
expect_token_type_and_value(Token::Type::Chord, "es5");
|
||||
expect_token_type_and_value(Token::Type::Chord, "eb5");
|
||||
expect_token_type_and_value(Token::Type::Chord, "e##5");
|
||||
expect_token_type_and_value(Token::Type::Chord, "ef#5");
|
||||
expect_token_type_and_value(Token::Type::Chord, "esf5");
|
||||
expect_token_type_and_value(Token::Type::Chord, "g127");
|
||||
};
|
||||
|
||||
|
@ -51,6 +51,22 @@ static void test_note_resolution(
|
||||
}
|
||||
}
|
||||
|
||||
static void test_note_resolution(
|
||||
std::string_view name,
|
||||
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 = 4;
|
||||
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") = [] {
|
||||
@ -110,5 +126,12 @@ suite value_test = [] {
|
||||
test_note_resolution("b", i + -1, i * 12 + 11);
|
||||
}
|
||||
};
|
||||
|
||||
should("Support flat and sharp") = [] {
|
||||
test_note_resolution("c#", 61);
|
||||
test_note_resolution("cs", 61);
|
||||
test_note_resolution("cf", 59);
|
||||
test_note_resolution("cb", 59);
|
||||
};
|
||||
};
|
||||
};
|
||||
|
55
src/value.cc
55
src/value.cc
@ -1,40 +1,18 @@
|
||||
#include <musique.hh>
|
||||
|
||||
template<typename T, typename Index, typename Expected>
|
||||
concept Indexable = requires(T t, Index i) {
|
||||
{ t[i] } -> std::convertible_to<Expected>;
|
||||
};
|
||||
|
||||
/// Create hash out of note literal like `c` or `e#`
|
||||
constexpr u16 hash_note(Indexable<usize, char> auto const& note)
|
||||
{
|
||||
/// TODO Some assertion that we have snd character
|
||||
u8 snd = note[1];
|
||||
if (snd != '#') snd = 0;
|
||||
return u8(note[0]) | (snd << 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(u8 note)
|
||||
{
|
||||
switch (hash_note(note)) {
|
||||
case hash_note("c"): return 0;
|
||||
case hash_note("c#"): return 1;
|
||||
case hash_note("d"): return 2;
|
||||
case hash_note("d#"): return 3;
|
||||
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;
|
||||
case hash_note("g#"): return 8;
|
||||
case hash_note("a"): return 9;
|
||||
case hash_note("a#"): return 10;
|
||||
case hash_note("h"): return 11;
|
||||
case hash_note("b"): return 11;
|
||||
case hash_note("h#"): return 12;
|
||||
case hash_note("b#"): return 12;
|
||||
switch (note) {
|
||||
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 'h': return 11;
|
||||
case 'b': return 11;
|
||||
}
|
||||
// This should be unreachable since parser limits what character can pass as notes
|
||||
// but just to be sure return special value
|
||||
@ -63,7 +41,6 @@ constexpr std::string_view note_index_to_string(auto note_index)
|
||||
case 9: return "a";
|
||||
case 10: return "a#";
|
||||
case 11: return "b";
|
||||
case 12: return "b#";
|
||||
}
|
||||
unreachable();
|
||||
}
|
||||
@ -373,8 +350,16 @@ std::ostream& operator<<(std::ostream& os, Array const& v)
|
||||
|
||||
std::optional<Note> Note::from(std::string_view literal)
|
||||
{
|
||||
if (auto note = note_index(literal); note != u8(-1)) {
|
||||
return Note { .base = note };
|
||||
if (auto const base = note_index(literal[0]); base != u8(-1)) {
|
||||
Note note { .base = base };
|
||||
while (literal.remove_prefix(1), not literal.empty()) {
|
||||
switch (literal.front()) {
|
||||
case '#': case 's': ++note.base; break;
|
||||
case 'b': case 'f': --note.base; break;
|
||||
default: return note;
|
||||
}
|
||||
}
|
||||
return note;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user