diff --git a/doc/cheatsheet.txt b/doc/cheatsheet.txt index 589c9a3..b527bc8 100644 --- a/doc/cheatsheet.txt +++ b/doc/cheatsheet.txt @@ -23,6 +23,11 @@ Arytmetyka (aktualnie brak wsparcia dla precedensji operatorów) 3 * (4 * 12 - 8) +Przesuwanie dźwięków o półtony + c + 1 == c# + c - 1 == b + c12 + 1 == d12 + Wywołanie funkcji foo 1 2 3 diff --git a/src/interpreter.cc b/src/interpreter.cc index 06d1451..8fd9b43 100644 --- a/src/interpreter.cc +++ b/src/interpreter.cc @@ -10,8 +10,8 @@ constexpr auto binary_operator() return [](Interpreter&, std::vector args) -> Result { auto result = std::move(args.front()); for (auto &v : std::span(args).subspan(1)) { - assert(result.type == Value::Type::Number, "LHS should be a number"); - assert(v.type == Value::Type::Number, "RHS should be a number"); + assert(result.type == Value::Type::Number, "LHS should be a number"); // TODO(assert) + assert(v.type == Value::Type::Number, "RHS should be a number"); // TODO(assert) if constexpr (std::is_same_v>) { result.n = Binary_Operation{}(std::move(result.n), std::move(v).n); } else { @@ -36,7 +36,7 @@ template constexpr auto comparison_operator() { return [](Interpreter&, std::vector args) -> Result { - assert(args.size() == 2, "(in)Equality only allows for 2 operands"); // TODO(assert) + assert(args.size() == 2, "Ordering only allows for 2 operands"); // TODO(assert) assert(args.front().type == args.back().type, "Only values of the same type can be ordered"); // TODO(assert) switch (args.front().type) { @@ -118,6 +118,42 @@ static inline Result create_chord(std::vector &chord, Interpreter &i return {}; } +/// Creates implementation of plus/minus operator that support following operations: +/// number, number -> number (standard math operations) +/// n: number, m: music -> music +/// m: music, n: number -> music moves m by n semitones (+ goes up, - goes down) +template +[[gnu::always_inline]] +static inline auto plus_minus_operator() +{ + return [](Interpreter&, std::vector args) -> Result { + assert(args.size() == 2, "Binary operator only accepts 2 arguments"); + auto lhs = std::move(args.front()); + auto rhs = std::move(args.back()); + + if (lhs.type == rhs.type && lhs.type == Value::Type::Number) { + return Value::from(Binary_Operation{}(std::move(lhs).n, std::move(rhs).n)); + } + + if (lhs.type == Value::Type::Music && rhs.type == Value::Type::Number) { +music_number_operation: + for (auto ¬e : lhs.chord.notes) { + note.base = Binary_Operation{}(note.base, rhs.n.as_int()); + note.simplify_inplace(); + } + return lhs; + } + + if (lhs.type == Value::Type::Number && rhs.type == Value::Type::Music) { + std::swap(lhs, rhs); + goto music_number_operation; + } + + assert(false, "Unsupported types for this operation"); // TODO(assert) + unreachable(); + }; +} + Interpreter::Interpreter() { { // Context initialization @@ -189,8 +225,8 @@ Interpreter::Interpreter() return Value::from(std::move(chord)); }); - operators["+"] = binary_operator>(); - operators["-"] = binary_operator>(); + operators["+"] = plus_minus_operator>(); + operators["-"] = plus_minus_operator>(); operators["*"] = binary_operator>(); operators["/"] = binary_operator>(); diff --git a/src/musique.hh b/src/musique.hh index 3d731a7..2f72fab 100644 --- a/src/musique.hh +++ b/src/musique.hh @@ -472,7 +472,7 @@ struct Block struct Note { /// Base of a note, like `c` (=0), `c#` (=1) `d` (=2) - u8 base; + i32 base; /// Octave in MIDI acceptable range (from -1 to 9 inclusive) std::optional octave = std::nullopt; @@ -490,9 +490,11 @@ struct Note u8 into_midi_note(i8 default_octave) const; bool operator==(Note const&) const; + + void simplify_inplace(); }; -std::ostream& operator<<(std::ostream& os, Note const& note); +std::ostream& operator<<(std::ostream& os, Note note); struct Chord { diff --git a/src/value.cc b/src/value.cc index 300f58b..e563afe 100644 --- a/src/value.cc +++ b/src/value.cc @@ -41,8 +41,15 @@ constexpr u8 note_index(Indexable auto const& note) return -1; } -constexpr std::string_view note_index_to_string(u8 note_index) +#include + +constexpr std::string_view note_index_to_string(auto note_index) { + note_index %= 12; + if (note_index < 0) { + note_index = 12 + note_index; + } + switch (note_index) { case 0: return "c"; case 1: return "c#"; @@ -178,6 +185,7 @@ Result Value::operator()(Interpreter &i, std::vector args) if (args.size() == 2) { note.length = args[1].n; } + note.simplify_inplace(); } return *this; @@ -380,8 +388,19 @@ u8 Note::into_midi_note(i8 default_octave) const return (octave + 1) * 12 + base; } -std::ostream& operator<<(std::ostream& os, Note const& note) +void Note::simplify_inplace() { + if (octave) { + octave = std::clamp(*octave + int(base / 12), -1, 9); + if ((base %= 12) < 0) { + base = 12 + base; + } + } +} + +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); @@ -416,13 +435,15 @@ Chord Chord::from(std::string_view source) std::ostream& operator<<(std::ostream& os, Chord const& chord) { - os << "chord["; + if (chord.notes.size() == 1) { + return os << chord.notes.front(); + } + 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 << ']'; }