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
|
// 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
|
||||||
|
@ -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");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
55
src/value.cc
55
src/value.cc
@ -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;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user