Extended chord literal syntax with flats

This commit is contained in:
Robert Bendun 2022-05-29 02:57:36 +02:00
parent dc1322e4ea
commit a665d95692
4 changed files with 51 additions and 38 deletions

View File

@ -102,10 +102,9 @@ auto Lexer::next_token() -> Result<Token>
// lex chord declaration // lex chord declaration
if (consume_if(Notes_Symbols)) { if (consume_if(Notes_Symbols)) {
// Allow `c#` while (consume_if("#sfb")) {}
consume_if('#');
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, // If we encounter any letter that is not part of chord declaration,
// then we have symbol, not chord declaration // then we have symbol, not chord declaration

View File

@ -134,6 +134,12 @@ suite lexer_test = [] {
expect_token_type_and_value(Token::Type::Chord, "c1"); 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, "d1257");
expect_token_type_and_value(Token::Type::Chord, "e#5"); 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"); expect_token_type_and_value(Token::Type::Chord, "g127");
}; };

View File

@ -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 = [] { suite value_test = [] {
"Value"_test = [] { "Value"_test = [] {
should("be properly created using Value::from") = [] { should("be properly created using Value::from") = [] {
@ -110,5 +126,12 @@ suite value_test = [] {
test_note_resolution("b", i + -1, i * 12 + 11); 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);
};
}; };
}; };

View File

@ -1,40 +1,18 @@
#include <musique.hh> #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 /// Finds numeric value of note. This form is later used as in
/// note to midi resolution in formula octave * 12 + note_index /// 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)) { switch (note) {
case hash_note("c"): return 0; case 'c': return 0;
case hash_note("c#"): return 1; case 'd': return 2;
case hash_note("d"): return 2; case 'e': return 4;
case hash_note("d#"): return 3; case 'f': return 5;
case hash_note("e"): return 4; case 'g': return 7;
case hash_note("e#"): return 5; case 'a': return 9;
case hash_note("f"): return 5; case 'h': return 11;
case hash_note("f#"): return 6; case 'b': return 11;
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;
} }
// 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
// but just to be sure return special value // 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 9: return "a";
case 10: return "a#"; case 10: return "a#";
case 11: return "b"; case 11: return "b";
case 12: return "b#";
} }
unreachable(); unreachable();
} }
@ -373,8 +350,16 @@ std::ostream& operator<<(std::ostream& os, Array const& v)
std::optional<Note> Note::from(std::string_view literal) std::optional<Note> Note::from(std::string_view literal)
{ {
if (auto note = note_index(literal); note != u8(-1)) { if (auto const base = note_index(literal[0]); base != u8(-1)) {
return Note { .base = note }; 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; return std::nullopt;
} }