diff --git a/include/musique.hh b/include/musique.hh index 41bad3a..db7eb04 100644 --- a/include/musique.hh +++ b/include/musique.hh @@ -761,11 +761,12 @@ struct Block usize size() const; }; -/// Representation of musical note +/// Representation of musical note or musical pause struct Note { /// Base of a note, like `c` (=0), `c#` (=1) `d` (=2) - i32 base; + /// Or nullopt where there is no note - case when we have pause + std::optional base = std::nullopt; /// Octave in MIDI acceptable range (from -1 to 9 inclusive) std::optional octave = std::nullopt; diff --git a/src/builtin_functions.cc b/src/builtin_functions.cc index 70fe8ab..d0c87fd 100644 --- a/src/builtin_functions.cc +++ b/src/builtin_functions.cc @@ -50,7 +50,7 @@ static inline Result create_chord(std::vector &chord, Interpreter &i break; case Value::Type::Music: - std::move(arg.chord.notes.begin(), arg.chord.notes.end(), std::back_inserter(chord)); + std::copy_if(arg.chord.notes.begin(), arg.chord.notes.end(), std::back_inserter(chord), [](Note const& n) { return bool(n.base); }); break; default: @@ -294,13 +294,17 @@ static Result builtin_par(Interpreter &i, std::vector args) { std::for_each(chord.notes.begin(), chord.notes.end(), [&](Note ¬e) { note = ctx.fill(note); }); for (auto const& note : chord.notes) { - i.midi_connection->send_note_on(0, *note.into_midi_note(), 127); + if (note.base) { + i.midi_connection->send_note_on(0, *note.into_midi_note(), 127); + } } auto result = builtin_play(i, std::span(args).subspan(1)); for (auto const& note : chord.notes) { - i.midi_connection->send_note_off(0, *note.into_midi_note(), 127); + if (note.base) { + i.midi_connection->send_note_off(0, *note.into_midi_note(), 127); + } } return result; } diff --git a/src/builtin_operators.cc b/src/builtin_operators.cc index 2f7ad1c..14d25d7 100644 --- a/src/builtin_operators.cc +++ b/src/builtin_operators.cc @@ -49,8 +49,10 @@ static Result plus_minus_operator(Interpreter &interpreter, std::vector plus_minus_operator(Interpreter &interpreter, std::vector Interpreter::play(Chord chord) // Turn all notes on for (auto const& note : chord.notes) { - midi_connection->send_note_on(0, *note.into_midi_note(), 127); + if (note.base) { + midi_connection->send_note_on(0, *note.into_midi_note(), 127); + } } // Turn off each note at right time @@ -243,7 +245,9 @@ Result Interpreter::play(Chord chord) 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); + if (note.base) { + midi_connection->send_note_off(0, *note.into_midi_note(), 127); + } } return {}; diff --git a/src/lexer.cc b/src/lexer.cc index 5bd30c2..710e845 100644 --- a/src/lexer.cc +++ b/src/lexer.cc @@ -3,7 +3,7 @@ #include -constexpr std::string_view Notes_Symbols = "abcedefgh"; +constexpr std::string_view Notes_Symbols = "abcedefghp"; constexpr std::string_view Valid_Operator_Chars = "+-*/:%" // arithmetic "|&^" // logic & bit operations diff --git a/src/parser.cc b/src/parser.cc index 1cbf874..650e027 100644 --- a/src/parser.cc +++ b/src/parser.cc @@ -531,6 +531,9 @@ static usize precedense(std::string_view op) if (one_of(op, "*", "/", "%", "&")) return 400; if (one_of(op, "**")) return 500; + + std::cerr << op << std::endl; + unreachable(); } diff --git a/src/value.cc b/src/value.cc index b2506e7..03c31e0 100644 --- a/src/value.cc +++ b/src/value.cc @@ -17,14 +17,13 @@ constexpr u8 note_index(u8 note) 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 - return -1; + // Parser should limit range of characters that is called with this function + unreachable(); } #include -constexpr std::string_view note_index_to_string(auto note_index) +constexpr std::string_view note_index_to_string(int note_index) { note_index %= 12; if (note_index < 0) { @@ -411,12 +410,17 @@ std::ostream& operator<<(std::ostream& os, Array const& v) std::optional Note::from(std::string_view literal) { + if (literal.starts_with('p')) { + return 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; + case '#': case 's': ++*note.base; break; + case 'b': case 'f': --*note.base; break; default: return note; } } @@ -432,27 +436,37 @@ std::optional Note::into_midi_note() const u8 Note::into_midi_note(i8 default_octave) const { + assert(bool(this->base), "Pause don't translate into MIDI"); auto const octave = this->octave.has_value() ? *this->octave : default_octave; // octave is in range [-1, 9] where Note { .base = 0, .octave = -1 } is midi note 0 - return (octave + 1) * 12 + base; + return (octave + 1) * 12 + *base; } void Note::simplify_inplace() { - if (octave) { - octave = std::clamp(*octave + int(base / 12), -1, 9); - if ((base %= 12) < 0) { - base = 12 + base; + if (base && octave) { + octave = std::clamp(*octave + int(*base / 12), -1, 9); + if ((*base %= 12) < 0) { + base = 12 + *base; } } } std::partial_ordering Note::operator<=>(Note const& rhs) const { - if (octave.has_value() == rhs.octave.has_value()) { - if (octave.has_value()) - return (12 * *octave) + base <=> (12 * *rhs.octave) + rhs.base; - return base <=> rhs.base; + if (base.has_value() == rhs.base.has_value()) { + if (!base.has_value()) { + if (length.has_value() == rhs.length.has_value() && length.has_value()) { + return *length <=> *rhs.length; + } + return std::partial_ordering::unordered; + } + + if (octave.has_value() == rhs.octave.has_value()) { + if (octave.has_value()) + return (12 * *octave) + *base <=> (12 * *rhs.octave) + *rhs.base; + return *base <=> *rhs.base; + } } return std::partial_ordering::unordered; } @@ -460,9 +474,13 @@ std::partial_ordering Note::operator<=>(Note const& rhs) const std::ostream& operator<<(std::ostream& os, Note note) { note.simplify_inplace(); - os << note_index_to_string(note.base); - if (note.octave) { - os << ":oct=" << int(*note.octave); + if (note.base) { + os << note_index_to_string(*note.base); + if (note.octave) { + os << ":oct=" << int(*note.octave); + } + } else { + os << "p"; } if (note.length) { os << ":len=" << *note.length; @@ -544,7 +562,7 @@ std::size_t std::hash::operator()(Value const& value) const break; case Value::Type::Music: value_hash = std::accumulate(value.chord.notes.begin(), value.chord.notes.end(), value_hash, [](size_t h, Note const& n) { - h = hash_combine(h, n.base); + h = hash_combine(h, std::hash>{}(n.base)); h = hash_combine(h, std::hash>{}(n.length)); h = hash_combine(h, std::hash>{}(n.octave)); return h;