diff --git a/src/lexer.cc b/src/lexer.cc index 27c5b4e..678dbd3 100644 --- a/src/lexer.cc +++ b/src/lexer.cc @@ -102,10 +102,9 @@ auto Lexer::next_token() -> Result // 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 diff --git a/src/tests/lex.cc b/src/tests/lex.cc index 40c0484..f107d0c 100644 --- a/src/tests/lex.cc +++ b/src/tests/lex.cc @@ -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"); }; diff --git a/src/tests/value.cc b/src/tests/value.cc index f5d00df..3a35b7d 100644 --- a/src/tests/value.cc +++ b/src/tests/value.cc @@ -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); + }; }; }; diff --git a/src/value.cc b/src/value.cc index 3010d3d..885e74f 100644 --- a/src/value.cc +++ b/src/value.cc @@ -1,40 +1,18 @@ #include -template -concept Indexable = requires(T t, Index i) { - { t[i] } -> std::convertible_to; -}; - -/// Create hash out of note literal like `c` or `e#` -constexpr u16 hash_note(Indexable 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 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::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; }