From 80bc4c039c873aac3ab1286176e86c161ec4b4ca Mon Sep 17 00:00:00 2001 From: Robert Bendun Date: Wed, 25 May 2022 00:08:48 +0200 Subject: [PATCH] Chords! --- src/interpreter.cc | 29 +++++++++++++++----- src/lexer.cc | 11 ++------ src/musique.hh | 18 ++++++++++--- src/value.cc | 67 ++++++++++++++++++++++++++++++++++------------ 4 files changed, 89 insertions(+), 36 deletions(-) diff --git a/src/interpreter.cc b/src/interpreter.cc index 0ee9d23..f8e475c 100644 --- a/src/interpreter.cc +++ b/src/interpreter.cc @@ -113,7 +113,7 @@ Interpreter::Interpreter() global.force_define("play", +[](Interpreter &i, std::vector args) -> Result { for (auto &arg : args) { assert(arg.type == Value::Type::Music, "Only music values can be played"); // TODO(assert) - i.play(arg.note); + i.play(arg.chord); } return Value{}; }); @@ -264,16 +264,31 @@ void Interpreter::leave_scope() env = env->leave(); } -void Interpreter::play(Note note) +void Interpreter::play(Chord chord) { assert(midi_connection, "To play midi Interpreter requires instance of MIDI connection"); auto &ctx = context_stack.back(); - note = ctx.fill(std::move(note)); - auto dur = ctx.length_to_duration(note.length); + // Fill all notes that don't have octave or length with defaults + std::transform(chord.notes.begin(), chord.notes.end(), chord.notes.begin(), [&](Note note) { return ctx.fill(note); }); - midi_connection->send_note_on(0, *note.into_midi_note(), 127); - std::this_thread::sleep_for(dur); - midi_connection->send_note_off(0, *note.into_midi_note(), 127); + // Sort that we have smaller times first + std::sort(chord.notes.begin(), chord.notes.end(), [](Note const& lhs, Note const& rhs) { return lhs.length < rhs.length; }); + + Number max_time = *chord.notes.back().length; + + // Turn all notes on + for (auto const& note : chord.notes) { + midi_connection->send_note_on(0, *note.into_midi_note(), 127); + } + + // Turn off each note at right time + for (auto const& note : chord.notes) { + if (max_time != Number(0)) { + max_time -= *note.length; + std::this_thread::sleep_for(ctx.length_to_duration(*note.length)); + } + midi_connection->send_note_off(0, *note.into_midi_note(), 127); + } } diff --git a/src/lexer.cc b/src/lexer.cc index 46d8dc7..27c5b4e 100644 --- a/src/lexer.cc +++ b/src/lexer.cc @@ -105,12 +105,7 @@ auto Lexer::next_token() -> Result // Allow `c#` consume_if('#'); - // Any of the following sequences are allowed - // c,,,,,,,,,,,,,,,, - // c1,,,,2,3212 - // c1234''''' - // during lexing - while (consume_if(",'") || consume_if(unicode::is_digit)) {} + while (consume_if('_') || consume_if(unicode::is_digit)) {} // If we encounter any letter that is not part of chord declaration, // then we have symbol, not chord declaration @@ -125,9 +120,7 @@ auto Lexer::next_token() -> Result if (consume_if(std::bind(unicode::is_identifier, _1, unicode::First_Character::Yes))) { symbol_lexing: for (auto predicate = std::bind(unicode::is_identifier, _1, unicode::First_Character::No); - consume_if(predicate); - ) { - } + consume_if(predicate);) {} Token t = { Token::Type::Symbol, finish(), token_location }; if (std::find(Keywords.begin(), Keywords.end(), t.source) != Keywords.end()) { diff --git a/src/musique.hh b/src/musique.hh index 47c64a0..3d731a7 100644 --- a/src/musique.hh +++ b/src/musique.hh @@ -494,6 +494,17 @@ struct Note std::ostream& operator<<(std::ostream& os, Note const& note); +struct Chord +{ + std::vector notes; + + static Chord from(std::string_view source); + + bool operator==(Chord const&) const = default; +}; + +std::ostream& operator<<(std::ostream& os, Chord const& chord); + /// Eager Array struct Array { @@ -523,6 +534,7 @@ struct Value static Value from(Block &&l); static Value from(Array &&array); static Value from(Note n); + static Value from(Chord chord); enum class Type { @@ -533,7 +545,7 @@ struct Value Intrinsic, Block, Array, - Music + Music, }; Value() = default; @@ -551,7 +563,7 @@ struct Value Number n; Intrinsic intr; Block blk; - Note note; + Chord chord; Array array; // TODO Most strings should not be allocated by Value, but reference to string allocated previously @@ -647,7 +659,7 @@ struct Interpreter void leave_scope(); /// Play note resolving any missing parameters with context via `midi_connection` member. - void play(Note n); + void play(Chord); }; namespace errors diff --git a/src/value.cc b/src/value.cc index 1ff988c..300f58b 100644 --- a/src/value.cc +++ b/src/value.cc @@ -83,7 +83,7 @@ Result Value::from(Token t) return Value::from(*maybe_note); } - unimplemented("only simple note values (like c or e#) are supported now"); + return Value::from(Chord::from(t.source)); default: unimplemented(); @@ -150,7 +150,15 @@ Value Value::from(Note n) { Value v; v.type = Type::Music; - v.note = n; + v.chord = { .notes = { n } }; + return v; +} + +Value Value::from(Chord chord) +{ + Value v; + v.type = Type::Music; + v.chord = std::move(chord); return v; } @@ -164,11 +172,12 @@ Result Value::operator()(Interpreter &i, std::vector args) assert(args.size() == 1 || args.size() == 2, "music value can be called only in form note []"); // TODO(assert) assert(args[0].type == Type::Number, "expected octave to be a number"); // TODO(assert) - note.octave = args[0].n.as_int(); - - if (args.size() == 2) { - assert(args[1].type == Type::Number, "expected length to be a number"); // TODO(assert) - note.length = args[1].n; + assert(args.size() == 2 ? args[1].type == Type::Number : true, "expected length to be a number"); // TODO(assert) + for (auto ¬e : chord.notes) { + note.octave = args[0].n.as_int(); + if (args.size() == 2) { + note.length = args[1].n; + } } return *this; @@ -224,7 +233,7 @@ bool Value::operator==(Value const& other) const case Type::Intrinsic: return intr == other.intr; case Type::Block: return false; // TODO Reconsider if functions are comparable case Type::Bool: return b == other.b; - case Type::Music: return note == other.note; + case Type::Music: return chord == other.chord; case Type::Array: return array == other.array; } @@ -272,7 +281,7 @@ std::ostream& operator<<(std::ostream& os, Value const& v) return os << v.array; case Value::Type::Music: - return os << v.note; + return os << v.chord; } unreachable(); } @@ -374,18 +383,13 @@ u8 Note::into_midi_note(i8 default_octave) const std::ostream& operator<<(std::ostream& os, Note const& note) { os << note_index_to_string(note.base); - os << ":oct="; if (note.octave) { - os << int(*note.octave); - } else { - os << '_'; + os << ":oct=" << int(*note.octave); } - os << ":len="; if (note.length) { - os << *note.length; - } else { - os << '_'; + os << ":len=" << *note.length; } + return os; } @@ -393,3 +397,32 @@ bool Note::operator==(Note const& other) const { return octave == other.octave && base == other.base && length == other.length; } + +Chord Chord::from(std::string_view source) +{ + auto note = Note::from(source); + assert(note.has_value(), "don't know how this could happen"); + + Chord chord; + source.remove_prefix(1 + (source[1] == '#')); + chord.notes.push_back(*std::move(note)); + + for (char digit : source) { + chord.notes.push_back(Note { .base = u8(digit - '0') }); + } + + return chord; +} + +std::ostream& operator<<(std::ostream& os, Chord const& chord) +{ + os << "chord["; + + for (auto it = chord.notes.begin(); it != chord.notes.end(); ++it) { + os << *it; + if (std::next(it) != chord.notes.end()) + os << "; "; + } + + return os << ']'; +}